mirror of
https://github.com/vale981/poetry2nix
synced 2025-03-05 09:11:39 -05:00

Currently Poetry does not have a notion of build-time dependencies (setup-requires) in it's lock file. When installing a dependency built with Poetry this has to be added via an override which is really bad UX. This change always adds Poetry as an interim solution until we have resolved this in upstream Poetry.
318 lines
11 KiB
Nix
318 lines
11 KiB
Nix
{ pkgs ? import <nixpkgs> { }
|
|
, lib ? pkgs.lib
|
|
, poetry ? null
|
|
, poetryLib ? import ./lib.nix { inherit lib pkgs; }
|
|
}:
|
|
let
|
|
inherit (poetryLib) isCompatible readTOML moduleName;
|
|
|
|
# Poetry2nix version
|
|
version = "1.9.2";
|
|
|
|
/* The default list of poetry2nix override overlays */
|
|
defaultPoetryOverrides = (import ./overrides.nix { inherit pkgs lib; });
|
|
mkEvalPep508 = import ./pep508.nix {
|
|
inherit lib poetryLib;
|
|
stdenv = pkgs.stdenv;
|
|
};
|
|
getFunctorFn = fn: if builtins.typeOf fn == "set" then fn.__functor else fn;
|
|
|
|
# Map SPDX identifiers to license names
|
|
spdxLicenses = lib.listToAttrs (lib.filter (pair: pair.name != null) (builtins.map (v: { name = if lib.hasAttr "spdxId" v then v.spdxId else null; value = v; }) (lib.attrValues lib.licenses)));
|
|
# Get license by id falling back to input string
|
|
getLicenseBySpdxId = spdxId: spdxLicenses.${spdxId} or spdxId;
|
|
|
|
# Experimental withPlugins functionality
|
|
toPluginAble = (import ./plugins.nix { inherit pkgs lib; }).toPluginAble;
|
|
|
|
/*
|
|
Returns an attrset { python, poetryPackages, pyProject, poetryLock } for the given pyproject/lockfile.
|
|
*/
|
|
mkPoetryPackages =
|
|
{ projectDir ? null
|
|
, pyproject ? projectDir + "/pyproject.toml"
|
|
, poetrylock ? projectDir + "/poetry.lock"
|
|
, overrides ? [ defaultPoetryOverrides ]
|
|
, python ? pkgs.python3
|
|
, pwd ? projectDir
|
|
, preferWheels ? false
|
|
, __isBootstrap ? false # Hack: Always add Poetry as a build input unless bootstrapping
|
|
}@attrs:
|
|
let
|
|
poetryPkg = poetry.override { inherit python; };
|
|
pyProject = readTOML pyproject;
|
|
poetryLock = readTOML poetrylock;
|
|
lockFiles =
|
|
let
|
|
lockfiles = lib.getAttrFromPath [ "metadata" "files" ] poetryLock;
|
|
in
|
|
lib.listToAttrs (lib.mapAttrsToList (n: v: { name = moduleName n; value = v; }) lockfiles);
|
|
specialAttrs = [
|
|
"overrides"
|
|
"poetrylock"
|
|
"projectDir"
|
|
"pwd"
|
|
"preferWheels"
|
|
];
|
|
passedAttrs = builtins.removeAttrs attrs specialAttrs;
|
|
evalPep508 = mkEvalPep508 python;
|
|
|
|
# Filter packages by their PEP508 markers & pyproject interpreter version
|
|
partitions =
|
|
let
|
|
supportsPythonVersion = pkgMeta: if pkgMeta ? marker then (evalPep508 pkgMeta.marker) else true;
|
|
in
|
|
lib.partition supportsPythonVersion poetryLock.package;
|
|
compatible = partitions.right;
|
|
incompatible = partitions.wrong;
|
|
|
|
# Create an overriden version of pythonPackages
|
|
#
|
|
# We need to avoid mixing multiple versions of pythonPackages in the same
|
|
# closure as python can only ever have one version of a dependency
|
|
baseOverlay = self: super:
|
|
let
|
|
getDep = depName: self.${depName};
|
|
lockPkgs = builtins.listToAttrs (
|
|
builtins.map
|
|
(
|
|
pkgMeta: rec {
|
|
name = moduleName pkgMeta.name;
|
|
value = self.mkPoetryDep (
|
|
pkgMeta // {
|
|
inherit pwd preferWheels;
|
|
inherit __isBootstrap;
|
|
source = pkgMeta.source or null;
|
|
files = lockFiles.${name};
|
|
pythonPackages = self;
|
|
sourceSpec = pyProject.tool.poetry.dependencies.${name} or pyProject.tool.poetry.dev-dependencies.${name} or { };
|
|
}
|
|
);
|
|
}
|
|
)
|
|
compatible
|
|
);
|
|
in
|
|
lockPkgs;
|
|
overlays = builtins.map
|
|
getFunctorFn
|
|
(
|
|
[
|
|
(
|
|
self: super:
|
|
let
|
|
hooks = self.callPackage ./hooks { };
|
|
in
|
|
{
|
|
mkPoetryDep = self.callPackage ./mk-poetry-dep.nix {
|
|
inherit pkgs lib python poetryLib;
|
|
};
|
|
poetry = poetryPkg;
|
|
# The canonical name is setuptools-scm
|
|
setuptools-scm = super.setuptools_scm;
|
|
|
|
__toPluginAble = toPluginAble self;
|
|
|
|
inherit (hooks) pipBuildHook removePathDependenciesHook poetry2nixFixupHook;
|
|
}
|
|
)
|
|
# Null out any filtered packages, we don't want python.pkgs from nixpkgs
|
|
(self: super: builtins.listToAttrs (builtins.map (x: { name = moduleName x.name; value = null; }) incompatible))
|
|
# Create poetry2nix layer
|
|
baseOverlay
|
|
] ++ # User provided overrides
|
|
overrides
|
|
);
|
|
packageOverrides = lib.foldr lib.composeExtensions (self: super: { }) overlays;
|
|
py = python.override { inherit packageOverrides; self = py; };
|
|
in
|
|
{
|
|
python = py;
|
|
poetryPackages = map (pkg: py.pkgs.${moduleName pkg.name}) compatible;
|
|
poetryLock = poetryLock;
|
|
inherit pyProject;
|
|
};
|
|
|
|
/* Returns a package with a python interpreter and all packages specified in the poetry.lock lock file.
|
|
In editablePackageSources you can pass a mapping from package name to source directory to have
|
|
those packages available in the resulting environment, whose source changes are immediately available.
|
|
|
|
Example:
|
|
poetry2nix.mkPoetryEnv { poetrylock = ./poetry.lock; python = python3; }
|
|
*/
|
|
mkPoetryEnv =
|
|
{ projectDir ? null
|
|
, pyproject ? projectDir + "/pyproject.toml"
|
|
, poetrylock ? projectDir + "/poetry.lock"
|
|
, overrides ? [ defaultPoetryOverrides ]
|
|
, pwd ? projectDir
|
|
, python ? pkgs.python3
|
|
, preferWheels ? false
|
|
# Example: { my-app = ./src; }
|
|
, editablePackageSources ? { }
|
|
}:
|
|
let
|
|
py = mkPoetryPackages (
|
|
{
|
|
inherit pyproject poetrylock overrides python pwd preferWheels;
|
|
}
|
|
);
|
|
|
|
editablePackage = import ./editable.nix {
|
|
inherit pkgs lib poetryLib editablePackageSources;
|
|
inherit (py) pyProject python;
|
|
};
|
|
|
|
in
|
|
py.python.withPackages (_: py.poetryPackages ++ lib.optional (editablePackageSources != { }) editablePackage);
|
|
|
|
/* Creates a Python application from pyproject.toml and poetry.lock
|
|
|
|
The result also contains a .dependencyEnv attribute which is a python
|
|
environment of all dependencies and this apps modules. This is useful if
|
|
you rely on dependencies to invoke your modules for deployment: e.g. this
|
|
allows `gunicorn my-module:app`.
|
|
*/
|
|
mkPoetryApplication =
|
|
{ projectDir ? null
|
|
, src ? poetryLib.cleanPythonSources { src = projectDir; }
|
|
, pyproject ? projectDir + "/pyproject.toml"
|
|
, poetrylock ? projectDir + "/poetry.lock"
|
|
, overrides ? [ defaultPoetryOverrides ]
|
|
, meta ? { }
|
|
, python ? pkgs.python3
|
|
, pwd ? projectDir
|
|
, preferWheels ? false
|
|
, __isBootstrap ? false # Hack: Always add Poetry as a build input unless bootstrapping
|
|
, ...
|
|
}@attrs:
|
|
let
|
|
poetryPython = mkPoetryPackages {
|
|
inherit pyproject poetrylock overrides python pwd preferWheels __isBootstrap;
|
|
};
|
|
py = poetryPython.python;
|
|
|
|
inherit (poetryPython) pyProject;
|
|
specialAttrs = [
|
|
"overrides"
|
|
"poetrylock"
|
|
"projectDir"
|
|
"pwd"
|
|
"pyproject"
|
|
"preferWheels"
|
|
];
|
|
passedAttrs = builtins.removeAttrs attrs specialAttrs;
|
|
|
|
# Get dependencies and filter out depending on interpreter version
|
|
getDeps = depAttr:
|
|
let
|
|
compat = isCompatible (poetryLib.getPythonVersion py);
|
|
deps = pyProject.tool.poetry.${depAttr} or { };
|
|
depAttrs = builtins.map (d: lib.toLower d) (builtins.attrNames deps);
|
|
in
|
|
builtins.map
|
|
(
|
|
dep:
|
|
let
|
|
pkg = py.pkgs."${moduleName dep}";
|
|
constraints = deps.${dep}.python or "";
|
|
isCompat = compat constraints;
|
|
in
|
|
if isCompat then pkg else null
|
|
)
|
|
depAttrs;
|
|
getInputs = attr: attrs.${attr} or [ ];
|
|
mkInput = attr: extraInputs: getInputs attr ++ extraInputs;
|
|
buildSystemPkgs = poetryLib.getBuildSystemPkgs {
|
|
inherit pyProject;
|
|
pythonPackages = py.pkgs;
|
|
};
|
|
app = py.pkgs.buildPythonPackage (
|
|
passedAttrs // {
|
|
pname = moduleName pyProject.tool.poetry.name;
|
|
version = pyProject.tool.poetry.version;
|
|
|
|
inherit src;
|
|
|
|
format = "pyproject";
|
|
# Like buildPythonApplication, but without the toPythonModule part
|
|
# Meaning this ends up looking like an application but it also
|
|
# provides python modules
|
|
namePrefix = "";
|
|
|
|
buildInputs = mkInput "buildInputs" buildSystemPkgs;
|
|
propagatedBuildInputs = mkInput "propagatedBuildInputs" (getDeps "dependencies") ++ ([ py.pkgs.setuptools ]);
|
|
nativeBuildInputs = mkInput "nativeBuildInputs" [ pkgs.yj py.pkgs.removePathDependenciesHook ];
|
|
checkInputs = mkInput "checkInputs" (getDeps "dev-dependencies");
|
|
|
|
passthru = {
|
|
python = py;
|
|
dependencyEnv = (
|
|
lib.makeOverridable ({ app, ... }@attrs:
|
|
let
|
|
args = builtins.removeAttrs attrs [ "app" ] // {
|
|
extraLibs = [ app ];
|
|
};
|
|
in
|
|
py.buildEnv.override args)
|
|
) { inherit app; };
|
|
};
|
|
|
|
meta = lib.optionalAttrs (lib.hasAttr "description" pyProject.tool.poetry) {
|
|
inherit (pyProject.tool.poetry) description;
|
|
} // lib.optionalAttrs (lib.hasAttr "homepage" pyProject.tool.poetry) {
|
|
inherit (pyProject.tool.poetry) homepage;
|
|
} // {
|
|
inherit (py.meta) platforms;
|
|
license = getLicenseBySpdxId (pyProject.tool.poetry.license or "unknown");
|
|
} // meta;
|
|
|
|
}
|
|
);
|
|
in
|
|
app;
|
|
|
|
/* Poetry2nix CLI used to supplement SHA-256 hashes for git dependencies */
|
|
cli = import ./cli.nix { inherit pkgs lib version; };
|
|
in
|
|
{
|
|
inherit mkPoetryEnv mkPoetryApplication mkPoetryPackages cli version;
|
|
|
|
inherit (poetryLib) cleanPythonSources;
|
|
|
|
/*
|
|
The default list of poetry2nix override overlays
|
|
|
|
Can be overriden by calling defaultPoetryOverrides.overrideOverlay which takes an overlay function
|
|
*/
|
|
defaultPoetryOverrides = {
|
|
__functor = defaultPoetryOverrides;
|
|
overrideOverlay = fn: self: super:
|
|
let
|
|
defaultSet = defaultPoetryOverrides self super;
|
|
customSet = fn self super;
|
|
in
|
|
defaultSet // customSet;
|
|
};
|
|
|
|
/*
|
|
Convenience functions for specifying overlays with or without the poerty2nix default overrides
|
|
*/
|
|
overrides = {
|
|
/*
|
|
Returns the specified overlay in a list
|
|
*/
|
|
withoutDefaults = overlay: [
|
|
overlay
|
|
];
|
|
|
|
/*
|
|
Returns the specified overlay and returns a list
|
|
combining it with poetry2nix default overrides
|
|
*/
|
|
withDefaults = overlay: [
|
|
defaultPoetryOverrides
|
|
overlay
|
|
];
|
|
};
|
|
}
|