Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion build/checks/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,19 @@ in
);

venv = testSet.mkVirtualEnv "render-mkderivation-env" {
myapp = [ "toml" ];
myapp = [
"toml" # Extra
"round" # PEP-735 dependency group
];
};
in
pkgs.runCommand "render-mkderivation-test" { nativeBuildInputs = [ venv ]; } ''
# Assert that extra was enabled
python -c "import tomli_w"

# Assert that dependency group was enabled
python -c "import wheel"

# Script from myapp
hello

Expand Down
3 changes: 3 additions & 0 deletions build/checks/fixtures/myapp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ dependencies = [
[project.optional-dependencies]
toml = ["tomli-w"]

[dependency-groups]
round = ["wheel"]

[project.scripts]
hello = "myapp:hello"

Expand Down
73 changes: 40 additions & 33 deletions build/lib/renderers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ in
passthru = {
dependencies = mkSpec filteredDeps.dependencies;
optional-dependencies = mapAttrs (_: mkSpec) filteredDeps.extras;
dependency-groups = mapAttrs (_: mkSpec) filteredDeps.groups;
};

nativeBuildInputs = [
Expand Down Expand Up @@ -125,41 +126,46 @@ in
# Synthetic pyproject.toml
#
# We don't use the provided build-system to build an editable package, we use hatchling.
pyproject = {
# PEP-621 project table
project =
{
# Both name and version are required.
inherit (project') name version;
}
// optionalAttrs (project' ? dependencies) {
inherit (project') dependencies;
}
// optionalAttrs (project' ? optional-dependencies) {
inherit (project') optional-dependencies;
}
// optionalAttrs (project' ? scripts) {
inherit (project') scripts;
}
// optionalAttrs (project' ? gui-scripts) {
inherit (project') gui-scripts;
}
// optionalAttrs (project' ? entry-points) {
inherit (project') entry-points;
pyproject =
{
# PEP-621 project table
project =
{
# Both name and version are required.
inherit (project') name version;
}
// optionalAttrs (project' ? dependencies) {
inherit (project') dependencies;
}
// optionalAttrs (project' ? optional-dependencies) {
inherit (project') optional-dependencies;
}
// optionalAttrs (project' ? scripts) {
inherit (project') scripts;
}
// optionalAttrs (project' ? gui-scripts) {
inherit (project') gui-scripts;
}
// optionalAttrs (project' ? entry-points) {
inherit (project') entry-points;
};

# Allow empty package
tool.hatch.build.targets.wheel.bypass-selection = true;

# Include our editable pointer file in build
tool.hatch.build.targets.wheel.force-include."_${pname}.pth" = "_${pname}.pth";

# Build editable package using hatchling
build-system = {
requires = [ "hatchling" ];
build-backend = "hatchling.build";
};

# Allow empty package
tool.hatch.build.targets.wheel.bypass-selection = true;

# Include our editable pointer file in build
tool.hatch.build.targets.wheel.force-include."_${pname}.pth" = "_${pname}.pth";

# Build editable package using hatchling
build-system = {
requires = [ "hatchling" ];
build-backend = "hatchling.build";
}
// optionalAttrs (project.pyproject ? dependency-groups) {
# PEP-735 dependency groups
inherit (project.pyproject) dependency-groups;
};
};
in
{
inherit pname;
Expand All @@ -172,6 +178,7 @@ in
)
);
optional-dependencies = mapAttrs (_: mkSpec) filteredDeps.extras;
dependency-groups = mapAttrs (_: mkSpec) filteredDeps.groups;
};

# Convert created JSON format pyproject.toml into TOML and include a generated pth file
Expand Down
33 changes: 24 additions & 9 deletions build/lib/resolvers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let
genericClosure
match
genAttrs
throwIf
;

in
Expand All @@ -30,16 +31,21 @@ in
let
pkg = set.${name};
dependencies = pkg.passthru.dependencies or { };
optional-dependencies = pkg.passthru.optional-dependencies or { };
in
[ name ]
++ concatMap (name: recurse name dependencies.${name}) (attrNames dependencies)
++ concatMap (
extra:
name':
let
extra' = optional-dependencies.${extra};
extra' = pkg.passthru.optional-dependencies.${name'} or { };
group' = pkg.passthru.dependency-groups.${name'} or { };
in
concatMap (name: recurse name extra'.${name}) (attrNames extra')
throwIf (extra' == { } && group' == { })
"Extra/group name '${name'}' does not match either extra or dependency group"
concatMap
(name: recurse name extra'.${name})
(attrNames extra')
++ concatMap (name: recurse name group'.${name}) (attrNames group')
) extras;

# Memoise known build systems with no extras enabled for better performance
Expand Down Expand Up @@ -74,16 +80,25 @@ in
let
m = match "(.+)@(.*)" key;
in
# We're looking for a package with extra
# We're looking for a package with extra or dependency group
if m != null then
(
let
pkg = set.${elemAt m 0};
dependencies = pkg.passthru.optional-dependencies.${elemAt m 1};
name' = elemAt m 1;
extras' = pkg.passthru.optional-dependencies.${name'} or { };
groups' = pkg.passthru.dependency-groups.${name'} or { };
in
concatMap (name: [ (mkKey name) ] ++ map (extra: mkKey "${name}@${extra}") dependencies.${name}) (
attrNames dependencies
)
throwIf (extras' == { } && groups' == { })
"Extra/group name '${name'}' does not match either extra or dependency group"
(
concatMap (name: [ (mkKey name) ] ++ map (extra: mkKey "${name}@${extra}") extras'.${name}) (
attrNames extras'
)
++ concatMap (name: [ (mkKey name) ] ++ map (group: mkKey "${name}@${group}") groups'.${name}) (
attrNames groups'
)
)
)
# Root package with no extra
else
Expand Down
3 changes: 3 additions & 0 deletions build/lib/test_renderers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ in
"wheel"
];
passthru = {
dependency-groups = { };
dependencies = {
numpy = [ ];
python-dateutil = [ ];
Expand Down Expand Up @@ -221,6 +222,7 @@ in
"pdm-backend"
];
passthru = {
dependency-groups = { };
dependencies = {
blinker = [ ];
cachecontrol = [ "filecache" ];
Expand Down Expand Up @@ -293,6 +295,7 @@ in
passthru = {
dependencies = { };
optional-dependencies = { };
dependency-groups = { };
};
pname = "uv-fixture";
version = "0.1.0";
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
pkgs = nixpkgs.legacyPackages.${system};
in
(lib.mapAttrs' (name: drv: lib.nameValuePair "nixpkgs-${name}" drv) (
pkgs.callPackages ./test { pyproject = import ./default.nix { inherit lib; }; }
pkgs.callPackages ./test { pyproject = self; }
))
// (lib.mapAttrs' (name: drv: lib.nameValuePair "build-${name}" drv) (
pkgs.callPackages ./build/checks { pyproject-nix = self; }
Expand Down
3 changes: 2 additions & 1 deletion lib/expected/poetry.parseDependencies.testParseDeps.json
Original file line number Diff line number Diff line change
Expand Up @@ -2055,5 +2055,6 @@
"url": null
}
]
}
},
"groups": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@
}
]
},
"passthru": {
"dependency-groups": {}
},
"pname": "poetry",
"pyproject": true,
"version": "1.4.2"
Expand Down
3 changes: 3 additions & 0 deletions lib/expected/project.loadPyprojectDynamic.testPep621.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@
}
]
},
"passthru": {
"dependency-groups": {}
},
"pname": "pandas",
"pyproject": true
}
7 changes: 5 additions & 2 deletions lib/expected/project.loadPyprojectDynamic.testPoetry.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
{
"pname": "keyring",
"version": "25.3.0"
"version": "25.2.1"
},
{
"pname": "lockfile",
Expand Down Expand Up @@ -206,14 +206,17 @@
},
{
"pname": "types-requests",
"version": "2.32.0.20240712"
"version": "2.32.0.20240914"
},
{
"pname": "typing-extensions",
"version": "4.12.2"
}
]
},
"passthru": {
"dependency-groups": {}
},
"pname": "poetry",
"pyproject": true,
"version": "1.4.2"
Expand Down
17 changes: 17 additions & 0 deletions lib/fixtures/pep735.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[project]
name = "uv-fixture"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

