nixpkgs/pkgs/development/tools/poetry2nix/poetry2nix/default.nix

420 lines
14 KiB
Nix
Raw Normal View History

2020-05-05 22:20:44 +02:00
{ pkgs ? import <nixpkgs> { }
2019-12-29 17:43:45 +01:00
, lib ? pkgs.lib
, poetry ? null
2021-02-02 14:12:10 +01:00
, poetryLib ? import ./lib.nix { inherit lib pkgs; stdenv = pkgs.stdenv; }
2019-12-29 17:43:45 +01:00
}:
let
2021-03-04 13:33:58 +01:00
# Poetry2nix version
2021-06-08 15:54:33 +02:00
version = "1.17.1";
2021-03-04 13:33:58 +01:00
2020-05-05 22:20:44 +02:00
inherit (poetryLib) isCompatible readTOML moduleName;
2019-12-29 17:43:45 +01:00
/* The default list of poetry2nix override overlays */
mkEvalPep508 = import ./pep508.nix {
2020-01-10 14:08:46 +01:00
inherit lib poetryLib;
2019-12-29 17:43:45 +01:00
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
2020-01-13 16:45:00 +01:00
getLicenseBySpdxId = spdxId: spdxLicenses.${spdxId} or spdxId;
2019-12-29 17:43:45 +01:00
2020-07-14 16:18:39 +02:00
# Experimental withPlugins functionality
toPluginAble = (import ./plugins.nix { inherit pkgs lib; }).toPluginAble;
2020-10-01 22:56:57 +02:00
mkInputAttrs =
{ py
, pyProject
, attrs
, includeBuildSystem ? true
}:
let
getInputs = attr: attrs.${attr} or [ ];
# 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
);
buildSystemPkgs = poetryLib.getBuildSystemPkgs {
inherit pyProject;
pythonPackages = py.pkgs;
};
mkInput = attr: extraInputs: getInputs attr ++ extraInputs;
in
{
buildInputs = mkInput "buildInputs" (if includeBuildSystem then buildSystemPkgs else [ ]);
propagatedBuildInputs = mkInput "propagatedBuildInputs" (getDeps "dependencies") ++ ([ py.pkgs.setuptools ]);
nativeBuildInputs = mkInput "nativeBuildInputs" [ ];
checkInputs = mkInput "checkInputs" (getDeps "dev-dependencies");
};
2020-07-22 16:57:19 +02:00
in
lib.makeScope pkgs.newScope (self: {
2021-03-04 13:33:58 +01:00
inherit version;
2021-02-02 14:12:10 +01:00
/* Returns a package of editable sources whose changes will be available without needing to restart the
nix-shell.
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.
*/
mkPoetryEditablePackage =
{ projectDir ? null
, pyproject ? projectDir + "/pyproject.toml"
, python ? pkgs.python3
, pyProject ? readTOML pyproject
# Example: { my-app = ./src; }
, editablePackageSources
}:
assert editablePackageSources != { };
import ./editable.nix {
inherit pyProject python pkgs lib poetryLib editablePackageSources;
};
/* Returns a package containing scripts defined in tool.poetry.scripts.
*/
mkPoetryScriptsPackage =
{ projectDir ? null
, pyproject ? projectDir + "/pyproject.toml"
, python ? pkgs.python3
, pyProject ? readTOML pyproject
, scripts ? pyProject.tool.poetry.scripts
}:
assert scripts != { };
import ./shell-scripts.nix {
inherit lib python scripts;
};
2020-07-14 16:18:39 +02:00
2020-02-24 14:36:01 +01:00
/*
Returns an attrset { python, poetryPackages, pyProject, poetryLock } for the given pyproject/lockfile.
*/
mkPoetryPackages =
2020-03-02 17:23:25 +01:00
{ projectDir ? null
, pyproject ? projectDir + "/pyproject.toml"
, poetrylock ? projectDir + "/poetry.lock"
2020-07-22 16:57:19 +02:00
, overrides ? self.defaultPoetryOverrides
2019-12-29 17:43:45 +01:00
, python ? pkgs.python3
2020-03-02 17:23:25 +01:00
, pwd ? projectDir
2020-05-05 22:20:44 +02:00
, preferWheels ? false
2021-02-02 14:12:10 +01:00
# Example: { my-app = ./src; }
, editablePackageSources ? { }
2020-07-14 16:18:39 +02:00
, __isBootstrap ? false # Hack: Always add Poetry as a build input unless bootstrapping
2020-03-27 15:49:31 +01:00
}@attrs:
2020-05-05 22:20:44 +02:00
let
poetryPkg = poetry.override { inherit python; };
pyProject = readTOML pyproject;
2021-02-02 14:12:10 +01:00
scripts = pyProject.tool.poetry.scripts or { };
hasScripts = scripts != { };
scriptsPackage = self.mkPoetryScriptsPackage {
inherit python scripts;
};
hasEditable = editablePackageSources != { };
editablePackage = self.mkPoetryEditablePackage {
inherit pyProject python editablePackageSources;
};
2020-05-05 22:20:44 +02:00
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
2020-10-01 22:56:57 +02:00
supportsPythonVersion = pkgMeta: if pkgMeta ? marker then (evalPep508 pkgMeta.marker) else true && isCompatible (poetryLib.getPythonVersion python) pkgMeta.python-versions;
2020-03-27 15:49:31 +01:00
in
2020-05-05 22:20:44 +02:00
lib.partition supportsPythonVersion poetryLock.package;
compatible = partitions.right;
incompatible = partitions.wrong;
2021-06-01 23:59:39 +02:00
# Create an overridden version of pythonPackages
2020-05-05 22:20:44 +02:00
#
# 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};
2020-05-28 22:40:34 +02:00
lockPkgs = builtins.listToAttrs (
builtins.map
(
pkgMeta: rec {
name = moduleName pkgMeta.name;
value = self.mkPoetryDep (
pkgMeta // {
inherit pwd preferWheels;
2020-07-14 16:18:39 +02:00
inherit __isBootstrap;
2020-05-28 22:40:34 +02:00
source = pkgMeta.source or null;
files = lockFiles.${name};
pythonPackages = self;
sourceSpec = pyProject.tool.poetry.dependencies.${name} or pyProject.tool.poetry.dev-dependencies.${name} or { };
}
);
}
)
2020-10-01 22:56:57 +02:00
(lib.reverseList compatible)
2020-05-28 22:40:34 +02:00
);
2020-05-05 22:20:44 +02:00
in
lockPkgs;
2020-05-28 22:40:34 +02:00
overlays = builtins.map
getFunctorFn
2020-05-05 22:20:44 +02:00
(
2020-03-27 15:49:31 +01:00
[
(
self: super:
let
2020-05-05 22:20:44 +02:00
hooks = self.callPackage ./hooks { };
2020-03-27 15:49:31 +01:00
in
2020-05-05 22:20:44 +02:00
{
mkPoetryDep = self.callPackage ./mk-poetry-dep.nix {
2020-10-01 22:56:57 +02:00
inherit pkgs lib python poetryLib evalPep508;
2020-05-05 22:20:44 +02:00
};
2020-10-01 22:56:57 +02:00
# Use poetry-core from the poetry build (pep517/518 build-system)
poetry-core = if __isBootstrap then null else poetryPkg.passthru.python.pkgs.poetry-core;
poetry = if __isBootstrap then null else poetryPkg;
2020-07-14 16:18:39 +02:00
__toPluginAble = toPluginAble self;
2020-08-26 15:24:02 +02:00
inherit (hooks) pipBuildHook removePathDependenciesHook poetry2nixFixupHook wheelUnpackHook;
2021-06-08 15:54:33 +02:00
} // lib.optionalAttrs (! super ? setuptools-scm) {
# The canonical name is setuptools-scm
setuptools-scm = super.setuptools_scm;
2020-05-05 22:20:44 +02:00
}
2020-03-27 15:49:31 +01:00
)
# Null out any filtered packages, we don't want python.pkgs from nixpkgs
2020-05-05 22:20:44 +02:00
(self: super: builtins.listToAttrs (builtins.map (x: { name = moduleName x.name; value = null; }) incompatible))
2020-03-27 15:49:31 +01:00
# Create poetry2nix layer
baseOverlay
] ++ # User provided overrides
2020-07-22 16:57:19 +02:00
(if builtins.typeOf overrides == "list" then overrides else [ overrides ])
2020-03-27 15:49:31 +01:00
);
2020-05-05 22:20:44 +02:00
packageOverrides = lib.foldr lib.composeExtensions (self: super: { }) overlays;
py = python.override { inherit packageOverrides; self = py; };
2020-10-01 22:56:57 +02:00
inputAttrs = mkInputAttrs { inherit py pyProject; attrs = { }; includeBuildSystem = false; };
2021-06-01 23:59:39 +02:00
requiredPythonModules = python.pkgs.requiredPythonModules;
/* Include all the nested dependencies which are required for each package.
This guarantees that using the "poetryPackages" attribute will return
complete list of dependencies for the poetry project to be portable.
*/
storePackages = requiredPythonModules (builtins.foldl' (acc: v: acc ++ v) [ ] (lib.attrValues inputAttrs));
2020-05-05 22:20:44 +02:00
in
{
python = py;
2021-02-02 14:12:10 +01:00
poetryPackages = storePackages
++ lib.optional hasScripts scriptsPackage
++ lib.optional hasEditable editablePackage;
2020-05-05 22:20:44 +02:00
poetryLock = poetryLock;
inherit pyProject;
};
2019-12-29 17:43:45 +01:00
/* Returns a package with a python interpreter and all packages specified in the poetry.lock lock file.
2020-05-28 22:40:34 +02:00
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.
2019-12-29 17:43:45 +01:00
Example:
poetry2nix.mkPoetryEnv { poetrylock = ./poetry.lock; python = python3; }
*/
mkPoetryEnv =
2020-03-02 17:23:25 +01:00
{ projectDir ? null
, pyproject ? projectDir + "/pyproject.toml"
, poetrylock ? projectDir + "/poetry.lock"
2020-07-22 16:57:19 +02:00
, overrides ? self.defaultPoetryOverrides
2020-03-02 17:23:25 +01:00
, pwd ? projectDir
2019-12-29 17:43:45 +01:00
, python ? pkgs.python3
2020-05-05 22:20:44 +02:00
, preferWheels ? false
2020-05-28 22:40:34 +02:00
, editablePackageSources ? { }
2019-12-29 17:43:45 +01:00
}:
2020-05-05 22:20:44 +02:00
let
2021-02-02 14:12:10 +01:00
poetryPython = self.mkPoetryPackages {
inherit pyproject poetrylock overrides python pwd preferWheels editablePackageSources;
2020-08-26 15:24:02 +02:00
};
2021-02-02 14:12:10 +01:00
inherit (poetryPython) poetryPackages;
2020-05-28 22:40:34 +02:00
2020-05-05 22:20:44 +02:00
in
2021-02-02 14:12:10 +01:00
poetryPython.python.withPackages (_: poetryPackages);
2019-12-29 17:43:45 +01:00
2020-05-28 22:40:34 +02:00
/* 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`.
*/
2019-12-29 17:43:45 +01:00
mkPoetryApplication =
2020-03-02 17:23:25 +01:00
{ projectDir ? null
2020-07-22 16:57:19 +02:00
, src ? self.cleanPythonSources { src = projectDir; }
2020-03-02 17:23:25 +01:00
, pyproject ? projectDir + "/pyproject.toml"
, poetrylock ? projectDir + "/poetry.lock"
2020-07-22 16:57:19 +02:00
, overrides ? self.defaultPoetryOverrides
2020-05-05 22:20:44 +02:00
, meta ? { }
2019-12-29 17:43:45 +01:00
, python ? pkgs.python3
2020-03-02 17:23:25 +01:00
, pwd ? projectDir
2020-05-05 22:20:44 +02:00
, preferWheels ? false
2020-07-14 16:18:39 +02:00
, __isBootstrap ? false # Hack: Always add Poetry as a build input unless bootstrapping
2019-12-29 17:43:45 +01:00
, ...
2020-03-27 15:49:31 +01:00
}@attrs:
2020-05-05 22:20:44 +02:00
let
2020-07-22 16:57:19 +02:00
poetryPython = self.mkPoetryPackages {
2020-07-14 16:18:39 +02:00
inherit pyproject poetrylock overrides python pwd preferWheels __isBootstrap;
2020-05-05 22:20:44 +02:00
};
py = poetryPython.python;
inherit (poetryPython) pyProject;
specialAttrs = [
"overrides"
"poetrylock"
"projectDir"
"pwd"
"pyproject"
"preferWheels"
];
passedAttrs = builtins.removeAttrs attrs specialAttrs;
2020-10-01 22:56:57 +02:00
inputAttrs = mkInputAttrs { inherit py pyProject attrs; };
2020-05-28 22:40:34 +02:00
app = py.pkgs.buildPythonPackage (
2020-10-01 22:56:57 +02:00
passedAttrs // inputAttrs // {
nativeBuildInputs = inputAttrs.nativeBuildInputs ++ [ py.pkgs.removePathDependenciesHook ];
} // {
2020-05-05 22:20:44 +02:00
pname = moduleName pyProject.tool.poetry.name;
version = pyProject.tool.poetry.version;
inherit src;
format = "pyproject";
2020-05-28 22:40:34 +02:00
# Like buildPythonApplication, but without the toPythonModule part
# Meaning this ends up looking like an application but it also
# provides python modules
namePrefix = "";
2020-05-05 22:20:44 +02:00
passthru = {
python = py;
2020-05-28 22:40:34 +02:00
dependencyEnv = (
lib.makeOverridable ({ app, ... }@attrs:
let
args = builtins.removeAttrs attrs [ "app" ] // {
extraLibs = [ app ];
};
in
py.buildEnv.override args)
) { inherit app; };
2020-05-05 22:20:44 +02:00
};
2020-10-01 22:56:57 +02:00
meta = lib.optionalAttrs (lib.hasAttr "description" pyProject.tool.poetry)
{
inherit (pyProject.tool.poetry) description;
} // lib.optionalAttrs (lib.hasAttr "homepage" pyProject.tool.poetry) {
2020-05-28 22:40:34 +02:00
inherit (pyProject.tool.poetry) homepage;
} // {
2020-05-05 22:20:44 +02:00
inherit (py.meta) platforms;
license = getLicenseBySpdxId (pyProject.tool.poetry.license or "unknown");
2020-05-28 22:40:34 +02:00
} // meta;
2020-05-05 22:20:44 +02:00
}
);
2020-05-28 22:40:34 +02:00
in
app;
2019-12-29 17:43:45 +01:00
/* Poetry2nix CLI used to supplement SHA-256 hashes for git dependencies */
2020-07-22 16:57:19 +02:00
cli = import ./cli.nix {
inherit pkgs lib;
inherit (self) version;
};
# inherit mkPoetryEnv mkPoetryApplication mkPoetryPackages;
2020-03-02 17:23:25 +01:00
inherit (poetryLib) cleanPythonSources;
2019-12-29 17:43:45 +01:00
2020-07-22 16:57:19 +02:00
/*
Create a new default set of overrides with the same structure as the built-in ones
2019-12-29 17:43:45 +01:00
*/
2020-07-22 16:57:19 +02:00
mkDefaultPoetryOverrides = defaults: {
__functor = defaults;
extend = overlay:
let
composed = lib.foldr lib.composeExtensions overlay [ defaults ];
in
self.mkDefaultPoetryOverrides composed;
overrideOverlay = fn:
2020-03-27 15:49:31 +01:00
let
2020-07-22 16:57:19 +02:00
overlay = self: super:
let
defaultSet = defaults self super;
customSet = fn self super;
in
defaultSet // customSet;
2020-03-27 15:49:31 +01:00
in
2020-07-22 16:57:19 +02:00
self.mkDefaultPoetryOverrides overlay;
2019-12-29 17:43:45 +01:00
};
2020-03-02 17:23:25 +01:00
2020-07-22 16:57:19 +02:00
/*
The default list of poetry2nix override overlays
Can be overriden by calling defaultPoetryOverrides.overrideOverlay which takes an overlay function
*/
defaultPoetryOverrides = self.mkDefaultPoetryOverrides (import ./overrides.nix { inherit pkgs lib; });
2020-03-02 17:23:25 +01:00
/*
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: [
2020-07-22 16:57:19 +02:00
self.defaultPoetryOverrides
2020-03-02 17:23:25 +01:00
overlay
];
};
2020-07-22 16:57:19 +02:00
})