aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-tidy3
-rw-r--r--doc/manual/change-authors.yml7
-rw-r--r--doc/manual/rl-next/nix-flake-show-description.md37
-rw-r--r--flake.nix20
-rw-r--r--meson.build15
-rw-r--r--package.nix28
-rw-r--r--releng/README.md2
-rw-r--r--releng/create_release.xsh19
-rw-r--r--releng/version.py1
-rw-r--r--src/asan-options/asan-options.cc17
-rw-r--r--src/libcmd/installable-attr-path.cc12
-rw-r--r--src/libcmd/installable-attr-path.hh14
-rw-r--r--src/libexpr/flake/flakeref.cc11
-rw-r--r--src/libexpr/get-drvs.cc288
-rw-r--r--src/libexpr/get-drvs.hh3
-rw-r--r--src/libfetchers/fetchers.hh31
-rw-r--r--src/libfetchers/git.cc17
-rw-r--r--src/libfetchers/github.cc114
-rw-r--r--src/libfetchers/indirect.cc34
-rw-r--r--src/libfetchers/mercurial.cc7
-rw-r--r--src/libfetchers/tarball.cc26
-rw-r--r--src/meson.build11
-rw-r--r--src/nix/flake.cc36
-rw-r--r--src/nix/meson.build1
-rw-r--r--tests/functional/fetchers.sh91
-rw-r--r--tests/functional/flakes/show.sh28
-rw-r--r--tests/functional/flakes/subdir-flake.sh20
-rw-r--r--tests/functional/meson.build2
-rw-r--r--tests/functional/repl_characterization/meson.build1
-rw-r--r--tests/functional/test-libstoreconsumer/meson.build1
-rw-r--r--tests/nixos/tarball-flakes.nix2
-rw-r--r--tests/unit/meson.build17
-rw-r--r--version.json1
33 files changed, 669 insertions, 248 deletions
diff --git a/.clang-tidy b/.clang-tidy
index 3b5dcd91a..0cc1f2520 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -16,3 +16,6 @@ Checks:
- -bugprone-unchecked-optional-access
# many warnings, seems like a questionable lint
- -bugprone-branch-clone
+
+CheckOptions:
+ bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml
index 775149180..630af29ff 100644
--- a/doc/manual/change-authors.yml
+++ b/doc/manual/change-authors.yml
@@ -70,10 +70,17 @@ horrors:
iFreilicht:
github: iFreilicht
+isabelroses:
+ forgejo: isabelroses
+ github: isabelroses
+
jade:
forgejo: jade
github: lf-
+kjeremy:
+ github: kjeremy
+
kloenk:
forgejo: kloenk
github: kloenk
diff --git a/doc/manual/rl-next/nix-flake-show-description.md b/doc/manual/rl-next/nix-flake-show-description.md
new file mode 100644
index 000000000..e819a3a29
--- /dev/null
+++ b/doc/manual/rl-next/nix-flake-show-description.md
@@ -0,0 +1,37 @@
+---
+synopsis: "Lix will now show the package descriptions in when running `nix flake show`."
+cls: [1540]
+issues: []
+credits: [kjeremy, isabelroses]
+category: Improvements
+---
+
+When running `nix flake show`, Lix will now show the package descriptions, if they exist.
+
+Before:
+
+```shell
+$ nix flake show
+path:/home/isabel/dev/lix-show?lastModified=1721736108&narHash=sha256-Zo8HP1ur7Q2b39hKUEG8EAh/opgq8xJ2jvwQ/htwO4Q%3D
+└───packages
+ └───x86_64-linux
+ ├───aNoDescription: package 'simple'
+ ├───bOneLineDescription: package 'simple'
+ ├───cMultiLineDescription: package 'simple'
+ ├───dLongDescription: package 'simple'
+ └───eEmptyDescription: package 'simple'
+```
+
+After:
+
+```shell
+$ nix flake show
+path:/home/isabel/dev/lix-show?lastModified=1721736108&narHash=sha256-Zo8HP1ur7Q2b39hKUEG8EAh/opgq8xJ2jvwQ/htwO4Q%3D
+└───packages
+ └───x86_64-linux
+ ├───aNoDescription: package 'simple'
+ ├───bOneLineDescription: package 'simple' - 'one line'
+ ├───cMultiLineDescription: package 'simple' - 'line one'
+ ├───dLongDescription: package 'simple' - 'abcdefghijklmnopqrstuvwxyz'
+ └───eEmptyDescription: package 'simple'
+```
diff --git a/flake.nix b/flake.nix
index cec970974..a1fc947b7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -33,7 +33,7 @@
# This notice gets echoed as a dev shell hook, and can be turned off with
# `touch .nocontribmsg`
- sgr = ''['';
+ sgr = builtins.fromJSON ''"\u001b["'';
freezePage = "https://wiki.lix.systems/books/lix-contributors/page/freezes-and-recommended-contributions";
codebaseOverview = "https://wiki.lix.systems/books/lix-contributors/page/codebase-overview";
contribNotice = builtins.toFile "lix-contrib-notice" ''
@@ -59,7 +59,8 @@
(Run `touch .nocontribmsg` to hide this message.)
'';
- officialRelease = false;
+ versionJson = builtins.fromJSON (builtins.readFile ./version.json);
+ officialRelease = versionJson.official_release;
# Set to true to build the release notes for the next release.
buildUnreleasedNotes = true;
@@ -275,6 +276,19 @@
# System tests.
tests = import ./tests/nixos { inherit lib nixpkgs nixpkgsFor; } // {
+ # This is x86_64-linux only, just because we have significantly
+ # cheaper x86_64-linux compute in CI.
+ # It is clangStdenv because clang's sanitizers are nicer.
+ asanBuild = self.packages.x86_64-linux.nix-clangStdenv.override {
+ sanitize = [
+ "address"
+ "undefined"
+ ];
+ # it is very hard to make *every* CI build use this option such
+ # that we don't wind up building Lix twice, so we do it here where
+ # we are already doing so.
+ werror = true;
+ };
# Make sure that nix-env still produces the exact same result
# on a particular version of Nixpkgs.
@@ -406,7 +420,7 @@
pkgs: stdenv:
let
nix = pkgs.callPackage ./package.nix {
- inherit stdenv officialRelease versionSuffix;
+ inherit stdenv versionSuffix;
busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox;
internalApiDocs = false;
};
diff --git a/meson.build b/meson.build
index 56f447501..ed50dff78 100644
--- a/meson.build
+++ b/meson.build
@@ -199,7 +199,11 @@ configdata = { }
# Dependencies
#
-boehm = dependency('bdw-gc', required : get_option('gc'), version : '>=8.2.6')
+gc_opt = get_option('gc').disable_if(
+ 'address' in get_option('b_sanitize'),
+ error_message: 'gc does far too many memory crimes for ASan'
+)
+boehm = dependency('bdw-gc', required : gc_opt, version : '>=8.2.6')
configdata += {
'HAVE_BOEHMGC': boehm.found().to_int(),
}
@@ -482,7 +486,14 @@ if cxx.get_id() == 'clang' and get_option('b_sanitize') != ''
add_project_link_arguments('-shared-libsan', language : 'cpp')
endif
+# Clang gets grumpy about missing libasan symbols if -shared-libasan is not
+# passed when building shared libs, at least on Linux
+if cxx.get_id() == 'clang' and 'address' in get_option('b_sanitize')
+ add_project_link_arguments('-shared-libasan', language : 'cpp')
+endif
+
add_project_link_arguments('-pthread', language : 'cpp')
+
if cxx.get_linker_id() in ['ld.bfd', 'ld.gold']
add_project_link_arguments('-Wl,--no-copy-dt-needed-entries', language : 'cpp')
endif
@@ -497,7 +508,7 @@ endif
# maintainers/buildtime_report.sh BUILD-DIR to simply work in clang builds.
#
# They can also be manually viewed at https://ui.perfetto.dev
-if get_option('profile-build').require(meson.get_compiler('cpp').get_id() == 'clang').enabled()
+if get_option('profile-build').require(cxx.get_id() == 'clang').enabled()
add_project_arguments('-ftime-trace', language: 'cpp')
endif
diff --git a/package.nix b/package.nix
index 06f1107e0..7ebe2721b 100644
--- a/package.nix
+++ b/package.nix
@@ -20,6 +20,7 @@
doxygen,
editline-lix ? __forDefaults.editline-lix,
editline,
+ expect,
git,
gtest,
jq,
@@ -52,16 +53,24 @@
pname ? "lix",
versionSuffix ? "",
- officialRelease ? false,
+ officialRelease ? __forDefaults.versionJson.official_release,
# Set to true to build the release notes for the next release.
buildUnreleasedNotes ? true,
internalApiDocs ? false,
+ # List of Meson sanitize options. Accepts values of b_sanitize, e.g.
+ # "address", "undefined", "thread".
+ sanitize ? null,
+ # Turn compiler warnings into errors.
+ werror ? false,
+
# Not a real argument, just the only way to approximate let-binding some
# stuff for argument defaults.
__forDefaults ? {
canRunInstalled = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
+ versionJson = builtins.fromJSON (builtins.readFile ./version.json);
+
boehmgc-nix = boehmgc.override { enableLargeConfig = true; };
editline-lix = editline.overrideAttrs (prev: {
@@ -77,8 +86,7 @@ let
inherit (lib) fileset;
inherit (stdenv) hostPlatform buildPlatform;
- versionJson = builtins.fromJSON (builtins.readFile ./version.json);
- version = versionJson.version + versionSuffix;
+ version = __forDefaults.versionJson.version + versionSuffix;
aws-sdk-cpp-nix =
if aws-sdk-cpp == null then
@@ -170,6 +178,12 @@ stdenv.mkDerivation (finalAttrs: {
dontBuild = false;
mesonFlags =
+ let
+ sanitizeOpts = lib.optionals (sanitize != null) (
+ [ "-Db_sanitize=${builtins.concatStringsSep "," sanitize}" ]
+ ++ lib.optional (builtins.elem "address" sanitize) "-Dgc=disabled"
+ );
+ in
lib.optionals hostPlatform.isLinux [
# You'd think meson could just find this in PATH, but busybox is in buildInputs,
# which don't actually get added to PATH. And buildInputs is correct over
@@ -185,8 +199,10 @@ stdenv.mkDerivation (finalAttrs: {
(lib.mesonEnable "internal-api-docs" internalApiDocs)
(lib.mesonBool "enable-tests" finalAttrs.finalPackage.doCheck)
(lib.mesonBool "enable-docs" canRunInstalled)
+ (lib.mesonBool "werror" werror)
]
- ++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}";
+ ++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}"
+ ++ sanitizeOpts;
# We only include CMake so that Meson can locate toml11, which only ships CMake dependency metadata.
dontUseCmakeConfigure = true;
@@ -243,6 +259,8 @@ stdenv.mkDerivation (finalAttrs: {
++ lib.optional (hostPlatform.canExecute buildPlatform) aws-sdk-cpp-nix
++ lib.optionals (finalAttrs.dontBuild) maybePropagatedInputs;
+ nativeCheckInputs = [ expect ];
+
checkInputs = [
gtest
rapidcheck
@@ -371,8 +389,6 @@ stdenv.mkDerivation (finalAttrs: {
pegtl
;
- inherit officialRelease;
-
# The collection of dependency logic for this derivation is complicated enough that
# it's easier to parameterize the devShell off an already called package.nix.
mkDevShell =
diff --git a/releng/README.md b/releng/README.md
index cfacf4b8e..2aa3b959f 100644
--- a/releng/README.md
+++ b/releng/README.md
@@ -30,7 +30,7 @@ First, we prepare the release. `python -m releng prepare` is used for this.
Then we tag the release with `python -m releng tag`:
* Git HEAD is detached.
-* `officialRelease = true` is set in `flake.nix`, this is committed, and a
+* `"official_release": true` is set in `version.json`, this is committed, and a
release is tagged.
* The tag is merged back into the last branch (either `main` for new releases
or `release-MAJOR` for maintenance releases) with `git merge -s ours VERSION`
diff --git a/releng/create_release.xsh b/releng/create_release.xsh
index 358124359..62114350b 100644
--- a/releng/create_release.xsh
+++ b/releng/create_release.xsh
@@ -11,7 +11,7 @@ from . import environment
from .environment import RelengEnvironment
from . import keys
from . import docker
-from .version import VERSION, RELEASE_NAME, MAJOR
+from .version import VERSION, RELEASE_NAME, MAJOR, OFFICIAL_RELEASE
from .gitutils import verify_are_on_tag, git_preconditions
from . import release_notes
@@ -39,12 +39,18 @@ def setup_creds(env: RelengEnvironment):
def official_release_commit_tag(force_tag=False):
- print('[+] Setting officialRelease in flake.nix and tagging')
+ print('[+] Setting officialRelease in version.json and tagging')
prev_branch = $(git symbolic-ref --short HEAD).strip()
git switch --detach
- sed -i 's/officialRelease = false/officialRelease = true/' flake.nix
- git add flake.nix
+
+ # Must be done in two parts due to buffering (opening the file immediately
+ # would truncate it).
+ new_version_json = $(jq --indent 4 '.official_release = true' version.json)
+ with open('version.json', 'w') as fh:
+ fh.write(new_version_json)
+ git add version.json
+
message = f'release: {VERSION} "{RELEASE_NAME}"\n\nRelease produced with releng/create_release.xsh'
git commit -m @(message)
git tag @(['-f'] if force_tag else []) -a -m @(message) @(VERSION)
@@ -250,15 +256,14 @@ def build_manual(eval_result):
def upload_manual(env: RelengEnvironment):
- stable = json.loads($(nix eval --json '.#nix.officialRelease'))
- if stable:
+ if OFFICIAL_RELEASE:
version = MAJOR
else:
version = 'nightly'
print('[+] aws s3 sync manual')
aws s3 sync @(MANUAL)/ @(env.docs_bucket)/manual/lix/@(version)/
- if stable:
+ if OFFICIAL_RELEASE:
aws s3 sync @(MANUAL)/ @(env.docs_bucket)/manual/lix/stable/
diff --git a/releng/version.py b/releng/version.py
index 47ef23504..4ad188d46 100644
--- a/releng/version.py
+++ b/releng/version.py
@@ -4,3 +4,4 @@ version_json = json.load(open('version.json'))
VERSION = version_json['version']
MAJOR = '.'.join(VERSION.split('.')[:2])
RELEASE_NAME = version_json['release_name']
+OFFICIAL_RELEASE = version_json['official_release']
diff --git a/src/asan-options/asan-options.cc b/src/asan-options/asan-options.cc
new file mode 100644
index 000000000..c4cf360af
--- /dev/null
+++ b/src/asan-options/asan-options.cc
@@ -0,0 +1,17 @@
+/// @file This is very bothersome code that has to be included in every
+/// executable to get the correct default ASan options. I am so sorry.
+
+extern "C" [[gnu::retain]] const char *__asan_default_options()
+{
+ // We leak a bunch of memory knowingly on purpose. It's not worthwhile to
+ // diagnose that memory being leaked for now.
+ //
+ // Instruction bytes are useful for finding the actual code that
+ // corresponds to an ASan report.
+ //
+ // TODO: setting log_path=asan.log or not: neither works, since you can't
+ // write to the fs in certain places in the testsuite, but you also cannot
+ // write arbitrarily to stderr in other places so the reports get eaten.
+ // pain 🥖
+ return "halt_on_error=1:abort_on_error=1:detect_leaks=0:print_summary=1:dump_instruction_bytes=1";
+}
diff --git a/src/libcmd/installable-attr-path.cc b/src/libcmd/installable-attr-path.cc
index eb15fecc3..9891b263c 100644
--- a/src/libcmd/installable-attr-path.cc
+++ b/src/libcmd/installable-attr-path.cc
@@ -1,23 +1,11 @@
-#include "globals.hh"
#include "installable-attr-path.hh"
#include "outputs-spec.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
-#include "derivations.hh"
-#include "eval-inline.hh"
#include "eval.hh"
#include "get-drvs.hh"
-#include "store-api.hh"
-#include "shared.hh"
#include "flake/flake.hh"
-#include "eval-cache.hh"
-#include "url.hh"
-#include "registry.hh"
-#include "build-result.hh"
-
-#include <regex>
-#include <queue>
#include <nlohmann/json.hpp>
diff --git a/src/libcmd/installable-attr-path.hh b/src/libcmd/installable-attr-path.hh
index 86c2f8219..edc9c2906 100644
--- a/src/libcmd/installable-attr-path.hh
+++ b/src/libcmd/installable-attr-path.hh
@@ -1,25 +1,11 @@
#pragma once
///@file
-#include "globals.hh"
#include "installable-value.hh"
#include "outputs-spec.hh"
#include "command.hh"
-#include "attr-path.hh"
#include "common-eval-args.hh"
-#include "derivations.hh"
-#include "eval-inline.hh"
#include "eval.hh"
-#include "get-drvs.hh"
-#include "store-api.hh"
-#include "shared.hh"
-#include "eval-cache.hh"
-#include "url.hh"
-#include "registry.hh"
-#include "build-result.hh"
-
-#include <regex>
-#include <queue>
#include <nlohmann/json.hpp>
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index a95df04ba..3be4ea550 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -169,14 +169,13 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (subdir != "") {
if (parsedURL.query.count("dir"))
throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
- parsedURL.query.insert_or_assign("dir", subdir);
}
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
- FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")),
+ FlakeRef(Input::fromURL(parsedURL, isFlake), subdir),
fragment);
}
@@ -204,7 +203,13 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::string fragment;
std::swap(fragment, parsedURL.fragment);
- auto input = Input::fromURL(parsedURL, isFlake);
+ // This has a special meaning for flakes and must not be passed to libfetchers.
+ // Of course this means that libfetchers cannot have fetchers
+ // expecting an argument `dir` 🫠
+ ParsedURL urlForFetchers(parsedURL);
+ urlForFetchers.query.erase("dir");
+
+ auto input = Input::fromURL(urlForFetchers, isFlake);
input.parent = baseDir;
return std::make_pair(
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 8e3969aac..d7869d09b 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -1,6 +1,7 @@
#include "get-drvs.hh"
#include "eval-inline.hh"
#include "derivations.hh"
+#include "eval.hh"
#include "store-api.hh"
#include "path-with-outputs.hh"
@@ -101,66 +102,134 @@ StorePath DrvInfo::queryOutPath()
return *outPath;
}
+void DrvInfo::fillOutputs(bool withPaths)
+{
+ auto fillDefault = [&]() {
+ std::optional<StorePath> outPath = std::nullopt;
+ if (withPaths) {
+ outPath.emplace(this->queryOutPath());
+ }
+ this->outputs.emplace("out", outPath);
+ };
+
+ // lol. lmao even.
+ if (this->attrs == nullptr) {
+ fillDefault();
+ return;
+ }
+
+ Attr * outputs = this->attrs->get(this->state->sOutputs);
+ if (outputs == nullptr) {
+ fillDefault();
+ return;
+ }
+
+ // NOTE(Qyriad): I don't think there is any codepath that can cause this to error.
+ this->state->forceList(
+ *outputs->value,
+ outputs->pos,
+ "while evaluating the 'outputs' attribute of a derivation"
+ );
+
+ for (auto [idx, elem] : enumerate(outputs->value->listItems())) {
+ // NOTE(Qyriad): This error should be *extremely* rare in practice.
+ // It is impossible to construct with `stdenv.mkDerivation`,
+ // `builtins.derivation`, or even `derivationStrict`. As far as we can tell,
+ // it is only possible by overriding a derivation attrset already created by
+ // one of those with `//` to introduce the failing `outputs` entry.
+ auto errMsg = fmt("while evaluating output %d of a derivation", idx);
+ std::string_view outputName = state->forceStringNoCtx(
+ *elem,
+ outputs->pos,
+ errMsg
+ );
+
+ if (withPaths) {
+ // Find the attr with this output's name...
+ Attr * out = this->attrs->get(this->state->symbols.create(outputName));
+ if (out == nullptr) {
+ // FIXME: throw error?
+ continue;
+ }
+
+ // Meanwhile we couldn't figure out any circumstances
+ // that cause this to error.
+ state->forceAttrs(*out->value, outputs->pos, errMsg);
+
+ // ...and evaluate its `outPath` attribute.
+ Attr * outPath = out->value->attrs->get(this->state->sOutPath);
+ if (outPath == nullptr) {
+ continue;
+ // FIXME: throw error?
+ }
+
+ NixStringContext context;
+ // And idk what could possibly cause this one to error
+ // that wouldn't error before here.
+ auto storePath = state->coerceToStorePath(
+ outPath->pos,
+ *outPath->value,
+ context,
+ errMsg
+ );
+ this->outputs.emplace(outputName, storePath);
+ } else {
+ this->outputs.emplace(outputName, std::nullopt);
+ }
+ }
+}
DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
{
+ // If we haven't already cached the outputs set, then do so now.
if (outputs.empty()) {
- /* Get the ‘outputs’ list. */
- Bindings::iterator i;
- if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
- state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
-
- /* For each output... */
- for (auto elem : i->value->listItems()) {
- std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
-
- if (withPaths) {
- /* Evaluate the corresponding set. */
- Bindings::iterator out = attrs->find(state->symbols.create(output));
- if (out == attrs->end()) continue; // FIXME: throw error?
- state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
-
- /* And evaluate its ‘outPath’ attribute. */
- Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
- if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
- NixStringContext context;
- outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
- } else
- outputs.emplace(output, std::nullopt);
- }
- } else
- outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
+ // FIXME: this behavior seems kind of busted, since whether or not this
+ // DrvInfo will have paths is forever determined by the *first* call to
+ // this function??
+ fillOutputs(withPaths);
}
+ // Things that operate on derivations like packages, like `nix-env` and `nix build`,
+ // allow derivations to specify which outputs should be used in those user-facing
+ // cases if the user didn't specify an output explicitly.
+ // If the caller just wanted all the outputs for this derivation, though,
+ // then we're done here.
if (!onlyOutputsToInstall || !attrs)
return outputs;
- Bindings::iterator i;
- if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
- Outputs result;
- auto out = outputs.find(queryOutputName());
- if (out == outputs.end())
- throw Error("derivation does not have output '%s'", queryOutputName());
- result.insert(*out);
- return result;
+ // Regardless of `meta.outputsToInstall`, though, you can select into a derivation
+ // output by its attribute, e.g. `pkgs.lix.dev`, which (lol?) sets the magic
+ // attribute `outputSpecified = true`, and changes the `outputName` attr to the
+ // explicitly selected-into output.
+ if (Attr * outSpecAttr = attrs->get(state->sOutputSpecified)) {
+ bool outputSpecified = this->state->forceBool(
+ *outSpecAttr->value,
+ outSpecAttr->pos,
+ "while evaluating the 'outputSpecified' attribute of a derivation"
+ );
+ if (outputSpecified) {
+ auto maybeOut = outputs.find(queryOutputName());
+ if (maybeOut == outputs.end()) {
+ throw Error("derivation does not have output '%s'", queryOutputName());
+ }
+ return Outputs{*maybeOut};
+ }
}
- else {
- /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
- const Value * outTI = queryMeta("outputsToInstall");
- if (!outTI) return outputs;
- auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
- /* ^ this shows during `nix-env -i` right under the bad derivation */
- if (!outTI->isList()) throw errMsg;
- Outputs result;
- for (auto elem : outTI->listItems()) {
- if (elem->type() != nString) throw errMsg;
- auto out = outputs.find(elem->string.s);
- if (out == outputs.end()) throw errMsg;
- result.insert(*out);
- }
- return result;
+ /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
+ const Value * outTI = queryMeta("outputsToInstall");
+ if (!outTI) return outputs;
+ auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
+ /* ^ this shows during `nix-env -i` right under the bad derivation */
+ if (!outTI->isList()) throw errMsg;
+ Outputs result;
+ for (auto elem : outTI->listItems()) {
+ if (elem->type() != nString) throw errMsg;
+ auto out = outputs.find(elem->string.s);
+ if (out == outputs.end()) throw errMsg;
+ result.insert(*out);
}
+ return result;
}
@@ -350,56 +419,95 @@ static void getDerivations(EvalState & state, Value & vIn,
Value v;
state.autoCallFunction(autoArgs, vIn, v);
- /* Process the expression. */
- if (!getDerivation(state, v, pathPrefix, drvs, ignoreAssertionFailures)) ;
-
- else if (v.type() == nAttrs) {
-
- /* Dont consider sets we've already seen, e.g. y in
- `rec { x.d = derivation {...}; y = x; }`. */
- if (!done.insert(v.attrs).second) return;
-
- /* !!! undocumented hackery to support combining channels in
- nix-env.cc. */
- bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
-
- /* Consider the attributes in sorted order to get more
- deterministic behaviour in nix-env operations (e.g. when
- there are names clashes between derivations, the derivation
- bound to the attribute with the "lower" name should take
- precedence). */
- for (auto & i : v.attrs->lexicographicOrder(state.symbols)) {
- debug("evaluating attribute '%1%'", state.symbols[i->name]);
- if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
- continue;
- std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
- if (combineChannels)
- getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
- else if (getDerivation(state, *i->value, pathPrefix2, drvs, ignoreAssertionFailures)) {
- /* If the value of this attribute is itself a set,
- should we recurse into it? => Only if it has a
- `recurseForDerivations = true' attribute. */
- if (i->value->type() == nAttrs) {
- Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
- if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
- getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
- }
- }
- }
+ bool shouldRecurse = getDerivation(state, v, pathPrefix, drvs, ignoreAssertionFailures);
+ if (!shouldRecurse) {
+ // We're done here.
+ return;
}
- else if (v.type() == nList) {
+ if (v.type() == nList) {
// NOTE we can't really deduplicate here because small lists don't have stable addresses
// and can cause spurious duplicate detections due to v being on the stack.
for (auto [n, elem] : enumerate(v.listItems())) {
- std::string pathPrefix2 = addToPath(pathPrefix, fmt("%d", n));
- if (getDerivation(state, *elem, pathPrefix2, drvs, ignoreAssertionFailures))
- getDerivations(state, *elem, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
+ std::string joinedAttrPath = addToPath(pathPrefix, fmt("%d", n));
+ bool shouldRecurse = getDerivation(state, *elem, joinedAttrPath, drvs, ignoreAssertionFailures);
+ if (shouldRecurse) {
+ getDerivations(
+ state,
+ *elem,
+ joinedAttrPath,
+ autoArgs,
+ drvs,
+ done,
+ ignoreAssertionFailures
+ );
+ }
}
+
+ return;
+ } else if (v.type() != nAttrs) {
+ state.error<TypeError>(
+ "expression was expected to be a derivation or collection of derivations, but instead was %s",
+ showType(v.type(), true)
+ ).debugThrow();
+ }
+
+ /* Dont consider sets we've already seen, e.g. y in
+ `rec { x.d = derivation {...}; y = x; }`. */
+ auto const &[_, didInsert] = done.insert(v.attrs);
+ if (!didInsert) {
+ return;
}
- else
- state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
+ // FIXME: what the fuck???
+ /* !!! undocumented hackery to support combining channels in
+ nix-env.cc. */
+ bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
+
+ /* Consider the attributes in sorted order to get more
+ deterministic behaviour in nix-env operations (e.g. when
+ there are names clashes between derivations, the derivation
+ bound to the attribute with the "lower" name should take
+ precedence). */
+ for (auto & attr : v.attrs->lexicographicOrder(state.symbols)) {
+ debug("evaluating attribute '%1%'", state.symbols[attr->name]);
+ // FIXME: only consider attrs with identifier-like names?? Why???
+ if (!std::regex_match(std::string(state.symbols[attr->name]), attrRegex)) {
+ continue;
+ }
+ std::string joinedAttrPath = addToPath(pathPrefix, state.symbols[attr->name]);
+ if (combineChannels) {
+ getDerivations(state, *attr->value, joinedAttrPath, autoArgs, drvs, done, ignoreAssertionFailures);
+ } else if (getDerivation(state, *attr->value, joinedAttrPath, drvs, ignoreAssertionFailures)) {
+ /* If the value of this attribute is itself a set,
+ should we recurse into it? => Only if it has a
+ `recurseForDerivations = true' attribute. */
+ if (attr->value->type() == nAttrs) {
+ Attr * recurseForDrvs = attr->value->attrs->get(state.sRecurseForDerivations);
+ if (recurseForDrvs == nullptr) {
+ continue;
+ }
+ bool shouldRecurse = state.forceBool(
+ *recurseForDrvs->value,
+ attr->pos,
+ fmt("while evaluating the '%s' attribute", Magenta("recurseForDerivations"))
+ );
+ if (!shouldRecurse) {
+ continue;
+ }
+
+ getDerivations(
+ state,
+ *attr->value,
+ joinedAttrPath,
+ autoArgs,
+ drvs,
+ done,
+ ignoreAssertionFailures
+ );
+ }
+ }
+ }
}
diff --git a/src/libexpr/get-drvs.hh b/src/libexpr/get-drvs.hh
index 44aac3015..fd927b9e5 100644
--- a/src/libexpr/get-drvs.hh
+++ b/src/libexpr/get-drvs.hh
@@ -37,13 +37,14 @@ private:
bool checkMeta(Value & v);
+ void fillOutputs(bool withPaths = true);
+
public:
/**
* path towards the derivation
*/
std::string attrPath;
- DrvInfo(EvalState & state) : state(&state) { };
DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs);
DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
diff --git a/src/libfetchers/fetchers.hh b/src/libfetchers/fetchers.hh
index 2bb4248be..40f2b6294 100644
--- a/src/libfetchers/fetchers.hh
+++ b/src/libfetchers/fetchers.hh
@@ -159,6 +159,37 @@ struct InputScheme
std::optional<std::string> commitMsg) const;
virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0;
+
+protected:
+ void emplaceURLQueryIntoAttrs(
+ const ParsedURL & parsedURL,
+ Attrs & attrs,
+ const StringSet & numericParams,
+ const StringSet & booleanParams) const
+ {
+ for (auto &[name, value] : parsedURL.query) {
+ if (name == "url") {
+ throw BadURL(
+ "URL '%s' must not override url via query param!",
+ parsedURL.to_string()
+ );
+ } else if (numericParams.count(name) != 0) {
+ if (auto n = string2Int<uint64_t>(value)) {
+ attrs.insert_or_assign(name, *n);
+ } else {
+ throw BadURL(
+ "URL '%s' has non-numeric parameter '%s'",
+ parsedURL.to_string(),
+ name
+ );
+ }
+ } else if (booleanParams.count(name) != 0) {
+ attrs.emplace(name, Explicit<bool> { value == "1" });
+ } else {
+ attrs.emplace(name, value);
+ }
+ }
+ }
};
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index 9fd8d7bbf..8e3165ff6 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -273,18 +273,15 @@ struct GitInputScheme : InputScheme
Attrs attrs;
attrs.emplace("type", "git");
-
- for (auto & [name, value] : url.query) {
- if (name == "rev" || name == "ref")
- attrs.emplace(name, value);
- else if (name == "shallow" || name == "submodules" || name == "allRefs")
- attrs.emplace(name, Explicit<bool> { value == "1" });
- else
- url2.query.emplace(name, value);
- }
-
attrs.emplace("url", url2.to_string());
+ emplaceURLQueryIntoAttrs(
+ url,
+ attrs,
+ {"lastModified", "revCount"},
+ {"shallow", "submodules", "allRefs"}
+ );
+
return inputFromAttrs(attrs);
}
diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc
index 60fefd1f3..b971781ae 100644
--- a/src/libfetchers/github.cc
+++ b/src/libfetchers/github.cc
@@ -1,3 +1,4 @@
+#include "attrs.hh"
#include "filetransfer.hh"
#include "cache.hh"
#include "globals.hh"
@@ -36,18 +37,11 @@ struct GitArchiveInputScheme : InputScheme
auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
- std::optional<Hash> rev;
- std::optional<std::string> ref;
- std::optional<std::string> host_url;
+ std::optional<std::string> refOrRev;
auto size = path.size();
if (size == 3) {
- if (std::regex_match(path[2], revRegex))
- rev = Hash::parseAny(path[2], htSHA1);
- else if (std::regex_match(path[2], refRegex))
- ref = path[2];
- else
- throw BadURL("in URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[2]);
+ refOrRev = path[2];
} else if (size > 3) {
std::string rs;
for (auto i = std::next(path.begin(), 2); i != path.end(); i++) {
@@ -58,61 +52,91 @@ struct GitArchiveInputScheme : InputScheme
}
if (std::regex_match(rs, refRegex)) {
- ref = rs;
+ refOrRev = rs;
} else {
throw BadURL("in URL '%s', '%s' is not a branch/tag name", url.url, rs);
}
} else if (size < 2)
throw BadURL("URL '%s' is invalid", url.url);
+ Attrs attrs;
+ attrs.emplace("type", type());
+ attrs.emplace("owner", path[0]);
+ attrs.emplace("repo", path[1]);
+
for (auto &[name, value] : url.query) {
- if (name == "rev") {
- if (rev)
- throw BadURL("URL '%s' contains multiple commit hashes", url.url);
- rev = Hash::parseAny(value, htSHA1);
- }
- else if (name == "ref") {
- if (!std::regex_match(value, refRegex))
- throw BadURL("URL '%s' contains an invalid branch/tag name", url.url);
- if (ref)
- throw BadURL("URL '%s' contains multiple branch/tag names", url.url);
- ref = value;
- }
- else if (name == "host") {
- if (!std::regex_match(value, hostRegex))
- throw BadURL("URL '%s' contains an invalid instance host", url.url);
- host_url = value;
+ if (name == "rev" || name == "ref") {
+ if (refOrRev) {
+ throw BadURL("URL '%s' already contains a ref or rev", url.url);
+ } else {
+ refOrRev = value;
+ }
+ } else if (name == "lastModified") {
+ if (auto n = string2Int<uint64_t>(value)) {
+ attrs.emplace(name, *n);
+ } else {
+ throw Error(
+ "Attribute 'lastModified' in URL '%s' must be an integer",
+ url.to_string()
+ );
+ }
+ } else {
+ attrs.emplace(name, value);
}
- // FIXME: barf on unsupported attributes
}
- if (ref && rev)
- throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev());
+ if (refOrRev) attrs.emplace("refOrRev", *refOrRev);
- Input input;
- input.attrs.insert_or_assign("type", type());
- input.attrs.insert_or_assign("owner", path[0]);
- input.attrs.insert_or_assign("repo", path[1]);
- if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
- if (ref) input.attrs.insert_or_assign("ref", *ref);
- if (host_url) input.attrs.insert_or_assign("host", *host_url);
-
- return input;
+ return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
- if (maybeGetStrAttr(attrs, "type") != type()) return {};
+ // Attributes can contain refOrRev and it needs to be figured out
+ // which one it is (see inputFromURL for when that may happen).
+ // The correct one (ref or rev) will be written into finalAttrs and
+ // it needs to be mutable for that.
+ Attrs finalAttrs(attrs);
+ auto type_ = maybeGetStrAttr(finalAttrs, "type");
+ if (type_ != type()) return {};
+
+ auto owner = getStrAttr(finalAttrs, "owner");
+ auto repo = getStrAttr(finalAttrs, "repo");
+
+ auto url = fmt("%s:%s/%s", *type_, owner, repo);
+ if (auto host = maybeGetStrAttr(finalAttrs, "host")) {
+ if (!std::regex_match(*host, hostRegex)) {
+ throw BadURL("URL '%s' contains an invalid instance host", url);
+ }
+ }
- for (auto & [name, value] : attrs)
- if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host")
- throw Error("unsupported input attribute '%s'", name);
+ if (auto refOrRev = maybeGetStrAttr(finalAttrs, "refOrRev")) {
+ finalAttrs.erase("refOrRev");
+ if (std::regex_match(*refOrRev, revRegex)) {
+ finalAttrs.emplace("rev", *refOrRev);
+ } else if (std::regex_match(*refOrRev, refRegex)) {
+ finalAttrs.emplace("ref", *refOrRev);
+ } else {
+ throw Error(
+ "in URL '%s', '%s' is not a commit hash or a branch/tag name",
+ url,
+ *refOrRev
+ );
+ }
+ } else if (auto ref = maybeGetStrAttr(finalAttrs, "ref")) {
+ if (!std::regex_match(*ref, refRegex)) {
+ throw BadURL("URL '%s' contains an invalid branch/tag name", url);
+ }
+ }
- getStrAttr(attrs, "owner");
- getStrAttr(attrs, "repo");
+ for (auto & [name, value] : finalAttrs) {
+ if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host") {
+ throw Error("unsupported input attribute '%s'", name);
+ }
+ }
Input input;
- input.attrs = attrs;
+ input.attrs = finalAttrs;
return input;
}
diff --git a/src/libfetchers/indirect.cc b/src/libfetchers/indirect.cc
index c73505b31..8c0176e84 100644
--- a/src/libfetchers/indirect.cc
+++ b/src/libfetchers/indirect.cc
@@ -17,6 +17,8 @@ struct IndirectInputScheme : InputScheme
std::optional<Hash> rev;
std::optional<std::string> ref;
+ Attrs attrs;
+
if (path.size() == 1) {
} else if (path.size() == 2) {
if (std::regex_match(path[1], revRegex))
@@ -26,29 +28,21 @@ struct IndirectInputScheme : InputScheme
else
throw BadURL("in flake URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[1]);
} else if (path.size() == 3) {
- if (!std::regex_match(path[1], refRegex))
- throw BadURL("in flake URL '%s', '%s' is not a branch/tag name", url.url, path[1]);
ref = path[1];
- if (!std::regex_match(path[2], revRegex))
- throw BadURL("in flake URL '%s', '%s' is not a commit hash", url.url, path[2]);
rev = Hash::parseAny(path[2], htSHA1);
} else
throw BadURL("GitHub URL '%s' is invalid", url.url);
std::string id = path[0];
- if (!std::regex_match(id, flakeRegex))
- throw BadURL("'%s' is not a valid flake ID", id);
- // FIXME: forbid query params?
+ attrs.emplace("type", "indirect");
+ attrs.emplace("id", id);
+ if (rev) attrs.emplace("rev", rev->gitRev());
+ if (ref) attrs.emplace("ref", *ref);
- Input input;
- input.direct = false;
- input.attrs.insert_or_assign("type", "indirect");
- input.attrs.insert_or_assign("id", id);
- if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
- if (ref) input.attrs.insert_or_assign("ref", *ref);
+ emplaceURLQueryIntoAttrs(url, attrs, {}, {});
- return input;
+ return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
@@ -63,6 +57,18 @@ struct IndirectInputScheme : InputScheme
if (!std::regex_match(id, flakeRegex))
throw BadURL("'%s' is not a valid flake ID", id);
+ // TODO come up with a nicer error message for those two.
+ if (auto rev = maybeGetStrAttr(attrs, "rev")) {
+ if (!std::regex_match(*rev, revRegex)) {
+ throw BadURL("in flake '%s', '%s' is not a commit hash", id, *rev);
+ }
+ }
+ if (auto ref = maybeGetStrAttr(attrs, "ref")) {
+ if (!std::regex_match(*ref, refRegex)) {
+ throw BadURL("in flake '%s', '%s' is not a valid branch/tag name", id, *ref);
+ }
+ }
+
Input input;
input.direct = false;
input.attrs = attrs;
diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc
index 23cf7b51d..b4150e9df 100644
--- a/src/libfetchers/mercurial.cc
+++ b/src/libfetchers/mercurial.cc
@@ -56,12 +56,7 @@ struct MercurialInputScheme : InputScheme
Attrs attrs;
attrs.emplace("type", "hg");
- for (auto &[name, value] : url.query) {
- if (name == "rev" || name == "ref")
- attrs.emplace(name, value);
- else
- url2.query.emplace(name, value);
- }
+ emplaceURLQueryIntoAttrs(url, attrs, {"revCount"}, {});
attrs.emplace("url", url2.to_string());
diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc
index 6ce35aeb2..b11665805 100644
--- a/src/libfetchers/tarball.cc
+++ b/src/libfetchers/tarball.cc
@@ -201,29 +201,17 @@ struct CurlInputScheme : InputScheme
if (!isValidURL(_url, requireTree))
return std::nullopt;
- Input input;
-
auto url = _url;
- url.scheme = parseUrlScheme(url.scheme).transport;
-
- auto narHash = url.query.find("narHash");
- if (narHash != url.query.end())
- input.attrs.insert_or_assign("narHash", narHash->second);
+ Attrs attrs;
+ attrs.emplace("type", inputType());
- if (auto i = get(url.query, "rev"))
- input.attrs.insert_or_assign("rev", *i);
-
- if (auto i = get(url.query, "revCount"))
- if (auto n = string2Int<uint64_t>(*i))
- input.attrs.insert_or_assign("revCount", *n);
+ url.scheme = parseUrlScheme(url.scheme).transport;
- url.query.erase("rev");
- url.query.erase("revCount");
+ emplaceURLQueryIntoAttrs(url, attrs, {"revCount"}, {});
- input.attrs.insert_or_assign("type", inputType());
- input.attrs.insert_or_assign("url", url.to_string());
- return input;
+ attrs.emplace("url", url.to_string());
+ return inputFromAttrs(attrs);
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
@@ -235,7 +223,7 @@ struct CurlInputScheme : InputScheme
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount", "lastModified"};
for (auto & [name, value] : attrs)
if (!allowedNames.count(name))
- throw Error("unsupported %s input attribute '%s'", *type, name);
+ throw Error("unsupported %s input attribute '%s'. If you wanted to fetch a tarball with a query parameter, please use '{ type = \"tarball\"; url = \"...\"; }'", *type, name);
Input input;
input.attrs = attrs;
diff --git a/src/meson.build b/src/meson.build
index 3fc5595b8..e918ae392 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -12,10 +12,19 @@ subdir('libmain')
# libcmd depends on everything
subdir('libcmd')
-
# The rest of the subdirectories aren't separate components,
# just source files in another directory, so we process them here.
+# Static library that just sets default ASan options. It needs to be included
+# in every executable.
+asanoptions = static_library(
+ 'libasanoptions',
+ files('asan-options/asan-options.cc'),
+)
+libasanoptions = declare_dependency(
+ link_whole: asanoptions
+)
+
build_remote_sources = files(
'build-remote/build-remote.cc',
)
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 63b250d13..9d18b81b8 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -15,6 +15,7 @@
#include "registry.hh"
#include "eval-cache.hh"
#include "markdown.hh"
+#include "terminal.hh"
#include <nlohmann/json.hpp>
#include <queue>
@@ -1225,25 +1226,42 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto showDerivation = [&]()
{
auto name = visitor.getAttr(state->sName)->getString();
+ std::optional<std::string> description;
+ if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) {
+ if (auto aDescription = aMeta->maybeGetAttr(state->sDescription))
+ description = aDescription->getString();
+ }
+
if (json) {
- std::optional<std::string> description;
- if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) {
- if (auto aDescription = aMeta->maybeGetAttr(state->sDescription))
- description = aDescription->getString();
- }
j.emplace("type", "derivation");
j.emplace("name", name);
if (description)
j.emplace("description", *description);
} else {
- logger->cout("%s: %s '%s'",
- headerPrefix,
+ auto type =
attrPath.size() == 2 && attrPathS[0] == "devShell" ? "development environment" :
attrPath.size() >= 2 && attrPathS[0] == "devShells" ? "development environment" :
attrPath.size() == 3 && attrPathS[0] == "checks" ? "derivation" :
attrPath.size() >= 1 && attrPathS[0] == "hydraJobs" ? "derivation" :
- "package",
- name);
+ "package";
+ if (description && !description->empty()) {
+ // Trim the string and only display the first line of the description.
+ auto desc = nix::trim(*description);
+ auto firstLineDesc = desc.substr(0, desc.find('\n'));
+
+ std::string output = fmt("%s: %s '%s' - '%s'", headerPrefix, type, name, firstLineDesc);
+ if (output.size() > getWindowSize().second) {
+ // we resize to 4 less then the window size to account for the "...'" we append to
+ // the final string, we also include the ' since that is removed when we truncate the string
+ output.resize(getWindowSize().second - 4);
+ output.append("...'");
+ }
+
+ logger->cout("%s", output.c_str());
+ }
+ else {
+ logger->cout("%s: %s '%s'", headerPrefix, type, name);
+ }
}
};
diff --git a/src/nix/meson.build b/src/nix/meson.build
index 22f148fcb..97387e402 100644
--- a/src/nix/meson.build
+++ b/src/nix/meson.build
@@ -80,6 +80,7 @@ nix = executable(
profiles_md_gen,
nix2_commands_sources,
dependencies : [
+ libasanoptions,
liblixcmd,
liblixutil_mstatic,
liblixstore_mstatic,
diff --git a/tests/functional/fetchers.sh b/tests/functional/fetchers.sh
new file mode 100644
index 000000000..0f888dc33
--- /dev/null
+++ b/tests/functional/fetchers.sh
@@ -0,0 +1,91 @@
+source common.sh
+
+requireGit
+
+clearStore
+
+testFetchTreeError() {
+ rawFetchTreeArg="${1?fetchTree arg missing}"
+ messageSubstring="${2?messageSubstring missing}"
+
+ output="$(nix eval --impure --raw --expr "(builtins.fetchTree $rawFetchTreeArg).outPath" 2>&1)" && status=0 || status=$?
+ grepQuiet "$messageSubstring" <<<"$output"
+ test "$status" -ne 0
+}
+
+# github/gitlab/sourcehut fetcher input validation
+for provider in github gitlab sourcehut; do
+ # ref/rev validation
+ testFetchTreeError \
+ "{ type = \"$provider\"; owner = \"foo\"; repo = \"bar\"; ref = \",\"; }" \
+ "URL '$provider:foo/bar' contains an invalid branch/tag name"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/,\"" \
+ "URL '$provider:foo/bar', ',' is not a commit hash or a branch/tag name"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/f16d8f43dd0998cdb315a2cccf2e4d10027e7ca4?rev=abc\"" \
+ "URL '$provider://host/foo/bar/f16d8f43dd0998cdb315a2cccf2e4d10027e7ca4?rev=abc' already contains a ref or rev"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/ref?ref=ref2\"" \
+ "URL '$provider://host/foo/bar/ref?ref=ref2' already contains a ref or rev"
+
+ # host validation
+ testFetchTreeError \
+ "{ type = \"$provider\"; owner = \"foo\"; repo = \"bar\"; host = \"git_hub.com\"; }" \
+ "URL '$provider:foo/bar' contains an invalid instance host"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/ref?host=git_hub.com\"" \
+ "URL '$provider:foo/bar' contains an invalid instance host"
+
+ # invalid attributes
+ testFetchTreeError \
+ "{ type = \"$provider\"; owner = \"foo\"; repo = \"bar\"; wrong = true; }" \
+ "unsupported input attribute 'wrong'"
+
+ testFetchTreeError \
+ "\"$provider://host/foo/bar/ref?wrong=1\"" \
+ "unsupported input attribute 'wrong'"
+done
+
+# unsupported attributes w/ tarball fetcher
+testFetchTreeError \
+ "\"https://host/foo?wrong=1\"" \
+ "unsupported tarball input attribute 'wrong'. If you wanted to fetch a tarball with a query parameter, please use '{ type = \"tarball\"; url = \"...\"; }"
+
+# test for unsupported attributes / validation in git fetcher
+testFetchTreeError \
+ "\"git+https://github.com/owner/repo?invalid=1\"" \
+ "unsupported Git input attribute 'invalid'"
+
+testFetchTreeError \
+ "\"git+https://github.com/owner/repo?url=foo\"" \
+ "URL 'git+https://github.com/owner/repo?url=foo' must not override url via query param!"
+
+testFetchTreeError \
+ "\"git+https://github.com/owner/repo?ref=foo.lock\"" \
+ "invalid Git branch/tag name 'foo.lock'"
+
+testFetchTreeError \
+ "{ type = \"git\"; url =\"https://github.com/owner/repo\"; ref = \"foo.lock\"; }" \
+ "invalid Git branch/tag name 'foo.lock'"
+
+# same for mercurial
+testFetchTreeError \
+ "\"hg+https://forge.tld/owner/repo?invalid=1\"" \
+ "unsupported Mercurial input attribute 'invalid'"
+
+testFetchTreeError \
+ "{ type = \"hg\"; url = \"https://forge.tld/owner/repo\"; invalid = 1; }" \
+ "unsupported Mercurial input attribute 'invalid'"
+
+testFetchTreeError \
+ "\"hg+https://forge.tld/owner/repo?ref=,\"" \
+ "invalid Mercurial branch/tag name ','"
+
+testFetchTreeError \
+ "{ type = \"hg\"; url = \"https://forge.tld/owner/repo\"; ref = \",\"; }" \
+ "invalid Mercurial branch/tag name ','"
diff --git a/tests/functional/flakes/show.sh b/tests/functional/flakes/show.sh
index a3d300552..25f481575 100644
--- a/tests/functional/flakes/show.sh
+++ b/tests/functional/flakes/show.sh
@@ -85,3 +85,31 @@ assert show_output.legacyPackages.${builtins.currentSystem}.AAAAAASomeThingsFail
assert show_output.legacyPackages.${builtins.currentSystem}.simple.name == "simple";
true
'
+
+cat >flake.nix<<EOF
+{
+ outputs = inputs: {
+ packages.$system = {
+ aNoDescription = import ./simple.nix;
+ bOneLineDescription = import ./simple.nix // { meta.description = "one line"; };
+ cMultiLineDescription = import ./simple.nix // { meta.description = ''
+ line one
+ line two
+ ''; };
+ dLongDescription = import ./simple.nix // { meta.description = ''
+ abcdefghijklmnopqrstuvwxyz
+ ''; };
+ eEmptyDescription = import ./simple.nix // { meta.description = ""; };
+ };
+ };
+}
+EOF
+unbuffer sh -c '
+ stty rows 20 cols 100
+ nix flake show > show-output.txt
+'
+test "$(awk -F '[:] ' '/aNoDescription/{print $NF}' ./show-output.txt)" = "package 'simple'"
+test "$(awk -F '[:] ' '/bOneLineDescription/{print $NF}' ./show-output.txt)" = "package 'simple' - 'one line'"
+test "$(awk -F '[:] ' '/cMultiLineDescription/{print $NF}' ./show-output.txt)" = "package 'simple' - 'line one'"
+test "$(awk -F '[:] ' '/dLongDescription/{print $NF}' ./show-output.txt)" = "package 'simple' - 'abcdefghijklmnopqrs...'"
+test "$(awk -F '[:] ' '/eEmptyDescription/{print $NF}' ./show-output.txt)" = "package 'simple'"
diff --git a/tests/functional/flakes/subdir-flake.sh b/tests/functional/flakes/subdir-flake.sh
new file mode 100644
index 000000000..399518502
--- /dev/null
+++ b/tests/functional/flakes/subdir-flake.sh
@@ -0,0 +1,20 @@
+source common.sh
+requireGit
+clearStore
+
+container="$TEST_HOME"/flake-container
+flake_dir="$container"/flake-dir
+
+createGitRepo "$container"
+mkdir -p "$flake_dir"
+writeSimpleFlake "$flake_dir"
+git -C "$container" add flake-dir
+
+pushd "$flake_dir" &>/dev/null
+ info="$(nix flake info --json)"
+ [[ "$(jq -r '.resolvedUrl' <<<"$info")" == git+file://*/flake-container?dir=flake-dir ]]
+ [[ "$(jq -r '.url' <<<"$info")" == git+file://*/flake-container?dir=flake-dir ]]
+
+ # Make sure we can actually access & build stuff in this flake.
+ nix build "path:$flake_dir#foo" -L
+popd &>/dev/null
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index cbf6a1563..7a9c7182f 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -71,6 +71,7 @@ functional_tests_scripts = [
'flakes/build-paths.sh',
'flakes/flake-registry.sh',
'flakes/flake-in-submodule.sh',
+ 'flakes/subdir-flake.sh',
'gc.sh',
'nix-collect-garbage-d.sh',
'nix-collect-garbage-dry-run.sh',
@@ -94,6 +95,7 @@ functional_tests_scripts = [
'fetchGitRefs.sh',
'gc-runtime.sh',
'tarball.sh',
+ 'fetchers.sh',
'fetchGit.sh',
'fetchurl.sh',
'fetchPath.sh',
diff --git a/tests/functional/repl_characterization/meson.build b/tests/functional/repl_characterization/meson.build
index 56410cfd2..79de9a5f5 100644
--- a/tests/functional/repl_characterization/meson.build
+++ b/tests/functional/repl_characterization/meson.build
@@ -7,6 +7,7 @@ repl_characterization_tester = executable(
'test-repl-characterization',
repl_characterization_tester_sources,
dependencies : [
+ libasanoptions,
liblixutil,
liblixutil_test_support,
sodium,
diff --git a/tests/functional/test-libstoreconsumer/meson.build b/tests/functional/test-libstoreconsumer/meson.build
index ad96aac12..63d0c97ac 100644
--- a/tests/functional/test-libstoreconsumer/meson.build
+++ b/tests/functional/test-libstoreconsumer/meson.build
@@ -2,6 +2,7 @@ libstoreconsumer_tester = executable(
'test-libstoreconsumer',
'main.cc',
dependencies : [
+ libasanoptions,
liblixutil,
liblixstore,
sodium,
diff --git a/tests/nixos/tarball-flakes.nix b/tests/nixos/tarball-flakes.nix
index ca7627bd1..5deba4a12 100644
--- a/tests/nixos/tarball-flakes.nix
+++ b/tests/nixos/tarball-flakes.nix
@@ -69,7 +69,7 @@ in
# Check that we got redirected to the immutable URL.
locked_url = info["locked"]["url"]
- assert locked_url == "http://localhost/stable/${nixpkgs.rev}.tar.gz", f"{locked_url=} != http://localhost/stable/${nixpkgs.rev}.tar.gz"
+ assert locked_url == "http://localhost/stable/${nixpkgs.rev}.tar.gz?rev=${nixpkgs.rev}&revCount=1234", f"{locked_url=} != http://localhost/stable/${nixpkgs.rev}.tar.gz"
# Check that we got the rev and revCount attributes.
revision = info["revision"]
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index c449b2276..55c7566bd 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -11,6 +11,10 @@
# functions, the result would be way less readable than just a bit of copypasta.
# It's only ~200 lines; better to just refactor the tests themselves which we'll want to do anyway.
+default_test_env = {
+ 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1'
+}
+
libutil_test_support_sources = files(
'libutil-support/tests/cli-literate-parser.cc',
'libutil-support/tests/hash.cc',
@@ -63,6 +67,7 @@ libutil_tester = executable(
'liblixutil-tests',
libutil_tests_sources,
dependencies : [
+ libasanoptions,
rapidcheck,
gtest,
boehm,
@@ -78,7 +83,7 @@ test(
'libutil-unit-tests',
libutil_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libutil/data',
},
suite : 'check',
@@ -132,6 +137,7 @@ libstore_tester = executable(
'liblixstore-tests',
libstore_tests_sources,
dependencies : [
+ libasanoptions,
liblixstore_test_support,
liblixutil_test_support,
liblixstore_mstatic,
@@ -147,7 +153,7 @@ test(
'libstore-unit-tests',
libstore_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libstore/data',
},
suite : 'check',
@@ -196,6 +202,7 @@ libexpr_tester = executable(
'liblixexpr-tests',
libexpr_tests_sources,
dependencies : [
+ libasanoptions,
liblixexpr_test_support,
liblixstore_test_support,
liblixstore_mstatic,
@@ -214,7 +221,7 @@ test(
'libexpr-unit-tests',
libexpr_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libexpr/data',
},
suite : 'check',
@@ -226,6 +233,7 @@ libcmd_tester = executable(
'liblixcmd-tests',
files('libcmd/args.cc'),
dependencies : [
+ libasanoptions,
liblixcmd,
liblixutil,
liblixmain,
@@ -241,7 +249,7 @@ test(
'libcmd-unit-tests',
libcmd_tester,
args : tests_args,
- env : {
+ env : default_test_env + {
# No special meaning here, it's just a file laying around that is unlikely to go anywhere
# any time soon.
'_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/nix-env/buildenv.nix',
@@ -272,6 +280,7 @@ test(
'libmain-unit-tests',
libmain_tester,
args : tests_args,
+ env : default_test_env,
suite : 'check',
protocol : 'gtest',
)
diff --git a/version.json b/version.json
index 48db2994f..809358e6d 100644
--- a/version.json
+++ b/version.json
@@ -1,4 +1,5 @@
{
"version": "2.91.0-dev",
+ "official_release": false,
"release_name": "TBA"
}