diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27f60574e0c..cb97fd211dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,7 +128,7 @@ jobs: - run: exec bash -c "nix-channel --update && nix-env -iA nixpkgs.hello && hello" docker_push_image: - needs: [check_secrets, tests] + needs: [check_secrets, tests, vm_tests] permissions: contents: read packages: write @@ -194,7 +194,13 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main - - run: nix build -L .#hydraJobs.tests.githubFlakes .#hydraJobs.tests.tarballFlakes .#hydraJobs.tests.functional_user + - run: | + nix build -L \ + .#hydraJobs.tests.functional_user \ + .#hydraJobs.tests.githubFlakes \ + .#hydraJobs.tests.nix-docker \ + .#hydraJobs.tests.tarballFlakes \ + ; flake_regressions: needs: vm_tests diff --git a/doc/manual/source/installation/installing-docker.md b/doc/manual/source/installation/installing-docker.md index 6f77d6a5708..9354c1a7228 100644 --- a/doc/manual/source/installation/installing-docker.md +++ b/doc/manual/source/installation/installing-docker.md @@ -57,3 +57,21 @@ $ nix build ./\#hydraJobs.dockerImage.x86_64-linux $ docker load -i ./result/image.tar.gz $ docker run -ti nix:2.5pre20211105 ``` + +# Docker image with non-root Nix + +If you would like to run Nix in a container under a user other than `root`, +you can build an image with a non-root single-user installation of Nix +by specifying the `uid`, `gid`, `uname`, and `gname` arguments to `docker.nix`: + +```console +$ nix build --file docker.nix \ + --arg uid 1000 \ + --arg gid 1000 \ + --argstr uname user \ + --argstr gname user \ + --argstr name nix-user \ + --out-link nix-user.tar.gz +$ docker load -i nix-user.tar.gz +$ docker run -ti nix-user +``` diff --git a/docker.nix b/docker.nix index bd16b71cd71..19479e57c18 100644 --- a/docker.nix +++ b/docker.nix @@ -9,6 +9,10 @@ , maxLayers ? 100 , nixConf ? {} , flake-registry ? null +, uid ? 0 +, gid ? 0 +, uname ? "root" +, gname ? "root" }: let defaultPkgs = with pkgs; [ @@ -50,6 +54,15 @@ let description = "Unprivileged account (don't use!)"; }; + } // lib.optionalAttrs (uid != 0) { + "${uname}" = { + uid = uid; + shell = "${pkgs.bashInteractive}/bin/bash"; + home = "/home/${uname}"; + gid = gid; + groups = [ "${gname}" ]; + description = "Nix user"; + }; } // lib.listToAttrs ( map ( @@ -70,6 +83,8 @@ let root.gid = 0; nixbld.gid = 30000; nobody.gid = 65534; + } // lib.optionalAttrs (gid != 0) { + "${gname}".gid = gid; }; userToPasswd = ( @@ -150,6 +165,8 @@ let in "${n} = ${vStr}") (defaultNixConf // nixConf))) + "\n"; + userHome = if uid == 0 then "/root" else "/home/${uname}"; + baseSystem = let nixpkgs = pkgs.path; @@ -237,26 +254,26 @@ let mkdir -p $out/etc/nix cat $nixConfContentsPath > $out/etc/nix/nix.conf - mkdir -p $out/root - mkdir -p $out/nix/var/nix/profiles/per-user/root + mkdir -p $out${userHome} + mkdir -p $out/nix/var/nix/profiles/per-user/${uname} ln -s ${profile} $out/nix/var/nix/profiles/default-1-link ln -s $out/nix/var/nix/profiles/default-1-link $out/nix/var/nix/profiles/default - ln -s /nix/var/nix/profiles/default $out/root/.nix-profile + ln -s /nix/var/nix/profiles/default $out${userHome}/.nix-profile - ln -s ${channel} $out/nix/var/nix/profiles/per-user/root/channels-1-link - ln -s $out/nix/var/nix/profiles/per-user/root/channels-1-link $out/nix/var/nix/profiles/per-user/root/channels + ln -s ${channel} $out/nix/var/nix/profiles/per-user/${uname}/channels-1-link + ln -s $out/nix/var/nix/profiles/per-user/${uname}/channels-1-link $out/nix/var/nix/profiles/per-user/${uname}/channels - mkdir -p $out/root/.nix-defexpr - ln -s $out/nix/var/nix/profiles/per-user/root/channels $out/root/.nix-defexpr/channels - echo "${channelURL} ${channelName}" > $out/root/.nix-channels + mkdir -p $out${userHome}/.nix-defexpr + ln -s $out/nix/var/nix/profiles/per-user/${uname}/channels $out${userHome}/.nix-defexpr/channels + echo "${channelURL} ${channelName}" > $out${userHome}/.nix-channels mkdir -p $out/bin $out/usr/bin ln -s ${pkgs.coreutils}/bin/env $out/usr/bin/env ln -s ${pkgs.bashInteractive}/bin/bash $out/bin/sh '' + (lib.optionalString (flake-registry-path != null) '' - nixCacheDir="/root/.cache/nix" + nixCacheDir="${userHome}/.cache/nix" mkdir -p $out$nixCacheDir globalFlakeRegistryPath="$nixCacheDir/flake-registry.json" ln -s ${flake-registry-path} $out$globalFlakeRegistryPath @@ -268,7 +285,7 @@ let in pkgs.dockerTools.buildLayeredImageWithNixDb { - inherit name tag maxLayers; + inherit name tag maxLayers uid gid uname gname; contents = [ baseSystem ]; @@ -279,25 +296,28 @@ pkgs.dockerTools.buildLayeredImageWithNixDb { fakeRootCommands = '' chmod 1777 tmp chmod 1777 var/tmp + chown -R ${toString uid}:${toString gid} .${userHome} + chown -R ${toString uid}:${toString gid} nix ''; config = { - Cmd = [ "/root/.nix-profile/bin/bash" ]; + Cmd = [ "${userHome}/.nix-profile/bin/bash" ]; + User = "${toString uid}:${toString gid}"; Env = [ - "USER=root" + "USER=${uname}" "PATH=${lib.concatStringsSep ":" [ - "/root/.nix-profile/bin" + "${userHome}/.nix-profile/bin" "/nix/var/nix/profiles/default/bin" "/nix/var/nix/profiles/default/sbin" ]}" "MANPATH=${lib.concatStringsSep ":" [ - "/root/.nix-profile/share/man" + "${userHome}/.nix-profile/share/man" "/nix/var/nix/profiles/default/share/man" ]}" "SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt" "GIT_SSL_CAINFO=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt" "NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt" - "NIX_PATH=/nix/var/nix/profiles/per-user/root/channels:/root/.nix-defexpr/channels" + "NIX_PATH=/nix/var/nix/profiles/per-user/${uname}/channels:${userHome}/.nix-defexpr/channels" ]; }; diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix index 13766f2c040..a8005ce16c9 100644 --- a/packaging/dependencies.nix +++ b/packaging/dependencies.nix @@ -70,6 +70,9 @@ let pkgs.buildPackages.meson pkgs.buildPackages.ninja ] ++ prevAttrs.nativeBuildInputs or []; + mesonCheckFlags = prevAttrs.mesonCheckFlags or [] ++ [ + "--print-errorlogs" + ]; }; mesonBuildLayer = finalAttrs: prevAttrs: diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index bc882c6df12..b346e64c886 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -29,12 +29,12 @@ EvalSettings evalSettings { { { "flake", - [](ref store, std::string_view rest) { + [](EvalState & state, std::string_view rest) { experimentalFeatureSettings.require(Xp::Flakes); // FIXME `parseFlakeRef` should take a `std::string_view`. auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false); debug("fetching flake search path element '%s''", rest); - auto [accessor, _] = flakeRef.resolve(store).lazyFetch(store); + auto [accessor, _] = flakeRef.resolve(state.store).lazyFetch(state.store); return SourcePath(accessor); }, }, diff --git a/src/libexpr-c/nix_api_expr.cc b/src/libexpr-c/nix_api_expr.cc index 333e99460ba..6144a7986c4 100644 --- a/src/libexpr-c/nix_api_expr.cc +++ b/src/libexpr-c/nix_api_expr.cc @@ -67,7 +67,7 @@ nix_err nix_value_call_multi(nix_c_context * context, EvalState * state, nix_val if (context) context->last_err_code = NIX_OK; try { - state->state.callFunction(fn->value, nargs, (nix::Value * *)args, value->value, nix::noPos); + state->state.callFunction(fn->value, {(nix::Value * *) args, nargs}, value->value, nix::noPos); state->state.forceValue(value->value, nix::noPos); } NIXC_CATCH_ERRS diff --git a/src/libexpr-test-support/tests/libexpr.hh b/src/libexpr-test-support/tests/libexpr.hh index 045607e875e..095ea1d0e4b 100644 --- a/src/libexpr-test-support/tests/libexpr.hh +++ b/src/libexpr-test-support/tests/libexpr.hh @@ -40,6 +40,12 @@ namespace nix { return v; } + Value * maybeThunk(std::string input, bool forceValue = true) { + Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root)); + assert(e); + return e->maybeThunk(state, state.baseEnv); + } + Symbol createSymbol(const char * value) { return state.symbols.create(value); } diff --git a/src/libexpr-tests/eval.cc b/src/libexpr-tests/eval.cc index 93d3f658f52..61f6be0db6f 100644 --- a/src/libexpr-tests/eval.cc +++ b/src/libexpr-tests/eval.cc @@ -138,4 +138,27 @@ TEST(nix_isAllowedURI, non_scheme_colon) { ASSERT_FALSE(isAllowedURI("https://foo/bar:baz", allowed)); } -} // namespace nix \ No newline at end of file +class EvalStateTest : public LibExprTest {}; + +TEST_F(EvalStateTest, getBuiltins_ok) { + auto evaled = maybeThunk("builtins"); + auto & builtins = state.getBuiltins(); + ASSERT_TRUE(builtins.type() == nAttrs); + ASSERT_EQ(evaled, &builtins); +} + +TEST_F(EvalStateTest, getBuiltin_ok) { + auto & builtin = state.getBuiltin("toString"); + ASSERT_TRUE(builtin.type() == nFunction); + // FIXME + // auto evaled = maybeThunk("builtins.toString"); + // ASSERT_EQ(evaled, &builtin); + auto & builtin2 = state.getBuiltin("true"); + ASSERT_EQ(state.forceBool(builtin2, noPos, "in unit test"), true); +} + +TEST_F(EvalStateTest, getBuiltin_fail) { + ASSERT_THROW(state.getBuiltin("nonexistent"), EvalError); +} + +} // namespace nix diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh index 805e13ad56c..a8fcce539d7 100644 --- a/src/libexpr/eval-settings.hh +++ b/src/libexpr/eval-settings.hh @@ -7,7 +7,7 @@ namespace nix { -class Store; +class EvalState; struct EvalSettings : Config { @@ -20,7 +20,7 @@ struct EvalSettings : Config * The return value is (a) whether the entry was valid, and, if so, * what does it map to. */ - using LookupPathHook = std::optional(ref store, std::string_view); + using LookupPathHook = std::optional(EvalState & state, std::string_view); /** * Map from "scheme" to a `LookupPathHook`. diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d92701828db..0a4cfcb5a1e 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -454,7 +454,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info) /* Install value the base environment. */ staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(name2), v)); + getBuiltins().payload.attrs->push_back(Attr(symbols.create(name2), v)); } } @@ -522,16 +522,26 @@ Value * EvalState::addPrimOp(PrimOp && primOp) else { staticBaseEnv->vars.emplace_back(envName, baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(primOp.name), v)); + getBuiltins().payload.attrs->push_back(Attr(symbols.create(primOp.name), v)); } return v; } +Value & EvalState::getBuiltins() +{ + return *baseEnv.values[0]; +} + + Value & EvalState::getBuiltin(const std::string & name) { - return *baseEnv.values[0]->attrs()->find(symbols.create(name))->value; + auto it = getBuiltins().attrs()->get(symbols.create(name)); + if (it) + return *it->value; + else + error("builtin '%1%' not found", name).debugThrow(); } @@ -594,14 +604,14 @@ std::optional EvalState::getDoc(Value & v) if (isFunctor(v)) { try { Value & functor = *v.attrs()->find(sFunctor)->value; - Value * vp = &v; + Value * vp[] = {&v}; Value partiallyApplied; // The first paramater is not user-provided, and may be // handled by code that is opaque to the user, like lib.const = x: y: y; // So preferably we show docs that are relevant to the // "partially applied" function returned by e.g. `const`. // We apply the first argument: - callFunction(functor, 1, &vp, partiallyApplied, noPos); + callFunction(functor, vp, partiallyApplied, noPos); auto _level = addCallDepth(noPos); return getDoc(partiallyApplied); } @@ -1476,7 +1486,7 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v) v.mkLambda(&env, this); } -void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos) +void EvalState::callFunction(Value & fun, std::span args, Value & vRes, const PosIdx pos) { auto _level = addCallDepth(pos); @@ -1491,16 +1501,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto makeAppChain = [&]() { vRes = vCur; - for (size_t i = 0; i < nrArgs; ++i) { + for (auto arg : args) { auto fun2 = allocValue(); *fun2 = vRes; - vRes.mkPrimOpApp(fun2, args[i]); + vRes.mkPrimOpApp(fun2, arg); } }; const Attr * functor; - while (nrArgs > 0) { + while (args.size() > 0) { if (vCur.isLambda()) { @@ -1603,15 +1613,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & throw; } - nrArgs--; - args += 1; + args = args.subspan(1); } else if (vCur.isPrimOp()) { size_t argsLeft = vCur.primOp()->arity; - if (nrArgs < argsLeft) { + if (args.size() < argsLeft) { /* We don't have enough arguments, so create a tPrimOpApp chain. */ makeAppChain(); return; @@ -1623,15 +1632,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (countCalls) primOpCalls[fn->name]++; try { - fn->fun(*this, vCur.determinePos(noPos), args, vCur); + fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur); } catch (Error & e) { if (fn->addTrace) addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); throw; } - nrArgs -= argsLeft; - args += argsLeft; + args = args.subspan(argsLeft); } } @@ -1647,7 +1655,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto arity = primOp->primOp()->arity; auto argsLeft = arity - argsDone; - if (nrArgs < argsLeft) { + if (args.size() < argsLeft) { /* We still don't have enough arguments, so extend the tPrimOpApp chain. */ makeAppChain(); return; @@ -1679,8 +1687,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & throw; } - nrArgs -= argsLeft; - args += argsLeft; + args = args.subspan(argsLeft); } } @@ -1691,13 +1698,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & Value * args2[] = {allocValue(), args[0]}; *args2[0] = vCur; try { - callFunction(*functor->value, 2, args2, vCur, functor->pos); + callFunction(*functor->value, args2, vCur, functor->pos); } catch (Error & e) { e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)"); throw; } - nrArgs--; - args++; + args = args.subspan(1); } else @@ -1740,7 +1746,7 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v) for (size_t i = 0; i < args.size(); ++i) vArgs[i] = args[i]->maybeThunk(state, env); - state.callFunction(vFun, args.size(), vArgs.data(), v, pos); + state.callFunction(vFun, vArgs, v, pos); } @@ -3154,7 +3160,7 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pat auto scheme = value.substr(0, colPos); auto rest = value.substr(colPos + 1); if (auto * hook = get(settings.lookupPathHooks, scheme)) { - auto res = (*hook)(store, rest); + auto res = (*hook)(*this, rest); if (res) return finish(std::move(*res)); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 5733901a202..11190fb6493 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -481,7 +481,7 @@ public: * * If the specified search path element is a URI, download it. * - * If it is not found, return `std::nullopt` + * If it is not found, return `std::nullopt`. */ std::optional resolveLookupPathPath( const LookupPath::Path & elem, @@ -667,8 +667,19 @@ private: public: + /** + * Retrieve a specific builtin, equivalent to evaluating `builtins.${name}`. + * @param name The attribute name of the builtin to retrieve. + * @throws EvalError if the builtin does not exist. + */ Value & getBuiltin(const std::string & name); + /** + * Retrieve the `builtins` attrset, equivalent to evaluating the reference `builtins`. + * Always returns an attribute set value. + */ + Value & getBuiltins(); + struct Doc { Pos pos; @@ -734,13 +745,12 @@ public: bool isFunctor(Value & fun); - // FIXME: use std::span - void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos); + void callFunction(Value & fun, std::span args, Value & vRes, const PosIdx pos); void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos) { Value * args[] = {&arg}; - callFunction(fun, 1, args, vRes, pos); + callFunction(fun, args, vRes, pos); } /** diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index fa19b4f7b90..895ba7ce9ff 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -731,7 +731,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a /* Call the `operator' function with `e' as argument. */ Value newElements; - state.callFunction(*op->value, 1, &e, newElements, noPos); + state.callFunction(*op->value, {&e, 1}, newElements, noPos); state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure"); /* Add the values returned by the operator to the work set. */ @@ -2505,7 +2505,7 @@ bool EvalState::callPathFilter( // assert that type is not "unknown" Value * args []{&arg1, fileTypeToString(*this, st.type)}; Value res; - callFunction(*filterFun, 2, args, res, pos); + callFunction(*filterFun, args, res, pos); return forceBool(res, pos, "while evaluating the return value of the path filter function"); } @@ -3555,7 +3555,7 @@ static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args for (auto [n, elem] : enumerate(args[2]->listItems())) { Value * vs []{vCur, elem}; vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue(); - state.callFunction(*args[0], 2, vs, *vCur, pos); + state.callFunction(*args[0], vs, *vCur, pos); } state.forceValue(v, pos); } else { @@ -3705,7 +3705,7 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value Value * vs[] = {a, b}; Value vBool; - state.callFunction(*args[0], 2, vs, vBool, noPos); + state.callFunction(*args[0], vs, vBool, noPos); return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort"); }; @@ -5005,7 +5005,7 @@ void EvalState::createBaseEnv() /* Now that we've added all primops, sort the `builtins' set, because attribute lookups expect it to be sorted. */ - baseEnv.values[0]->payload.attrs->sort(); + getBuiltins().payload.attrs->sort(); staticBaseEnv->sort(); diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index 17de25ed55b..8e5b8a913c2 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -849,7 +849,7 @@ void callFlake(EvalState & state, assert(vFetchFinalTree); Value * args[] = {vLocks, &vOverrides, *vFetchFinalTree}; - state.callFunction(*vCallFlake, 3, args, vRes, noPos); + state.callFunction(*vCallFlake, args, vRes, noPos); } void initLib(const Settings & settings) diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index c394836da2f..de01e1afcde 100644 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -340,13 +340,15 @@ static void main_nix_build(int argc, char * * argv) exprs = {state->parseStdin()}; else for (auto i : remainingArgs) { - auto baseDir = inShebang && !packages ? absPath(dirOf(script)) : i; - - if (fromArgs) + if (fromArgs) { + auto shebangBaseDir = absPath(dirOf(script)); exprs.push_back(state->parseExprFromString( std::move(i), - (inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript) ? lookupFileArg(*state, baseDir) : state->rootPath(".") + (inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript) + ? lookupFileArg(*state, shebangBaseDir) + : state->rootPath(".") )); + } else { auto absolute = i; try { diff --git a/src/nix/flake.md b/src/nix/flake.md index 78be3232b9c..200ced938e5 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -84,6 +84,8 @@ Here are some examples of flake references in their URL-like representation: repository on GitHub. * `github:NixOS/nixpkgs/nixos-20.09`: The `nixos-20.09` branch of the `nixpkgs` repository. +* `github:NixOS/nixpkgs/pull/357207/head`: The `357207` pull request + of the nixpkgs repository. * `github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293`: A specific revision of the `nixpkgs` repository. * `github:edolstra/nix-warez?dir=blender`: A flake in a subdirectory diff --git a/src/nix/main.cc b/src/nix/main.cc index eff2d60a473..b0e26e093f1 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -435,7 +435,8 @@ void mainWrapped(int argc, char * * argv) evalSettings.pureEval = false; EvalState state({}, openStore("dummy://"), fetchSettings, evalSettings); auto builtinsJson = nlohmann::json::object(); - for (auto & builtin : *state.baseEnv.values[0]->attrs()) { + for (auto & builtinPtr : state.getBuiltins().attrs()->lexicographicOrder(state.symbols)) { + auto & builtin = *builtinPtr; auto b = nlohmann::json::object(); if (!builtin.value->isPrimOp()) continue; auto primOp = builtin.value->primOp(); diff --git a/tests/functional/flakes/develop.sh b/tests/functional/flakes/develop.sh index b3e438e99d6..c222f0fbbeb 100755 --- a/tests/functional/flakes/develop.sh +++ b/tests/functional/flakes/develop.sh @@ -122,7 +122,7 @@ expectStderr 1 nix develop --unset-env-var FOO --set-env-var FOO 'BAR' --no-writ grepQuiet "error: Cannot set environment variable 'FOO' that is unset with '--unset-env-var'" # Check that multiple `--ignore-env`'s are okay. -expectStderr 0 nix develop --ignore-env --set-env-var FOO 'BAR' --ignore-env .#hello +expectStderr 0 nix develop --ignore-env --set-env-var FOO 'BAR' --ignore-env .#hello < /dev/null # Determine the bashInteractive executable. nix build --no-write-lock-file './nixpkgs#bashInteractive' --out-link ./bash-interactive diff --git a/tests/functional/nix-shell.sh b/tests/functional/nix-shell.sh index 2b78216f452..b054b7f7519 100755 --- a/tests/functional/nix-shell.sh +++ b/tests/functional/nix-shell.sh @@ -167,6 +167,35 @@ EOF chmod a+x $TEST_ROOT/marco/polo/default.nix (cd $TEST_ROOT/marco && ./polo/default.nix | grepQuiet "Polo") +# https://github.com/NixOS/nix/issues/11892 +mkdir $TEST_ROOT/issue-11892 +cat >$TEST_ROOT/issue-11892/shebangscript <$TEST_ROOT/issue-11892/my_package.nix < \$out/bin/my_package + cat \$out/bin/my_package + chmod a+x \$out/bin/my_package + ''; +} +EOF +chmod a+x $TEST_ROOT/issue-11892/shebangscript +$TEST_ROOT/issue-11892/shebangscript \ + | tee /dev/stderr \ + | grepQuiet "ok baz11892" + ##################### # Flake equivalents # diff --git a/tests/functional/shell.nix b/tests/functional/shell.nix index 9cae14b780f..4b1a0623a81 100644 --- a/tests/functional/shell.nix +++ b/tests/functional/shell.nix @@ -37,7 +37,7 @@ let pkgs = rec { mkdir -p $out ln -s ${setupSh} $out/setup ''; - }; + } // { inherit mkDerivation; }; shellDrv = mkDerivation { name = "shellDrv"; @@ -94,5 +94,9 @@ let pkgs = rec { chmod a+rx $out/bin/ruby ''; + inherit (cfg) shell; + + callPackage = f: args: f (pkgs // args); + inherit pkgs; }; in pkgs diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix index 17bfdea3822..c5f4a23aa08 100644 --- a/tests/nixos/default.nix +++ b/tests/nixos/default.nix @@ -124,6 +124,8 @@ in nix-copy = runNixOSTestFor "x86_64-linux" ./nix-copy.nix; + nix-docker = runNixOSTestFor "x86_64-linux" ./nix-docker.nix; + nssPreload = runNixOSTestFor "x86_64-linux" ./nss-preload.nix; githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix; diff --git a/tests/nixos/nix-docker-test.sh b/tests/nixos/nix-docker-test.sh new file mode 100644 index 00000000000..1f65e1a9446 --- /dev/null +++ b/tests/nixos/nix-docker-test.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# docker.nix test script. Runs inside a built docker.nix container. + +set -eEuo pipefail + +export NIX_CONFIG='substituters = http://cache:5000?trusted=1' + +cd /tmp + +# Test getting a fetched derivation +test "$("$(nix-build -E '(import {}).hello')"/bin/hello)" = "Hello, world!" + +# Test building a simple derivation +# shellcheck disable=SC2016 +nix-build -E ' +let + pkgs = import {}; +in +builtins.derivation { + name = "test"; + system = builtins.currentSystem; + builder = "${pkgs.bash}/bin/bash"; + args = ["-c" "echo OK > $out"]; +}' +test "$(cat result)" = OK + +# Ensure #!/bin/sh shebang works +echo '#!/bin/sh' > ./shebang-test +echo 'echo OK' >> ./shebang-test +chmod +x ./shebang-test +test "$(./shebang-test)" = OK + +# Ensure #!/usr/bin/env shebang works +echo '#!/usr/bin/env bash' > ./shebang-test +echo 'echo OK' >> ./shebang-test +chmod +x ./shebang-test +test "$(./shebang-test)" = OK + +# Test nix-shell +{ + echo '#!/usr/bin/env nix-shell' + echo '#! nix-shell -i bash' + echo '#! nix-shell -p hello' + echo 'hello' +} > ./nix-shell-test +chmod +x ./nix-shell-test +test "$(./nix-shell-test)" = "Hello, world!" diff --git a/tests/nixos/nix-docker.nix b/tests/nixos/nix-docker.nix new file mode 100644 index 00000000000..dfd50898894 --- /dev/null +++ b/tests/nixos/nix-docker.nix @@ -0,0 +1,53 @@ +# Test the container built by ../../docker.nix. + +{ lib, config, nixpkgs, hostPkgs, ... }: + +let + pkgs = config.nodes.machine.nixpkgs.pkgs; + + nixImage = import ../../docker.nix { + inherit (config.nodes.machine.nixpkgs) pkgs; + }; + nixUserImage = import ../../docker.nix { + inherit (config.nodes.machine.nixpkgs) pkgs; + name = "nix-user"; + uid = 1000; + gid = 1000; + uname = "user"; + gname = "user"; + }; + + containerTestScript = ./nix-docker-test.sh; + +in { + name = "nix-docker"; + + nodes = + { machine = + { config, lib, pkgs, ... }: + { virtualisation.diskSize = 4096; + }; + cache = + { config, lib, pkgs, ... }: + { virtualisation.additionalPaths = [ pkgs.stdenv pkgs.hello ]; + services.harmonia.enable = true; + networking.firewall.allowedTCPPorts = [ 5000 ]; + }; + }; + + testScript = { nodes }: '' + cache.wait_for_unit("harmonia.service") + + machine.succeed("mkdir -p /etc/containers") + machine.succeed("""echo '{"default":[{"type":"insecureAcceptAnything"}]}' > /etc/containers/policy.json""") + + machine.succeed("${pkgs.podman}/bin/podman load -i ${nixImage}") + machine.succeed("${pkgs.podman}/bin/podman run --rm nix nix --version") + machine.succeed("${pkgs.podman}/bin/podman run --rm -i nix < ${containerTestScript}") + + machine.succeed("${pkgs.podman}/bin/podman load -i ${nixUserImage}") + machine.succeed("${pkgs.podman}/bin/podman run --rm nix-user nix --version") + machine.succeed("${pkgs.podman}/bin/podman run --rm -i nix-user < ${containerTestScript}") + machine.succeed("[[ $(${pkgs.podman}/bin/podman run --rm nix-user stat -c %u /nix/store) = 1000 ]]") + ''; +}