[dependency-groups]
group-a = ["foo"]
group-b = ["foo>1.0"]
group-c = ["foo<1.0"]
all = ["foo", {include-group = "group-a"}, {include-group = "group-b"}, {include-group = "group-c"}]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
35 changes: 33 additions & 2 deletions lib/pep621.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ let
foldl'
split
filter
concatMap
isAttrs
;
inherit (lib)
isString
Expand Down Expand Up @@ -51,21 +53,49 @@ fix (_self: {
pyproject,
extrasAttrPaths ? [ ],
extrasListPaths ? { },
groupsAttrPaths ? [ ],
groupsListPaths ? { },
}:
let
# Fold extras from all considered attributes into one set
extras' =
foldl' (acc: attr: acc // getAttrPath attr { } pyproject) (pyproject.project.optional-dependencies
(foldl' (acc: attr: acc // getAttrPath attr { } pyproject) (pyproject.project.optional-dependencies
or { }
) extrasAttrPaths
) extrasAttrPaths)
// filterAttrs (_: deps: length deps > 0) (
mapAttrs' (path: attr: nameValuePair attr (getAttrPath path [ ] pyproject)) extrasListPaths
);

depGroups' =
(foldl' (acc: attr: acc // getAttrPath attr { } pyproject) (pyproject.dependency-groups or { }
) groupsAttrPaths)
// filterAttrs (_: deps: length deps > 0) (
mapAttrs' (path: attr: nameValuePair attr (getAttrPath path [ ] pyproject)) groupsListPaths
);

in
{
dependencies = map pep508.parseString (pyproject.project.dependencies or [ ]);
extras = mapAttrs (_: map pep508.parseString) extras';
build-systems = pep518.parseBuildSystems pyproject;

# PEP-735 dependency groups
groups =
let
groups' = mapAttrs (
_group:
concatMap (
x:
if isString x then
[ (pep508.parseString x) ]
else if isAttrs x then
groups'.${x.include-group}
else
throw "Unsupported dependency group: ${x}"
)
) depGroups';
in
groups';
};

/*
Expand Down Expand Up @@ -115,6 +145,7 @@ fix (_self: {
dependencies = filterList dependencies.dependencies;
extras = mapAttrs (_: filterList) dependencies.extras;
build-systems = filterList dependencies.build-systems;
groups = mapAttrs (_: filterList) dependencies.groups;
}
);
})
3 changes: 3 additions & 0 deletions lib/poetry.nix
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ lib.fix (
_: group: map parseDependency (normalizeDependendenciesToList group.dependencies)
) pyproject.tool.poetry.group or { };
build-systems = pep518.parseBuildSystems pyproject;

# PEP-735 dependency groups
groups = { };
};

/*
Expand Down
Loading