diff --git a/flake.nix b/flake.nix index 0325f33b1060..acc08ad048d3 100644 --- a/flake.nix +++ b/flake.nix @@ -388,6 +388,44 @@ postUnpack = "sourceRoot=$sourceRoot/perl"; }); + passthru.python-bindings = + let + python = final.python3; + in python.pkgs.buildPythonPackage { + name = "nix"; + format = "other"; + + src = self; + postUnpack = "sourceRoot=$sourceRoot/python"; + + nativeBuildInputs = [ + final.buildPackages.ninja + (final.buildPackages.meson.override { python3 = python; }) + final.buildPackages.pkgconfig + final.buildPackages.nix + ]; + + strictDeps = true; + + buildInputs = [ + final.nix + final.boost + ]; + + passthru.shell = python.withPackages (p: [ + final.nix.python-bindings + ]); + + installCheckPhase = '' + export TEST_ROOT=$(mktemp -d) + export NIX_STATE_DIR=$TEST_ROOT/var/nix + + export PYTHONPATH=$out/${python.sitePackages} + + ninja test + ''; + }; + meta.platforms = systems; }; @@ -442,6 +480,8 @@ # Perl bindings for various platforms. perlBindings = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.perl-bindings); + pythonBindings = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.python-bindings); + # Binary tarball for various platforms, containing a Nix store # with the closure of 'nix' package, and the second half of # the installation script. @@ -564,6 +604,7 @@ checks = forAllSystems (system: { binaryTarball = self.hydraJobs.binaryTarball.${system}; perlBindings = self.hydraJobs.perlBindings.${system}; + pythonBindings = self.hydraJobs.pythonBindings.${system}; installTests = self.hydraJobs.installTests.${system}; nixpkgsLibTests = self.hydraJobs.tests.nixpkgsLibTests.${system}; } // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems)) { diff --git a/python/meson.build b/python/meson.build index f7d86300c474..30678c45e8e2 100644 --- a/python/meson.build +++ b/python/meson.build @@ -6,6 +6,7 @@ project('python-nix', 'cpp', python_mod = import('python3') python_dep = dependency('python3', required : true) nix_expr_dep = dependency('nix-expr', required: true) +nix_main_dep = dependency('nix-main', required: true) python = python_mod.find_python() test('python test', python, args : files('tests.py')) diff --git a/python/src/eval.cc b/python/src/eval.cc index b3e5af118ee3..9a7ee4cb7b28 100644 --- a/python/src/eval.cc +++ b/python/src/eval.cc @@ -6,6 +6,7 @@ #include #include +#include namespace pythonnix { @@ -25,8 +26,9 @@ static PyObject *_eval(const char *expression, PyObject *vars) { if (!staticEnv) { return nullptr; } + auto staticEnvPointer = std::make_shared(*staticEnv); - auto e = state.parseExprFromString(expression, ".", *staticEnv); + auto e = state.parseExprFromString(expression, ".", staticEnvPointer); nix::Value v; e->eval(state, *env, v); @@ -40,6 +42,9 @@ PyObject *eval(PyObject *self, PyObject *args, PyObject *keywds) { const char *expression = nullptr; PyObject *vars = nullptr; + // TODO: Only do this once + nix::initNix(); + const char *kwlist[] = {"expression", "vars", nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|O!", diff --git a/python/src/meson.build b/python/src/meson.build index a50f866d84bd..499af79b3692 100644 --- a/python/src/meson.build +++ b/python/src/meson.build @@ -6,7 +6,7 @@ src = [ ] python_mod.extension_module('nix', src, - dependencies : [python_dep, nix_expr_dep], + dependencies : [python_dep, nix_expr_dep, nix_main_dep], install: true, install_dir: python_mod.sysconfig_path('platlib'), cpp_args: ['-std=c++17', '-fvisibility=hidden']) diff --git a/python/src/nix-to-python.cc b/python/src/nix-to-python.cc index 399f8d6f2ce8..63793f56ac95 100644 --- a/python/src/nix-to-python.cc +++ b/python/src/nix-to-python.cc @@ -22,8 +22,10 @@ PyObject *nixToPythonObject(nix::EvalState &state, nix::Value &v, copyContext(v, context); return PyUnicode_FromString(v.string.s); - case nix::nPath: - return PyUnicode_FromString(state.copyPathToStore(context, v.path).c_str()); + case nix::nPath: { + auto p = state.copyPathToStore(context, v.path).to_string(); + return PyUnicode_FromStringAndSize(p.data(), p.length()); + } case nix::nNull: Py_RETURN_NONE; @@ -36,19 +38,13 @@ PyObject *nixToPythonObject(nix::EvalState &state, nix::Value &v, return (PyObject *)nullptr; } - nix::StringSet names; - for (auto &j : *v.attrs) { - names.insert(j.name); - } - for (auto &j : names) { - nix::Attr &a(*v.attrs->find(state.symbols.create(j))); - - auto value = nixToPythonObject(state, *a.value, context); + const std::string & name = state.symbols[j.name]; + auto value = nixToPythonObject(state, *j.value, context); if (!value) { return nullptr; } - PyDict_SetItemString(dict.get(), j.c_str(), value); + PyDict_SetItemString(dict.get(), name.c_str(), value); } return dict.release(); } else { diff --git a/python/src/python-to-nix.cc b/python/src/python-to-nix.cc index f79134dfa4ac..f6326eef488b 100644 --- a/python/src/python-to-nix.cc +++ b/python/src/python-to-nix.cc @@ -70,16 +70,16 @@ nix::Value *pythonToNixValue(nix::EvalState &state, PyObject *obj) { auto v = state.allocValue(); if (obj == Py_True && obj == Py_False) { - nix::mkBool(*v, obj == Py_True); + v->mkBool(obj == Py_True); } else if (obj == Py_None) { - nix::mkNull(*v); + v->mkNull(); } else if (PyBytes_Check(obj)) { auto str = checkNullByte(PyBytes_AS_STRING(obj), PyBytes_GET_SIZE(obj)); if (!str) { return nullptr; } - nix::mkString(*v, str); + v->mkString(str); } else if (PyUnicode_Check(obj)) { Py_ssize_t size; const char *utf8 = PyUnicode_AsUTF8AndSize(obj, &size); @@ -88,13 +88,13 @@ nix::Value *pythonToNixValue(nix::EvalState &state, PyObject *obj) { return nullptr; } - nix::mkString(*v, utf8); + v->mkString(utf8); } else if (PyFloat_Check(obj)) { - nix::mkFloat(*v, PyFloat_AS_DOUBLE(obj)); + v->mkFloat(PyFloat_AS_DOUBLE(obj)); } else if (PyLong_Check(obj)) { - nix::mkInt(*v, PyLong_AsLong(obj)); + v->mkInt(PyLong_AsLong(obj)); } else if (PyList_Check(obj)) { - state.mkList(*v, PyList_GET_SIZE(obj)); + v->mkList(PyList_GET_SIZE(obj)); for (Py_ssize_t i = 0; i < PyList_GET_SIZE(obj); i++) { auto val = pythonToNixValue(state, PyList_GET_ITEM(obj, i)); if (!val) { @@ -103,7 +103,7 @@ nix::Value *pythonToNixValue(nix::EvalState &state, PyObject *obj) { v->listElems()[i] = val; } } else if (PyTuple_Check(obj)) { - state.mkList(*v, PyTuple_GET_SIZE(obj)); + v->mkList(PyTuple_GET_SIZE(obj)); for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(obj); i++) { auto val = pythonToNixValue(state, PyTuple_GET_ITEM(obj, i)); if (!val) { @@ -116,11 +116,12 @@ nix::Value *pythonToNixValue(nix::EvalState &state, PyObject *obj) { if (!attrs) { return nullptr; } - state.mkAttrs(*v, attrs->size()); - for (auto &attr : *attrs) { - v->attrs->push_back(nix::Attr(attr.first, attr.second)); + auto attrsValue = attrs.value(); + auto bindings = state.buildBindings(attrsValue.size()); + for (auto &attr : attrsValue) { + bindings.insert(attr.first, attr.second); } - v->attrs->sort(); + v->mkAttrs(bindings); } return v; } @@ -133,7 +134,7 @@ std::optional pythonToNixEnv(nix::EvalState &state, *env = &state.allocEnv(vars ? PyDict_Size(vars) : 0); (*env)->up = &state.baseEnv; - nix::StaticEnv staticEnv(false, &state.staticBaseEnv); + nix::StaticEnv staticEnv(false, state.staticBaseEnv.get()); if (!vars) { return staticEnv; @@ -150,7 +151,7 @@ std::optional pythonToNixEnv(nix::EvalState &state, if (!attrVal) { return {}; } - staticEnv.vars[state.symbols.create(name)] = displ; + staticEnv.vars.emplace_back(state.symbols.create(name), displ); (*env)->values[displ++] = attrVal; }