push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
# shellcheck shell=bash
# This setup hook add $out/bin to the PATH environment variable.
export PATH
addBinToPath () {
# shellcheck disable=SC2154
PATH="$out/bin:$PATH"
export PATH
}
postHooks+=(addBinToPath)

View File

@@ -0,0 +1,45 @@
# shellcheck shell=bash
# getSortedMapKeys
# Stores the sorted keys of the input associative array referenced by inputMapRef in the indexed arrray referenced by
# outputArrRef.
#
# Note from the Bash manual on arrays:
# There is no maximum limit on the size of an array, nor any requirement that members be indexed or assigned contiguously.
# - https://www.gnu.org/software/bash/manual/html_node/Arrays.html
#
# Since no guarantees are made about the order in which associative maps are traversed, this function is primarly
# useful for getting rid of yet another source of non-determinism. As an added benefit, it checks that the arguments
# provided are of correct type, unlike native parameter expansion which will accept expansions of strings.
#
# Arguments:
# - inputMapRef: a reference to an associative array (not mutated)
# - outputArrRef: a reference to an indexed array (contents are replaced entirely)
#
# Returns 0.
getSortedMapKeys() {
if (($# != 2)); then
nixErrorLog "expected two arguments!"
nixErrorLog "usage: getSortedMapKeys inputMapRef outputArrRef"
exit 1
fi
local -rn inputMapRef="$1"
# shellcheck disable=SC2178
# Don't warn about outputArrRef being used as an array because it is an array.
local -rn outputArrRef="$2"
if ! isDeclaredMap "${!inputMapRef}"; then
nixErrorLog "first argument inputMapRef must be a reference to an associative array"
exit 1
elif ! isDeclaredArray "${!outputArrRef}"; then
nixErrorLog "second argument outputArrRef must be a reference to an indexed array"
exit 1
fi
# shellcheck disable=SC2034
local -a keys=("${!inputMapRef[@]}")
sortArray keys "${!outputArrRef}"
return 0
}

View File

@@ -0,0 +1,17 @@
{
callPackages,
isDeclaredArray,
isDeclaredMap,
makeSetupHook,
sortArray,
}:
makeSetupHook {
name = "getSortedMapKeys";
propagatedBuildInputs = [
isDeclaredArray
isDeclaredMap
sortArray
];
passthru.tests = callPackages ./tests.nix { };
meta.description = "Gets the sorted indices of an associative array";
} ./getSortedMapKeys.bash

View File

@@ -0,0 +1,80 @@
# NOTE: Tests related to getSortedMapKeys go here.
{
getSortedMapKeys,
lib,
testers,
}:
let
inherit (lib.attrsets) recurseIntoAttrs;
inherit (testers) shellcheck shfmt testEqualArrayOrMap;
check =
{
name,
valuesMap,
expectedArray,
}:
(testEqualArrayOrMap {
inherit name valuesMap expectedArray;
script = ''
set -eu
nixLog "running getSortedMapKeys with valuesMap to populate actualArray"
getSortedMapKeys valuesMap actualArray
'';
}).overrideAttrs
(prevAttrs: {
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ getSortedMapKeys ];
});
in
recurseIntoAttrs {
shellcheck = shellcheck {
name = "getSortedMapKeys";
src = ./getSortedMapKeys.bash;
};
shfmt = shfmt {
name = "getSortedMapKeys";
src = ./getSortedMapKeys.bash;
};
empty = check {
name = "empty";
valuesMap = { };
expectedArray = [ ];
};
singleton = check {
name = "singleton";
valuesMap = {
"apple" = "fruit";
};
expectedArray = [ "apple" ];
};
keysAreSorted = check {
name = "keysAreSorted";
valuesMap = {
"apple" = "fruit";
"bee" = "insect";
"carrot" = "vegetable";
};
expectedArray = [
"apple"
"bee"
"carrot"
];
};
# NOTE: While keys can be whitespace, they cannot be null (empty).
keysCanBeWhitespace = check {
name = "keysCanBeWhitespace";
valuesMap = {
" " = 1;
" " = 2;
};
expectedArray = [
" "
" "
];
};
}

View File

@@ -0,0 +1,14 @@
# shellcheck shell=bash
# isDeclaredArray
# Tests if inputArrayRef refers to a declared, indexed array.
#
# Arguments:
# - inputArrayRef: a reference to an indexed array (not mutated)
#
# Returns 0 if the indexed array is declared, 1 otherwise.
isDeclaredArray() {
# NOTE: We must dereference the name ref to get the type of the underlying variable.
# shellcheck disable=SC2034
local -nr inputArrayRef="$1" && [[ ${!inputArrayRef@a} =~ a ]]
}

View File

@@ -0,0 +1,9 @@
{
callPackages,
makeSetupHook,
}:
makeSetupHook {
name = "isDeclaredArray";
passthru.tests = callPackages ./tests.nix { };
meta.description = "Tests if an array is declared";
} ./isDeclaredArray.bash

View File

@@ -0,0 +1,353 @@
# NOTE: Tests related to isDeclaredArray go here.
{
isDeclaredArray,
lib,
runCommand,
testers,
}:
let
inherit (lib.attrsets) recurseIntoAttrs;
inherit (testers) shellcheck shfmt testBuildFailure';
commonArgs = {
__structuredAttrs = true;
strictDeps = true;
preferLocalBuild = true;
nativeBuildInputs = [ isDeclaredArray ];
};
check =
let
mkLine =
intro: values:
"${if intro == null then "" else intro + " "}check${if values == null then "" else "=" + values}";
mkScope =
scope: line:
if scope == null then
line
else if scope == "function" then
''
foo() {
${line}
}
foo
''
else
throw "Invalid scope: ${scope}";
in
{
name,
scope,
intro,
values,
}:
runCommand name commonArgs ''
set -eu
${mkScope scope (mkLine intro values)}
if isDeclaredArray check; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
in
recurseIntoAttrs {
shellcheck = shellcheck {
name = "isDeclaredArray";
src = ./isDeclaredArray.bash;
};
shfmt = shfmt {
name = "isDeclaredArray";
src = ./isDeclaredArray.bash;
};
undeclaredFails = testBuildFailure' {
name = "undeclaredFails";
drv = runCommand "undeclared" commonArgs ''
set -eu
if isDeclaredArray undeclared; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
expectedBuilderLogEntries = [
"test failed"
];
};
mapFails = testBuildFailure' {
name = "mapFails";
drv = runCommand "map" commonArgs ''
set -eu
local -A map
if isDeclaredArray map; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
expectedBuilderLogEntries = [
"test failed"
];
};
emptyStringNamerefFails = testBuildFailure' {
name = "emptyStringNamerefFails";
drv = runCommand "emptyStringNameref" commonArgs ''
set -eu
if isDeclaredArray ""; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
expectedBuilderLogEntries = [
"local: `': not a valid identifier"
"test failed"
];
};
namerefToEmptyStringFails = testBuildFailure' {
name = "namerefToEmptyStringFails";
drv = check {
name = "namerefToEmptyString";
scope = null;
intro = "local -n";
values = "";
};
expectedBuilderLogEntries = [
"local: `': not a valid identifier"
# The test fails in such a way that it exits immediately, without returning to the else branch.
];
};
sameScopeEmptyStringFails = testBuildFailure' {
name = "sameScopeEmptyStringFails";
drv = check {
name = "sameScopeEmptyString";
scope = null;
intro = null;
values = "";
};
expectedBuilderLogEntries = [
"test failed"
];
};
sameScopeEmptyArray = check {
name = "sameScopeEmptyArray";
scope = null;
intro = null;
values = "()";
};
sameScopeSingletonArray = check {
name = "sameScopeSingletonArray";
scope = null;
intro = null;
values = ''("hello!")'';
};
sameScopeLocalUnsetArray = check {
name = "sameScopeLocalUnsetArray";
scope = null;
intro = "local -a";
values = null;
};
sameScopeLocalEmptyArray = check {
name = "sameScopeLocalEmptyArray";
scope = null;
intro = "local -a";
values = "()";
};
sameScopeLocalSingletonArray = check {
name = "sameScopeLocalSingletonArray";
scope = null;
intro = "local -a";
values = ''("hello!")'';
};
sameScopeDeclareUnsetArray = check {
name = "sameScopeDeclareUnsetArray";
scope = null;
intro = "declare -a";
values = null;
};
sameScopeDeclareEmptyArray = check {
name = "sameScopeDeclareEmptyArray";
scope = null;
intro = "declare -a";
values = "()";
};
sameScopeDeclareSingletonArray = check {
name = "sameScopeDeclareSingletonArray";
scope = null;
intro = "declare -a";
values = ''("hello!")'';
};
previousScopeEmptyStringFails = testBuildFailure' {
name = "previousScopeEmptyStringFails";
drv = check {
name = "previousScopeEmptyString";
scope = "function";
intro = null;
values = "";
};
expectedBuilderLogEntries = [
"test failed"
];
};
# Works because the variable isn't lexically scoped.
previousScopeEmptyArray = check {
name = "previousScopeEmptyArray";
scope = "function";
intro = null;
values = "()";
};
# Works because the variable isn't lexically scoped.
previousScopeSingletonArray = check {
name = "previousScopeSingletonArray";
scope = "function";
intro = null;
values = ''("hello!")'';
};
previousScopeLocalUnsetArrayFails = testBuildFailure' {
name = "previousScopeLocalUnsetArrayFails";
drv = check {
name = "previousScopeLocalUnsetArray";
scope = "function";
intro = "local -a";
values = null;
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeLocalEmptyArrayFails = testBuildFailure' {
name = "previousScopeLocalEmptyArrayFails";
drv = check {
name = "previousScopeLocalEmptyArray";
scope = "function";
intro = "local -a";
values = "()";
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeLocalSingletonArrayFails = testBuildFailure' {
name = "previousScopeLocalSingletonArrayFails";
drv = check {
name = "previousScopeLocalSingletonArray";
scope = "function";
intro = "local -a";
values = ''("hello!")'';
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeLocalGlobalUnsetArray = check {
name = "previousScopeLocalGlobalUnsetArray";
scope = "function";
intro = "local -ag";
values = null;
};
previousScopeLocalGlobalEmptyArray = check {
name = "previousScopeLocalGlobalEmptyArray";
scope = "function";
intro = "local -ag";
values = "()";
};
previousScopeLocalGlobalSingletonArray = check {
name = "previousScopeLocalGlobalSingletonArray";
scope = "function";
intro = "local -ag";
values = ''("hello!")'';
};
previousScopeDeclareUnsetArrayFails = testBuildFailure' {
name = "previousScopeDeclareUnsetArrayFails";
drv = check {
name = "previousScopeDeclareUnsetArray";
scope = "function";
intro = "declare -a";
values = null;
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeDeclareEmptyArrayFails = testBuildFailure' {
name = "previousScopeDeclareEmptyArrayFails";
drv = check {
name = "previousScopeDeclareEmptyArray";
scope = "function";
intro = "declare -a";
values = "()";
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeDeclareSingletonArrayFails = testBuildFailure' {
name = "previousScopeDeclareSingletonArrayFails";
drv = check {
name = "previousScopeDeclareSingletonArray";
scope = "function";
intro = "declare -a";
values = ''("hello!")'';
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeDeclareGlobalUnsetArray = check {
name = "previousScopeDeclareGlobalUnsetArray";
scope = "function";
intro = "declare -ag";
values = null;
};
previousScopeDeclareGlobalEmptyArray = check {
name = "previousScopeDeclareGlobalEmptyArray";
scope = "function";
intro = "declare -ag";
values = "()";
};
previousScopeDeclareGlobalSingletonArray = check {
name = "previousScopeDeclareGlobalSingletonArray";
scope = "function";
intro = "declare -ag";
values = ''("hello!")'';
};
}

View File

@@ -0,0 +1,14 @@
# shellcheck shell=bash
# isDeclaredMap
# Tests if inputMapRef refers to a declared, associative array.
#
# Arguments:
# - inputMapRef: a reference to an associative array (not mutated)
#
# Returns 0 if the associative array is declared, 1 otherwise.
isDeclaredMap() {
# NOTE: We must dereference the name ref to get the type of the underlying variable.
# shellcheck disable=SC2034
local -nr inputMapRef="$1" && [[ ${!inputMapRef@a} =~ A ]]
}

View File

@@ -0,0 +1,9 @@
{
callPackages,
makeSetupHook,
}:
makeSetupHook {
name = "isDeclaredMap";
passthru.tests = callPackages ./tests.nix { };
meta.description = "Tests if an associative array is declared";
} ./isDeclaredMap.bash

View File

@@ -0,0 +1,377 @@
# NOTE: Tests related to isDeclaredMap go here.
{
isDeclaredMap,
lib,
runCommand,
testers,
}:
let
inherit (lib.attrsets) recurseIntoAttrs;
inherit (testers) shellcheck shfmt testBuildFailure';
commonArgs = {
__structuredAttrs = true;
strictDeps = true;
preferLocalBuild = true;
nativeBuildInputs = [ isDeclaredMap ];
};
check =
let
mkLine =
intro: values:
"${if intro == null then "" else intro + " "}check${if values == null then "" else "=" + values}";
mkScope =
scope: line:
if scope == null then
line
else if scope == "function" then
''
foo() {
${line}
}
foo
''
else
throw "Invalid scope: ${scope}";
in
{
name,
scope,
intro,
values,
}:
runCommand name commonArgs ''
set -eu
${mkScope scope (mkLine intro values)}
if isDeclaredMap check; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
in
recurseIntoAttrs {
shellcheck = shellcheck {
name = "isDeclaredMap";
src = ./isDeclaredMap.bash;
};
shfmt = shfmt {
name = "isDeclaredMap";
src = ./isDeclaredMap.bash;
};
undeclaredFails = testBuildFailure' {
name = "undeclaredFails";
drv = runCommand "undeclared" commonArgs ''
set -eu
if isDeclaredMap undeclared; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
expectedBuilderLogEntries = [
"test failed"
];
};
arrayFails = testBuildFailure' {
name = "arrayFails";
drv = runCommand "array" commonArgs ''
set -eu
local -a array
if isDeclaredMap array; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
expectedBuilderLogEntries = [
"test failed"
];
};
emptyStringNamerefFails = testBuildFailure' {
name = "emptyStringNamerefFails";
drv = runCommand "emptyStringNameref" commonArgs ''
set -eu
if isDeclaredMap ""; then
nixLog "test passed"
touch "$out"
else
nixErrorLog "test failed"
exit 1
fi
'';
expectedBuilderLogEntries = [
"local: `': not a valid identifier"
"test failed"
];
};
namerefToEmptyStringFails = testBuildFailure' {
name = "namerefToEmptyStringFails";
drv = check {
name = "namerefToEmptyString";
scope = null;
intro = "local -n";
values = "";
};
expectedBuilderLogEntries = [
"local: `': not a valid identifier"
# The test fails in such a way that it exits immediately, without returning to the else branch.
];
};
sameScopeEmptyStringFails = testBuildFailure' {
name = "sameScopeEmptyStringFails";
drv = check {
name = "sameScopeEmptyString";
scope = null;
intro = null;
values = "";
};
expectedBuilderLogEntries = [
"test failed"
];
};
sameScopeEmptyMapFails = testBuildFailure' {
name = "sameScopeEmptyMapFails";
drv = check {
name = "sameScopeEmptyMap";
scope = null;
intro = null;
values = "()";
};
expectedBuilderLogEntries = [
"test failed"
];
};
# Fails because maps must be declared with the -A flag.
sameScopeSingletonMapFails = testBuildFailure' {
name = "sameScopeSingletonMapFails";
drv = check {
name = "sameScopeSingletonMap";
scope = null;
intro = null;
values = ''([greeting]="hello!")'';
};
expectedBuilderLogEntries = [
"greeting: unbound variable"
];
};
sameScopeLocalUnsetMap = check {
name = "sameScopeLocalUnsetMap";
scope = null;
intro = "local -A";
values = null;
};
sameScopeLocalEmptyMap = check {
name = "sameScopeLocalEmptyMap";
scope = null;
intro = "local -A";
values = "()";
};
sameScopeLocalSingletonMap = check {
name = "sameScopeLocalSingletonMap";
scope = null;
intro = "local -A";
values = ''([greeting]="hello!")'';
};
sameScopeDeclareUnsetMap = check {
name = "sameScopeDeclareUnsetMap";
scope = null;
intro = "declare -A";
values = null;
};
sameScopeDeclareEmptyMap = check {
name = "sameScopeDeclareEmptyMap";
scope = null;
intro = "declare -A";
values = "()";
};
sameScopeDeclareSingletonMap = check {
name = "sameScopeDeclareSingletonMap";
scope = null;
intro = "declare -A";
values = ''([greeting]="hello!")'';
};
previousScopeEmptyStringFails = testBuildFailure' {
name = "previousScopeEmptyStringFails";
drv = check {
name = "previousScopeEmptyString";
scope = "function";
intro = null;
values = "";
};
expectedBuilderLogEntries = [
"test failed"
];
};
# Fails because () is ambiguous and defaults to array rather than associative array.
previousScopeEmptyMapFails = testBuildFailure' {
name = "previousScopeEmptyMapFails";
drv = check {
name = "previousScopeEmptyMap";
scope = "function";
intro = null;
values = "()";
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeSingletonMapFails = testBuildFailure' {
name = "previousScopeSingletonMapFails";
drv = check {
name = "previousScopeSingletonMap";
scope = "function";
intro = null;
values = ''([greeting]="hello!")'';
};
expectedBuilderLogEntries = [
"greeting: unbound variable"
];
};
previousScopeLocalUnsetMapFails = testBuildFailure' {
name = "previousScopeLocalUnsetMapFails";
drv = check {
name = "previousScopeLocalUnsetMap";
scope = "function";
intro = "local -A";
values = null;
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeLocalEmptyMapFails = testBuildFailure' {
name = "previousScopeLocalEmptyMapFails";
drv = check {
name = "previousScopeLocalEmptyMap";
scope = "function";
intro = "local -A";
values = "()";
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeLocalSingletonMapFails = testBuildFailure' {
name = "previousScopeLocalSingletonMapFails";
drv = check {
name = "previousScopeLocalSingletonMap";
scope = "function";
intro = "local -A";
values = ''([greeting]="hello!")'';
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeLocalGlobalUnsetMap = check {
name = "previousScopeLocalGlobalUnsetMap";
scope = "function";
intro = "local -Ag";
values = null;
};
previousScopeLocalGlobalEmptyMap = check {
name = "previousScopeLocalGlobalEmptyMap";
scope = "function";
intro = "local -Ag";
values = "()";
};
previousScopeLocalGlobalSingletonMap = check {
name = "previousScopeLocalGlobalSingletonMap";
scope = "function";
intro = "local -Ag";
values = ''([greeting]="hello!")'';
};
previousScopeDeclareUnsetMapFails = testBuildFailure' {
name = "previousScopeDeclareUnsetMapFails";
drv = check {
name = "previousScopeDeclareUnsetMap";
scope = "function";
intro = "declare -A";
values = null;
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeDeclareEmptyMapFails = testBuildFailure' {
name = "previousScopeDeclareEmptyMapFails";
drv = check {
name = "previousScopeDeclareEmptyMap";
scope = "function";
intro = "declare -A";
values = "()";
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeDeclareSingletonMapFails = testBuildFailure' {
name = "previousScopeDeclareSingletonMapFails";
drv = check {
name = "previousScopeDeclareSingletonMap";
scope = "function";
intro = "declare -A";
values = ''([greeting]="hello!")'';
};
expectedBuilderLogEntries = [
"test failed"
];
};
previousScopeDeclareGlobalUnsetMap = check {
name = "previousScopeDeclareGlobalUnsetMap";
scope = "function";
intro = "declare -Ag";
values = null;
};
previousScopeDeclareGlobalEmptyMap = check {
name = "previousScopeDeclareGlobalEmptyMap";
scope = "function";
intro = "declare -Ag";
values = "()";
};
previousScopeDeclareGlobalSingletonMap = check {
name = "previousScopeDeclareGlobalSingletonMap";
scope = "function";
intro = "declare -Ag";
values = ''([greeting]="hello!")'';
};
}

View File

@@ -0,0 +1,11 @@
{
callPackages,
isDeclaredArray,
makeSetupHook,
}:
makeSetupHook {
name = "sortArray";
propagatedBuildInputs = [ isDeclaredArray ];
passthru.tests = callPackages ./tests.nix { };
meta.description = "Sorts an array";
} ./sortArray.bash

View File

@@ -0,0 +1,53 @@
# shellcheck shell=bash
# sortArray
# Sorts the indexed array referenced by inputArrRef and stores the result in the indexed array referenced by
# outputArrRef.
#
# Arguments:
# - inputArrRef: a reference to an indexed array (not mutated, may alias outputArrRef)
# - outputArrRef: a reference to an indexed array (contents are replaced entirely, may alias inputArrRef)
#
# Returns 0.
sortArray() {
if (($# != 2)); then
nixErrorLog "expected two arguments!"
nixErrorLog "usage: sortArray inputArrRef outputArrRef"
exit 1
fi
local -rn inputArrRef="$1"
local -rn outputArrRef="$2"
if ! isDeclaredArray "${!inputArrRef}"; then
nixErrorLog "first argument inputArrRef must be a reference to an indexed array"
exit 1
elif ! isDeclaredArray "${!outputArrRef}"; then
nixErrorLog "second argument outputArrRef must be a reference to an indexed array"
exit 1
fi
local -a sortedArray=()
# Guard on the length of the input array, as empty array will expand to nothing, but printf will still see it as an
# argument, producing an empty string.
if ((${#inputArrRef[@]} > 0)); then
# NOTE from Bash's printf documentation:
# The format is reused as necessary to consume all of the arguments. If the format requires more arguments than
# are supplied, the extra format specifications behave as if a zero value or null string, as appropriate, had
# been supplied.
# - https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-printf
# NOTE from sort manpage:
# If you use a non-POSIX locale (e.g., by setting LC_ALL to 'en_US'), then sort may produce output that is sorted
# differently than you're accustomed to. In that case, set the LC_ALL environment variable to 'C'. Setting only
# LC_COLLATE has two problems. First, it is ineffective if LC_ALL is also set. Second, it has undefined behavior
# if LC_CTYPE (or LANG, if LC_CTYPE is unset) is set to an incompatible value. For example, you get undefined
# behavior if LC_CTYPE is ja_JP.PCK but LC_COLLATE is en_US.UTF-8.
# - https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html#FOOT1
mapfile -d $'\0' -t sortedArray < <(printf '%s\0' "${inputArrRef[@]}" | LC_ALL=C sort --stable --zero-terminated)
fi
outputArrRef=("${sortedArray[@]}")
return 0
}

View File

@@ -0,0 +1,178 @@
# NOTE: Tests related to sortArray go here.
{
lib,
sortArray,
testers,
}:
let
inherit (lib.attrsets) recurseIntoAttrs;
inherit (testers) shellcheck shfmt testEqualArrayOrMap;
check =
{
name,
valuesArray,
expectedArray,
}:
(testEqualArrayOrMap {
inherit name valuesArray expectedArray;
script = ''
set -eu
nixLog "running sortArray with valuesArray to populate actualArray"
sortArray valuesArray actualArray
'';
}).overrideAttrs
(prevAttrs: {
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ sortArray ];
});
checkInPlace =
{
name,
valuesArray,
expectedArray,
}:
(testEqualArrayOrMap {
inherit name valuesArray expectedArray;
script = ''
set -eu
nixLog "running sortArray with valuesArray as input and output"
sortArray valuesArray valuesArray
nixLog "copying valuesArray to actualArray"
actualArray=("''${valuesArray[@]}")
'';
}).overrideAttrs
(prevAttrs: {
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ sortArray ];
});
in
recurseIntoAttrs {
shellcheck = shellcheck {
name = "sortArray";
src = ./sortArray.bash;
};
shfmt = shfmt {
name = "sortArray";
src = ./sortArray.bash;
};
empty = check {
name = "empty";
valuesArray = [ ];
expectedArray = [ ];
};
singleton = check {
name = "singleton";
valuesArray = [ "apple" ];
expectedArray = [ "apple" ];
};
oneDuplicate = check {
name = "oneDuplicate";
valuesArray = [
"apple"
"apple"
];
expectedArray = [
"apple"
"apple"
];
};
oneUnique = check {
name = "oneUnique";
valuesArray = [
"bee"
"apple"
"bee"
];
expectedArray = [
"apple"
"bee"
"bee"
];
};
duplicatesWithSpacesAndLineBreaks = check {
name = "duplicatesWithSpacesAndLineBreaks";
valuesArray = [
"dog"
"bee"
''
line
break
''
"cat"
"zebra"
"bee"
"cat"
"elephant"
"dog with spaces"
''
line
break
''
];
expectedArray = [
"bee"
"bee"
"cat"
"cat"
"dog"
"dog with spaces"
"elephant"
# NOTE: lead whitespace is removed, so the following entries start with `l`.
''
line
break
''
''
line
break
''
"zebra"
];
};
duplicatesWithSpacesAndLineBreaksInPlace = checkInPlace {
name = "duplicatesWithSpacesAndLineBreaksInPlace";
valuesArray = [
"dog"
"bee"
''
line
break
''
"cat"
"zebra"
"bee"
"cat"
"elephant"
"dog with spaces"
''
line
break
''
];
expectedArray = [
"bee"
"bee"
"cat"
"cat"
"dog"
"dog with spaces"
"elephant"
# NOTE: lead whitespace is removed, so the following entries start with `l`.
''
line
break
''
''
line
break
''
"zebra"
];
};
}

View File

@@ -0,0 +1,68 @@
# Check whether RPATHs or wrapper scripts contain references to
# $TMPDIR. This is a serious security bug because it allows any user
# to inject files into search paths of other users' processes.
#
# It might be better to have Nix scan build output for any occurrence
# of $TMPDIR (which would also be good for reproducibility), but at
# the moment that would produce too many spurious errors (e.g. debug
# info or assertion messages that refer to $TMPDIR).
fixupOutputHooks+=('if [[ -z "${noAuditTmpdir-}" && -e "$prefix" ]]; then auditTmpdir "$prefix"; fi')
auditTmpdir() {
local dir="$1"
[ -e "$dir" ] || return 0
echo "checking for references to $TMPDIR/ in $dir..."
local tmpdir elf_fifo script_fifo
tmpdir="$(mktemp -d)"
elf_fifo="$tmpdir/elf"
script_fifo="$tmpdir/script"
mkfifo "$elf_fifo" "$script_fifo"
# Classifier: identify ELF and script files
(
find "$dir" -type f -not -path '*/.build-id/*' -print0 \
| while IFS= read -r -d $'\0' file; do
if isELF "$file"; then
printf '%s\0' "$file" >&3
elif isScript "$file"; then
filename=${file##*/}
dir=${file%/*}
if [ -e "$dir/.$filename-wrapped" ]; then
printf '%s\0' "$file" >&4
fi
fi
done
exec 3>&- 4>&-
) 3> "$elf_fifo" 4> "$script_fifo" &
# Handler: check RPATHs concurrently
(
xargs -0 -r -P "$NIX_BUILD_CORES" -n 1 sh -c '
if { printf :; patchelf --print-rpath "$1"; } | grep -q -F ":$TMPDIR/"; then
echo "RPATH of binary $1 contains a forbidden reference to $TMPDIR/"
exit 1
fi
' _ < "$elf_fifo"
) &
local pid_elf=$!
# Handler: check wrapper scripts concurrently
local pid_script
(
xargs -0 -r -P "$NIX_BUILD_CORES" -n 1 sh -c '
if grep -q -F "$TMPDIR/" "$1"; then
echo "wrapper script $1 contains a forbidden reference to $TMPDIR/"
exit 1
fi
' _ < "$script_fifo"
) &
local pid_script=$!
wait "$pid_elf" || { echo "Some binaries contain forbidden references to $TMPDIR/. Check the error above!"; exit 1; }
wait "$pid_script" || { echo "Some scripts contain forbidden references to $TMPDIR/. Check the error above!"; exit 1; }
rm -r "$tmpdir"
}

View File

@@ -0,0 +1,104 @@
# shellcheck shell=bash
declare -a autoPatchelfLibs
declare -a extraAutoPatchelfLibs
gatherLibraries() {
autoPatchelfLibs+=("$1/lib")
}
# shellcheck disable=SC2154
# (targetOffset is referenced but not assigned.)
addEnvHooks "$targetOffset" gatherLibraries
# Can be used to manually add additional directories with shared object files
# to be included for the next autoPatchelf invocation.
addAutoPatchelfSearchPath() {
local -a findOpts=()
while [ $# -gt 0 ]; do
case "$1" in
--) shift; break;;
--no-recurse) shift; findOpts+=("-maxdepth" 1);;
--*)
echo "addAutoPatchelfSearchPath: ERROR: Invalid command line" \
"argument: $1" >&2
return 1;;
*) break;;
esac
done
local dir=
while IFS= read -r -d '' dir; do
extraAutoPatchelfLibs+=("$dir")
done < <(find "$@" "${findOpts[@]}" \! -type d \
\( -name '*.so' -o -name '*.so.*' \) -print0 \
| sed -z 's#/[^/]*$##' \
| uniq -z
)
}
autoPatchelf() {
local norecurse=
while [ $# -gt 0 ]; do
case "$1" in
--) shift; break;;
--no-recurse) shift; norecurse=1;;
--*)
echo "autoPatchelf: ERROR: Invalid command line" \
"argument: $1" >&2
return 1;;
*) break;;
esac
done
concatTo ignoreMissingDepsArray autoPatchelfIgnoreMissingDeps
concatTo appendRunpathsArray appendRunpaths
concatTo runtimeDependenciesArray runtimeDependencies
concatTo patchelfFlagsArray patchelfFlags
concatTo autoPatchelfFlagsArray autoPatchelfFlags
# Check if ignoreMissingDepsArray contains "1" and if so, replace it with
# "*", printing a deprecation warning.
for dep in "${ignoreMissingDepsArray[@]}"; do
if [ "$dep" == "1" ]; then
echo "autoPatchelf: WARNING: setting 'autoPatchelfIgnoreMissingDeps" \
"= true;' is deprecated and will be removed in a future release." \
"Use 'autoPatchelfIgnoreMissingDeps = [ \"*\" ];' instead." >&2
ignoreMissingDepsArray=( "*" )
break
fi
done
auto-patchelf \
${norecurse:+--no-recurse} \
--ignore-missing "${ignoreMissingDepsArray[@]}" \
--paths "$@" \
--libs "${autoPatchelfLibs[@]}" \
"${extraAutoPatchelfLibs[@]}" \
--runtime-dependencies "${runtimeDependenciesArray[@]/%//lib}" \
--append-rpaths "${appendRunpathsArray[@]}" \
"${autoPatchelfFlagsArray[@]}" \
--extra-args "${patchelfFlagsArray[@]}"
}
autoPatchelfPostFixup() {
# XXX: This should ultimately use fixupOutputHooks but we currently don't have
# a way to enforce the order. If we have $runtimeDependencies set, the setup
# hook of patchelf is going to ruin everything and strip out those additional
# RPATHs.
#
# So what we do here is basically run in postFixup and emulate the same
# behaviour as fixupOutputHooks because the setup hook for patchelf is run in
# fixupOutput and the postFixup hook runs later.
if [[ -z "${dontAutoPatchelf-}" ]]; then
autoPatchelf -- $(for output in $(getAllOutputNames); do
[ -e "${!output}" ] || continue
[ "${output}" = debug ] && continue
echo "${!output}"
done)
fi
}
postFixupHooks+=(autoPatchelfPostFixup)

View File

@@ -0,0 +1,15 @@
appendToVar preConfigurePhases autoreconfPhase
autoreconfPhase() {
runHook preAutoreconf
local flagsArray=()
if [[ -v autoreconfFlags ]]; then
concatTo flagsArray autoreconfFlags
else
flagsArray+=(--install --force --verbose)
fi
autoreconf "${flagsArray[@]}"
runHook postAutoreconf
}

View File

@@ -0,0 +1,27 @@
fixupOutputHooks+=('if [ -z "${dontGzipMan-}" ]; then compressManPages "$prefix"; fi')
compressManPages() {
local dir="$1"
if [ -L "$dir"/share ] || [ -L "$dir"/share/man ] || [ ! -d "$dir/share/man" ]
then return
fi
echo "gzipping man pages under $dir/share/man/"
# Compress all uncompressed manpages. Don't follow symlinks, etc.
# gzip -f is needed to not error out on hard links.
find "$dir"/share/man/ -type f -a '!' -regex '.*\.\(bz2\|gz\|xz\)$' -print0 \
| xargs -0 -n1 -P "$NIX_BUILD_CORES" gzip -n -f
# Point symlinks to compressed manpages.
find "$dir"/share/man/ -type l -a '!' -regex '.*\.\(bz2\|gz\|xz\)$' -print0 \
| sort -z \
| while IFS= read -r -d $'\0' f
do
local target
target="$(readlink -f "$f")"
if [ -f "$target".gz ]; then
ln -sf "$target".gz "$f".gz && rm "$f"
fi
done
}

View File

@@ -0,0 +1,43 @@
# shellcheck shell=bash
# Setup hook that installs specified desktop items.
#
# Example usage in a derivation:
#
# { …, makeDesktopItem, copyDesktopItems, … }:
#
# let desktopItem = makeDesktopItem { … }; in
# stdenv.mkDerivation {
# …
# nativeBuildInputs = [ copyDesktopItems ];
#
# desktopItems = [ desktopItem ];
# …
# }
#
# This hook will copy files which are either given by full path
# or all '*.desktop' files placed inside the 'share/applications'
# folder of each `desktopItems` argument.
postInstallHooks+=(copyDesktopItems)
copyDesktopItems() {
if [ "${dontCopyDesktopItems-}" = 1 ]; then return; fi
if [ -z "$desktopItems" ]; then
return
fi
applications="${!outputBin}/share/applications"
for desktopItem in $desktopItems; do
if [[ -f "$desktopItem" ]]; then
echo "Copying '$desktopItem' into '${applications}'"
install -D -m 444 -t "${applications}" "$desktopItem"
else
for f in "$desktopItem"/share/applications/*.desktop; do
echo "Copying '$f' into '${applications}'"
install -D -m 444 -t "${applications}" "$f"
done
fi
done
}

View File

@@ -0,0 +1,46 @@
# shellcheck shell=bash
# Setup hook that installs specified pkgconfig items.
#
# Example usage in a derivation:
#
# { …, makePkgconfigItem, copyPkgconfigItems, … }:
#
# let pkgconfigItem = makePkgconfigItem { … }; in
# stdenv.mkDerivation {
# …
# nativeBuildInputs = [ copyPkgconfigItems ];
#
# pkgconfigItems = [ pkgconfigItem ];
# …
# }
#
# This hook will copy files which are either given by full path
# or all '*.pc' files placed inside the 'lib/pkgconfig'
# folder of each `pkgconfigItems` argument.
postInstallHooks+=(copyPkgconfigItems)
copyPkgconfigItems() {
if [ "${dontCopyPkgconfigItems-}" = 1 ]; then return; fi
if [ -z "$pkgconfigItems" ]; then
return
fi
pkgconfigdir="${!outputDev}/lib/pkgconfig"
for pkgconfigItem in $pkgconfigItems; do
if [[ -f "$pkgconfigItem" ]]; then
substituteAllInPlace "$pkgconfigItem"
echo "Copying '$pkgconfigItem' into '${pkgconfigdir}'"
install -D -m 444 -t "${pkgconfigdir}" "$pkgconfigItem"
substituteAllInPlace "${pkgconfigdir}"/*
else
for f in "$pkgconfigItem"/lib/pkgconfig/*.pc; do
echo "Copying '$f' into '${pkgconfigdir}'"
install -D -m 444 -t "${pkgconfigdir}" "$f"
substituteAllInPlace "${pkgconfigdir}"/*
done
fi
done
}

View File

@@ -0,0 +1,73 @@
addLinkDLLPaths() {
addToSearchPath "LINK_DLL_FOLDERS" "$1/lib"
addToSearchPath "LINK_DLL_FOLDERS" "$1/bin"
}
addEnvHooks "$targetOffset" addLinkDLLPaths
addOutputDLLPaths() {
for output in $(getAllOutputNames); do
addToSearchPath "LINK_DLL_FOLDERS" "${!output}/lib"
addToSearchPath "LINK_DLL_FOLDERS" "${!output}/bin"
done
}
postInstallHooks+=(addOutputDLLPaths)
_dllDeps() {
"$OBJDUMP" -p "$1" \
| sed -n 's/.*DLL Name: \(.*\)/\1/p' \
| sort -u
}
_linkDeps() {
local target="$1" dir="$2" check="$3"
echo 'target:' "$target"
local dll
_dllDeps "$target" | while read dll; do
echo ' dll:' "$dll"
if [[ -e "$dir/$dll" ]]; then continue; fi
# Locate the DLL - it should be an *executable* file on $LINK_DLL_FOLDERS.
local dllPath="$(PATH="$(dirname "$target"):$LINK_DLL_FOLDERS" type -P "$dll")"
if [[ -z "$dllPath" ]]; then
if [[ -z "$check" || -n "${allowedImpureDLLsMap[$dll]}" ]]; then
continue
fi
echo unable to find $dll in $LINK_DLL_FOLDERS >&2
exit 1
fi
echo ' linking to:' "$dllPath"
CYGWIN+=\ winsymlinks:nativestrict ln -sr "$dllPath" "$dir"
# That DLL might have its own (transitive) dependencies,
# so add also all DLLs from its directory to be sure.
_linkDeps "$dllPath" "$dir" ""
done
}
linkDLLs() {
if [ ! -d "$prefix" ]; then return; fi
(
set -e
shopt -s globstar nullglob
local -a allowedImpureDLLsArray
concatTo allowedImpureDLLsArray allowedImpureDLLs
local -A allowedImpureDLLsMap;
for dll in "${allowedImpureDLLsArray[@]}"; do
allowedImpureDLLsMap[$dll]=1
done
cd "$prefix"
# Iterate over any DLL that we depend on.
local target
for target in {bin,libexec}/**/*.{exe,dll}; do
[[ ! -f "$target" || ! -x "$target" ]] ||
_linkDeps "$target" "$(dirname "$target")" "1"
done
)
}
fixupOutputHooks+=(linkDLLs)

View File

@@ -0,0 +1,263 @@
# shellcheck shell=bash
fixupOutputHooks+=('convertDesktopFiles $prefix')
# Get a param out of a desktop file. First parameter is the file and the second
# is the key who's value we should fetch.
getDesktopParam() {
local file="$1"
local key="$2"
local line k v
while read -r line; do
if [[ "$line" = *=* ]]; then
k="${line%%=*}"
v="${line#*=}"
if [[ "$k" = "$key" ]]; then
echo "$v"
return
fi
fi
done < "$file"
return 1
}
# Convert a freedesktop.org icon theme for a given app to a .icns file. When possible, missing
# icons are synthesized from SVG or rescaled from existing ones (when within the size threshold).
convertIconTheme() {
local -r out=$1
local -r sharePath=$2
local -r iconName=$3
local -r theme=${4:-hicolor}
# Sizes based on archived Apple documentation:
# https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon#app-icon-sizes
local -ra iconSizes=(16 32 128 256 512)
local -ra scales=([1]="" [2]="@2")
# Based loosely on the algorithm at:
# https://specifications.freedesktop.org/icon-theme-spec/latest/#icon_lookup
# Assumes threshold = 2 for ease of implementation.
function findIcon() {
local -r iconSize=$1
local -r scale=$2
local scaleSuffix=${scales[$scale]}
local exactSize=${iconSize}x${iconSize}${scaleSuffix}
local -a validSizes=(
${exactSize}
$((iconSize + 1))x$((iconSize + 1))${scaleSuffix}
$((iconSize + 2))x$((iconSize + 2))${scaleSuffix}
$((iconSize - 1))x$((iconSize - 1))${scaleSuffix}
$((iconSize - 2))x$((iconSize - 2))${scaleSuffix}
)
local fallbackIcon=
for iconIndex in "${!candidateIcons[@]}"; do
for maybeSize in "${validSizes[@]}"; do
icon=${candidateIcons[$iconIndex]}
if [[ $icon = */$maybeSize/* ]]; then
if [[ $maybeSize = $exactSize ]]; then
echo "fixed $icon"
return 0
else
echo "threshold $icon"
return 0
fi
elif [[ -a $icon && -z "$fallbackIcon" ]]; then
fallbackIcon="$icon"
fi
done
done
if [[ -n "$fallbackIcon" ]]; then
echo "fallback $fallbackIcon"
return 0
fi
echo "scalable"
}
function resizeIcon() {
local -r in=$1
local -r out=$2
local -r iconSize=$3
local -r scale=$4
local density=$((72 * scale))x$((72 * scale))
local dim=$((iconSize * scale))
echo "desktopToDarwinBundle: resizing icon $in to $out, size $dim" >&2
magick convert -scale "${dim}x${dim}" -density "$density" -units PixelsPerInch "$in" "$out"
convertIfUnsupportedIcon "$out" "$iconSize" "$scale"
}
function synthesizeIcon() {
local -r in=$1
local -r out=$2
local -r iconSize=$3
local -r scale=$4
if [[ $in != '-' ]]; then
local density=$((72 * scale))x$((72 * scale))
local dim=$((iconSize * scale))
echo "desktopToDarwinBundle: rasterizing svg $in to $out, size $dim" >&2
rsvg-convert --keep-aspect-ratio --width "$dim" --height "$dim" "$in" --output "$out"
magick convert -density "$density" -units PixelsPerInch "$out" "$out"
convertIfUnsupportedIcon "$out" "$iconSize" "$scale"
else
return 1
fi
}
# macOS does not correctly display 16x and 32x png icons on app bundles
# they need to be converted to argb
function convertIfUnsupportedIcon() {
local -r in=$1
local -r iconSize=$2
local -r scale=$3
local -r out=${in%.png}.argb
if [[ ($scale -eq 1) && ($iconSize -eq 32 || $iconSize -eq 16) ]]; then
echo "desktopToDarwinBundle: converting ${iconSize}x icon to argb" >&2
icnsutil convert "$out" "$in"
rm "$in"
fi
}
function getIcons() {
local -r sharePath=$1
local -r iconname=$2
local -r theme=$3
local -r resultdir=$(mktemp -d)
local -ar candidateIcons=(
"${sharePath}/icons/${theme}/"*"/${iconname}.png"
"${sharePath}/icons/${theme}/"*"/${iconname}.xpm"
)
local -a scalableIcon=("${sharePath}/icons/${theme}/scalable/${iconname}.svg"*)
if [[ ${#scalableIcon[@]} = 0 ]]; then
scalableIcon=('-')
fi
# Tri-state variable, NONE means no icons have been found, an empty
# icns file will be generated, not sure that's necessary because macOS
# will default to a generic icon if no icon can be found.
#
# OTHER means an appropriate icon was found.
#
# Any other value is a path to an icon file that isn't scalable or
# within the threshold. This is used as a fallback in case no better
# icon can be found and will be scaled as much as
# necessary to result in appropriate icon sizes.
local foundIcon=NONE
for iconSize in "${iconSizes[@]}"; do
for scale in "${!scales[@]}"; do
local iconResult=$(findIcon $iconSize $scale)
local type=${iconResult%% *}
local icon=${iconResult#* }
local scaleSuffix=${scales[$scale]}
local result=${resultdir}/${iconSize}x${iconSize}${scales[$scale]}${scaleSuffix:+x}.png
echo "desktopToDarwinBundle: using $type icon $icon for size $iconSize$scaleSuffix" >&2
case $type in
fixed)
local density=$((72 * scale))x$((72 * scale))
magick convert -density "$density" -units PixelsPerInch "$icon" "$result"
convertIfUnsupportedIcon "$result" "$iconSize" "$scale"
foundIcon=OTHER
;;
threshold)
# Synthesize an icon of the exact size if a scalable icon is available
# instead of scaling one and ending up with a fuzzy icon.
if ! synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale"; then
resizeIcon "$icon" "$result" "$iconSize" "$scale"
fi
foundIcon=OTHER
;;
scalable)
synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
foundIcon=OTHER
;;
fallback)
# Use the largest size available to scale to
# appropriate sizes.
if [[ $foundIcon != OTHER ]]; then
foundIcon=$icon
fi
;;
*)
;;
esac
done
done
if [[ $foundIcon != NONE && $foundIcon != OTHER ]]; then
# Ideally we'd only resize to whatever the closest sizes are,
# starting from whatever icon sizes are available.
for iconSize in 16 32 128 256 512; do
local result=${resultdir}/${iconSize}x${iconSize}.png
resizeIcon "$foundIcon" "$result" "$iconSize" 1
done
fi
echo "$resultdir"
}
iconsdir=$(getIcons "$sharePath" "apps/${iconName}" "$theme")
if [[ -n "$(ls -A1 "$iconsdir")" ]]; then
icnsutil compose --toc "$out/${iconName}.icns" "$iconsdir/"*
else
echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
touch "$out/${iconName}.icns"
fi
}
processExecFieldCodes() {
local -r file=$1
local -r execRaw=$(getDesktopParam "${file}" "Exec")
local -r execNoK="${execRaw/\%k/${file}}"
local -r execNoKC="${execNoK/\%c/$(getDesktopParam "${file}" "Name")}"
local -r icon=$(getDesktopParam "${file}" "Icon")
local -r execNoKCI="${execNoKC/\%i/${icon:+--icon }${icon}}"
local -r execNoKCIfu="${execNoKCI/ \%[fu]/}"
local -r exec="${execNoKCIfu/ \%[FU]/}"
if [[ "$exec" != "$execRaw" ]]; then
echo 1>&2 "desktopToDarwinBundle: Application bundles do not understand desktop entry field codes. Changed '$execRaw' to '$exec'."
fi
echo "$exec"
}
# For a given .desktop file, generate a darwin '.app' bundle for it.
convertDesktopFile() {
local -r file=$1
local -r sharePath=$(dirname "$(dirname "$file")")
local -r name=$(getDesktopParam "${file}" "Name")
local -r macOSExec=$(getDesktopParam "${file}" "X-macOS-Exec")
if [[ "$macOSExec" ]]; then
local -r exec="$macOSExec"
else
local -r exec=$(processExecFieldCodes "${file}")
fi
local -r iconName=$(getDesktopParam "${file}" "Icon")
local -r squircle=$(getDesktopParam "${file}" "X-macOS-SquircleIcon")
mkdir -p "${!outputBin}/Applications/${name}.app/Contents/MacOS"
mkdir -p "${!outputBin}/Applications/${name}.app/Contents/Resources"
convertIconTheme "${!outputBin}/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
write-darwin-bundle "${!outputBin}" "$name" "$exec" "$iconName" "$squircle"
}
convertDesktopFiles() {
local dir="$1/share/applications/"
if [ -d "${dir}" ]; then
for desktopFile in $(find "$dir" -iname "*.desktop"); do
convertDesktopFile "$desktopFile";
done
fi
}

View File

@@ -0,0 +1,21 @@
# Exit with backtrace and error message
#
# Usage: die "Error message"
die() {
# Let us be a little sloppy with errors, because otherwise the final
# invocation of `caller` below will cause the script to exit.
set +e
# Print our error message
printf "\nBuilder called die: %b\n" "$*"
printf "Backtrace:\n"
# Print a backtrace.
local frame=0
while caller $frame; do
((frame++));
done
printf "\n"
exit 1
}

View File

@@ -0,0 +1,20 @@
appendToVar postPhases cleanupBuildDir
# Force GCC to build with coverage instrumentation. Also disable
# optimisation, since it may confuse things.
export NIX_CFLAGS_COMPILE="${NIX_CFLAGS_COMPILE:-} -O0 --coverage"
# Get rid of everything that isn't a gcno file or a C source file.
# Also strip the `.tmp_' prefix from gcno files. (The Linux kernel
# creates these.)
cleanupBuildDir() {
if ! [ -e $out/.build ]; then return; fi
find $out/.build/ -type f -a ! \
\( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hh" -o -name "*.y" -o -name "*.l" -o -name "*.gcno" \) \
| xargs rm -f --
for i in $(find $out/.build/ -name ".tmp_*.gcno"); do
mv "$i" "$(echo $i | sed s/.tmp_//)"
done
}

View File

@@ -0,0 +1,22 @@
addXMLCatalogs () {
local d i
# xml/dtd and xml/xsl are deprecated. Catalogs should be
# installed underneath share/xml.
for d in $1/share/xml $1/xml/dtd $1/xml/xsl; do
if [ -d $d ]; then
for i in $(find $d -name catalog.xml); do
XML_CATALOG_FILES+=" $i"
done
fi
done
}
if [ -z "${libxmlHookDone-}" ]; then
libxmlHookDone=1
# Set up XML_CATALOG_FILES. An empty initial value prevents
# xmllint and xsltproc from looking in /etc/xml/catalog.
export XML_CATALOG_FILES=''
if [ -z "$XML_CATALOG_FILES" ]; then XML_CATALOG_FILES=" "; fi
addEnvHooks "$hostOffset" addXMLCatalogs
fi

View File

@@ -0,0 +1,43 @@
# On macOS, binaries refer to dynamic library dependencies using
# either relative paths (e.g. "libicudata.dylib", searched relative to
# $DYLD_LIBRARY_PATH) or absolute paths
# (e.g. "/nix/store/.../lib/libicudata.dylib"). In Nix, the latter is
# preferred since it allows programs to just work. When linking
# against a library (e.g. "-licudata"), the linker uses the install
# name embedded in the dylib (which can be shown using "otool -D").
# Most packages create dylibs with absolute install names, but some do
# not. This setup hook fixes dylibs by setting their install names to
# their absolute path (using "install_name_tool -id"). It also
# rewrites references in other dylibs to absolute paths.
fixupOutputHooks+=('fixDarwinDylibNamesIn $prefix')
fixDarwinDylibNames() {
local flags=()
local old_id
for fn in "$@"; do
flags+=(-change "$(basename "$fn")" "$fn")
done
for fn in "$@"; do
if [ -L "$fn" ]; then continue; fi
echo "$fn: fixing dylib"
set +e
int_out=$(@targetPrefix@install_name_tool -id "$fn" "${flags[@]}" "$fn" 2>&1)
result=$?
set -e
if [ "$result" -ne 0 ] &&
! grep -q -e "shared library stub file and can't be changed" \
-e "is not a Mach-O file" <<< "$int_out"
then
echo "$int_out" >&2
exit "$result"
fi
done
}
fixDarwinDylibNamesIn() {
local dir="$1"
fixDarwinDylibNames $(find "$dir" -name "*.dylib" -o -name "*.so" -o -name "*.so.*")
}

View File

@@ -0,0 +1,2 @@
{ makeSetupHook }:
makeSetupHook { name = "flatten-include-hack-hook"; } ./flatten-include-hack-hook.sh

View File

@@ -0,0 +1,16 @@
# shellcheck shell=bash
# This is a horrible hack. You should not use this.
flattenIncludes() {
(
cd "${!outputInclude}/include" || exit
for file in */*; do
target=$(basename "$file")
echo "[HACK] Symlinking include $file to flattened path $target..."
ln -s "$file" "$target"
done
)
}
preFixupHooks+=(flattenIncludes)

View File

@@ -0,0 +1,11 @@
unpackPhase="unpackGog"
unpackGog() {
runHook preUnpackGog
innoextract --silent --extract --exclude-temp "${src}"
find . -depth -print -execdir rename -f 'y/A-Z/a-z/' '{}' \;
runHook postUnpackGog
}

View File

@@ -0,0 +1,6 @@
appendToVar prePhases moveBuildDir
moveBuildDir() {
mkdir -p $out/.build
cd $out/.build
}

View File

@@ -0,0 +1,5 @@
ld-is-cc-hook() {
LD=$CC
}
preConfigureHooks+=(ld-is-cc-hook)

View File

@@ -0,0 +1,25 @@
appendToVar postPhases coverageReportPhase
coverageReportPhase() {
lcov --directory . --capture --output-file app.info
set -o noglob
lcov --remove app.info ${lcovFilter:-"/nix/store/*"} > app2.info
set +o noglob
mv app2.info app.info
mkdir -p $out/coverage
genhtml app.info $lcovExtraTraceFiles -o $out/coverage > log
# Grab the overall coverage percentage so that Hydra can plot it over time.
mkdir -p $out/nix-support
lineCoverage="$(sed 's/.*lines\.*: \([0-9\.]\+\)%.*/\1/; t ; d' log)"
functionCoverage="$(sed 's/.*functions\.*: \([0-9\.]\+\)%.*/\1/; t ; d' log)"
if [ -z "$lineCoverage" -o -z "$functionCoverage" ]; then
echo "failed to get coverage statistics"
exit 1
fi
echo "lineCoverage $lineCoverage %" >> $out/nix-support/hydra-metrics
echo "functionCoverage $functionCoverage %" >> $out/nix-support/hydra-metrics
echo "report coverage $out/coverage" >> $out/nix-support/hydra-build-products
}

View File

@@ -0,0 +1,37 @@
# symlinks are often created in postFixup
# don't use fixupOutputHooks, it is before postFixup
postFixupHooks+=(_makeSymlinksRelativeInAllOutputs)
# For every symlink in $output that refers to another file in $output
# ensure that the symlink is relative. This removes references to the output
# has from the resulting store paths and thus the NAR files.
_makeSymlinksRelative() {
local symlinkTarget
if [ "${dontRewriteSymlinks-}" ] || [ ! -e "$prefix" ]; then
return
fi
while IFS= read -r -d $'\0' f; do
symlinkTarget=$(readlink "$f")
if [[ "$symlinkTarget"/ != "$prefix"/* ]]; then
# skip this symlink as it doesn't point to $prefix
continue
fi
if [ ! -e "$symlinkTarget" ]; then
echo "the symlink $f is broken, it points to $symlinkTarget (which is missing)"
fi
echo "rewriting symlink $f to be relative to $prefix"
ln -snrf "$symlinkTarget" "$f"
done < <(find $prefix -type l -print0)
}
_makeSymlinksRelativeInAllOutputs() {
local output
for output in $(getAllOutputNames); do
prefix="${!output}" _makeSymlinksRelative
done
}

View File

@@ -0,0 +1,235 @@
# Assert that FILE exists and is executable
#
# assertExecutable FILE
assertExecutable() {
local file="$1"
[[ -f "$file" && -x "$file" ]] || \
die "Cannot wrap '$file' because it is not an executable file"
}
# construct an executable file that wraps the actual executable
# makeWrapper EXECUTABLE OUT_PATH ARGS
# ARGS:
# --argv0 NAME : set the name of the executed process to NAME
# (if unset or empty, defaults to EXECUTABLE)
# --inherit-argv0 : the executable inherits argv0 from the wrapper.
# (use instead of --argv0 '$0')
# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH
# --set VAR VAL : add VAR with value VAL to the executable's environment
# --set-default VAR VAL : like --set, but only adds VAR if not already set in
# the environment
# --unset VAR : remove VAR from the environment
# --chdir DIR : change working directory (use instead of --run "cd DIR")
# --run COMMAND : run command before the executable
# --add-flag ARG : prepend the single argument ARG to the invocation of the executable
# (that is, *before* any arguments passed on the command line)
# --append-flag ARG : append the single argument ARG to the invocation of the executable
# (that is, *after* any arguments passed on the command line)
# --add-flags ARGS : prepend ARGS verbatim to the Bash-interpreted invocation of the executable
# --append-flags ARGS : append ARGS verbatim to the Bash-interpreted invocation of the executable
# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP
# --suffix
# --prefix-each ENV SEP VALS : like --prefix, but VALS is a list
# --suffix-each ENV SEP VALS : like --suffix, but VALS is a list
# --prefix-contents ENV SEP FILES : like --suffix-each, but contents of FILES
# are read first and used as VALS
# --suffix-contents
makeWrapper() { makeShellWrapper "$@"; }
makeShellWrapper() {
local original="$1"
local wrapper="$2"
local params varName value command separator n fileNames
local argv0 flagsBefore flagsAfter flags
assertExecutable "$original"
# Write wrapper code which adds `value` to the beginning or end of
# the list variable named by `varName`, depending on the `mode`
# specified.
#
# A value which is already part of the list will not be added
# again. If this is the case and the `suffix` mode is used, the
# list won't be touched at all. The `prefix` mode will however
# move the last matching instance of the value to the beginning
# of the list. Any remaining duplicates of the value will be left
# as-is.
addValue() {
local mode="$1" # `prefix` or `suffix` to add to the beginning or end respectively
local varName="$2" # name of list variable to add to
local separator="$3" # character used to separate elements of list
local value="$4" # one value, or multiple values separated by `separator`, to add to list
# Disable file globbing, since bash will otherwise try to find
# filenames matching the the value to be prefixed/suffixed if
# it contains characters considered wildcards, such as `?` and
# `*`. We want the value as is, except we also want to split
# it on on the separator; hence we can't quote it.
local reenableGlob=0
if [[ ! -o noglob ]]; then
reenableGlob=1
fi
set -o noglob
if [[ -n "$value" ]]; then
local old_ifs=$IFS
IFS=$separator
if [[ "$mode" == '--prefix'* ]]; then
# Keep the order of the components as written when
# prefixing; normally, they would be added in the
# reverse order.
local tmp=
for v in $value; do
tmp=$v${tmp:+$separator}$tmp
done
value="$tmp"
fi
for v in $value; do
{
echo "$varName=\${$varName:+${separator@Q}\$$varName${separator@Q}}" # add separators on both ends unless empty
if [[ "$mode" == '--prefix'* ]]; then # -- in prefix mode --
echo "$varName=\${$varName/${separator@Q}${v@Q}${separator@Q}/${separator@Q}}" # remove the first instance of the value (if any)
echo "$varName=${v@Q}\$$varName" # prepend the value
elif [[ "$mode" == '--suffix'* ]]; then # -- in suffix mode --
echo "if [[ \$$varName != *${separator@Q}${v@Q}${separator@Q}* ]]; then" # if the value isn't already in the list
echo " $varName=\$$varName${v@Q}" # append the value
echo "fi"
else
echo "unknown mode $mode!" 1>&2
exit 1
fi
echo "$varName=\${$varName#${separator@Q}}" # remove leading separator
echo "$varName=\${$varName%${separator@Q}}" # remove trailing separator
echo "export $varName"
} >> "$wrapper"
done
IFS=$old_ifs
fi
if (( reenableGlob )); then
set +o noglob
fi
}
mkdir -p "$(dirname "$wrapper")"
echo "#! @shell@ -e" > "$wrapper"
params=("$@")
for ((n = 2; n < ${#params[*]}; n += 1)); do
p="${params[$n]}"
if [[ "$p" == "--set" ]]; then
varName="${params[$((n + 1))]}"
value="${params[$((n + 2))]}"
n=$((n + 2))
echo "export $varName=${value@Q}" >> "$wrapper"
elif [[ "$p" == "--set-default" ]]; then
varName="${params[$((n + 1))]}"
value="${params[$((n + 2))]}"
n=$((n + 2))
echo "export $varName=\${$varName-${value@Q}}" >> "$wrapper"
elif [[ "$p" == "--unset" ]]; then
varName="${params[$((n + 1))]}"
n=$((n + 1))
echo "unset $varName" >> "$wrapper"
elif [[ "$p" == "--chdir" ]]; then
dir="${params[$((n + 1))]}"
n=$((n + 1))
echo "cd ${dir@Q}" >> "$wrapper"
elif [[ "$p" == "--run" ]]; then
command="${params[$((n + 1))]}"
n=$((n + 1))
echo "$command" >> "$wrapper"
elif [[ ("$p" == "--suffix") || ("$p" == "--prefix") ]]; then
varName="${params[$((n + 1))]}"
separator="${params[$((n + 2))]}"
value="${params[$((n + 3))]}"
n=$((n + 3))
addValue "$p" "$varName" "$separator" "$value"
elif [[ ("$p" == "--suffix-each") || ("$p" == "--prefix-each") ]]; then
varName="${params[$((n + 1))]}"
separator="${params[$((n + 2))]}"
values="${params[$((n + 3))]}"
n=$((n + 3))
for value in $values; do
addValue "$p" "$varName" "$separator" "$value"
done
elif [[ ("$p" == "--suffix-contents") || ("$p" == "--prefix-contents") ]]; then
varName="${params[$((n + 1))]}"
separator="${params[$((n + 2))]}"
fileNames="${params[$((n + 3))]}"
n=$((n + 3))
for fileName in $fileNames; do
contents="$(cat "$fileName")"
addValue "$p" "$varName" "$separator" "$contents"
done
elif [[ "$p" == "--add-flag" ]]; then
flags=${params[n + 1]@Q}
n=$((n + 1))
flagsBefore="${flagsBefore-} $flags"
elif [[ "$p" == "--append-flag" ]]; then
flags=${params[n + 1]@Q}
n=$((n + 1))
flagsAfter="${flagsAfter-} $flags"
elif [[ "$p" == "--add-flags" ]]; then
flags="${params[$((n + 1))]}"
n=$((n + 1))
flagsBefore="${flagsBefore-} $flags"
elif [[ "$p" == "--append-flags" ]]; then
flags="${params[$((n + 1))]}"
n=$((n + 1))
flagsAfter="${flagsAfter-} $flags"
elif [[ "$p" == "--argv0" ]]; then
argv0="${params[$((n + 1))]}"
n=$((n + 1))
elif [[ "$p" == "--inherit-argv0" ]]; then
# Whichever comes last of --argv0 and --inherit-argv0 wins
argv0='$0'
elif [[ "$p" == "--resolve-argv0" ]]; then
# this is noop in shell wrappers, since bash will always resolve $0
resolve_argv0=1
else
die "makeWrapper doesn't understand the arg $p"
fi
done
echo exec ${argv0:+-a \"$argv0\"} \""$original"\" \
"${flagsBefore-}" '"$@"' "${flagsAfter-}" >> "$wrapper"
chmod +x "$wrapper"
}
addSuffix() {
suffix="$1"
shift
for name in "$@"; do
echo "$name$suffix"
done
}
filterExisting() {
for fn in "$@"; do
if test -e "$fn"; then
echo "$fn"
fi
done
}
# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
wrapProgram() { wrapProgramShell "$@"; }
wrapProgramShell() {
local prog="$1"
local hidden
assertExecutable "$prog"
hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
while [ -e "$hidden" ]; do
hidden="${hidden}_"
done
mv "$prog" "$hidden"
makeShellWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
}

View File

@@ -0,0 +1,12 @@
appendToVar prePhases moveBuildDir
moveBuildDir() {
mkdir -p $out/.build
cd $out/.build
}
appendToVar postPhases removeBuildDir
removeBuildDir() {
rm -rf $out/.build
}

View File

@@ -0,0 +1,27 @@
# This setup hook moves $out/{man,doc,info} to $out/share.
preFixupHooks+=(_moveToShare)
_moveToShare() {
if [ -n "$__structuredAttrs" ]; then
if [ -z "${forceShare-}" ]; then
forceShare=( man doc info )
fi
else
forceShare=( ${forceShare:-man doc info} )
fi
if [[ -z "$out" ]]; then return; fi
for d in "${forceShare[@]}"; do
if [ -d "$out/$d" ]; then
if [ -d "$out/share/$d" ]; then
echo "both $d/ and share/$d/ exist!"
else
echo "moving $out/$d to $out/share/$d"
mkdir -p $out/share
mv $out/$d $out/share/
fi
fi
done
}

View File

@@ -0,0 +1,22 @@
# This setup hook, for each output, moves everything in $output/lib64
# to $output/lib, and replaces $output/lib64 with a symlink to
# $output/lib. The rationale is that lib64 directories are unnecessary
# in Nix (since 32-bit and 64-bit builds of a package are in different
# store paths anyway).
# If the move would overwrite anything, it should fail on rmdir.
fixupOutputHooks+=(_moveLib64)
_moveLib64() {
if [ "${dontMoveLib64-}" = 1 ]; then return; fi
if [ ! -e "$prefix/lib64" -o -L "$prefix/lib64" ]; then return; fi
echo "moving $prefix/lib64/* to $prefix/lib"
mkdir -p $prefix/lib
shopt -s dotglob
for i in $prefix/lib64/*; do
mv --no-clobber "$i" $prefix/lib
done
shopt -u dotglob
rmdir $prefix/lib64
ln -s lib $prefix/lib64
}

View File

@@ -0,0 +1,19 @@
# This setup hook, for each output, moves everything in $output/sbin
# to $output/bin, and replaces $output/sbin with a symlink to
# $output/bin.
fixupOutputHooks+=(_moveSbin)
_moveSbin() {
if [ "${dontMoveSbin-}" = 1 ]; then return; fi
if [ ! -e "$prefix/sbin" -o -L "$prefix/sbin" ]; then return; fi
echo "moving $prefix/sbin/* to $prefix/bin"
mkdir -p $prefix/bin
shopt -s dotglob
for i in $prefix/sbin/*; do
mv "$i" $prefix/bin
done
shopt -u dotglob
rmdir $prefix/sbin
ln -s bin $prefix/sbin
}

View File

@@ -0,0 +1,25 @@
# shellcheck shell=bash
# This setup hook, for each output, moves everything in
# $output/lib/systemd/user to $output/share/systemd/user, and replaces
# $output/lib/systemd/user with a symlink to
# $output/share/systemd/user.
fixupOutputHooks+=(_moveSystemdUserUnits)
_moveSystemdUserUnits() {
if [ "${dontMoveSystemdUserUnits:-0}" = 1 ]; then return; fi
if [ ! -e "${prefix:?}/lib/systemd/user" ]; then return; fi
local source="$prefix/lib/systemd/user"
local target="$prefix/share/systemd/user"
echo "moving $source/* to $target"
mkdir -p "$target"
(
shopt -s dotglob
for i in "$source"/*; do
mv "$i" "$target"
done
)
rmdir "$source"
ln -s "$target" "$source"
}

View File

@@ -0,0 +1,214 @@
# The base package for automatic multiple-output splitting. Used in stdenv as well.
preConfigureHooks+=(_multioutConfig)
preFixupHooks+=(_multioutDocs)
preFixupHooks+=(_multioutDevs)
postFixupHooks+=(_multioutPropagateDev)
# _assignFirst varName otherVarNames*
#
# Set the value of the variable named $varName to the first of otherVarNames
# that refers to a non-empty variable name.
#
# If none of otherVarNames refers to a non-empty variable, the error message is
# specific to this function's use case, which is setting up the output variables.
_assignFirst() {
local varName="$1"
local _var
local REMOVE=REMOVE # slightly hacky - we allow REMOVE (i.e. not a variable name)
shift
for _var in "$@"; do
if [ -n "${!_var-}" ]; then eval "${varName}"="${_var}"; return; fi
done
echo
echo "error: _assignFirst: could not find a non-empty variable whose name to assign to ${varName}."
echo " The following variables were all unset or empty:"
echo " $*"
if [ -z "${out:-}" ]; then
echo ' If you do not want an "out" output in your derivation, make sure to define'
echo ' the other specific required outputs. This can be achieved by picking one'
echo " of the above as an output."
echo ' You do not have to remove "out" if you want to have a different default'
echo ' output, because the first output is taken as a default.'
echo
fi
return 1 # none found
}
# Same as _assignFirst, but only if "$1" = ""
_overrideFirst() {
if [ -z "${!1-}" ]; then
_assignFirst "$@"
fi
}
# Setup chains of sane default values with easy overridability.
# The variables are global to be usable anywhere during the build.
# Typical usage in package is defining outputBin = "dev";
_overrideFirst outputDev "dev" "out"
_overrideFirst outputBin "bin" "out"
_overrideFirst outputInclude "$outputDev"
# so-libs are often among the main things to keep, and so go to $out
_overrideFirst outputLib "lib" "out"
_overrideFirst outputDoc "doc" "out"
_overrideFirst outputDevdoc "devdoc" REMOVE # documentation for developers
# man and info pages are small and often useful to distribute with binaries
_overrideFirst outputMan "man" "$outputBin"
_overrideFirst outputDevman "devman" "devdoc" "$outputMan"
_overrideFirst outputInfo "info" "$outputBin"
# Add standard flags to put files into the desired outputs.
_multioutConfig() {
if [ "$(getAllOutputNames)" = "out" ] || [ -z "${setOutputFlags-1}" ]; then return; fi;
# try to detect share/doc/${shareDocName}
# Note: sadly, $configureScript detection comes later in configurePhase,
# and reordering would cause more trouble than worth.
if [ -z "${shareDocName:-}" ]; then
local confScript="${configureScript:-}"
if [ -z "$confScript" ] && [ -x ./configure ]; then
confScript=./configure
fi
if [ -f "$confScript" ]; then
local shareDocName="$(sed -n "s/^PACKAGE_TARNAME='\(.*\)'$/\1/p" < "$confScript")"
fi
# PACKAGE_TARNAME sometimes contains garbage.
if [ -z "$shareDocName" ] || echo "$shareDocName" | grep -q '[^a-zA-Z0-9_-]'; then
shareDocName="$(echo "$name" | sed 's/-[^a-zA-Z].*//')"
fi
fi
prependToVar configureFlags \
--bindir="${!outputBin}"/bin --sbindir="${!outputBin}"/sbin \
--includedir="${!outputInclude}"/include \
--mandir="${!outputMan}"/share/man --infodir="${!outputInfo}"/share/info \
--docdir="${!outputDoc}"/share/doc/"${shareDocName}" \
--libdir="${!outputLib}"/lib --libexecdir="${!outputLib}"/libexec \
--localedir="${!outputLib}"/share/locale
prependToVar installFlags \
pkgconfigdir="${!outputDev}"/lib/pkgconfig \
m4datadir="${!outputDev}"/share/aclocal aclocaldir="${!outputDev}"/share/aclocal
}
# Add rpath prefixes to library paths, and avoid stdenv doing it for $out.
_addRpathPrefix "${!outputLib}"
NIX_NO_SELF_RPATH=1
# Move subpaths that match pattern $1 from under any output/ to the $2 output/
# Beware: only globbing patterns are accepted, e.g.: * ? [abc]
# A special target "REMOVE" is allowed: moveToOutput foo REMOVE
moveToOutput() {
local patt="$1"
local dstOut="$2"
local output
for output in $(getAllOutputNames); do
if [ "${!output}" = "$dstOut" ]; then continue; fi
local srcPath
for srcPath in "${!output}"/$patt; do
# apply to existing files/dirs, *including* broken symlinks
if [ ! -e "$srcPath" ] && [ ! -L "$srcPath" ]; then continue; fi
if [ "$dstOut" = REMOVE ]; then
echo "Removing $srcPath"
rm -r "$srcPath"
else
local dstPath="$dstOut${srcPath#${!output}}"
echo "Moving $srcPath to $dstPath"
if [ -d "$dstPath" ] && [ -d "$srcPath" ]
then # attempt directory merge
# check the case of trying to move an empty directory
rmdir "$srcPath" --ignore-fail-on-non-empty
if [ -d "$srcPath" ]; then
mv -t "$dstPath" "$srcPath"/*
rmdir "$srcPath"
fi
else # usual move
mkdir -p "$(readlink -m "$dstPath/..")"
mv "$srcPath" "$dstPath"
fi
fi
# remove empty directories, printing iff at least one gets removed
local srcParent="$(readlink -m "$srcPath/..")"
if [ -n "$(find "$srcParent" -maxdepth 0 -type d -empty 2>/dev/null)" ]; then
echo "Removing empty $srcParent/ and (possibly) its parents"
rmdir -p --ignore-fail-on-non-empty "$srcParent" \
2> /dev/null || true # doesn't ignore failure for some reason
fi
done
done
}
# Move documentation to the desired outputs.
_multioutDocs() {
local REMOVE=REMOVE # slightly hacky - we expand ${!outputFoo}
moveToOutput share/info "${!outputInfo}"
moveToOutput share/doc "${!outputDoc}"
moveToOutput share/gtk-doc "${!outputDevdoc}"
moveToOutput share/devhelp/books "${!outputDevdoc}"
# the default outputMan is in $bin
moveToOutput share/man "${!outputMan}"
moveToOutput share/man/man3 "${!outputDevman}"
}
# Move development-only stuff to the desired outputs.
_multioutDevs() {
if [ "$(getAllOutputNames)" = "out" ] || [ -z "${moveToDev-1}" ]; then return; fi;
moveToOutput include "${!outputInclude}"
# these files are sometimes provided even without using the corresponding tool
moveToOutput lib/pkgconfig "${!outputDev}"
moveToOutput share/pkgconfig "${!outputDev}"
moveToOutput lib/cmake "${!outputDev}"
moveToOutput share/aclocal "${!outputDev}"
# don't move *.la, as libtool needs them in the directory of the library
for f in "${!outputDev}"/{lib,share}/pkgconfig/*.pc; do
echo "Patching '$f' includedir to output ${!outputInclude}"
sed -i "/^includedir=/s,=\${prefix},=${!outputInclude}," "$f"
done
}
# Make the "dev" propagate other outputs needed for development.
_multioutPropagateDev() {
if [ "$(getAllOutputNames)" = "out" ]; then return; fi;
local outputFirst
for outputFirst in $(getAllOutputNames); do
break
done
local propagaterOutput="$outputDev"
if [ -z "$propagaterOutput" ]; then
propagaterOutput="$outputFirst"
fi
# Default value: propagate binaries, includes and libraries
if [ -z "${propagatedBuildOutputs+1}" ]; then
local po_dirty="$outputBin $outputInclude $outputLib"
set +o pipefail
propagatedBuildOutputs=`echo "$po_dirty" \
| tr -s ' ' '\n' | grep -v -F "$propagaterOutput" \
| sort -u | tr '\n' ' ' `
set -o pipefail
fi
# The variable was explicitly set to empty or we resolved it so
if [ -z "$propagatedBuildOutputs" ]; then
return
fi
mkdir -p "${!propagaterOutput}"/nix-support
for output in $propagatedBuildOutputs; do
echo -n " ${!output}" >> "${!propagaterOutput}"/nix-support/propagated-build-inputs
done
}

View File

@@ -0,0 +1,89 @@
# shellcheck shell=bash
# Guard against double inclusion.
if (("${noBrokenSymlinksHookInstalled:-0}" > 0)); then
nixInfoLog "skipping because the hook has been propagated more than once"
return 0
fi
declare -ig noBrokenSymlinksHookInstalled=1
# symlinks are often created in postFixup
# don't use fixupOutputHooks, it is before postFixup
postFixupHooks+=(noBrokenSymlinksInAllOutputs)
# A symlink is "dangling" if it points to a non-existent target.
# A symlink is "reflexive" if it points to itself.
# A symlink is "unreadable" if the readlink command fails, e.g. because of permission errors.
# A symlink is considered "broken" if it is either dangling, reflexive or unreadable.
noBrokenSymlinks() {
local -r output="${1:?}"
local path
local pathParent
local symlinkTarget
local -i numDanglingSymlinks=0
local -i numReflexiveSymlinks=0
local -i numUnreadableSymlinks=0
# NOTE(@connorbaker): This hook doesn't check for cycles in symlinks.
if [[ ! -e $output ]]; then
nixWarnLog "skipping non-existent output $output"
return 0
fi
nixInfoLog "running on $output"
# NOTE: path is absolute because we're running `find` against an absolute path (`output`).
while IFS= read -r -d $'\0' path; do
pathParent="$(dirname "$path")"
if ! symlinkTarget="$(readlink "$path")"; then
nixErrorLog "the symlink $path is unreadable"
numUnreadableSymlinks+=1
continue
fi
# Canonicalize symlinkTarget to an absolute path.
if [[ $symlinkTarget == /* ]]; then
nixInfoLog "symlink $path points to absolute target $symlinkTarget"
else
nixInfoLog "symlink $path points to relative target $symlinkTarget"
# Use --no-symlinks to avoid dereferencing again and --canonicalize-missing to avoid existence
# checks at this step (which can lead to infinite recursion).
symlinkTarget="$(realpath --no-symlinks --canonicalize-missing "$pathParent/$symlinkTarget")"
fi
# use $TMPDIR like audit-tmpdir.sh
if [[ $symlinkTarget = "$TMPDIR"/* ]]; then
nixErrorLog "the symlink $path points to $TMPDIR directory: $symlinkTarget"
numDanglingSymlinks+=1
continue
fi
if [[ $symlinkTarget != "$NIX_STORE"/* ]]; then
nixInfoLog "symlink $path points outside the Nix store; ignoring"
continue
fi
if [[ $path == "$symlinkTarget" ]]; then
nixErrorLog "the symlink $path is reflexive"
numReflexiveSymlinks+=1
elif [[ ! -e $symlinkTarget ]]; then
nixErrorLog "the symlink $path points to a missing target: $symlinkTarget"
numDanglingSymlinks+=1
else
nixDebugLog "the symlink $path is irreflexive and points to a target which exists"
fi
done < <(find "$output" -type l -print0)
if ((numDanglingSymlinks > 0 || numReflexiveSymlinks > 0 || numUnreadableSymlinks > 0)); then
nixErrorLog "found $numDanglingSymlinks dangling symlinks, $numReflexiveSymlinks reflexive symlinks and $numUnreadableSymlinks unreadable symlinks"
exit 1
fi
return 0
}
noBrokenSymlinksInAllOutputs() {
if [[ -z ${dontCheckForBrokenSymlinks-} ]]; then
for output in $(getAllOutputNames); do
noBrokenSymlinks "${!output}"
done
fi
}

View File

@@ -0,0 +1,57 @@
{
lib,
callPackage,
makeSetupHook,
gnused,
}:
let
tests = import ./test { inherit callPackage; };
in
{
patchRcPathBash = makeSetupHook {
name = "patch-rc-path-bash";
meta = with lib; {
description = "Setup-hook to inject source-time PATH prefix to a Bash/Ksh/Zsh script";
maintainers = with maintainers; [ ShamrockLee ];
};
passthru.tests = {
inherit (tests) test-bash;
};
} ./patch-rc-path-bash.sh;
patchRcPathCsh = makeSetupHook {
name = "patch-rc-path-csh";
substitutions = {
sed = "${gnused}/bin/sed";
};
meta = with lib; {
description = "Setup-hook to inject source-time PATH prefix to a Csh script";
maintainers = with maintainers; [ ShamrockLee ];
};
passthru.tests = {
inherit (tests) test-csh;
};
} ./patch-rc-path-csh.sh;
patchRcPathFish = makeSetupHook {
name = "patch-rc-path-fish";
meta = with lib; {
description = "Setup-hook to inject source-time PATH prefix to a Fish script";
maintainers = with maintainers; [ ShamrockLee ];
};
passthru.tests = {
inherit (tests) test-fish;
};
} ./patch-rc-path-fish.sh;
patchRcPathPosix = makeSetupHook {
name = "patch-rc-path-posix";
substitutions = {
sed = "${gnused}/bin/sed";
};
meta = with lib; {
description = "Setup-hook to inject source-time PATH prefix to a POSIX shell script";
maintainers = with maintainers; [ ShamrockLee ];
};
passthru.tests = {
inherit (tests) test-posix;
};
} ./patch-rc-path-posix.sh;
}

View File

@@ -0,0 +1,50 @@
patchRcPathBash(){
local FILE_TO_PATCH="$1"
local SOURCETIME_PATH="$2"
local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to add to PATH the source-time utilities for Nixpkgs packaging
if [[ -n "\${NIXPKGS_SOURCETIME_PATH-}" ]]; then
NIXPKGS_SOURCETIME_PATH_OLD="\$NIXPKGS_SOURCETIME_PATH;\${NIXPKGS_SOURCETIME_PATH_OLD-}"
fi
NIXPKGS_SOURCETIME_PATH="$SOURCETIME_PATH"
if [[ -n "\$PATH" ]]; then
PATH="\$NIXPKGS_SOURCETIME_PATH:\$PATH"
else
PATH="\$NIXPKGS_SOURCETIME_PATH"
fi
export PATH
# End of lines to add to PATH source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
if [[ -n "\${PATH-}" ]]; then
# Remove the inserted section
PATH="\${PATH/\$NIXPKGS_SOURCETIME_PATH}"
# Remove the duplicated colons
PATH="\${PATH//::/:}"
# Remove the prefixing colon
if [[ -n "\$PATH" && "\${PATH:0:1}" == ":" ]]; then
PATH="\${PATH:1}"
fi
# Remove the trailing colon
if [[ -n "\$PATH" && "\${PATH:\${#PATH}-1}" == ":" ]]; then
PATH="\${PATH::}"
fi
export PATH
fi
if [[ -n "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]]; then
IFS="" read -r -d ";" NIXPKGS_SOURCETIME_PATH <<< "\$NIXPKGS_SOURCETIME_PATH_OLD"
NIXPKGS_SOURCETIME_PATH_OLD="\${NIXPKGS_SOURCETIME_PATH_OLD:\${#NIXPKGS_SOURCETIME_PATH}+1}"
else
unset NIXPKGS_SOURCETIME_PATH
fi
if [[ -z "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]]; then
unset NIXPKGS_SOURCETIME_PATH_OLD
fi
# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
rm "$FILE_TO_WORK_ON"
}

View File

@@ -0,0 +1,57 @@
patchRcPathCsh(){
local FILE_TO_PATCH="$1"
local SOURCETIME_PATH="$2"
local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to add to PATH the source-time utilities for Nixpkgs packaging
if (\$?NIXPKGS_SOURCETIME_PATH) then
if ("\$NIXPKGS_SOURCETIME_PATH" != "") then
if (\$?NIXPKGS_SOURCETIME_PATH_OLD) then
if ("\$NIXPKGS_SOURCETIME_PATH_OLD" != "")
set NIXPKGS_SOURCETIME_PATH_OLD = (\$NIXPKGS_SOURCETIME_PATH \$NIXPKGS_SOURCETIME_PATH_OLD)
else
set NIXPKGS_SOURCETIME_PATH_OLD = \$NIXPKGS_SOURCETIME_PATH
endif
else
set NIXPKGS_SOURCETIME_PATH_OLD = \$NIXPKGS_SOURCETIME_PATH
endif
endif
endif
set NIXPKGS_SOURCETIME_PATH = "$SOURCETIME_PATH"
if (! \$?PATH) then
setenv PATH ""
endif
if ("\$PATH" != "") then
setenv PATH "\${NIXPKGS_SOURCETIME_PATH}:\$PATH"
else
setenv PATH "\$NIXPKGS_SOURCETIME_PATH"
endif
# End of lines to add to PATH source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
if (\$?PATH) then
if ("\$PATH" != "") then
# Remove the inserted section, the duplicated colons, and the leading and trailing colon
setenv PATH \`echo "\$PATH" | @sed@ "s#\${NIXPKGS_SOURCETIME_PATH}##" | @sed@ "s#::#:#g" | @sed@ "s#^:##" | @sed@ 's#:\$##'\`
endif
endif
if (\$?NIXPKGS_SOURCETIME_PATH_OLD) then
if ("\$NIXPKGS_SOURCETIME_PATH_OLD" != "") then
set NIXPKGS_SOURCETIME_PATH = \$NIXPKGS_SOURCETIME_PATH_OLD[1]
set NIXPKGS_SOURCETIME_PATH_OLD = \$NIXPKGS_SOURCETIME_PATH_OLD[2-]
else
unset NIXPKGS_SOURCETIME_PATH
endif
if (NIXPKGS_SOURCETIME_PATH_OLD == "") then
unset NIXPKGS_SOURCETIME_PATH_OLD
endif
else
unset NIXPKGS_SOURCETIME_PATH
endif
# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
rm "$FILE_TO_WORK_ON"
}

View File

@@ -0,0 +1,50 @@
patchRcPathFish(){
local FILE_TO_PATCH="$1"
local SOURCETIME_PATH="$2"
local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to add to PATH the source-time utilities for Nixpkgs packaging
if set -q NIXPKGS_SOURCETIME_PATH && test (count \$NIXPKGS_SOURCETIME_PATH) -gt 0
set --unpath NIXPKGS_SOURCETIME_PATH_OLD "\$NIXPKGS_SOURCETIME_PATH" \$NIXPKGS_SOURCETIME_PATH_OLD
end
set --path NIXPKGS_SOURCETIME_PATH $SOURCETIME_PATH
set -g --path PATH \$NIXPKGS_SOURCETIME_PATH \$PATH
# End of lines to add to PATH source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
if set -q PATH && test "\$PATH" != "" && test (count \$PATH) -ge (count \$NIXPKGS_SOURCETIME_PATH)
# Remove the inserted section
for i in (seq 0 (math (count \$PATH) - (count \$NIXPKGS_SOURCETIME_PATH)))
for j in (seq 1 (count \$NIXPKGS_SOURCETIME_PATH))
if test \$PATH[(math \$i + \$j)] != \$NIXPKGS_SOURCETIME_PATH[\$j]
set i -1
break
end
end
if test \$i -eq -1
continue
end
if test \$i -eq 0
set -g --path PATH \$PATH[(math (count \$NIXPKGS_SOURCETIME_PATH) + 1)..]
else
set -g --path PATH \$PATH[..\$i] \$PATH[(math (count \$NIXPKGS_SOURCETIME_PATH) + 1 + \$i)..]
end
break
end
end
if set -q NIXPKGS_SOURCETIME_PATH_OLD && test (count \$NIXPKGS_SOURCETIME_PATH_OLD) -gt 0
set --path NIXPKGS_SOURCETIME_PATH \$NIXPKGS_SOURCETIME_PATH_OLD[1]
set --unpath NIXPKGS_SOURCETIME_PATH_OLD \$NIXPKGS_SOURCETIME_PATH_OLD[2..]
else
set -e NIXPKGS_SOURCETIME_PATH
end
if set -q NIXPKGS_SOURCETIME_PATH_OLD && test (count \$NIXPKGS_SOURCETIME_PATH_OLD) -eq 0
set -e NIXPKGS_SOURCETIME_PATH_OLD
end
# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
rm "$FILE_TO_WORK_ON"
}

View File

@@ -0,0 +1,39 @@
patchRcPathPosix(){
local FILE_TO_PATCH="$1"
local SOURCETIME_PATH="$2"
local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to add to PATH the source-time utilities for Nixpkgs packaging
if [ -n "\${NIXPKGS_SOURCETIME_PATH-}" ]; then
NIXPKGS_SOURCETIME_PATH_OLD="\$NIXPKGS_SOURCETIME_PATH;\${NIXPKGS_SOURCETIME_PATH_OLD-}"
fi
NIXPKGS_SOURCETIME_PATH="$SOURCETIME_PATH"
if [ -n "\$PATH" ]; then
PATH="\$NIXPKGS_SOURCETIME_PATH:\$PATH";
else
PATH="\$NIXPKGS_SOURCETIME_PATH"
fi
export PATH
# End of lines to add to PATH source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
cat <<EOF >> "$FILE_TO_WORK_ON"
# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
if [ -n "\${PATH-}" ]; then
PATH="\$(echo "\$PATH" | @sed@ "s#\$NIXPKGS_SOURCETIME_PATH##" | @sed@ "s#::#:#g" | @sed@ "s#^:##" | @sed@ "s#:\\\$##")"
export PATH
fi
if [ -n "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]; then
NIXPKGS_SOURCETIME_PATH="\$(echo "\$NIXPKGS_SOURCETIME_PATH_OLD" | @sed@ "s#\\([^;]\\);.*#\\1#")"
NIXPKGS_SOURCETIME_PATH_OLD="\$(echo "\$NIXPKGS_SOURCETIME_PATH_OLD" | @sed@ "s#[^;];\\(.*\\)#\\1#")"
else
unset NIXPKGS_SOURCETIME_PATH
fi
if [ -z "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]; then
unset NIXPKGS_SOURCETIME_PATH_OLD
fi
# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
EOF
cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
rm "$FILE_TO_WORK_ON"
}

View File

@@ -0,0 +1,432 @@
{ callPackage }:
{
test-bash = callPackage (
{
lib,
runCommandLocal,
bash,
hello,
ksh,
patchRcPathBash,
shellcheck,
zsh,
}:
runCommandLocal "patch-rc-path-bash-test"
{
nativeBuildInputs = [
bash
ksh
patchRcPathBash
shellcheck
zsh
];
meta = {
description = "Package test of patchActivateBash";
inherit (patchRcPathBash.meta) maintainers;
};
}
''
set -eu -o pipefail
# Check the setup hook script
echo "Running shellcheck against ${./test-sourcing-bash}"
shellcheck -s bash --exclude SC1090 ${./test-sourcing-bash}
shellcheck -s ksh --exclude SC1090 ${./test-sourcing-bash}
# Test patching a blank file
echo > blank.bash
echo "Generating blank_patched.bash from blank.bash"
cp blank.bash blank_patched.bash
patchRcPathBash blank_patched.bash "$PWD/delta:$PWD/foxtrot"
echo "Running shellcheck against blank_patched.bash"
shellcheck -s bash blank_patched.bash
shellcheck -s ksh blank_patched.bash
echo "Testing in Bash if blank.bash and blank_patched.bash modifies PATH the same way"
bash ${./test-sourcing-bash} ./blank.bash ./blank_patched.bash
echo "Testing in Ksh if blank.bash and blank_patched.bash modifies PATH the same way"
ksh ${./test-sourcing-bash} "$PWD/blank.bash" "$PWD/blank_patched.bash"
echo "Testing in Zsh if blank.bash and blank_patched.bash modifies PATH the same way"
zsh ${./test-sourcing-bash} ./blank.bash ./blank_patched.bash
# Test patching silent_hello
echo "hello > /dev/null" > silent_hello.bash
echo "Generating silent_hello_patched.bash from silent_hello.bash"
cp silent_hello.bash silent_hello_patched.bash
patchRcPathBash silent_hello_patched.bash "${hello}/bin"
echo "Running shellcheck against silent_hello_patched.bash"
shellcheck -s bash silent_hello_patched.bash
echo "Testing in Bash if silent_hello_patched.bash get sourced without error"
bash -eu -o pipefail -c ". ./silent_hello_patched.bash"
echo "Testing in Ksh if silent_hello_patched.bash get sourced without error"
ksh -eu -o pipefail -c ". ./silent_hello_patched.bash"
echo "Testing in Zsh if silent_hello_patched.bash get sourced without error"
zsh -eu -o pipefail -c ". ./silent_hello_patched.bash"
# Check the sample source
echo "Running shellcheck against sample_source.bash"
shellcheck -s bash ${./sample_source.bash}
shellcheck -s ksh ${./sample_source.bash}
# Test patching the sample source
cp ${./sample_source.bash} sample_source_patched.bash
chmod u+w sample_source_patched.bash
echo "Generating sample_source_patched.bash from ./sample_source.bash"
patchRcPathBash sample_source_patched.bash "$PWD/delta:$PWD/foxtrot"
echo "Running shellcheck against sample_source_patched.bash"
shellcheck -s bash sample_source_patched.bash
echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
bash ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
echo "Testing in Ksh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
ksh ${./test-sourcing-bash} ${./sample_source.bash} "$PWD/sample_source_patched.bash"
echo "Testing in Zsh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
zsh ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
# Test double-patching the sample source
echo "Patching again sample_source_patched.bash"
patchRcPathBash sample_source_patched.bash "$PWD/foxtrot:$PWD/golf"
echo "Running shellcheck against sample_source_patched.bash"
shellcheck -s bash sample_source_patched.bash
shellcheck -s ksh sample_source_patched.bash
echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
bash ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
echo "Testing in Ksh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
ksh ${./test-sourcing-bash} ${./sample_source.bash} "$PWD/sample_source_patched.bash"
echo "Testing in Zsh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
zsh ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
# Create a dummy output
touch "$out"
''
) { };
test-csh = callPackage (
{
lib,
runCommandLocal,
gnused,
hello,
patchRcPathCsh,
tcsh,
}:
runCommandLocal "patch-rc-path-csh-test"
{
nativeBuildInputs = [
patchRcPathCsh
tcsh
];
meta = {
description = "Package test of patchActivateCsh";
inherit (patchRcPathCsh.meta) maintainers;
};
}
''
set -eu -o pipefail
# Test patching a blank file
echo > blank.csh
echo "Generating blank_patched.csh from blank.csh"
cp blank.csh blank_patched.csh
patchRcPathCsh blank_patched.csh "$PWD/delta:$PWD/foxtrot"
echo "Testing in Csh if blank.csh and blank_patched.csh modifies PATH the same way"
tcsh -e ${./test-sourcing-csh} blank.csh blank_patched.csh
# Test patching silent_hello file
echo "hello > /dev/null" > silent_hello.csh
echo "Generating silent_hello_patched.csh from silent_hello.csh"
cp silent_hello.csh silent_hello_patched.csh
patchRcPathCsh silent_hello_patched.csh "${hello}/bin"
echo "Testing in Csh if silent_hello_patched.csh get sourced without errer"
tcsh -e -c "source silent_hello_patched.csh"
# Generate the sample source
substitute ${./sample_source.csh.in} sample_source.csh --replace @sed@ ${gnused}/bin/sed
chmod u+rw sample_source.csh
# Test patching the sample source
echo "Generating sample_source_patched.csh from sample_source.csh"
cp sample_source.csh sample_source_patched.csh
chmod u+w sample_source_patched.csh
patchRcPathCsh sample_source_patched.csh "$PWD/delta:$PWD/foxtrot"
echo "Testing in Csh if sample_source.csh and sample_source_patched.csh modifies PATH the same way"
tcsh -e ${./test-sourcing-csh} sample_source.csh sample_source_patched.csh
# Test double-patching the sample source
echo "Patching again sample_source_patched.csh from sample_source.csh"
patchRcPathCsh sample_source_patched.csh "$PWD/foxtrot:$PWD/golf"
echo "Testing in Csh if sample_source.csh and sample_source_patched.csh modifies PATH the same way"
tcsh -e ${./test-sourcing-csh} sample_source.csh sample_source_patched.csh
# Create a dummy output
touch "$out"
''
) { };
test-fish = callPackage (
{
lib,
runCommandLocal,
fish,
hello,
patchRcPathFish,
}:
runCommandLocal "patch-rc-path-fish-test"
{
nativeBuildInputs = [
fish
patchRcPathFish
];
meta = {
description = "Package test of patchActivateFish";
inherit (patchRcPathFish.meta) maintainers;
};
}
''
set -eu -o pipefail
# Test patching a blank file
echo > blank.fish
echo "Generating blank_patched.fish from blank.fish"
cp blank.fish blank_patched.fish
patchRcPathFish blank_patched.fish "$PWD/delta:$PWD/foxtrot"
echo "Testing in Fish if blank.fish and blank_patched.fish modifies PATH the same way"
HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
HOME="$HOME_TEMP" fish ${./test-sourcing-fish} blank.fish blank_patched.fish
rm -r "$HOME_TEMP"
# Test patching silent_hello file
echo "hello > /dev/null" > silent_hello.fish
echo "Generating silent_hello_patched.fish from silent_hello.fish"
cp silent_hello.fish silent_hello_patched.fish
patchRcPathFish silent_hello_patched.fish "${hello}/bin"
echo "Testing in Fish if silent_hello_patched.fish get sourced without error"
HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
HOME="$HOME_TEMP" fish -c "source silent_hello_patched.fish"
rm -r "$HOME_TEMP"
# Test patching the sample source
cp ${./sample_source.fish} sample_source_patched.fish
chmod u+w sample_source_patched.fish
echo "Generating sample_source_patched.fish from ${./sample_source.fish}"
patchRcPathFish sample_source_patched.fish "$PWD/delta:$PWD/foxtrot"
echo "Testing in Fish if sample_source.fish and sample_source_patched.fish modifies PATH the same way"
HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
HOME="$HOME_TEMP" fish ${./test-sourcing-fish} ${./sample_source.fish} sample_source_patched.fish
rm -r "$HOME_TEMP"
# Test double-patching the sample source
echo "Patching again sample_source_patched.fish from ${./sample_source.fish}"
patchRcPathFish sample_source_patched.fish "$PWD/foxtrot:$PWD/golf"
echo "Testing in Fish if sample_source.fish and sample_source_patched.fish modifies PATH the same way"
HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
HOME="$HOME_TEMP" fish ${./test-sourcing-fish} ${./sample_source.fish} sample_source_patched.fish
rm -r "$HOME_TEMP"
# Create a dummy output
touch "$out"
''
) { };
test-posix = callPackage (
{
lib,
runCommandLocal,
bash,
dash,
gnused,
hello,
ksh,
patchRcPathPosix,
shellcheck,
}:
runCommandLocal "patch-rc-path-posix-test"
{
nativeBuildInputs = [
bash
dash
ksh
patchRcPathPosix
shellcheck
];
meta = {
description = "Package test of patchActivatePosix";
inherit (patchRcPathPosix.meta) maintainers;
};
}
''
set -eu -o pipefail
# Check the setup hook script
echo "Running shellcheck against ${./test-sourcing-posix}"
shellcheck -s sh --exclude SC1090 ${./test-sourcing-posix}
shellcheck -s dash --exclude SC1090 ${./test-sourcing-posix}
# Test patching a blank file
echo > blank.sh
echo "Generating blank_patched.sh from blank.sh"
cp blank.sh blank_patched.sh
patchRcPathPosix blank_patched.sh "$PWD/delta:$PWD/foxtrot"
echo "Running shellcheck against blank_patched.sh"
shellcheck -s sh blank_patched.sh
shellcheck -s dash blank_patched.sh
echo "Testing in Bash if blank.sh and blank_patched.sh modifies PATH the same way"
bash --posix ${./test-sourcing-posix} ./blank.sh ./blank_patched.sh
echo "Testing in Dash if blank.sh and blank_patched.sh modifies PATH the same way"
dash ${./test-sourcing-posix} ./blank.sh ./blank_patched.sh
echo "Testing in Ksh if ./blank.sh and ./blank_patched.sh modifies PATH the same way"
ksh ${./test-sourcing-posix} "$PWD/blank.sh" "$PWD/blank_patched.sh"
# Test patching silent_hello file
echo "hello > /dev/null" > silent_hello.sh
echo "Generating silent_hello_patched.sh from silent_hello.sh"
cp silent_hello.sh silent_hello_patched.sh
patchRcPathPosix silent_hello_patched.sh "${hello}/bin"
echo "Running shellcheck against silent_hello_patched.sh"
shellcheck -s sh silent_hello_patched.sh
shellcheck -s dash silent_hello_patched.sh
echo "Testing in Bash if silent_hello_patched.sh get sourced without error"
bash --posix -eu -c ". ./silent_hello_patched.sh"
echo "Testing in Dash if silent_hello_patched.sh get sourced without error"
dash -eu -c ". ./silent_hello_patched.sh"
echo "Testing in Ksh if silent_hello_patched.sh get sourced without error"
ksh -eu -c ". $PWD/silent_hello_patched.sh"
# Generate the sample source "$PWD/delta:$PWD/foxtrot" "$PWD/delta:$PWD/foxtrot"
substitute ${./sample_source.sh.in} sample_source.sh --replace @sed@ ${gnused}/bin/sed
chmod u+rw sample_source.sh
# Check the sample source
echo "Running shellcheck against sample_source.sh"
shellcheck -s sh sample_source.sh
shellcheck -s dash sample_source.sh
# Test patching the sample source
echo "Generating sample_source_patched.sh from sample_source.sh"
cp sample_source.sh sample_source_patched.sh
chmod u+w sample_source_patched.sh
patchRcPathPosix sample_source_patched.sh "$PWD/delta:$PWD/foxtrot"
echo "Running shellcheck against sample_source_patched.sh"
shellcheck -s sh sample_source_patched.sh
shellcheck -s dash sample_source_patched.sh
echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
bash --posix ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
echo "Testing in Dash if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
dash ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
echo "Testing in Ksh if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
ksh ${./test-sourcing-posix} "$PWD/sample_source.sh" "$PWD/sample_source_patched.sh"
# Test double-patching the sample source
echo "Patching again sample_source_patched.sh"
patchRcPathPosix sample_source_patched.sh "$PWD/foxtrot:$PWD/golf"
echo "Running shellcheck against sample_source_patched.sh"
shellcheck -s sh sample_source_patched.sh
shellcheck -s dash sample_source_patched.sh
echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
bash --posix ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
echo "Testing in Dash if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
dash ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
echo "Testing in Ksh if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
ksh ${./test-sourcing-posix} "$PWD/sample_source.sh" "$PWD/sample_source_patched.sh"
# Create a dummy output
touch "$out"
''
) { };
}

View File

@@ -0,0 +1,2 @@
PATH="$PWD/charlie:${PATH/:$PWD\/bravo}"
export PATH

View File

@@ -0,0 +1 @@
setenv PATH $PWD/charlie:`echo "$PATH" | @sed@ "s#:$PWD/bravo##"`

View File

@@ -0,0 +1,9 @@
begin
for p in $PATH
if test $p != "$PWD/bravo"
set TEMPORARY_PATH $TEMPORARY_PATH $p
end
end
set -g PATH $TEMPORARY_PATH
end
set PATH "$PWD/charlie" $PATH

View File

@@ -0,0 +1,2 @@
PATH="$PWD/charlie:$(echo "$PATH" | @sed@ "s#:$PWD/bravo##")"
export PATH

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -eu -o pipefail
UNPATCHED_SOURCE_FILE="$1"
PATCHED_SOURCE_FILE="$2"
ORIG_PATH="$PWD/alfa:$PWD/bravo"
RESULT_PATH_FROM_UNPATCHED="$(
PATH="$ORIG_PATH"; export PATH
. "$UNPATCHED_SOURCE_FILE"
echo "$PATH"
)"
RESULT_PATH_FROM_PATCHED="$(
PATH="$ORIG_PATH"; export PATH
. "$PATCHED_SOURCE_FILE"
echo "$PATH"
)"
if [[ "$RESULT_PATH_FROM_UNPATCHED" != "$RESULT_PATH_FROM_PATCHED" ]]; then
echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" >&2
exit 1
fi

View File

@@ -0,0 +1,13 @@
#/usr/bin/env tcsh
set UNPATCHED_SOURCE_FILE = "$1"
set PATCHED_SOURCE_FILE = "$2"
set ORIG_PATH = "${PWD}/alfa:${PWD}/bravo"
set RESULT_PATH_FROM_UNPATCHED = `setenv PATH "$ORIG_PATH"; source $UNPATCHED_SOURCE_FILE; echo $PATH`
set RESULT_PATH_FROM_PATCHED = `setenv PATH "$ORIG_PATH"; source $PATCHED_SOURCE_FILE; echo $PATH`
if ($RESULT_PATH_FROM_UNPATCHED != $RESULT_PATH_FROM_PATCHED) then
echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" > /dev/stderr
exit 1
endif

View File

@@ -0,0 +1,13 @@
#/usr/bin/env fish
set UNPATCHED_SOURCE_FILE $argv[1]
set PATCHED_SOURCE_FILE $argv[2]
set ORIG_PATH "$PWD/alfa:$PWD/bravo"
set RESULT_PATH_FROM_UNPATCHED (fish -c "set -g PATH \"$ORIG_PATH\"; source $UNPATCHED_SOURCE_FILE; echo \"\$PATH\"")
set RESULT_PATH_FROM_PATCHED (fish -c "set -g PATH \"$ORIG_PATH\"; source $PATCHED_SOURCE_FILE; echo \"\$PATH\"")
if test "$RESULT_PATH_FROM_UNPATCHED" != "$RESULT_PATH_FROM_PATCHED"
echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" >&2
exit 1
end

View File

@@ -0,0 +1,21 @@
#!/bin/sh
set -eu
UNPATCHED_SOURCE_FILE="$1"
PATCHED_SOURCE_FILE="$2"
ORIG_PATH="$PWD/alfa:$PWD/bravo"
RESULT_PATH_FROM_UNPATCHED="$(
PATH="$ORIG_PATH"; export PATH
. "$UNPATCHED_SOURCE_FILE"
echo "$PATH"
)"
RESULT_PATH_FROM_PATCHED="$(
PATH="$ORIG_PATH"; export PATH
. "$PATCHED_SOURCE_FILE"
echo "$PATH"
)"
if [ "$RESULT_PATH_FROM_UNPATCHED" != "$RESULT_PATH_FROM_PATCHED" ]; then
echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" > /dev/stderr
exit 1
fi

View File

@@ -0,0 +1,168 @@
# This setup hook causes the fixup phase to rewrite all script
# interpreter file names (`#! /path') to paths found in $PATH. E.g.,
# /bin/sh will be rewritten to /nix/store/<hash>-some-bash/bin/sh.
# /usr/bin/env gets special treatment so that ".../bin/env python" is
# rewritten to /nix/store/<hash>/bin/python. Interpreters that are
# already in the store are left untouched.
# A script file must be marked as executable, otherwise it will not be
# considered.
fixupOutputHooks+=(patchShebangsAuto)
# Run patch shebangs on a directory or file.
# Can take multiple paths as arguments.
# patchShebangs [--build | --host | --update] [--] PATH...
# Flags:
# --build : Lookup commands available at build-time
# --host : Lookup commands available at runtime
# --update : Update shebang paths that are in Nix store
# Example use cases,
# $ patchShebangs --host /nix/store/...-hello-1.0/bin
# $ patchShebangs --build configure
patchShebangs() {
local pathName
local update=false
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
pathName=HOST_PATH
shift
;;
--build)
pathName=PATH
shift
;;
--update)
update=true
shift
;;
--)
shift
break
;;
-*|--*)
echo "Unknown option $1 supplied to patchShebangs" >&2
return 1
;;
*)
break
;;
esac
done
echo "patching script interpreter paths in $@"
local f
local oldPath
local newPath
local arg0
local args
local oldInterpreterLine
local newInterpreterLine
if [[ $# -eq 0 ]]; then
echo "No arguments supplied to patchShebangs" >&2
return 0
fi
local f
while IFS= read -r -d $'\0' f; do
isScript "$f" || continue
# read exits unclean if the shebang does not end with a newline, but still assigns the variable.
# So if read returns errno != 0, we check if the assigned variable is non-empty and continue.
read -r oldInterpreterLine < "$f" || [ "$oldInterpreterLine" ]
read -r oldPath arg0 args <<< "${oldInterpreterLine:2}"
if [[ -z "${pathName:-}" ]]; then
if [[ -n $strictDeps && $f == "$NIX_STORE"* ]]; then
pathName=HOST_PATH
else
pathName=PATH
fi
fi
if [[ "$oldPath" == *"/bin/env" ]]; then
if [[ $arg0 == "-S" ]]; then
arg0=${args%% *}
[[ "$args" == *" "* ]] && args=${args#* } || args=
newPath="$(PATH="${!pathName}" type -P "env" || true)"
args="-S $(PATH="${!pathName}" type -P "$arg0" || true) $args"
# Check for unsupported 'env' functionality:
# - options: something starting with a '-' besides '-S'
# - environment variables: foo=bar
elif [[ $arg0 == "-"* || $arg0 == *"="* ]]; then
echo "$f: unsupported interpreter directive \"$oldInterpreterLine\" (set dontPatchShebangs=1 and handle shebang patching yourself)" >&2
exit 1
else
newPath="$(PATH="${!pathName}" type -P "$arg0" || true)"
fi
else
if [[ -z $oldPath ]]; then
# If no interpreter is specified linux will use /bin/sh. Set
# oldpath="/bin/sh" so that we get /nix/store/.../sh.
oldPath="/bin/sh"
fi
newPath="$(PATH="${!pathName}" type -P "$(basename "$oldPath")" || true)"
args="$arg0 $args"
fi
# Strip trailing whitespace introduced when no arguments are present
newInterpreterLine="$newPath $args"
newInterpreterLine=${newInterpreterLine%${newInterpreterLine##*[![:space:]]}}
if [[ -n "$oldPath" && ( "$update" == true || "${oldPath:0:${#NIX_STORE}}" != "$NIX_STORE" ) ]]; then
if [[ -n "$newPath" && "$newPath" != "$oldPath" ]]; then
echo "$f: interpreter directive changed from \"$oldInterpreterLine\" to \"$newInterpreterLine\""
# escape the escape chars so that sed doesn't interpret them
escapedInterpreterLine=${newInterpreterLine//\\/\\\\}
# Preserve times, see: https://github.com/NixOS/nixpkgs/pull/33281
timestamp=$(stat --printf "%y" "$f")
# Manually create temporary file instead of using sed -i
# (sed -i on $out/x creates tmpfile /nix/store/x which fails on macos + sandbox)
tmpFile=$(mktemp -t patchShebangs.XXXXXXXXXX)
sed -e "1 s|.*|#\!$escapedInterpreterLine|" "$f" > "$tmpFile"
# Make original file writable if it is read-only
local restoreReadOnly
if [[ ! -w "$f" ]]; then
chmod +w "$f"
restoreReadOnly=true
fi
# Replace the original file's content with the patched content
# (preserving permissions)
cat "$tmpFile" > "$f"
rm "$tmpFile"
if [[ -n "${restoreReadOnly:-}" ]]; then
chmod -w "$f"
fi
touch --date "$timestamp" "$f"
fi
fi
done < <(find "$@" -type f -perm -0100 -print0)
}
patchShebangsAuto () {
if [[ -z "${dontPatchShebangs-}" && -e "$prefix" ]]; then
# Dev output will end up being run on the build platform. An
# example case of this is sdl2-config. Otherwise, we can just
# use the runtime path (--host).
if [[ "$output" != out && "$output" = "$outputDev" ]]; then
patchShebangs --build "$prefix"
else
patchShebangs --host "$prefix"
fi
fi
}

View File

@@ -0,0 +1,22 @@
# Clear dependency_libs in libtool files for shared libraries.
# Shared libraries already encode their dependencies with locations. .la
# files do not always encode those locations, and sometimes encode the
# locations in the wrong Nix output. .la files are not needed for shared
# libraries, but without dependency_libs they do not hurt either.
fixupOutputHooks+=(_pruneLibtoolFiles)
_pruneLibtoolFiles() {
if [ "${dontPruneLibtoolFiles-}" ] || [ ! -e "$prefix" ]; then
return
fi
# Libtool uses "dlname" and "library_names" fields for shared libraries and
# the "old_library" field for static libraries. We are processing only
# those .la files that do not describe static libraries.
find "$prefix" -type f -name '*.la' \
-exec grep -q '^# Generated by .*libtool' {} \; \
-exec grep -q "^old_library=''" {} \; \
-exec sed -i {} -e "/^dependency_libs='[^']/ c dependency_libs='' #pruned" \;
}

View File

@@ -0,0 +1,11 @@
# Use the last part of the out path as hash input for the build.
# This should ensure that it is deterministic across rebuilds of the same
# derivation and not easily collide with other builds.
# We also truncate the hash so that it cannot cause reference cycles.
NIX_CFLAGS_COMPILE="${NIX_CFLAGS_COMPILE:-} -frandom-seed=$(
randSeed=${NIX_OUTPATH_USED_AS_RANDOM_SEED:-$out}
outbase="${randSeed##*/}"
randomseed="${outbase:0:10}"
echo $randomseed
)"
export NIX_CFLAGS_COMPILE

View File

@@ -0,0 +1,71 @@
# Since the same derivation can be depended on in multiple ways, we need to
# accumulate *each* role (i.e. host and target platforms relative the depending
# derivation) in which the derivation is used.
#
# The role is intended to be used as part of other variables names like
# - $NIX_SOMETHING${role_post}
function getRole() {
case $1 in
-1)
role_post='_FOR_BUILD'
;;
0)
role_post=''
;;
1)
role_post='_FOR_TARGET'
;;
*)
echo "@name@: used as improper sort of dependency" >&2
return 1
;;
esac
}
# `hostOffset` describes how the host platform of the package is slid relative
# to the depending package. `targetOffset` likewise describes the target
# platform of the package. Both are brought into scope of the setup hook defined
# for dependency whose setup hook is being processed relative to the package
# being built.
function getHostRole() {
getRole "$hostOffset"
}
function getTargetRole() {
getRole "$targetOffset"
}
# `depHostOffset` describes how the host platform of the dependencies are slid
# relative to the depending package. `depTargetOffset` likewise describes the
# target platform of dependenices. Both are brought into scope of the
# environment hook defined for the dependency being applied relative to the
# package being built.
function getHostRoleEnvHook() {
getRole "$depHostOffset"
}
function getTargetRoleEnvHook() {
getRole "$depTargetOffset"
}
# This variant is intended specifically for code-producing tool wrapper scripts
# `NIX_@wrapperName@_TARGET_*_@suffixSalt@` tracks this (needs to be an exported
# env var so can't use fancier data structures).
function getTargetRoleWrapper() {
case $targetOffset in
-1)
export NIX_@wrapperName@_TARGET_BUILD_@suffixSalt@=1
;;
0)
export NIX_@wrapperName@_TARGET_HOST_@suffixSalt@=1
;;
1)
export NIX_@wrapperName@_TARGET_TARGET_@suffixSalt@=1
;;
*)
echo "@name@: used as improper sort of dependency" >&2
return 1
;;
esac
}

View File

@@ -0,0 +1,120 @@
export NIX_SET_BUILD_ID=1
export NIX_LDFLAGS+=" --compress-debug-sections=zlib"
export NIX_CFLAGS_COMPILE+=" -ggdb -Wa,--compress-debug-sections"
export NIX_RUSTFLAGS+=" -g -C strip=none"
cksumAlgo=sha256
fixupOutputHooks+=(_separateDebugInfo)
postUnpackHooks+=(_recordPristineSourceHashes)
_recordPristineSourceHashes() {
# shellcheck disable=2154
[ -e "$sourceRoot" ] || return 0
local checksumFileName=__nix_source_checksums
echo "separate-debug-info: recording checksum of source files for debug support..."
find "$sourceRoot" -type f -exec cksum -a "$cksumAlgo" '{}' \+ > "$checksumFileName"
recordedSourceChecksumsFileName="$(readlink -f "$checksumFileName")"
}
_separateDebugInfo() {
# shellcheck disable=2154
[ -e "$prefix" ] || return 0
local debugOutput="${debug:-$out}"
if [ "$prefix" = "$debugOutput" ]; then return 0; fi
# in case there is nothing to strip, don't fail the build
mkdir -p "$debugOutput"
local dst="$debugOutput/lib/debug/.build-id"
local source
local sourceOverlay
# shellcheck disable=2154
if [ -e "$src" ]; then
source="$src"
if [ -n "${recordedSourceChecksumsFileName:-}" ]; then
sourceOverlay="$debugOutput/src/overlay"
else
sourceOverlay=""
fi
else
source=""
sourceOverlay=""
fi
# Find executables and dynamic libraries.
local i
while IFS= read -r -d $'\0' i; do
if ! isELF "$i"; then continue; fi
[ -z "${READELF:-}" ] && echo "_separateDebugInfo: '\$READELF' variable is empty, skipping." 1>&2 && break
[ -z "${OBJCOPY:-}" ] && echo "_separateDebugInfo: '\$OBJCOPY' variable is empty, skipping." 1>&2 && break
# Extract the Build ID. FIXME: there's probably a cleaner way.
local id
id="$($READELF -n "$i" | sed 's/.*Build ID: \([0-9a-f]*\).*/\1/; t; d')"
if [ "${#id}" != 40 ]; then
echo "could not find build ID of $i, skipping" >&2
continue
fi
# Extract the debug info.
echo "separating debug info from $i (build ID $id)"
local debuginfoDir="$dst/${id:0:2}"
local buildIdPrefix="$debuginfoDir/${id:2}"
local debuginfoFile="$buildIdPrefix.debug"
local executableSymlink="$buildIdPrefix.executable"
local sourceSymlink="$buildIdPrefix.source"
local sourceOverlaySymlink="$buildIdPrefix.sourceoverlay"
mkdir -p "$debuginfoDir"
if [ -f "$debuginfoFile" ]; then
echo "separate-debug-info: warning: multiple files with build id $id found, overwriting"
fi
# This may fail, e.g. if the binary is for a different
# architecture than we're building for. (This happens with
# firmware blobs in QEMU.)
if $OBJCOPY --only-keep-debug "$i" "$debuginfoFile"; then
# If we succeeded, also a create a symlink <original-name>.debug.
ln -sfn "$debuginfoFile" "$dst/../$(basename "$i")"
# also create a symlink mapping the build-id to the original elf file and the source
# debuginfod protocol relies on it
ln -sfn "$i" "$executableSymlink"
if [ -n "$source" ]; then
ln -sfn "$source" "$sourceSymlink"
fi
if [ -n "$sourceOverlay" ]; then
# create it lazily
if [ ! -d "$sourceOverlay" ]; then
echo "separate-debug-info: copying patched source files to $sourceOverlay..."
mkdir -p "$sourceOverlay"
pushd "$(dirname "$recordedSourceChecksumsFileName")" || { echo "separate-debug-info: failed to cd parent directory of $recordedSourceChecksumsFileName"; return 1; }
while IFS= read -r -d $'\0' modifiedSourceFile; do
if [ -z "$modifiedSourceFile" ]; then
continue
fi
# this can happen with files with '\n' in their name
if [ ! -f "$modifiedSourceFile" ]; then
echo "separate-debug-info: cannot save modified source file $modifiedSourceFile: does not exist. ignoring"
continue
fi
mkdir -p "$sourceOverlay/$(dirname "$modifiedSourceFile")"
cp -v "$modifiedSourceFile" "$sourceOverlay/$modifiedSourceFile"
done < <(LANG=C cksum -a "$cksumAlgo" --check --ignore-missing --quiet "$recordedSourceChecksumsFileName" 2>&1 | sed -n -e 's/: FAILED$/\x00/p' | sed -z -e 's/^\n//')
popd || { echo "separate-debug-info: failed to popd" ; return 1; }
fi
ln -sfn "$sourceOverlay" "$sourceOverlaySymlink"
fi
else
# If we failed, try to clean up unnecessary directories
rmdir -p "$dst/${id:0:2}" --ignore-fail-on-non-empty
fi
done < <(find "$prefix" -type f -print0 | sort -z)
}

View File

@@ -0,0 +1,13 @@
# This setup hook adds every JAR in the share/java subdirectories of
# the build inputs to $CLASSPATH.
export CLASSPATH
addPkgToClassPath () {
local jar
for jar in $1/share/java/*.jar; do
export CLASSPATH=''${CLASSPATH-}''${CLASSPATH:+:}''${jar}
done
}
addEnvHooks "$targetOffset" addPkgToClassPath

View File

@@ -0,0 +1,37 @@
updateSourceDateEpoch() {
local path="$1"
# Avoid passing option-looking directory to find. The example is diffoscope-269:
# https://salsa.debian.org/reproducible-builds/diffoscope/-/issues/378
[[ $path == -* ]] && path="./$path"
# Get the last modification time of all regular files, sort them,
# and get the most recent. Maybe we should use
# https://github.com/0-wiz-0/findnewest here.
local -a res=($(find "$path" -type f -not -newer "$NIX_BUILD_TOP/.." -printf '%T@ "%p"\0' \
| sort -n --zero-terminated | tail -n1 --zero-terminated | head -c -1))
local time="${res[0]//\.[0-9]*/}" # remove the fraction part
local newestFile="${res[1]}"
# Update $SOURCE_DATE_EPOCH if the most recent file we found is newer.
if [ "${time:-0}" -gt "$SOURCE_DATE_EPOCH" ]; then
echo "setting SOURCE_DATE_EPOCH to timestamp $time of file $newestFile"
export SOURCE_DATE_EPOCH="$time"
# Warn if the new timestamp is too close to the present. This
# may indicate that we were being applied to a file generated
# during the build, or that an unpacker didn't restore
# timestamps properly.
local now="$(date +%s)"
if [ "$time" -gt $((now - 60)) ]; then
echo "warning: file $newestFile may be generated; SOURCE_DATE_EPOCH may be non-deterministic"
fi
fi
}
postUnpackHooks+=(_updateSourceDateEpochFromSourceRoot)
_updateSourceDateEpochFromSourceRoot() {
if [ -n "$sourceRoot" ]; then
updateSourceDateEpoch "$sourceRoot"
fi
}

View File

@@ -0,0 +1,5 @@
setupDebugInfoDirs () {
addToSearchPath NIX_DEBUG_INFO_DIRS $1/lib/debug
}
addEnvHooks "$targetOffset" setupDebugInfoDirs

View File

@@ -0,0 +1,88 @@
# This setup hook modifies a Perl script so that any "-I" flags in its shebang
# line are rewritten into a "use lib ..." statement on the next line. This gets
# around a limitation in Darwin, which will not properly handle a script whose
# shebang line exceeds 511 characters.
#
# Each occurrence of "-I /path/to/lib1" or "-I/path/to/lib2" is removed from
# the shebang line, along with the single space that preceded it. These library
# paths are placed into a new line of the form
#
# use lib "/path/to/lib1", "/path/to/lib2";
#
# immediately following the shebang line. If a library appeared in the original
# list more than once, only its first occurrence will appear in the output
# list. In other words, the libraries are deduplicated, but the ordering of the
# first appearance of each one is preserved.
#
# Any flags other than "-I" in the shebang line are left as-is, and the
# interpreter is also left alone (although the script will abort if the
# interpreter does not seem to be either "perl" or else "env" with "perl" as
# its argument). Each line after the shebang line is left unchanged. Each file
# is modified in place.
#
# Usage:
# shortenPerlShebang SCRIPT...
shortenPerlShebang() {
while [ $# -gt 0 ]; do
_shortenPerlShebang "$1"
shift
done
}
_shortenPerlShebang() {
local program="$1"
echo "shortenPerlShebang: rewriting shebang line in $program"
if ! isScript "$program"; then
die "shortenPerlShebang: refusing to modify $program because it is not a script"
fi
local temp="$(mktemp)"
gawk '
(NR == 1) {
if (!($0 ~ /\/(perl|env +perl)\>/)) {
print "shortenPerlShebang: script does not seem to be a Perl script" > "/dev/stderr"
exit 1
}
idx = 0
while (match($0, / -I ?([^ ]+)/, pieces)) {
matches[idx] = pieces[1]
idx++
$0 = gensub(/ -I ?[^ ]+/, "", 1, $0)
}
print $0
if (idx > 0) {
prefix = "use lib "
for (idx in matches) {
path = matches[idx]
if (!(path in seen)) {
printf "%s\"%s\"", prefix, path
seen[path] = 1
prefix = ", "
}
}
print ";"
}
}
(NR > 1 ) {
print
}
' "$program" > "$temp" || die
# Preserve the mode of the original file
cp --preserve=mode --attributes-only "$program" "$temp"
mv "$temp" "$program"
# Measure the new shebang line length and make sure it's okay. We subtract
# one to account for the trailing newline that "head" included in its
# output.
local new_length=$(( $(head -n 1 "$program" | wc -c) - 1 ))
# Darwin is okay when the shebang line contains 511 characters, but not
# when it contains 512 characters.
if [ $new_length -ge 512 ]; then
die "shortenPerlShebang: shebang line is $new_length characters--still too long for Darwin!"
fi
}

View File

@@ -0,0 +1,16 @@
# This setup hook makes the fixup phase to repack all java archives in a
# deterministic fashion. The most important change being done is the resetting
# of the modification times of the archive entries
fixupOutputHooks+=('stripJavaArchivesIn $prefix')
stripJavaArchivesIn() {
local dir="$1"
echo "stripping java archives in $dir"
find $dir -type f -regextype posix-egrep -regex ".*\.(jar|war|hpi|apk)$" -print0 |
while IFS= read -rd '' f; do
echo "stripping java archive $f"
strip-nondeterminism --type jar "$f"
done
}

View File

@@ -0,0 +1,102 @@
# This setup hook strips libraries and executables in the fixup phase.
fixupOutputHooks+=(_doStrip)
_doStrip() {
# We don't bother to strip build platform code because it shouldn't make it
# to $out anyways---if it does, that's a bigger problem that a lack of
# stripping will help catch.
local -ra flags=(dontStripHost dontStripTarget)
local -ra debugDirs=(stripDebugList stripDebugListTarget)
local -ra allDirs=(stripAllList stripAllListTarget)
local -ra stripCmds=(STRIP STRIP_FOR_TARGET)
local -ra ranlibCmds=(RANLIB RANLIB_FOR_TARGET)
# TODO(structured-attrs): This doesn't work correctly if one of
# the items in strip*List or strip*Flags contains a space,
# even with structured attrs enabled. This is OK for now
# because very few packages set any of these, and it doesn't
# affect any of them.
#
# After __structuredAttrs = true is universal, come back and
# push arrays all the way through this logic.
# Strip only host paths by default. Leave targets as is.
stripDebugList=${stripDebugList[*]:-lib lib32 lib64 libexec bin sbin Applications Library/Frameworks}
stripDebugListTarget=${stripDebugListTarget[*]:-}
stripAllList=${stripAllList[*]:-}
stripAllListTarget=${stripAllListTarget[*]:-}
local i
for i in ${!stripCmds[@]}; do
local -n flag="${flags[$i]}"
local -n debugDirList="${debugDirs[$i]}"
local -n allDirList="${allDirs[$i]}"
local -n stripCmd="${stripCmds[$i]}"
local -n ranlibCmd="${ranlibCmds[$i]}"
# `dontStrip` disables them all
if [[ "${dontStrip-}" || "${flag-}" ]] || ! type -f "${stripCmd-}" 2>/dev/null 1>&2
then continue; fi
stripDirs "$stripCmd" "$ranlibCmd" "$debugDirList" "${stripDebugFlags[*]:--S -p}"
stripDirs "$stripCmd" "$ranlibCmd" "$allDirList" "${stripAllFlags[*]:--s -p}"
done
}
stripDirs() {
local cmd="$1"
local ranlibCmd="$2"
local paths="$3"
local stripFlags="$4"
local excludeFlags=()
local pathsNew=
[ -z "$cmd" ] && echo "stripDirs: Strip command is empty" 1>&2 && exit 1
[ -z "$ranlibCmd" ] && echo "stripDirs: Ranlib command is empty" 1>&2 && exit 1
local pattern
if [ -n "${stripExclude:-}" ]; then
for pattern in "${stripExclude[@]}"; do
excludeFlags+=(-a '!' '(' -name "$pattern" -o -wholename "$prefix/$pattern" ')' )
done
fi
local p
for p in ${paths}; do
if [ -e "$prefix/$p" ]; then
pathsNew="${pathsNew} $prefix/$p"
fi
done
paths=${pathsNew}
if [ -n "${paths}" ]; then
echo "stripping (with command $cmd and flags $stripFlags) in $paths"
local striperr
striperr="$(mktemp --tmpdir="$TMPDIR" 'striperr.XXXXXX')"
# Make sure we process files only once. `strip`ping the same file through different
# links in parallel can corrupt it:
# https://github.com/NixOS/nixpkgs/issues/246147#issuecomment-1657072039
# Do not strip lib/debug. This is a directory used by setup-hooks/separate-debug-info.sh.
# Print out each file's device and inode (which will be the same if two files are hardlinked
# or are the same file found through different symlinks), followed by its path...
find $paths -type f "${excludeFlags[@]}" -a '!' -path "$prefix/lib/debug/*" -printf '%D-%i,%p\0' |
# ... sort/uniq by device/inode, then cut them out and keep the path, ...
sort -t, -k1,1 -u -z | cut -d, -f2- -z |
# and finally strip each unique path in parallel.
xargs -r -0 -n1 -P "$NIX_BUILD_CORES" -- $cmd $stripFlags 2>"$striperr" || exit_code=$?
# xargs exits with status code 123 if some but not all of the
# processes fail. We don't care if some of the files couldn't
# be stripped, so ignore specifically this code.
[[ "$exit_code" = 123 || -z "$exit_code" ]] || (cat "$striperr" 1>&2 && exit 1)
rm "$striperr"
# 'strip' does not normally preserve archive index in .a files.
# This usually causes linking failures against static libs like:
# ld: ...-i686-w64-mingw32-stage-final-gcc-13.0.0-lib/i686-w64-mingw32/lib/libstdc++.dll.a:
# error adding symbols: archive has no index; run ranlib to add one
# Restore the index by running 'ranlib'.
find $paths -name '*.a' -type f -exec $ranlibCmd '{}' \; 2>/dev/null
fi
}

View File

@@ -0,0 +1,12 @@
appendToVar preConfigurePhases updateAutotoolsGnuConfigScriptsPhase
updateAutotoolsGnuConfigScriptsPhase() {
if [ -n "${dontUpdateAutotoolsGnuConfigScripts-}" ]; then return; fi
for script in config.sub config.guess; do
for f in $(find . -type f -name "$script"); do
echo "Updating Autotools / GNU config script to a newer upstream version: $f"
cp -f "@gnu_config@/$script" "$f"
done
done
}

View File

@@ -0,0 +1 @@
export NIX_CFLAGS_COMPILE+=" -D_GLIBCXX_USE_CXX11_ABI=0"

View File

@@ -0,0 +1,18 @@
# This setup hook validates each pkgconfig file in each output.
fixupOutputHooks+=(_validatePkgConfig)
_validatePkgConfig() {
local bail=0
for pc in $(find "$prefix" -name '*.pc'); do
# Do not fail immediately. It's nice to see all errors when
# there are multiple pkgconfig files.
if ! $PKG_CONFIG --validate "$pc"; then
bail=1
fi
done
if [ $bail -eq 1 ]; then
exit 1
fi
}

View File

@@ -0,0 +1,89 @@
fixupOutputHooks+=(_linkDLLs)
addEnvHooks "$targetOffset" linkDLLGetFolders
linkDLLGetFolders() {
addToSearchPath "LINK_DLL_FOLDERS" "$1/lib"
addToSearchPath "LINK_DLL_FOLDERS" "$1/bin"
}
_linkDLLs() {
linkDLLsInfolder "$prefix/bin"
}
# Try to links every known dependency of exe/dll in the folder of the 1str input
# into said folder, so they are found on invocation.
# (DLLs are first searched in the directory of the running exe file.)
# The links are relative, so relocating whole /nix/store won't break them.
linkDLLsInfolder() {
(
local folder
folder="$1"
if [ ! -d "$folder" ]; then
echo "Not linking DLLs in the non-existent folder $folder"
return
fi
cd "$folder" || exit
# Use associative arrays as set
local filesToChecks
local filesDone
declare -A filesToChecks # files that still needs to have their dependancies checked
declare -A filesDone # files that had their dependancies checked and who is copied to the bin folder if found
markFileAsDone() {
if [ ! "${filesDone[$1]+a}" ]; then filesDone[$1]=a; fi
if [ "${filesToChecks[$1]+a}" ]; then unset 'filesToChecks[$1]'; fi
}
addFileToLink() {
if [ "${filesDone[$1]+a}" ]; then return; fi
if [ ! "${filesToChecks[$1]+a}" ]; then filesToChecks[$1]=a; fi
}
# Compose path list where DLLs should be located:
# prefix $PATH by currently-built outputs
local DLLPATH=""
local outName
for outName in $(getAllOutputNames); do
addToSearchPath DLLPATH "${!outName}/bin"
done
DLLPATH="$DLLPATH:$LINK_DLL_FOLDERS"
echo DLLPATH="'$DLLPATH'"
for peFile in *.{exe,dll}; do
if [ -e "./$peFile" ]; then
addFileToLink "$peFile"
fi
done
local searchPaths
readarray -td: searchPaths < <(printf -- "%s" "$DLLPATH")
local linkCount=0
while [ ${#filesToChecks[*]} -gt 0 ]; do
local listOfDlls=("${!filesToChecks[@]}")
local file=${listOfDlls[0]}
markFileAsDone "$file"
if [ ! -e "./$file" ]; then
local pathsFound
readarray -d '' pathsFound < <(find -L "${searchPaths[@]}" -name "$file" -type f -print0)
if [ ${#pathsFound[@]} -eq 0 ]; then continue; fi
local dllPath
dllPath="${pathsFound[0]}"
CYGWIN+=" winsymlinks:nativestrict" ln -sr "$dllPath" .
echo "linking $dllPath"
file="$dllPath"
linkCount=$((linkCount + 1))
fi
# local dep_file
# Look at the files dependancies
for dep_file in $($OBJDUMP -p "$file" | sed -n 's/.*DLL Name: \(.*\)/\1/p' | sort -u); do
addFileToLink "$dep_file"
done
done
echo "Created $linkCount DLL link(s) in $folder"
)
}

View File

@@ -0,0 +1,257 @@
{
stdenv,
lib,
makeSetupHook,
makeWrapper,
gobject-introspection,
isGraphical ? false,
gtk3,
librsvg,
dconf,
withDconf ? !stdenv.targetPlatform.isDarwin && lib.meta.availableOn stdenv.targetPlatform dconf,
callPackage,
wrapGAppsHook3,
targetPackages,
}:
makeSetupHook {
name = "wrap-gapps-hook";
propagatedBuildInputs = [
# We use the wrapProgram function.
makeWrapper
]
++ lib.optionals isGraphical [
# TODO: remove this, packages should depend on GTK explicitly.
gtk3
librsvg
];
# depsTargetTargetPropagated will essentially be buildInputs when wrapGAppsHook3 is placed into nativeBuildInputs
# the librsvg and gtk3 above should be removed but kept to not break anything that implicitly depended on its binaries
depsTargetTargetPropagated =
assert (lib.assertMsg (!targetPackages ? raw) "wrapGAppsHook3 must be in nativeBuildInputs");
lib.optionals isGraphical [
# librsvg provides a module for gdk-pixbuf to allow rendering
# SVG icons. Most icon themes are SVG-based and so are some
# graphics in GTK (e.g. cross for closing window in window title bar)
# so it is pretty much required for applications using GTK.
librsvg
# TODO: remove this, packages should depend on GTK explicitly.
gtk3
]
++ lib.optionals withDconf [
# It is highly probable that a program will use GSettings,
# at minimum through GTK file chooser dialogue.
# Lets add a GIO module for “dconf” GSettings backend
# to avoid falling back to “memory” backend. This is
# required for GSettings-based settings to be persisted.
# Unfortunately, it also requires the user to have dconf
# D-Bus service enabled globally (e.g. through a NixOS module).
dconf.lib
];
passthru = {
tests =
let
sample-project = ./tests/sample-project;
testLib = callPackage ./tests/lib.nix { };
inherit (testLib) expectSomeLineContainingYInFileXToMentionZ;
in
rec {
# Simple derivation containing a program and a daemon.
basic = stdenv.mkDerivation {
name = "basic";
src = sample-project;
strictDeps = true;
nativeBuildInputs = [ wrapGAppsHook3 ];
installFlags = [
"bin-foo"
"libexec-bar"
];
};
# The wrapper for executable files should add path to dconf GIO module.
basic-contains-dconf =
let
tested = basic;
in
testLib.runTest "basic-contains-dconf" (
testLib.skip stdenv.hostPlatform.isDarwin ''
${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GIO_EXTRA_MODULES"
"${dconf.lib}/lib/gio/modules"
}
${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GIO_EXTRA_MODULES"
"${dconf.lib}/lib/gio/modules"
}
''
);
basic-contains-gdk-pixbuf =
let
tested = basic;
in
testLib.runTest "basic-contains-gdk-pixbuf" (
testLib.skip stdenv.hostPlatform.isDarwin ''
${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GDK_PIXBUF_MODULE_FILE"
"${lib.getLib librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
}
${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GDK_PIXBUF_MODULE_FILE"
"${lib.getLib librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
}
''
);
# Simple derivation containing a gobject-introspection typelib.
typelib-Mahjong = stdenv.mkDerivation {
name = "typelib-Mahjong";
src = sample-project;
strictDeps = true;
installFlags = [ "typelib-Mahjong" ];
};
# Simple derivation using a typelib.
typelib-user = stdenv.mkDerivation {
name = "typelib-user";
src = sample-project;
strictDeps = true;
nativeBuildInputs = [
gobject-introspection
wrapGAppsHook3
];
buildInputs = [
typelib-Mahjong
];
installFlags = [
"bin-foo"
"libexec-bar"
];
};
# Testing cooperation with gobject-introspection setup hook,
# which should populate GI_TYPELIB_PATH variable with paths
# to typelibs among the derivations dependencies.
# The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
typelib-user-has-gi-typelib-path =
let
tested = typelib-user;
in
testLib.runTest "typelib-user-has-gi-typelib-path" ''
${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH"
"${typelib-Mahjong}/lib/girepository-1.0"
}
${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH"
"${typelib-Mahjong}/lib/girepository-1.0"
}
'';
# Simple derivation containing a gobject-introspection typelib in lib output.
typelib-Bechamel = stdenv.mkDerivation {
name = "typelib-Bechamel";
outputs = [
"out"
"lib"
];
src = sample-project;
strictDeps = true;
makeFlags = [
"LIBDIR=${placeholder "lib"}/lib"
];
installFlags = [ "typelib-Bechamel" ];
};
# Simple derivation using a typelib from non-default output.
typelib-multiout-user = stdenv.mkDerivation {
name = "typelib-multiout-user";
src = sample-project;
strictDeps = true;
nativeBuildInputs = [
gobject-introspection
wrapGAppsHook3
];
buildInputs = [
typelib-Bechamel
];
installFlags = [
"bin-foo"
"libexec-bar"
];
};
# Testing cooperation with gobject-introspection setup hook,
# which should populate GI_TYPELIB_PATH variable with paths
# to typelibs among the derivations dependencies,
# even when they are not in default output.
# The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
typelib-multiout-user-has-gi-typelib-path =
let
tested = typelib-multiout-user;
in
testLib.runTest "typelib-multiout-user-has-gi-typelib-path" ''
${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH"
"${typelib-Bechamel.lib}/lib/girepository-1.0"
}
${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH"
"${typelib-Bechamel.lib}/lib/girepository-1.0"
}
'';
# Simple derivation that contains a typelib as well as a program using it.
typelib-self-user = stdenv.mkDerivation {
name = "typelib-self-user";
src = sample-project;
strictDeps = true;
nativeBuildInputs = [
gobject-introspection
wrapGAppsHook3
];
installFlags = [
"typelib-Cow"
"bin-foo"
"libexec-bar"
];
};
# Testing cooperation with gobject-introspection setup hook,
# which should add the path to derivations own typelibs
# to GI_TYPELIB_PATH variable.
# The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
# https://github.com/NixOS/nixpkgs/issues/85515
typelib-self-user-has-gi-typelib-path =
let
tested = typelib-self-user;
in
testLib.runTest "typelib-self-user-has-gi-typelib-path" ''
${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH"
"${typelib-self-user}/lib/girepository-1.0"
}
${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH"
"${typelib-self-user}/lib/girepository-1.0"
}
'';
};
};
} ./wrap-gapps-hook.sh

View File

@@ -0,0 +1,37 @@
{ lib, runCommand }:
rec {
runTest =
name: body:
runCommand name { strictDeps = true; } ''
set -o errexit
${body}
touch $out
'';
skip =
cond: text:
if cond then
''
echo "Skipping test $name" > /dev/stderr
''
else
text;
fail = text: ''
echo "FAIL: $name: ${text}" > /dev/stderr
exit 1
'';
expectSomeLineContainingYInFileXToMentionZ = file: filter: expected: ''
file=${lib.escapeShellArg file} filter=${lib.escapeShellArg filter} expected=${lib.escapeShellArg expected}
if ! grep --text --quiet "$filter" "$file"; then
${fail "The file $file should include a line containing $filter."}
fi
if ! grep --text "$filter" "$file" | grep --text --quiet "$expected"; then
${fail "The file $file should include a line containing $filter that also contains $expected."}
fi
'';
}

View File

@@ -0,0 +1,30 @@
PREFIX = $(out)
BINDIR = $(PREFIX)/bin
LIBEXECDIR = $(PREFIX)/libexec
LIBDIR = $(PREFIX)/lib
TYPELIBDIR = $(LIBDIR)/girepository-1.0
all:
echo "Compiling…"
install:
echo "Installing…"
bin:
mkdir -p $(BINDIR)
# Adds `bin-${foo}` targets, that install `${foo}` executable to `$(BINDIR)`.
bin-%: bin
touch $(BINDIR)/$(@:bin-%=%)
chmod +x $(BINDIR)/$(@:bin-%=%)
libexec:
mkdir -p $(LIBEXECDIR)
# Adds `libexec-${foo}` targets, that install `${foo}` executable to `$(LIBEXECDIR)`.
libexec-%: libexec
touch $(LIBEXECDIR)/$(@:libexec-%=%)
chmod +x $(LIBEXECDIR)/$(@:libexec-%=%)
typelib:
mkdir -p $(TYPELIBDIR)
# Adds `typelib-${foo}` targets, that install `${foo}-1.0.typelib` file to `$(TYPELIBDIR)`.
typelib-%: typelib
touch $(TYPELIBDIR)/$(@:typelib-%=%)-1.0.typelib

View File

@@ -0,0 +1,89 @@
# shellcheck shell=bash
gappsWrapperArgs=()
find_gio_modules() {
if [ -d "$1/lib/gio/modules" ] && [ -n "$(ls -A "$1/lib/gio/modules")" ] ; then
gappsWrapperArgs+=(--prefix GIO_EXTRA_MODULES : "$1/lib/gio/modules")
fi
}
addEnvHooks "${targetOffset:?}" find_gio_modules
gappsWrapperArgsHook() {
if [ -n "$GDK_PIXBUF_MODULE_FILE" ]; then
gappsWrapperArgs+=(--set GDK_PIXBUF_MODULE_FILE "$GDK_PIXBUF_MODULE_FILE")
fi
if [ -n "$GSETTINGS_SCHEMAS_PATH" ]; then
gappsWrapperArgs+=(--prefix XDG_DATA_DIRS : "$GSETTINGS_SCHEMAS_PATH")
fi
# Check for prefix as well
if [ -d "${prefix:?}/share" ]; then
gappsWrapperArgs+=(--prefix XDG_DATA_DIRS : "$prefix/share")
fi
if [ -d "$prefix/lib/gio/modules" ] && [ -n "$(ls -A "$prefix/lib/gio/modules")" ]; then
gappsWrapperArgs+=(--prefix GIO_EXTRA_MODULES : "$prefix/lib/gio/modules")
fi
for v in ${wrapPrefixVariables:-} GST_PLUGIN_SYSTEM_PATH_1_0 GI_TYPELIB_PATH GRL_PLUGIN_PATH; do
if [ -n "${!v:-}" ]; then
gappsWrapperArgs+=(--prefix "$v" : "${!v}")
fi
done
}
appendToVar preFixupPhases gappsWrapperArgsHook
wrapGApp() {
local program="$1"
shift 1
wrapProgram "$program" "${gappsWrapperArgs[@]}" "$@"
}
# Note: $gappsWrapperArgs still gets defined even if ${dontWrapGApps-} is set.
wrapGAppsHook() {
# guard against running multiple times (e.g. due to propagation)
[ -z "$wrapGAppsHookHasRun" ] || return 0
wrapGAppsHookHasRun=1
if [[ -z "${dontWrapGApps:-}" ]]; then
targetDirsThatExist=()
targetDirsRealPath=()
# wrap binaries
targetDirs=("${prefix}/bin" "${prefix}/libexec")
for targetDir in "${targetDirs[@]}"; do
if [[ -d "${targetDir}" ]]; then
targetDirsThatExist+=("${targetDir}")
targetDirsRealPath+=("$(realpath "${targetDir}")/")
find "${targetDir}" -type f -executable -print0 |
while IFS= read -r -d '' file; do
echo "Wrapping program '${file}'"
wrapGApp "${file}"
done
fi
done
# wrap links to binaries that point outside targetDirs
# Note: links to binaries within targetDirs do not need
# to be wrapped as the binaries have already been wrapped
if [[ ${#targetDirsThatExist[@]} -ne 0 ]]; then
find "${targetDirsThatExist[@]}" -type l -xtype f -executable -print0 |
while IFS= read -r -d '' linkPath; do
linkPathReal=$(realpath "${linkPath}")
for targetPath in "${targetDirsRealPath[@]}"; do
if [[ "$linkPathReal" == "$targetPath"* ]]; then
echo "Not wrapping link: '$linkPath' (already wrapped)"
continue 2
fi
done
echo "Wrapping link: '$linkPath'"
wrapGApp "${linkPath}"
done
fi
fi
}
fixupOutputHooks+=(wrapGAppsHook)

View File

@@ -0,0 +1,14 @@
# shellcheck shell=bash
# This setup hook set the HOME environment variable to a writable directory.
export HOME
writableTmpDirAsHome () {
if [ ! -w "$HOME" ]; then
HOME="$NIX_BUILD_TOP/.home"
mkdir -p "$HOME"
export HOME
fi
}
postHooks+=(writableTmpDirAsHome)