mirror of
https://github.com/NixOS/nix
synced 2025-06-27 16:51:15 +02:00
Merge remote-tracking branch 'origin/2.26-maintenance' into detsys-main
This commit is contained in:
commit
6749d26dbb
59 changed files with 708 additions and 403 deletions
2
.version
2
.version
|
@ -1 +1 @@
|
||||||
2.26.2
|
2.26.3
|
||||||
|
|
10
doc/manual/rl-next/curl-cloexec.md
Normal file
10
doc/manual/rl-next/curl-cloexec.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
synopsis: Set FD_CLOEXEC on sockets created by curl
|
||||||
|
issues: []
|
||||||
|
prs: [12439]
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
Curl creates sockets without setting FD_CLOEXEC/SOCK_CLOEXEC, this can cause connections to remain open forever when using commands like `nix shell`
|
||||||
|
|
||||||
|
This change sets the FD_CLOEXEC flag using a CURLOPT_SOCKOPTFUNCTION callback.
|
25
flake.nix
25
flake.nix
|
@ -167,6 +167,7 @@
|
||||||
f = import ./packaging/components.nix {
|
f = import ./packaging/components.nix {
|
||||||
inherit (final) lib;
|
inherit (final) lib;
|
||||||
inherit officialRelease;
|
inherit officialRelease;
|
||||||
|
pkgs = final;
|
||||||
src = self;
|
src = self;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -237,6 +238,30 @@
|
||||||
LANG=C.UTF-8 ${pkgs.changelog-d}/bin/changelog-d ${./doc/manual/rl-next} >$out
|
LANG=C.UTF-8 ${pkgs.changelog-d}/bin/changelog-d ${./doc/manual/rl-next} >$out
|
||||||
'';
|
'';
|
||||||
repl-completion = nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { };
|
repl-completion = nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { };
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks for our packaging expressions.
|
||||||
|
This shouldn't build anything significant; just check that things
|
||||||
|
(including derivations) are _set up_ correctly.
|
||||||
|
*/
|
||||||
|
packaging-overriding =
|
||||||
|
let
|
||||||
|
pkgs = nixpkgsFor.${system}.native;
|
||||||
|
nix = self.packages.${system}.nix;
|
||||||
|
in
|
||||||
|
assert (nix.appendPatches [ pkgs.emptyFile ]).libs.nix-util.src.patches == [ pkgs.emptyFile ];
|
||||||
|
if pkgs.stdenv.buildPlatform.isDarwin then
|
||||||
|
lib.warn "packaging-overriding check currently disabled because of a permissions issue on macOS" pkgs.emptyFile
|
||||||
|
else
|
||||||
|
# If this fails, something might be wrong with how we've wired the scope,
|
||||||
|
# or something could be broken in Nixpkgs.
|
||||||
|
pkgs.testers.testEqualContents {
|
||||||
|
assertion = "trivial patch does not change source contents";
|
||||||
|
expected = "${./.}";
|
||||||
|
actual =
|
||||||
|
# Same for all components; nix-util is an arbitrary pick
|
||||||
|
(nix.appendPatches [ pkgs.emptyFile ]).libs.nix-util.src;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// (lib.optionalAttrs (builtins.elem system linux64BitSystems)) {
|
// (lib.optionalAttrs (builtins.elem system linux64BitSystems)) {
|
||||||
dockerImage = self.hydraJobs.dockerImage.${system};
|
dockerImage = self.hydraJobs.dockerImage.${system};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
|
pkgs,
|
||||||
src,
|
src,
|
||||||
officialRelease,
|
officialRelease,
|
||||||
}:
|
}:
|
||||||
|
@ -7,7 +8,23 @@
|
||||||
scope:
|
scope:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (scope) callPackage;
|
inherit (scope)
|
||||||
|
callPackage
|
||||||
|
;
|
||||||
|
inherit
|
||||||
|
(scope.callPackage (
|
||||||
|
{ stdenv }:
|
||||||
|
{
|
||||||
|
inherit stdenv;
|
||||||
|
}
|
||||||
|
) { })
|
||||||
|
stdenv
|
||||||
|
;
|
||||||
|
inherit (pkgs.buildPackages)
|
||||||
|
meson
|
||||||
|
ninja
|
||||||
|
pkg-config
|
||||||
|
;
|
||||||
|
|
||||||
baseVersion = lib.fileContents ../.version;
|
baseVersion = lib.fileContents ../.version;
|
||||||
|
|
||||||
|
@ -20,6 +37,165 @@ let
|
||||||
}_${src.shortRev or "dirty"}";
|
}_${src.shortRev or "dirty"}";
|
||||||
|
|
||||||
fineVersion = baseVersion + fineVersionSuffix;
|
fineVersion = baseVersion + fineVersionSuffix;
|
||||||
|
|
||||||
|
root = ../.;
|
||||||
|
|
||||||
|
# Indirection for Nixpkgs to override when package.nix files are vendored
|
||||||
|
filesetToSource = lib.fileset.toSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Given a set of layers, create a mkDerivation-like function
|
||||||
|
*/
|
||||||
|
mkPackageBuilder =
|
||||||
|
exts: userFn: stdenv.mkDerivation (lib.extends (lib.composeManyExtensions exts) userFn);
|
||||||
|
|
||||||
|
setVersionLayer = finalAttrs: prevAttrs: {
|
||||||
|
preConfigure =
|
||||||
|
prevAttrs.prevAttrs or ""
|
||||||
|
+
|
||||||
|
# Update the repo-global .version file.
|
||||||
|
# Symlink ./.version points there, but by default only workDir is writable.
|
||||||
|
''
|
||||||
|
chmod u+w ./.version
|
||||||
|
echo ${finalAttrs.version} > ./.version
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
localSourceLayer =
|
||||||
|
finalAttrs: prevAttrs:
|
||||||
|
let
|
||||||
|
workDirPath =
|
||||||
|
# Ideally we'd pick finalAttrs.workDir, but for now `mkDerivation` has
|
||||||
|
# the requirement that everything except passthru and meta must be
|
||||||
|
# serialized by mkDerivation, which doesn't work for this.
|
||||||
|
prevAttrs.workDir;
|
||||||
|
|
||||||
|
workDirSubpath = lib.path.removePrefix root workDirPath;
|
||||||
|
sources =
|
||||||
|
assert prevAttrs.fileset._type == "fileset";
|
||||||
|
prevAttrs.fileset;
|
||||||
|
src = lib.fileset.toSource {
|
||||||
|
fileset = sources;
|
||||||
|
inherit root;
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
sourceRoot = "${src.name}/" + workDirSubpath;
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
# Clear what `derivation` can't/shouldn't serialize; see prevAttrs.workDir.
|
||||||
|
fileset = null;
|
||||||
|
workDir = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
resolveRelPath = p: lib.path.removePrefix root p;
|
||||||
|
|
||||||
|
makeFetchedSourceLayer =
|
||||||
|
finalScope: finalAttrs: prevAttrs:
|
||||||
|
let
|
||||||
|
workDirPath =
|
||||||
|
# Ideally we'd pick finalAttrs.workDir, but for now `mkDerivation` has
|
||||||
|
# the requirement that everything except passthru and meta must be
|
||||||
|
# serialized by mkDerivation, which doesn't work for this.
|
||||||
|
prevAttrs.workDir;
|
||||||
|
|
||||||
|
workDirSubpath = resolveRelPath workDirPath;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
sourceRoot = "${finalScope.patchedSrc.name}/" + workDirSubpath;
|
||||||
|
src = finalScope.patchedSrc;
|
||||||
|
version =
|
||||||
|
let
|
||||||
|
n = lib.length finalScope.patches;
|
||||||
|
in
|
||||||
|
if n == 0 then finalAttrs.version else finalAttrs.version + "+${toString n}";
|
||||||
|
|
||||||
|
# Clear what `derivation` can't/shouldn't serialize; see prevAttrs.workDir.
|
||||||
|
fileset = null;
|
||||||
|
workDir = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
mesonLayer = finalAttrs: prevAttrs: {
|
||||||
|
# NOTE:
|
||||||
|
# As of https://github.com/NixOS/nixpkgs/blob/8baf8241cea0c7b30e0b8ae73474cb3de83c1a30/pkgs/by-name/me/meson/setup-hook.sh#L26,
|
||||||
|
# `mesonBuildType` defaults to `plain` if not specified. We want our Nix-built binaries to be optimized by default.
|
||||||
|
# More on build types here: https://mesonbuild.com/Builtin-options.html#details-for-buildtype.
|
||||||
|
mesonBuildType = "release";
|
||||||
|
# NOTE:
|
||||||
|
# Users who are debugging Nix builds are expected to set the environment variable `mesonBuildType`, per the
|
||||||
|
# guidance in https://github.com/NixOS/nix/blob/8a3fc27f1b63a08ac983ee46435a56cf49ebaf4a/doc/manual/source/development/debugging.md?plain=1#L10.
|
||||||
|
# For this reason, we don't want to refer to `finalAttrs.mesonBuildType` here, but rather use the environment variable.
|
||||||
|
preConfigure =
|
||||||
|
prevAttrs.preConfigure or ""
|
||||||
|
+
|
||||||
|
lib.optionalString
|
||||||
|
(
|
||||||
|
!stdenv.hostPlatform.isWindows
|
||||||
|
# build failure
|
||||||
|
&& !stdenv.hostPlatform.isStatic
|
||||||
|
# LTO breaks exception handling on x86-64-darwin.
|
||||||
|
&& stdenv.system != "x86_64-darwin"
|
||||||
|
)
|
||||||
|
''
|
||||||
|
case "$mesonBuildType" in
|
||||||
|
release|minsize) appendToVar mesonFlags "-Db_lto=true" ;;
|
||||||
|
*) appendToVar mesonFlags "-Db_lto=false" ;;
|
||||||
|
esac
|
||||||
|
'';
|
||||||
|
nativeBuildInputs = [
|
||||||
|
meson
|
||||||
|
ninja
|
||||||
|
] ++ prevAttrs.nativeBuildInputs or [ ];
|
||||||
|
mesonCheckFlags = prevAttrs.mesonCheckFlags or [ ] ++ [
|
||||||
|
"--print-errorlogs"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
mesonBuildLayer = finalAttrs: prevAttrs: {
|
||||||
|
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
||||||
|
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
||||||
|
env =
|
||||||
|
prevAttrs.env or { }
|
||||||
|
// lib.optionalAttrs (
|
||||||
|
stdenv.isLinux
|
||||||
|
&& !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")
|
||||||
|
&& !(stdenv.hostPlatform.useLLVM or false)
|
||||||
|
) { LDFLAGS = "-fuse-ld=gold"; };
|
||||||
|
};
|
||||||
|
|
||||||
|
mesonLibraryLayer = finalAttrs: prevAttrs: {
|
||||||
|
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Work around weird `--as-needed` linker behavior with BSD, see
|
||||||
|
# https://github.com/mesonbuild/meson/issues/3593
|
||||||
|
bsdNoLinkAsNeeded =
|
||||||
|
finalAttrs: prevAttrs:
|
||||||
|
lib.optionalAttrs stdenv.hostPlatform.isBSD {
|
||||||
|
mesonFlags = [ (lib.mesonBool "b_asneeded" false) ] ++ prevAttrs.mesonFlags or [ ];
|
||||||
|
};
|
||||||
|
|
||||||
|
miscGoodPractice = finalAttrs: prevAttrs: {
|
||||||
|
strictDeps = prevAttrs.strictDeps or true;
|
||||||
|
enableParallelBuilding = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Append patches to the source layer.
|
||||||
|
*/
|
||||||
|
appendPatches =
|
||||||
|
scope: patches:
|
||||||
|
scope.overrideScope (
|
||||||
|
finalScope: prevScope: {
|
||||||
|
patches = prevScope.patches ++ patches;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
# This becomes the pkgs.nixComponents attribute set
|
# This becomes the pkgs.nixComponents attribute set
|
||||||
|
@ -27,6 +203,110 @@ in
|
||||||
version = baseVersion + versionSuffix;
|
version = baseVersion + versionSuffix;
|
||||||
inherit versionSuffix;
|
inherit versionSuffix;
|
||||||
|
|
||||||
|
inherit filesetToSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A user-provided extension function to apply to each component derivation.
|
||||||
|
*/
|
||||||
|
mesonComponentOverrides = finalAttrs: prevAttrs: { };
|
||||||
|
|
||||||
|
/**
|
||||||
|
An overridable derivation layer for handling the sources.
|
||||||
|
*/
|
||||||
|
sourceLayer = localSourceLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resolve a path value to either itself or a path in the `src`, depending
|
||||||
|
whether `overrideSource` was called.
|
||||||
|
*/
|
||||||
|
resolvePath = p: p;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Apply an extension function (i.e. overlay-shaped) to all component derivations.
|
||||||
|
*/
|
||||||
|
overrideAllMesonComponents =
|
||||||
|
f:
|
||||||
|
scope.overrideScope (
|
||||||
|
finalScope: prevScope: {
|
||||||
|
mesonComponentOverrides = lib.composeExtensions scope.mesonComponentOverrides f;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Provide an alternate source. This allows the expressions to be vendored without copying the sources,
|
||||||
|
but it does make the build non-granular; all components will use a complete source.
|
||||||
|
|
||||||
|
Packaging expressions will be ignored.
|
||||||
|
*/
|
||||||
|
overrideSource =
|
||||||
|
src:
|
||||||
|
scope.overrideScope (
|
||||||
|
finalScope: prevScope: {
|
||||||
|
sourceLayer = makeFetchedSourceLayer finalScope;
|
||||||
|
/**
|
||||||
|
Unpatched source for the build of Nix. Packaging expressions will be ignored.
|
||||||
|
*/
|
||||||
|
src = src;
|
||||||
|
/**
|
||||||
|
Patches for the whole Nix source. Changes to packaging expressions will be ignored.
|
||||||
|
*/
|
||||||
|
patches = [ ];
|
||||||
|
/**
|
||||||
|
Fetched and patched source to be used in component derivations.
|
||||||
|
*/
|
||||||
|
patchedSrc =
|
||||||
|
if finalScope.patches == [ ] then
|
||||||
|
src
|
||||||
|
else
|
||||||
|
pkgs.buildPackages.srcOnly (
|
||||||
|
pkgs.buildPackages.stdenvNoCC.mkDerivation {
|
||||||
|
name = "${finalScope.src.name or "nix-source"}-patched";
|
||||||
|
inherit (finalScope) src patches;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
resolvePath = p: finalScope.patchedSrc + "/${resolveRelPath p}";
|
||||||
|
appendPatches = appendPatches finalScope;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Append patches to be applied to the whole Nix source.
|
||||||
|
This affects all components.
|
||||||
|
|
||||||
|
Changes to the packaging expressions will be ignored.
|
||||||
|
*/
|
||||||
|
appendPatches =
|
||||||
|
patches:
|
||||||
|
# switch to "fetched" source first, so that patches apply to the whole tree.
|
||||||
|
(scope.overrideSource "${./..}").appendPatches patches;
|
||||||
|
|
||||||
|
mkMesonDerivation = mkPackageBuilder [
|
||||||
|
miscGoodPractice
|
||||||
|
scope.sourceLayer
|
||||||
|
setVersionLayer
|
||||||
|
mesonLayer
|
||||||
|
scope.mesonComponentOverrides
|
||||||
|
];
|
||||||
|
mkMesonExecutable = mkPackageBuilder [
|
||||||
|
miscGoodPractice
|
||||||
|
bsdNoLinkAsNeeded
|
||||||
|
scope.sourceLayer
|
||||||
|
setVersionLayer
|
||||||
|
mesonLayer
|
||||||
|
mesonBuildLayer
|
||||||
|
scope.mesonComponentOverrides
|
||||||
|
];
|
||||||
|
mkMesonLibrary = mkPackageBuilder [
|
||||||
|
miscGoodPractice
|
||||||
|
bsdNoLinkAsNeeded
|
||||||
|
scope.sourceLayer
|
||||||
|
mesonLayer
|
||||||
|
setVersionLayer
|
||||||
|
mesonBuildLayer
|
||||||
|
mesonLibraryLayer
|
||||||
|
scope.mesonComponentOverrides
|
||||||
|
];
|
||||||
|
|
||||||
nix-util = callPackage ../src/libutil/package.nix { };
|
nix-util = callPackage ../src/libutil/package.nix { };
|
||||||
nix-util-c = callPackage ../src/libutil-c/package.nix { };
|
nix-util-c = callPackage ../src/libutil-c/package.nix { };
|
||||||
nix-util-test-support = callPackage ../src/libutil-test-support/package.nix { };
|
nix-util-test-support = callPackage ../src/libutil-test-support/package.nix { };
|
||||||
|
@ -66,5 +346,33 @@ in
|
||||||
|
|
||||||
nix-perl-bindings = callPackage ../src/perl/package.nix { };
|
nix-perl-bindings = callPackage ../src/perl/package.nix { };
|
||||||
|
|
||||||
nix-everything = callPackage ../packaging/everything.nix { };
|
nix-everything = callPackage ../packaging/everything.nix { } // {
|
||||||
|
# Note: no `passthru.overrideAllMesonComponents`
|
||||||
|
# This would propagate into `nix.overrideAttrs f`, but then discard
|
||||||
|
# `f` when `.overrideAllMesonComponents` is used.
|
||||||
|
# Both "methods" should be views on the same fixpoint overriding mechanism
|
||||||
|
# for that to work. For now, we intentionally don't support the broken
|
||||||
|
# two-fixpoint solution.
|
||||||
|
/**
|
||||||
|
Apply an extension function (i.e. overlay-shaped) to all component derivations, and return the nix package.
|
||||||
|
*/
|
||||||
|
overrideAllMesonComponents = f: (scope.overrideAllMesonComponents f).nix-everything;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Append patches to be applied to the whole Nix source.
|
||||||
|
This affects all components.
|
||||||
|
|
||||||
|
Changes to the packaging expressions will be ignored.
|
||||||
|
*/
|
||||||
|
appendPatches = ps: (scope.appendPatches ps).nix-everything;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Provide an alternate source. This allows the expressions to be vendored without copying the sources,
|
||||||
|
but it does make the build non-granular; all components will use a complete source.
|
||||||
|
|
||||||
|
Packaging expressions will be ignored.
|
||||||
|
*/
|
||||||
|
overrideSource = src: (scope.overrideSource src).nix-everything;
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@ in
|
||||||
let
|
let
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
root = ../.;
|
|
||||||
|
|
||||||
stdenv = if prevStdenv.isDarwin && prevStdenv.isx86_64 then darwinStdenv else prevStdenv;
|
stdenv = if prevStdenv.isDarwin && prevStdenv.isx86_64 then darwinStdenv else prevStdenv;
|
||||||
|
|
||||||
# Fix the following error with the default x86_64-darwin SDK:
|
# Fix the following error with the default x86_64-darwin SDK:
|
||||||
|
@ -30,113 +28,6 @@ let
|
||||||
# all the way back to 10.6.
|
# all the way back to 10.6.
|
||||||
darwinStdenv = pkgs.overrideSDK prevStdenv { darwinMinVersion = "10.13"; };
|
darwinStdenv = pkgs.overrideSDK prevStdenv { darwinMinVersion = "10.13"; };
|
||||||
|
|
||||||
# Nixpkgs implements this by returning a subpath into the fetched Nix sources.
|
|
||||||
resolvePath = p: p;
|
|
||||||
|
|
||||||
# Indirection for Nixpkgs to override when package.nix files are vendored
|
|
||||||
filesetToSource = lib.fileset.toSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Given a set of layers, create a mkDerivation-like function
|
|
||||||
*/
|
|
||||||
mkPackageBuilder =
|
|
||||||
exts: userFn: stdenv.mkDerivation (lib.extends (lib.composeManyExtensions exts) userFn);
|
|
||||||
|
|
||||||
localSourceLayer =
|
|
||||||
finalAttrs: prevAttrs:
|
|
||||||
let
|
|
||||||
workDirPath =
|
|
||||||
# Ideally we'd pick finalAttrs.workDir, but for now `mkDerivation` has
|
|
||||||
# the requirement that everything except passthru and meta must be
|
|
||||||
# serialized by mkDerivation, which doesn't work for this.
|
|
||||||
prevAttrs.workDir;
|
|
||||||
|
|
||||||
workDirSubpath = lib.path.removePrefix root workDirPath;
|
|
||||||
sources =
|
|
||||||
assert prevAttrs.fileset._type == "fileset";
|
|
||||||
prevAttrs.fileset;
|
|
||||||
src = lib.fileset.toSource {
|
|
||||||
fileset = sources;
|
|
||||||
inherit root;
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sourceRoot = "${src.name}/" + workDirSubpath;
|
|
||||||
inherit src;
|
|
||||||
|
|
||||||
# Clear what `derivation` can't/shouldn't serialize; see prevAttrs.workDir.
|
|
||||||
fileset = null;
|
|
||||||
workDir = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
mesonLayer = finalAttrs: prevAttrs: {
|
|
||||||
# NOTE:
|
|
||||||
# As of https://github.com/NixOS/nixpkgs/blob/8baf8241cea0c7b30e0b8ae73474cb3de83c1a30/pkgs/by-name/me/meson/setup-hook.sh#L26,
|
|
||||||
# `mesonBuildType` defaults to `plain` if not specified. We want our Nix-built binaries to be optimized by default.
|
|
||||||
# More on build types here: https://mesonbuild.com/Builtin-options.html#details-for-buildtype.
|
|
||||||
mesonBuildType = "release";
|
|
||||||
# NOTE:
|
|
||||||
# Users who are debugging Nix builds are expected to set the environment variable `mesonBuildType`, per the
|
|
||||||
# guidance in https://github.com/NixOS/nix/blob/8a3fc27f1b63a08ac983ee46435a56cf49ebaf4a/doc/manual/source/development/debugging.md?plain=1#L10.
|
|
||||||
# For this reason, we don't want to refer to `finalAttrs.mesonBuildType` here, but rather use the environment variable.
|
|
||||||
preConfigure =
|
|
||||||
prevAttrs.preConfigure or ""
|
|
||||||
+
|
|
||||||
lib.optionalString
|
|
||||||
(
|
|
||||||
!stdenv.hostPlatform.isWindows
|
|
||||||
# build failure
|
|
||||||
&& !stdenv.hostPlatform.isStatic
|
|
||||||
# LTO breaks exception handling on x86-64-darwin.
|
|
||||||
&& stdenv.system != "x86_64-darwin"
|
|
||||||
)
|
|
||||||
''
|
|
||||||
case "$mesonBuildType" in
|
|
||||||
release|minsize) appendToVar mesonFlags "-Db_lto=true" ;;
|
|
||||||
*) appendToVar mesonFlags "-Db_lto=false" ;;
|
|
||||||
esac
|
|
||||||
'';
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.buildPackages.meson
|
|
||||||
pkgs.buildPackages.ninja
|
|
||||||
] ++ prevAttrs.nativeBuildInputs or [ ];
|
|
||||||
mesonCheckFlags = prevAttrs.mesonCheckFlags or [ ] ++ [
|
|
||||||
"--print-errorlogs"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
mesonBuildLayer = finalAttrs: prevAttrs: {
|
|
||||||
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [
|
|
||||||
pkgs.buildPackages.pkg-config
|
|
||||||
];
|
|
||||||
separateDebugInfo = !stdenv.hostPlatform.isStatic;
|
|
||||||
hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie";
|
|
||||||
env =
|
|
||||||
prevAttrs.env or { }
|
|
||||||
// lib.optionalAttrs (
|
|
||||||
stdenv.isLinux
|
|
||||||
&& !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")
|
|
||||||
&& !(stdenv.hostPlatform.useLLVM or false)
|
|
||||||
) { LDFLAGS = "-fuse-ld=gold"; };
|
|
||||||
};
|
|
||||||
|
|
||||||
mesonLibraryLayer = finalAttrs: prevAttrs: {
|
|
||||||
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Work around weird `--as-needed` linker behavior with BSD, see
|
|
||||||
# https://github.com/mesonbuild/meson/issues/3593
|
|
||||||
bsdNoLinkAsNeeded =
|
|
||||||
finalAttrs: prevAttrs:
|
|
||||||
lib.optionalAttrs stdenv.hostPlatform.isBSD {
|
|
||||||
mesonFlags = [ (lib.mesonBool "b_asneeded" false) ] ++ prevAttrs.mesonFlags or [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
miscGoodPractice = finalAttrs: prevAttrs: {
|
|
||||||
strictDeps = prevAttrs.strictDeps or true;
|
|
||||||
enableParallelBuilding = true;
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
scope: {
|
scope: {
|
||||||
inherit stdenv;
|
inherit stdenv;
|
||||||
|
@ -174,8 +65,13 @@ scope: {
|
||||||
installPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.installPhase;
|
installPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.installPhase;
|
||||||
});
|
});
|
||||||
|
|
||||||
libgit2 = pkgs.libgit2.overrideAttrs (attrs: {
|
libgit2 = pkgs.libgit2.overrideAttrs (
|
||||||
|
attrs:
|
||||||
|
{
|
||||||
cmakeFlags = attrs.cmakeFlags or [ ] ++ [ "-DUSE_SSH=exec" ];
|
cmakeFlags = attrs.cmakeFlags or [ ] ++ [ "-DUSE_SSH=exec" ];
|
||||||
|
}
|
||||||
|
# libgit2: Nixpkgs 24.11 has < 1.9.0, which needs our patches
|
||||||
|
// lib.optionalAttrs (!lib.versionAtLeast pkgs.libgit2.version "1.9.0") {
|
||||||
nativeBuildInputs =
|
nativeBuildInputs =
|
||||||
attrs.nativeBuildInputs or [ ]
|
attrs.nativeBuildInputs or [ ]
|
||||||
# gitMinimal does not build on Windows. See packbuilder patch.
|
# gitMinimal does not build on Windows. See packbuilder patch.
|
||||||
|
@ -202,28 +98,6 @@ scope: {
|
||||||
# binary patch; see `prePatch`
|
# binary patch; see `prePatch`
|
||||||
./patches/libgit2-packbuilder-callback-interruptible.patch
|
./patches/libgit2-packbuilder-callback-interruptible.patch
|
||||||
];
|
];
|
||||||
});
|
}
|
||||||
|
);
|
||||||
inherit resolvePath filesetToSource;
|
|
||||||
|
|
||||||
mkMesonDerivation = mkPackageBuilder [
|
|
||||||
miscGoodPractice
|
|
||||||
localSourceLayer
|
|
||||||
mesonLayer
|
|
||||||
];
|
|
||||||
mkMesonExecutable = mkPackageBuilder [
|
|
||||||
miscGoodPractice
|
|
||||||
bsdNoLinkAsNeeded
|
|
||||||
localSourceLayer
|
|
||||||
mesonLayer
|
|
||||||
mesonBuildLayer
|
|
||||||
];
|
|
||||||
mkMesonLibrary = mkPackageBuilder [
|
|
||||||
miscGoodPractice
|
|
||||||
bsdNoLinkAsNeeded
|
|
||||||
localSourceLayer
|
|
||||||
mesonLayer
|
|
||||||
mesonBuildLayer
|
|
||||||
mesonLibraryLayer
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,14 +64,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nlohmann_json
|
nlohmann_json
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
(lib.mesonEnable "markdown" enableMarkdown)
|
(lib.mesonEnable "markdown" enableMarkdown)
|
||||||
(lib.mesonOption "readline-flavor" readlineFlavor)
|
(lib.mesonOption "readline-flavor" readlineFlavor)
|
||||||
|
|
|
@ -36,14 +36,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nix-expr
|
nix-expr
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
rapidcheck
|
rapidcheck
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -46,14 +46,6 @@ mkMesonExecutable (finalAttrs: {
|
||||||
gtest
|
gtest
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ Strings EvalSettings::getDefaultNixPath()
|
||||||
{
|
{
|
||||||
Strings res;
|
Strings res;
|
||||||
auto add = [&](const Path & p, const std::string & s = std::string()) {
|
auto add = [&](const Path & p, const std::string & s = std::string()) {
|
||||||
if (pathAccessible(p)) {
|
if (std::filesystem::exists(p)) {
|
||||||
if (s.empty()) {
|
if (s.empty()) {
|
||||||
res.push_back(p);
|
res.push_back(p);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3070,8 +3070,11 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||||
auto i = lookupPathResolved.find(value);
|
auto i = lookupPathResolved.find(value);
|
||||||
if (i != lookupPathResolved.end()) return i->second;
|
if (i != lookupPathResolved.end()) return i->second;
|
||||||
|
|
||||||
auto finish = [&](SourcePath res) {
|
auto finish = [&](std::optional<SourcePath> res) {
|
||||||
debug("resolved search path element '%s' to '%s'", value, res);
|
if (res)
|
||||||
|
debug("resolved search path element '%s' to '%s'", value, *res);
|
||||||
|
else
|
||||||
|
debug("failed to resolve search path element '%s'", value);
|
||||||
lookupPathResolved.emplace(value, res);
|
lookupPathResolved.emplace(value, res);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
@ -3123,8 +3126,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("failed to resolve search path element '%s'", value);
|
return finish(std::nullopt);
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -77,14 +77,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nlohmann_json
|
nlohmann_json
|
||||||
] ++ lib.optional enableGC boehmgc;
|
] ++ lib.optional enableGC boehmgc;
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
(lib.mesonEnable "gc" enableGC)
|
(lib.mesonEnable "gc" enableGC)
|
||||||
];
|
];
|
||||||
|
|
|
@ -44,14 +44,6 @@ mkMesonExecutable (finalAttrs: {
|
||||||
gtest
|
gtest
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -41,14 +41,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nlohmann_json
|
nlohmann_json
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,14 +38,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nix-flake
|
nix-flake
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -46,14 +46,6 @@ mkMesonExecutable (finalAttrs: {
|
||||||
gtest
|
gtest
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nlohmann_json
|
nlohmann_json
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,14 +40,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nix-main
|
nix-main
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
openssl
|
openssl
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
platforms = lib.platforms.unix ++ lib.platforms.windows;
|
||||||
};
|
};
|
||||||
|
|
|
@ -315,20 +315,6 @@ void printVersion(const std::string & programName)
|
||||||
throw Exit();
|
throw Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void showManPage(const std::string & name)
|
|
||||||
{
|
|
||||||
restoreProcessContext();
|
|
||||||
setEnv("MANPATH", settings.nixManDir.c_str());
|
|
||||||
execlp("man", "man", name.c_str(), nullptr);
|
|
||||||
if (errno == ENOENT) {
|
|
||||||
// Not SysError because we don't want to suffix the errno, aka No such file or directory.
|
|
||||||
throw Error("The '%1%' command was not found, but it is needed for '%2%' and some other '%3%' commands' help text. Perhaps you could install the '%1%' command?", "man", name.c_str(), "nix-*");
|
|
||||||
}
|
|
||||||
throw SysError("command 'man %1%' failed", name.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int handleExceptions(const std::string & programName, std::function<void()> fun)
|
int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||||
{
|
{
|
||||||
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
||||||
|
|
|
@ -70,11 +70,6 @@ struct LegacyArgs : public MixCommonArgs, public RootArgs
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the manual page for the specified program.
|
|
||||||
*/
|
|
||||||
void showManPage(const std::string & name);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The constructor of this class starts a pager if standard output is a
|
* The constructor of this class starts a pager if standard output is a
|
||||||
* terminal and $PAGER is set. Standard output is redirected to the
|
* terminal and $PAGER is set. Standard output is redirected to the
|
||||||
|
|
|
@ -36,14 +36,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nix-store
|
nix-store
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -40,14 +40,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
rapidcheck
|
rapidcheck
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -52,14 +52,6 @@ mkMesonExecutable (finalAttrs: {
|
||||||
nix-store-test-support
|
nix-store-test-support
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -300,6 +300,14 @@ struct curlFileTransfer : public FileTransfer
|
||||||
return ((TransferItem *) userp)->readCallback(buffer, size, nitems);
|
return ((TransferItem *) userp)->readCallback(buffer, size, nitems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && LIBCURL_VERSION_NUM >= 0x071000
|
||||||
|
static int cloexec_callback(void *, curl_socket_t curlfd, curlsocktype purpose) {
|
||||||
|
unix::closeOnExec(curlfd);
|
||||||
|
vomit("cloexec set for fd %i", curlfd);
|
||||||
|
return CURL_SOCKOPT_OK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void init()
|
void init()
|
||||||
{
|
{
|
||||||
if (!req) req = curl_easy_init();
|
if (!req) req = curl_easy_init();
|
||||||
|
@ -359,6 +367,10 @@ struct curlFileTransfer : public FileTransfer
|
||||||
curl_easy_setopt(req, CURLOPT_SSL_VERIFYHOST, 0);
|
curl_easy_setopt(req, CURLOPT_SSL_VERIFYHOST, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(_WIN32) && LIBCURL_VERSION_NUM >= 0x071000
|
||||||
|
curl_easy_setopt(req, CURLOPT_SOCKOPTFUNCTION, cloexec_callback);
|
||||||
|
#endif
|
||||||
|
|
||||||
curl_easy_setopt(req, CURLOPT_CONNECTTIMEOUT, fileTransferSettings.connectTimeout.get());
|
curl_easy_setopt(req, CURLOPT_CONNECTTIMEOUT, fileTransferSettings.connectTimeout.get());
|
||||||
|
|
||||||
curl_easy_setopt(req, CURLOPT_LOW_SPEED_LIMIT, 1L);
|
curl_easy_setopt(req, CURLOPT_LOW_SPEED_LIMIT, 1L);
|
||||||
|
|
|
@ -65,7 +65,6 @@ Settings::Settings()
|
||||||
, nixStateDir(canonPath(getEnvNonEmpty("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
|
, nixStateDir(canonPath(getEnvNonEmpty("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
|
||||||
, nixConfDir(canonPath(getEnvNonEmpty("NIX_CONF_DIR").value_or(NIX_CONF_DIR)))
|
, nixConfDir(canonPath(getEnvNonEmpty("NIX_CONF_DIR").value_or(NIX_CONF_DIR)))
|
||||||
, nixUserConfFiles(getUserConfigFiles())
|
, nixUserConfFiles(getUserConfigFiles())
|
||||||
, nixManDir(canonPath(NIX_MAN_DIR))
|
|
||||||
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
|
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
|
||||||
{
|
{
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -243,7 +242,7 @@ Path Settings::getDefaultSSLCertFile()
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string nixVersion = PACKAGE_VERSION;
|
std::string nixVersion = PACKAGE_VERSION;
|
||||||
|
|
||||||
const std::string determinateNixVersion = DETERMINATE_NIX_VERSION;
|
const std::string determinateNixVersion = DETERMINATE_NIX_VERSION;
|
||||||
|
|
||||||
|
|
|
@ -84,11 +84,6 @@ public:
|
||||||
*/
|
*/
|
||||||
std::vector<Path> nixUserConfFiles;
|
std::vector<Path> nixUserConfFiles;
|
||||||
|
|
||||||
/**
|
|
||||||
* The directory where the man pages are stored.
|
|
||||||
*/
|
|
||||||
Path nixManDir;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File name of the socket the daemon listens to.
|
* File name of the socket the daemon listens to.
|
||||||
*/
|
*/
|
||||||
|
@ -1253,7 +1248,15 @@ void loadConfFile(AbstractConfig & config);
|
||||||
// Used by the Settings constructor
|
// Used by the Settings constructor
|
||||||
std::vector<Path> getUserConfigFiles();
|
std::vector<Path> getUserConfigFiles();
|
||||||
|
|
||||||
extern const std::string nixVersion;
|
/**
|
||||||
|
* The version of Nix itself.
|
||||||
|
*
|
||||||
|
* This is not `const`, so that the Nix CLI can provide a more detailed version
|
||||||
|
* number including the git revision, without having to "re-compile" the entire
|
||||||
|
* set of Nix libraries to include that version, even when those libraries are
|
||||||
|
* not affected by the change.
|
||||||
|
*/
|
||||||
|
extern std::string nixVersion;
|
||||||
|
|
||||||
extern const std::string determinateNixVersion;
|
extern const std::string determinateNixVersion;
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,10 @@ ref<LegacySSHStore::Connection> LegacySSHStore::openConnection()
|
||||||
command.push_back("--store");
|
command.push_back("--store");
|
||||||
command.push_back(remoteStore.get());
|
command.push_back(remoteStore.get());
|
||||||
}
|
}
|
||||||
conn->sshConn = master.startCommand(std::move(command));
|
conn->sshConn = master.startCommand(std::move(command), std::list{extraSshArgs});
|
||||||
|
if (connPipeSize) {
|
||||||
|
conn->sshConn->trySetBufferSize(*connPipeSize);
|
||||||
|
}
|
||||||
conn->to = FdSink(conn->sshConn->in.get());
|
conn->to = FdSink(conn->sshConn->in.get());
|
||||||
conn->from = FdSource(conn->sshConn->out.get());
|
conn->from = FdSource(conn->sshConn->out.get());
|
||||||
|
|
||||||
|
@ -100,19 +103,31 @@ std::string LegacySSHStore::getUri()
|
||||||
return *uriSchemes().begin() + "://" + host;
|
return *uriSchemes().begin() + "://" + host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<StorePath, UnkeyedValidPathInfo> LegacySSHStore::queryPathInfosUncached(
|
||||||
void LegacySSHStore::queryPathInfoUncached(const StorePath & path,
|
const StorePathSet & paths)
|
||||||
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
|
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
auto conn(connections->get());
|
auto conn(connections->get());
|
||||||
|
|
||||||
/* No longer support missing NAR hash */
|
/* No longer support missing NAR hash */
|
||||||
assert(GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4);
|
assert(GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4);
|
||||||
|
|
||||||
debug("querying remote host '%s' for info on '%s'", host, printStorePath(path));
|
debug("querying remote host '%s' for info on '%s'", host, concatStringsSep(", ", printStorePathSet(paths)));
|
||||||
|
|
||||||
auto infos = conn->queryPathInfos(*this, {path});
|
auto infos = conn->queryPathInfos(*this, paths);
|
||||||
|
|
||||||
|
for (const auto & [_, info] : infos) {
|
||||||
|
if (info.narHash == Hash::dummy)
|
||||||
|
throw Error("NAR hash is now mandatory");
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LegacySSHStore::queryPathInfoUncached(const StorePath & path,
|
||||||
|
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
auto infos = queryPathInfosUncached({path});
|
||||||
|
|
||||||
switch (infos.size()) {
|
switch (infos.size()) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -120,9 +135,6 @@ void LegacySSHStore::queryPathInfoUncached(const StorePath & path,
|
||||||
case 1: {
|
case 1: {
|
||||||
auto & [path2, info] = *infos.begin();
|
auto & [path2, info] = *infos.begin();
|
||||||
|
|
||||||
if (info.narHash == Hash::dummy)
|
|
||||||
throw Error("NAR hash is now mandatory");
|
|
||||||
|
|
||||||
assert(path == path2);
|
assert(path == path2);
|
||||||
return callback(std::make_shared<ValidPathInfo>(
|
return callback(std::make_shared<ValidPathInfo>(
|
||||||
std::move(path),
|
std::move(path),
|
||||||
|
@ -193,13 +205,19 @@ void LegacySSHStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||||
|
|
||||||
void LegacySSHStore::narFromPath(const StorePath & path, Sink & sink)
|
void LegacySSHStore::narFromPath(const StorePath & path, Sink & sink)
|
||||||
{
|
{
|
||||||
auto conn(connections->get());
|
narFromPath(path, [&](auto & source) {
|
||||||
conn->narFromPath(*this, path, [&](auto & source) {
|
|
||||||
copyNAR(source, sink);
|
copyNAR(source, sink);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LegacySSHStore::narFromPath(const StorePath & path, std::function<void(Source &)> fun)
|
||||||
|
{
|
||||||
|
auto conn(connections->get());
|
||||||
|
conn->narFromPath(*this, path, fun);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static ServeProto::BuildOptions buildSettings()
|
static ServeProto::BuildOptions buildSettings()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
@ -223,6 +241,19 @@ BuildResult LegacySSHStore::buildDerivation(const StorePath & drvPath, const Bas
|
||||||
return conn->getBuildDerivationResponse(*this);
|
return conn->getBuildDerivationResponse(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::function<BuildResult()> LegacySSHStore::buildDerivationAsync(
|
||||||
|
const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
|
const ServeProto::BuildOptions & options)
|
||||||
|
{
|
||||||
|
// Until we have C++23 std::move_only_function
|
||||||
|
auto conn = std::make_shared<Pool<Connection>::Handle>(connections->get());
|
||||||
|
(*conn)->putBuildDerivationRequest(*this, drvPath, drv, options);
|
||||||
|
|
||||||
|
return [this,conn]() -> BuildResult {
|
||||||
|
return (*conn)->getBuildDerivationResponse(*this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LegacySSHStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
|
void LegacySSHStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
|
||||||
{
|
{
|
||||||
|
@ -294,6 +325,32 @@ StorePathSet LegacySSHStore::queryValidPaths(const StorePathSet & paths,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StorePathSet LegacySSHStore::queryValidPaths(const StorePathSet & paths,
|
||||||
|
bool lock, SubstituteFlag maybeSubstitute)
|
||||||
|
{
|
||||||
|
auto conn(connections->get());
|
||||||
|
return conn->queryValidPaths(*this,
|
||||||
|
lock, paths, maybeSubstitute);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LegacySSHStore::addMultipleToStoreLegacy(Store & srcStore, const StorePathSet & paths)
|
||||||
|
{
|
||||||
|
auto conn(connections->get());
|
||||||
|
conn->to << ServeProto::Command::ImportPaths;
|
||||||
|
try {
|
||||||
|
srcStore.exportPaths(paths, conn->to);
|
||||||
|
} catch (...) {
|
||||||
|
conn->good = false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
conn->to.flush();
|
||||||
|
|
||||||
|
if (readInt(conn->from) != 1)
|
||||||
|
throw Error("remote machine failed to import closure");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LegacySSHStore::connect()
|
void LegacySSHStore::connect()
|
||||||
{
|
{
|
||||||
auto conn(connections->get());
|
auto conn(connections->get());
|
||||||
|
@ -307,6 +364,28 @@ unsigned int LegacySSHStore::getProtocol()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pid_t LegacySSHStore::getConnectionPid()
|
||||||
|
{
|
||||||
|
auto conn(connections->get());
|
||||||
|
#ifndef _WIN32
|
||||||
|
return conn->sshConn->sshPid;
|
||||||
|
#else
|
||||||
|
// TODO: Implement
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LegacySSHStore::ConnectionStats LegacySSHStore::getConnectionStats()
|
||||||
|
{
|
||||||
|
auto conn(connections->get());
|
||||||
|
return {
|
||||||
|
.bytesReceived = conn->from.read,
|
||||||
|
.bytesSent = conn->to.written,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The legacy ssh protocol doesn't support checking for trusted-user.
|
* The legacy ssh protocol doesn't support checking for trusted-user.
|
||||||
* Try using ssh-ng:// instead if you want to know.
|
* Try using ssh-ng:// instead if you want to know.
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "ssh.hh"
|
#include "ssh.hh"
|
||||||
#include "callback.hh"
|
#include "callback.hh"
|
||||||
#include "pool.hh"
|
#include "pool.hh"
|
||||||
|
#include "serve-protocol.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -24,6 +25,16 @@ struct LegacySSHStoreConfig : virtual CommonSSHStoreConfig
|
||||||
const Setting<int> maxConnections{this, 1, "max-connections",
|
const Setting<int> maxConnections{this, 1, "max-connections",
|
||||||
"Maximum number of concurrent SSH connections."};
|
"Maximum number of concurrent SSH connections."};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hack for hydra
|
||||||
|
*/
|
||||||
|
Strings extraSshArgs = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposed for hydra
|
||||||
|
*/
|
||||||
|
std::optional<size_t> connPipeSize;
|
||||||
|
|
||||||
const std::string name() override { return "SSH Store"; }
|
const std::string name() override { return "SSH Store"; }
|
||||||
|
|
||||||
static std::set<std::string> uriSchemes() { return {"ssh"}; }
|
static std::set<std::string> uriSchemes() { return {"ssh"}; }
|
||||||
|
@ -60,11 +71,24 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
||||||
void queryPathInfoUncached(const StorePath & path,
|
void queryPathInfoUncached(const StorePath & path,
|
||||||
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override;
|
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override;
|
||||||
|
|
||||||
|
std::map<StorePath, UnkeyedValidPathInfo> queryPathInfosUncached(
|
||||||
|
const StorePathSet & paths);
|
||||||
|
|
||||||
void addToStore(const ValidPathInfo & info, Source & source,
|
void addToStore(const ValidPathInfo & info, Source & source,
|
||||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||||
|
|
||||||
void narFromPath(const StorePath & path, Sink & sink) override;
|
void narFromPath(const StorePath & path, Sink & sink) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hands over the connection temporarily as source to the given
|
||||||
|
* function. The function must not consume beyond the NAR; it can
|
||||||
|
* not just blindly try to always read more bytes until it is
|
||||||
|
* cut-off.
|
||||||
|
*
|
||||||
|
* This is exposed for sake of Hydra.
|
||||||
|
*/
|
||||||
|
void narFromPath(const StorePath & path, std::function<void(Source &)> fun);
|
||||||
|
|
||||||
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
|
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
|
||||||
{ unsupported("queryPathFromHashPart"); }
|
{ unsupported("queryPathFromHashPart"); }
|
||||||
|
|
||||||
|
@ -93,6 +117,16 @@ public:
|
||||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
BuildMode buildMode) override;
|
BuildMode buildMode) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note, the returned function must only be called once, or we'll
|
||||||
|
* try to read from the connection twice.
|
||||||
|
*
|
||||||
|
* @todo Use C++23 `std::move_only_function`.
|
||||||
|
*/
|
||||||
|
std::function<BuildResult()> buildDerivationAsync(
|
||||||
|
const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
|
const ServeProto::BuildOptions & options);
|
||||||
|
|
||||||
void buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override;
|
void buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override;
|
||||||
|
|
||||||
void ensurePath(const StorePath & path) override
|
void ensurePath(const StorePath & path) override
|
||||||
|
@ -119,10 +153,36 @@ public:
|
||||||
StorePathSet queryValidPaths(const StorePathSet & paths,
|
StorePathSet queryValidPaths(const StorePathSet & paths,
|
||||||
SubstituteFlag maybeSubstitute = NoSubstitute) override;
|
SubstituteFlag maybeSubstitute = NoSubstitute) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom variation that atomically creates temp locks on the remote
|
||||||
|
* side.
|
||||||
|
*
|
||||||
|
* This exists to prevent a race where the remote host
|
||||||
|
* garbage-collects paths that are already there. Optionally, ask
|
||||||
|
* the remote host to substitute missing paths.
|
||||||
|
*/
|
||||||
|
StorePathSet queryValidPaths(const StorePathSet & paths,
|
||||||
|
bool lock,
|
||||||
|
SubstituteFlag maybeSubstitute = NoSubstitute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just exists because this is exactly what Hydra was doing, and we
|
||||||
|
* don't yet want an algorithmic change.
|
||||||
|
*/
|
||||||
|
void addMultipleToStoreLegacy(Store & srcStore, const StorePathSet & paths);
|
||||||
|
|
||||||
void connect() override;
|
void connect() override;
|
||||||
|
|
||||||
unsigned int getProtocol() override;
|
unsigned int getProtocol() override;
|
||||||
|
|
||||||
|
struct ConnectionStats {
|
||||||
|
size_t bytesReceived, bytesSent;
|
||||||
|
};
|
||||||
|
|
||||||
|
ConnectionStats getConnectionStats();
|
||||||
|
|
||||||
|
pid_t getConnectionPid();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The legacy ssh protocol doesn't support checking for trusted-user.
|
* The legacy ssh protocol doesn't support checking for trusted-user.
|
||||||
* Try using ssh-ng:// instead if you want to know.
|
* Try using ssh-ng:// instead if you want to know.
|
||||||
|
|
|
@ -136,7 +136,12 @@ LocalStore::LocalStore(
|
||||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||||
createDirs(perUserDir);
|
createDirs(perUserDir);
|
||||||
if (!readOnly) {
|
if (!readOnly) {
|
||||||
if (chmod(perUserDir.c_str(), 0755) == -1)
|
auto st = lstat(perUserDir);
|
||||||
|
|
||||||
|
// Skip chmod call if the directory already has the correct permissions (0755).
|
||||||
|
// This is to avoid failing when the executing user lacks permissions to change the directory's permissions
|
||||||
|
// even if it would be no-op.
|
||||||
|
if ((st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) != 0755 && chmod(perUserDir.c_str(), 0755) == -1)
|
||||||
throw SysError("could not set permissions on '%s' to 755", perUserDir);
|
throw SysError("could not set permissions on '%s' to 755", perUserDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,14 +71,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nlohmann_json
|
nlohmann_json
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags =
|
mesonFlags =
|
||||||
[
|
[
|
||||||
(lib.mesonEnable "seccomp-sandboxing" stdenv.hostPlatform.isLinux)
|
(lib.mesonEnable "seccomp-sandboxing" stdenv.hostPlatform.isLinux)
|
||||||
|
|
|
@ -240,4 +240,19 @@ Path SSHMaster::startMaster()
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void SSHMaster::Connection::trySetBufferSize(size_t size)
|
||||||
|
{
|
||||||
|
#ifdef F_SETPIPE_SZ
|
||||||
|
/* This `fcntl` method of doing this takes a positive `int`. Check
|
||||||
|
and convert accordingly.
|
||||||
|
|
||||||
|
The function overall still takes `size_t` because this is more
|
||||||
|
portable for a platform-agnostic interface. */
|
||||||
|
assert(size <= INT_MAX);
|
||||||
|
int pipesize = size;
|
||||||
|
fcntl(in.get(), F_SETPIPE_SZ, pipesize);
|
||||||
|
fcntl(out.get(), F_SETPIPE_SZ, pipesize);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,18 @@ public:
|
||||||
Pid sshPid;
|
Pid sshPid;
|
||||||
#endif
|
#endif
|
||||||
AutoCloseFD out, in;
|
AutoCloseFD out, in;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to set the buffer size in both directions to the
|
||||||
|
* designated amount, if possible. If not possible, does
|
||||||
|
* nothing.
|
||||||
|
*
|
||||||
|
* Current implementation is to use `fcntl` with `F_SETPIPE_SZ`,
|
||||||
|
* which is Linux-only. For this implementation, `size` must
|
||||||
|
* convertable to an `int`. In other words, it must be within
|
||||||
|
* `[0, INT_MAX]`.
|
||||||
|
*/
|
||||||
|
void trySetBufferSize(size_t size);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -230,18 +230,22 @@ void Store::addMultipleToStore(
|
||||||
{
|
{
|
||||||
std::atomic<size_t> nrDone{0};
|
std::atomic<size_t> nrDone{0};
|
||||||
std::atomic<size_t> nrFailed{0};
|
std::atomic<size_t> nrFailed{0};
|
||||||
std::atomic<uint64_t> bytesExpected{0};
|
|
||||||
std::atomic<uint64_t> nrRunning{0};
|
std::atomic<uint64_t> nrRunning{0};
|
||||||
|
|
||||||
using PathWithInfo = std::pair<ValidPathInfo, std::unique_ptr<Source>>;
|
using PathWithInfo = std::pair<ValidPathInfo, std::unique_ptr<Source>>;
|
||||||
|
|
||||||
|
uint64_t bytesExpected = 0;
|
||||||
|
|
||||||
std::map<StorePath, PathWithInfo *> infosMap;
|
std::map<StorePath, PathWithInfo *> infosMap;
|
||||||
StorePathSet storePathsToAdd;
|
StorePathSet storePathsToAdd;
|
||||||
for (auto & thingToAdd : pathsToCopy) {
|
for (auto & thingToAdd : pathsToCopy) {
|
||||||
|
bytesExpected += thingToAdd.first.narSize;
|
||||||
infosMap.insert_or_assign(thingToAdd.first.path, &thingToAdd);
|
infosMap.insert_or_assign(thingToAdd.first.path, &thingToAdd);
|
||||||
storePathsToAdd.insert(thingToAdd.first.path);
|
storePathsToAdd.insert(thingToAdd.first.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
act.setExpected(actCopyPath, bytesExpected);
|
||||||
|
|
||||||
auto showProgress = [&, nrTotal = pathsToCopy.size()]() {
|
auto showProgress = [&, nrTotal = pathsToCopy.size()]() {
|
||||||
act.progress(nrDone, nrTotal, nrRunning, nrFailed);
|
act.progress(nrDone, nrTotal, nrRunning, nrFailed);
|
||||||
};
|
};
|
||||||
|
@ -259,9 +263,6 @@ void Store::addMultipleToStore(
|
||||||
return StorePathSet();
|
return StorePathSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
bytesExpected += info.narSize;
|
|
||||||
act.setExpected(actCopyPath, bytesExpected);
|
|
||||||
|
|
||||||
return info.references;
|
return info.references;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -34,14 +34,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nix-util
|
nix-util
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -38,14 +38,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
rapidcheck
|
rapidcheck
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -45,14 +45,6 @@ mkMesonExecutable (finalAttrs: {
|
||||||
gtest
|
gtest
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -52,17 +52,6 @@ mkMesonLibrary (finalAttrs: {
|
||||||
nlohmann_json
|
nlohmann_json
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
#
|
|
||||||
# TODO: change release process to add `pre` in `.version`, remove it
|
|
||||||
# before tagging, and restore after.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
(lib.mesonEnable "cpuid" stdenv.hostPlatform.isx86_64)
|
(lib.mesonEnable "cpuid" stdenv.hostPlatform.isx86_64)
|
||||||
];
|
];
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "users.hh"
|
#include "users.hh"
|
||||||
#include "network-proxy.hh"
|
#include "network-proxy.hh"
|
||||||
#include "compatibility-settings.hh"
|
#include "compatibility-settings.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "users.hh"
|
#include "users.hh"
|
||||||
#include "tarball.hh"
|
#include "tarball.hh"
|
||||||
#include "self-exe.hh"
|
#include "self-exe.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
#include "eval-settings.hh" // for defexpr
|
#include "eval-settings.hh" // for defexpr
|
||||||
#include "terminal.hh"
|
#include "terminal.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
#include "posix-source-accessor.hh"
|
#include "posix-source-accessor.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "path-with-outputs.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#ifndef _WIN32 // TODO implement on Windows or provide allowed-to-noop interface
|
#ifndef _WIN32 // TODO implement on Windows or provide allowed-to-noop interface
|
||||||
# include "local-store.hh"
|
# include "local-store.hh"
|
||||||
|
|
|
@ -1088,6 +1088,8 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
|
||||||
nlohmann::json jsonObj2 = json ? json::object() : nlohmann::json(nullptr);
|
nlohmann::json jsonObj2 = json ? json::object() : nlohmann::json(nullptr);
|
||||||
for (auto & [inputName, input] : node.inputs) {
|
for (auto & [inputName, input] : node.inputs) {
|
||||||
if (auto inputNode = std::get_if<0>(&input)) {
|
if (auto inputNode = std::get_if<0>(&input)) {
|
||||||
|
if ((*inputNode)->lockedRef.input.isRelative())
|
||||||
|
continue;
|
||||||
auto storePath =
|
auto storePath =
|
||||||
dryRun
|
dryRun
|
||||||
? (*inputNode)->lockedRef.input.computeStorePath(*store)
|
? (*inputNode)->lockedRef.input.computeStorePath(*store)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "git.hh"
|
#include "git.hh"
|
||||||
#include "posix-source-accessor.hh"
|
#include "posix-source-accessor.hh"
|
||||||
#include "misc-store-flags.hh"
|
#include "misc-store-flags.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
|
|
|
@ -553,6 +553,9 @@ void mainWrapped(int argc, char * * argv)
|
||||||
|
|
||||||
int main(int argc, char * * argv)
|
int main(int argc, char * * argv)
|
||||||
{
|
{
|
||||||
|
// The CLI has a more detailed version than the libraries; see nixVersion.
|
||||||
|
nix::nixVersion = NIX_CLI_VERSION;
|
||||||
|
|
||||||
// Increase the default stack size for the evaluator and for
|
// Increase the default stack size for the evaluator and for
|
||||||
// libstdc++'s std::regex.
|
// libstdc++'s std::regex.
|
||||||
nix::setStackSize(64 * 1024 * 1024);
|
nix::setStackSize(64 * 1024 * 1024);
|
||||||
|
|
29
src/nix/man-pages.cc
Normal file
29
src/nix/man-pages.cc
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#include "man-pages.hh"
|
||||||
|
#include "file-system.hh"
|
||||||
|
#include "current-process.hh"
|
||||||
|
#include "environment-variables.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::filesystem::path getNixManDir()
|
||||||
|
{
|
||||||
|
return canonPath(NIX_MAN_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showManPage(const std::string & name)
|
||||||
|
{
|
||||||
|
restoreProcessContext();
|
||||||
|
setEnv("MANPATH", (getNixManDir().string() + ":").c_str());
|
||||||
|
execlp("man", "man", name.c_str(), nullptr);
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
// Not SysError because we don't want to suffix the errno, aka No such file or directory.
|
||||||
|
throw Error(
|
||||||
|
"The '%1%' command was not found, but it is needed for '%2%' and some other '%3%' commands' help text. Perhaps you could install the '%1%' command?",
|
||||||
|
"man",
|
||||||
|
name.c_str(),
|
||||||
|
"nix-*");
|
||||||
|
}
|
||||||
|
throw SysError("command 'man %1%' failed", name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/nix/man-pages.hh
Normal file
28
src/nix/man-pages.hh
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get path to the nix manual dir.
|
||||||
|
*
|
||||||
|
* Nix relies on the man pages being available at a NIX_MAN_DIR for
|
||||||
|
* displaying help messaged for legacy cli.
|
||||||
|
*
|
||||||
|
* NIX_MAN_DIR is a compile-time parameter, so man pages are unlikely to work
|
||||||
|
* for cases when the nix executable is installed out-of-store or as a static binary.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
std::filesystem::path getNixManDir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the manual page for the specified program.
|
||||||
|
*
|
||||||
|
* @param name Name of the man item.
|
||||||
|
*/
|
||||||
|
void showManPage(const std::string & name);
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,9 @@ subdir('nix-meson-build-support/windows-version')
|
||||||
|
|
||||||
configdata = configuration_data()
|
configdata = configuration_data()
|
||||||
|
|
||||||
|
# The CLI has a more detailed version string than the libraries; see `nixVersion`
|
||||||
|
configdata.set_quoted('NIX_CLI_VERSION', meson.project_version())
|
||||||
|
|
||||||
fs = import('fs')
|
fs = import('fs')
|
||||||
|
|
||||||
bindir = get_option('bindir')
|
bindir = get_option('bindir')
|
||||||
|
@ -90,6 +93,7 @@ nix_sources = [config_h] + files(
|
||||||
'ls.cc',
|
'ls.cc',
|
||||||
'main.cc',
|
'main.cc',
|
||||||
'make-content-addressed.cc',
|
'make-content-addressed.cc',
|
||||||
|
'man-pages.cc',
|
||||||
'nar.cc',
|
'nar.cc',
|
||||||
'optimise-store.cc',
|
'optimise-store.cc',
|
||||||
'path-from-hash-part.cc',
|
'path-from-hash-part.cc',
|
||||||
|
@ -182,6 +186,16 @@ if host_machine.system() != 'windows'
|
||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
fs = import('fs')
|
||||||
|
prefix = get_option('prefix')
|
||||||
|
|
||||||
|
mandir = get_option('mandir')
|
||||||
|
mandir = fs.is_absolute(mandir) ? mandir : prefix / mandir
|
||||||
|
|
||||||
|
cpp_args= [
|
||||||
|
'-DNIX_MAN_DIR="@0@"'.format(mandir)
|
||||||
|
]
|
||||||
|
|
||||||
include_dirs = [include_directories('.')]
|
include_dirs = [include_directories('.')]
|
||||||
|
|
||||||
this_exe = executable(
|
this_exe = executable(
|
||||||
|
@ -189,6 +203,7 @@ this_exe = executable(
|
||||||
sources,
|
sources,
|
||||||
dependencies : deps_private_subproject + deps_private + deps_other,
|
dependencies : deps_private_subproject + deps_private + deps_other,
|
||||||
include_directories : include_dirs,
|
include_directories : include_dirs,
|
||||||
|
cpp_args : cpp_args,
|
||||||
link_args: linker_export_flags,
|
link_args: linker_export_flags,
|
||||||
install : true,
|
install : true,
|
||||||
)
|
)
|
||||||
|
|
|
@ -91,14 +91,6 @@ mkMesonExecutable (finalAttrs: {
|
||||||
nix-cmd
|
nix-cmd
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../../.version
|
|
||||||
'';
|
|
||||||
|
|
||||||
mesonFlags = [
|
mesonFlags = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "posix-source-accessor.hh"
|
#include "posix-source-accessor.hh"
|
||||||
#include "misc-store-flags.hh"
|
#include "misc-store-flags.hh"
|
||||||
#include "terminal.hh"
|
#include "terminal.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
#include "daemon.hh"
|
#include "daemon.hh"
|
||||||
|
#include "man-pages.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
|
|
|
@ -194,7 +194,7 @@ StoreWrapper::computeFSClosure(int flipDirection, int includeOutputs, ...)
|
||||||
PPCODE:
|
PPCODE:
|
||||||
try {
|
try {
|
||||||
StorePathSet paths;
|
StorePathSet paths;
|
||||||
for (int n = 2; n < items; ++n)
|
for (int n = 3; n < items; ++n)
|
||||||
THIS->store->computeFSClosure(THIS->store->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
|
THIS->store->computeFSClosure(THIS->store->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
|
||||||
for (auto & i : paths)
|
for (auto & i : paths)
|
||||||
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
|
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
|
||||||
|
@ -208,7 +208,7 @@ StoreWrapper::topoSortPaths(...)
|
||||||
PPCODE:
|
PPCODE:
|
||||||
try {
|
try {
|
||||||
StorePathSet paths;
|
StorePathSet paths;
|
||||||
for (int n = 0; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
|
for (int n = 1; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
|
||||||
auto sorted = THIS->store->topoSortPaths(paths);
|
auto sorted = THIS->store->topoSortPaths(paths);
|
||||||
for (auto & i : sorted)
|
for (auto & i : sorted)
|
||||||
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
|
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
|
||||||
|
@ -234,7 +234,7 @@ StoreWrapper::exportPaths(int fd, ...)
|
||||||
PPCODE:
|
PPCODE:
|
||||||
try {
|
try {
|
||||||
StorePathSet paths;
|
StorePathSet paths;
|
||||||
for (int n = 1; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
|
for (int n = 2; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
|
||||||
FdSink sink(fd);
|
FdSink sink(fd);
|
||||||
THIS->store->exportPaths(paths, sink);
|
THIS->store->exportPaths(paths, sink);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ startDaemon() {
|
||||||
die "startDaemon: not supported when testing on NixOS. Is it really needed? If so add conditionals; e.g. if ! isTestOnNixOS; then ..."
|
die "startDaemon: not supported when testing on NixOS. Is it really needed? If so add conditionals; e.g. if ! isTestOnNixOS; then ..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Don’t start the daemon twice, as this would just make it loop indefinitely
|
# Don't start the daemon twice, as this would just make it loop indefinitely.
|
||||||
if [[ "${_NIX_TEST_DAEMON_PID-}" != '' ]]; then
|
if [[ "${_NIX_TEST_DAEMON_PID-}" != '' ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
@ -77,15 +77,19 @@ startDaemon() {
|
||||||
PATH=$DAEMON_PATH nix --extra-experimental-features 'nix-command' daemon &
|
PATH=$DAEMON_PATH nix --extra-experimental-features 'nix-command' daemon &
|
||||||
_NIX_TEST_DAEMON_PID=$!
|
_NIX_TEST_DAEMON_PID=$!
|
||||||
export _NIX_TEST_DAEMON_PID
|
export _NIX_TEST_DAEMON_PID
|
||||||
for ((i = 0; i < 300; i++)); do
|
for ((i = 0; i < 60; i++)); do
|
||||||
if [[ -S $NIX_DAEMON_SOCKET_PATH ]]; then
|
if [[ -S $NIX_DAEMON_SOCKET_PATH ]]; then
|
||||||
DAEMON_STARTED=1
|
DAEMON_STARTED=1
|
||||||
break;
|
break;
|
||||||
fi
|
fi
|
||||||
|
if ! kill -0 "$_NIX_TEST_DAEMON_PID"; then
|
||||||
|
echo "daemon died unexpectedly" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
done
|
done
|
||||||
if [[ -z ${DAEMON_STARTED+x} ]]; then
|
if [[ -z ${DAEMON_STARTED+x} ]]; then
|
||||||
fail "Didn’t manage to start the daemon"
|
fail "Didn't manage to start the daemon"
|
||||||
fi
|
fi
|
||||||
trap "killDaemon" EXIT
|
trap "killDaemon" EXIT
|
||||||
# Save for if daemon is killed
|
# Save for if daemon is killed
|
||||||
|
@ -98,7 +102,7 @@ killDaemon() {
|
||||||
die "killDaemon: not supported when testing on NixOS. Is it really needed? If so add conditionals; e.g. if ! isTestOnNixOS; then ..."
|
die "killDaemon: not supported when testing on NixOS. Is it really needed? If so add conditionals; e.g. if ! isTestOnNixOS; then ..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Don’t fail trying to stop a non-existant daemon twice
|
# Don't fail trying to stop a non-existant daemon twice.
|
||||||
if [[ "${_NIX_TEST_DAEMON_PID-}" == '' ]]; then
|
if [[ "${_NIX_TEST_DAEMON_PID-}" == '' ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
@ -220,7 +224,7 @@ assertStderr() {
|
||||||
|
|
||||||
needLocalStore() {
|
needLocalStore() {
|
||||||
if [[ "$NIX_REMOTE" == "daemon" ]]; then
|
if [[ "$NIX_REMOTE" == "daemon" ]]; then
|
||||||
skipTest "Can’t run through the daemon ($1)"
|
skipTest "Can't run through the daemon ($1)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,9 @@ if ! isTestOnNixOS; then
|
||||||
fi
|
fi
|
||||||
(! grep narHash "$subflake2/flake.lock")
|
(! grep narHash "$subflake2/flake.lock")
|
||||||
|
|
||||||
|
# Test `nix flake archive` with relative path flakes.
|
||||||
|
nix flake archive --json "$rootFlake"
|
||||||
|
|
||||||
# Test circular relative path flakes. FIXME: doesn't work at the moment.
|
# Test circular relative path flakes. FIXME: doesn't work at the moment.
|
||||||
if false; then
|
if false; then
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ source common.sh
|
||||||
#nix-hash --help | grepQuiet base32
|
#nix-hash --help | grepQuiet base32
|
||||||
|
|
||||||
# Can we ask for the version number?
|
# Can we ask for the version number?
|
||||||
nix-env --version | grep "$version"
|
nix-env --version | grep -F "${_NIX_TEST_CLIENT_VERSION:-$version}"
|
||||||
|
|
||||||
nix_env=$(type -P nix-env)
|
nix_env=$(type -P nix-env)
|
||||||
(PATH=""; ! $nix_env --help 2>&1 ) | grepQuiet -F "The 'man' command was not found, but it is needed for 'nix-env' and some other 'nix-*' commands' help text. Perhaps you could install the 'man' command?"
|
(PATH=""; ! $nix_env --help 2>&1 ) | grepQuiet -F "The 'man' command was not found, but it is needed for 'nix-env' and some other 'nix-*' commands' help text. Perhaps you could install the 'man' command?"
|
||||||
|
|
|
@ -75,16 +75,10 @@ mkMesonDerivation (
|
||||||
];
|
];
|
||||||
|
|
||||||
preConfigure =
|
preConfigure =
|
||||||
# "Inline" .version so it's not a symlink, and includes the suffix.
|
|
||||||
# Do the meson utils, without modification.
|
|
||||||
''
|
|
||||||
chmod u+w ./.version
|
|
||||||
echo ${version} > ../../../.version
|
|
||||||
''
|
|
||||||
# TEMP hack for Meson before make is gone, where
|
# TEMP hack for Meson before make is gone, where
|
||||||
# `src/nix-functional-tests` is during the transition a symlink and
|
# `src/nix-functional-tests` is during the transition a symlink and
|
||||||
# not the actual directory directory.
|
# not the actual directory directory.
|
||||||
+ ''
|
''
|
||||||
cd $(readlink -e $PWD)
|
cd $(readlink -e $PWD)
|
||||||
echo $PWD | grep tests/functional
|
echo $PWD | grep tests/functional
|
||||||
'';
|
'';
|
||||||
|
@ -105,6 +99,8 @@ mkMesonDerivation (
|
||||||
|
|
||||||
}
|
}
|
||||||
// lib.optionalAttrs (test-daemon != null) {
|
// lib.optionalAttrs (test-daemon != null) {
|
||||||
|
# TODO rename to _NIX_TEST_DAEMON_PACKAGE
|
||||||
NIX_DAEMON_PACKAGE = test-daemon;
|
NIX_DAEMON_PACKAGE = test-daemon;
|
||||||
|
_NIX_TEST_CLIENT_VERSION = nix-cli.version;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue