Merge staging-next into staging
This commit is contained in:
@@ -4,7 +4,7 @@ on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'shell.nix'
|
||||
- './ci/**'
|
||||
- 'ci/**'
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
||||
@@ -13743,7 +13743,6 @@
|
||||
github = "matthewpi";
|
||||
githubId = 26559841;
|
||||
name = "Matthew Penner";
|
||||
keys = [ { fingerprint = "5118 F1CC B7B0 6C17 4DD1 5267 3131 1906 AD4C F6D6"; } ];
|
||||
};
|
||||
matthiasbenaets = {
|
||||
email = "matthias.benaets@gmail.com";
|
||||
@@ -21203,12 +21202,6 @@
|
||||
githubId = 1694705;
|
||||
name = "Sam Stites";
|
||||
};
|
||||
stnley = {
|
||||
email = "michael@stnley.io";
|
||||
github = "stnley";
|
||||
githubId = 64174376;
|
||||
name = "Michael Stanley";
|
||||
};
|
||||
strager = {
|
||||
email = "strager.nds@gmail.com";
|
||||
github = "strager";
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
- [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](option.html#opt-services.kimai).
|
||||
|
||||
- [Omnom](https://github.com/asciimoo/omnom), a webpage bookmarking and snapshotting service. Available as [services.omnom](options.html#opt-services.omnom.enable).
|
||||
|
||||
- [Amazon CloudWatch Agent](https://github.com/aws/amazon-cloudwatch-agent), the official telemetry collector for AWS CloudWatch and AWS X-Ray. Available as [services.amazon-cloudwatch-agent](#opt-services.amazon-cloudwatch-agent.enable).
|
||||
|
||||
- [agorakit](https://github.com/agorakit/agorakit), an organization tool for citizens' collectives. Available with [services.agorakit](#opt-services.agorakit.enable).
|
||||
@@ -60,6 +62,8 @@
|
||||
[official website](https://www.nerdfonts.com/font-downloads) as the titles in preview images, with the "Nerd Font"
|
||||
suffix and any whitespaces trimmed.
|
||||
|
||||
- `gkraken` software and `hardware.gkraken.enable` option have been removed, use `coolercontrol` via `programs.coolercontrol.enable` option instead.
|
||||
|
||||
- the notmuch vim plugin now lives in a separate output of the `notmuch`
|
||||
package. Installing `notmuch` will not bring the notmuch vim package anymore,
|
||||
add `vimPlugins.notmuch-vim` to your (Neo)vim configuration if you want the
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.hardware.gkraken;
|
||||
in
|
||||
{
|
||||
options.hardware.gkraken = {
|
||||
enable = lib.mkEnableOption "gkraken's udev rules for NZXT AIO liquid coolers";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.udev.packages = with pkgs; [
|
||||
gkraken
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -65,7 +65,6 @@
|
||||
./hardware/digitalbitbox.nix
|
||||
./hardware/flipperzero.nix
|
||||
./hardware/flirc.nix
|
||||
./hardware/gkraken.nix
|
||||
./hardware/glasgow.nix
|
||||
./hardware/gpgsmartcards.nix
|
||||
./hardware/graphics.nix
|
||||
|
||||
@@ -33,6 +33,10 @@ in
|
||||
systemd-logind API). Instead of using the module you can now
|
||||
simply add the brightnessctl package to environment.systemPackages.
|
||||
'')
|
||||
(mkRemovedOptionModule [ "hardware" "gkraken" "enable" ] ''
|
||||
gkraken was deprecated by coolercontrol and thus removed from nixpkgs.
|
||||
Consider using programs.coolercontrol instead.
|
||||
'')
|
||||
(mkRemovedOptionModule [ "hardware" "u2f" ] ''
|
||||
The U2F modules module was removed, as all it did was adding the
|
||||
udev rules from libu2f-host to the system. Udev gained native support
|
||||
|
||||
@@ -672,10 +672,12 @@ let
|
||||
"IPv6AcceptRA"
|
||||
"IPv6DuplicateAddressDetection"
|
||||
"IPv6HopLimit"
|
||||
"IPv6RetransmissionTimeSec"
|
||||
"IPv4ReversePathFilter"
|
||||
"IPv4AcceptLocal"
|
||||
"IPv4RouteLocalnet"
|
||||
"IPv4ProxyARP"
|
||||
"IPv4ProxyARPPrivateVLAN"
|
||||
"IPv6ProxyNDP"
|
||||
"IPv6ProxyNDPAddress"
|
||||
"IPv6SendRA"
|
||||
@@ -726,10 +728,12 @@ let
|
||||
(assertMinimum "IPv6DuplicateAddressDetection" 0)
|
||||
(assertInt "IPv6HopLimit")
|
||||
(assertMinimum "IPv6HopLimit" 0)
|
||||
(assertInt "IPv6RetransmissionTimeSec")
|
||||
(assertValueOneOf "IPv4ReversePathFilter" ["no" "strict" "loose"])
|
||||
(assertValueOneOf "IPv4AcceptLocal" boolValues)
|
||||
(assertValueOneOf "IPv4RouteLocalnet" boolValues)
|
||||
(assertValueOneOf "IPv4ProxyARP" boolValues)
|
||||
(assertValueOneof "IPv4ProxyARPPrivateVLAN" boolValues)
|
||||
(assertValueOneOf "IPv6ProxyNDP" boolValues)
|
||||
(assertValueOneOf "IPv6SendRA" boolValues)
|
||||
(assertValueOneOf "DHCPPrefixDelegation" boolValues)
|
||||
@@ -776,6 +780,7 @@ let
|
||||
"Priority"
|
||||
"IncomingInterface"
|
||||
"OutgoingInterface"
|
||||
"L3MasterDevice"
|
||||
"SourcePort"
|
||||
"DestinationPort"
|
||||
"IPProtocol"
|
||||
@@ -790,6 +795,7 @@ let
|
||||
(assertRange "TypeOfService" 0 255)
|
||||
(assertRangeWithOptionalMask "FirewallMark" 1 4294967295)
|
||||
(assertInt "Priority")
|
||||
(assertValueOneOf "L3MasterDevice" boolValues)
|
||||
(assertPortOrPortRange "SourcePort")
|
||||
(assertPortOrPortRange "DestinationPort")
|
||||
(assertValueOneOf "InvertRule" boolValues)
|
||||
@@ -1033,6 +1039,7 @@ let
|
||||
"BootServerName"
|
||||
"BootFilename"
|
||||
"IPv6OnlyPreferredSec"
|
||||
"PersistLeases"
|
||||
])
|
||||
(assertInt "PoolOffset")
|
||||
(assertMinimum "PoolOffset" 0)
|
||||
@@ -1047,6 +1054,7 @@ let
|
||||
(assertValueOneOf "EmitRouter" boolValues)
|
||||
(assertValueOneOf "EmitTimezone" boolValues)
|
||||
(assertValueOneOf "BindToInterface" boolValues)
|
||||
(assertValueOneOf "PersistLeases" boolValues)
|
||||
];
|
||||
|
||||
sectionIPv6SendRA = checkUnitConfig "IPv6SendRA" [
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"packageVersion": "132.0.2-1",
|
||||
"packageVersion": "133.0-1",
|
||||
"source": {
|
||||
"rev": "132.0.2-1",
|
||||
"sha256": "7DB0QSQHNRw991yRR5+/Oo4fpXCR/Zklxb7ILRIH0WM="
|
||||
"rev": "133.0-1",
|
||||
"sha256": "1xf7gx3xm3c7dhch9gwpb0xp11lcyim1nrbm8sjljxdcs7iq9jy4"
|
||||
},
|
||||
"firefox": {
|
||||
"version": "132.0.2",
|
||||
"sha512": "nqldn7GpQaxaW1DaZ+Ik88z4xAHybLYbt0rX9OHocG1GnEtjJXFPLLnN9QwycQN31ryhjdZbVdssOe8rJ6V/rg=="
|
||||
"version": "133.0",
|
||||
"sha512": "b16f9898bee4121914caef48d4f7f44bf9d69aee168586b02bf1b4f4197844fd10179e1b63b273f52929fb348030df36328f24993cd666969da4ddc82562a90c"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,10 +919,19 @@ rec {
|
||||
, includeStorePaths ? true
|
||||
, includeNixDB ? false
|
||||
, passthru ? {}
|
||||
,
|
||||
, # Pipeline used to produce docker layers. If not set, popularity contest
|
||||
# algorithm is used. If set, maxLayers is ignored as the author of the
|
||||
# pipeline can use one of the available functions (like "limit_layers")
|
||||
# to control the amount of layers.
|
||||
# See: pkgs/build-support/flatten-references-graph/src/flatten_references_graph/pipe.py
|
||||
# for available functions, and it's test for how to use them.
|
||||
# WARNING!! this interface is highly experimental and subject to change.
|
||||
layeringPipeline ? null
|
||||
, # Enables debug logging for the layering pipeline.
|
||||
debug ? false
|
||||
}:
|
||||
assert
|
||||
(lib.assertMsg (maxLayers > 1)
|
||||
(lib.assertMsg (layeringPipeline == null -> maxLayers > 1)
|
||||
"the maxLayers argument of dockerTools.buildLayeredImage function must be greather than 1 (current value: ${toString maxLayers})");
|
||||
assert
|
||||
(lib.assertMsg (enableFakechroot -> !stdenv.hostPlatform.isDarwin) ''
|
||||
@@ -999,18 +1008,23 @@ rec {
|
||||
'';
|
||||
};
|
||||
|
||||
closureRoots = lib.optionals includeStorePaths /* normally true */ (
|
||||
[ baseJson customisationLayer ]
|
||||
);
|
||||
overallClosure = writeText "closure" (lib.concatStringsSep " " closureRoots);
|
||||
|
||||
# These derivations are only created as implementation details of docker-tools,
|
||||
# so they'll be excluded from the created images.
|
||||
unnecessaryDrvs = [ baseJson overallClosure customisationLayer ];
|
||||
layersJsonFile = buildPackages.dockerMakeLayers {
|
||||
inherit debug;
|
||||
closureRoots = optionals includeStorePaths [ baseJson customisationLayer ];
|
||||
excludePaths = [ baseJson customisationLayer ];
|
||||
pipeline =
|
||||
if layeringPipeline != null
|
||||
then layeringPipeline
|
||||
else import
|
||||
./popularity-contest-layering-pipeline.nix
|
||||
{ inherit lib jq runCommand; }
|
||||
{ inherit fromImage maxLayers; }
|
||||
;
|
||||
};
|
||||
|
||||
conf = runCommand "${baseName}-conf.json"
|
||||
{
|
||||
inherit fromImage maxLayers created mtime uid gid uname gname;
|
||||
inherit fromImage created mtime uid gid uname gname layersJsonFile;
|
||||
imageName = lib.toLower name;
|
||||
preferLocalBuild = true;
|
||||
passthru.imageTag =
|
||||
@@ -1018,7 +1032,6 @@ rec {
|
||||
then tag
|
||||
else
|
||||
lib.head (lib.strings.splitString "-" (baseNameOf (builtins.unsafeDiscardStringContext conf.outPath)));
|
||||
paths = buildPackages.referencesByPopularity overallClosure;
|
||||
nativeBuildInputs = [ jq ];
|
||||
} ''
|
||||
${if (tag == null) then ''
|
||||
@@ -1038,54 +1051,7 @@ rec {
|
||||
mtime="$(date -Iseconds -d "$mtime")"
|
||||
fi
|
||||
|
||||
paths() {
|
||||
cat $paths ${lib.concatMapStringsSep " "
|
||||
(path: "| (grep -v ${path} || true)")
|
||||
unnecessaryDrvs}
|
||||
}
|
||||
|
||||
# Compute the number of layers that are already used by a potential
|
||||
# 'fromImage' as well as the customization layer. Ensure that there is
|
||||
# still at least one layer available to store the image contents.
|
||||
usedLayers=0
|
||||
|
||||
# subtract number of base image layers
|
||||
if [[ -n "$fromImage" ]]; then
|
||||
(( usedLayers += $(tar -xOf "$fromImage" manifest.json | jq '.[0].Layers | length') ))
|
||||
fi
|
||||
|
||||
# one layer will be taken up by the customisation layer
|
||||
(( usedLayers += 1 ))
|
||||
|
||||
if ! (( $usedLayers < $maxLayers )); then
|
||||
echo >&2 "Error: usedLayers $usedLayers layers to store 'fromImage' and" \
|
||||
"'extraCommands', but only maxLayers=$maxLayers were" \
|
||||
"allowed. At least 1 layer is required to store contents."
|
||||
exit 1
|
||||
fi
|
||||
availableLayers=$(( maxLayers - usedLayers ))
|
||||
|
||||
# Create $maxLayers worth of Docker Layers, one layer per store path
|
||||
# unless there are more paths than $maxLayers. In that case, create
|
||||
# $maxLayers-1 for the most popular layers, and smush the remainaing
|
||||
# store paths in to one final layer.
|
||||
#
|
||||
# The following code is fiddly w.r.t. ensuring every layer is
|
||||
# created, and that no paths are missed. If you change the
|
||||
# following lines, double-check that your code behaves properly
|
||||
# when the number of layers equals:
|
||||
# maxLayers-1, maxLayers, and maxLayers+1, 0
|
||||
paths |
|
||||
jq -sR '
|
||||
rtrimstr("\n") | split("\n")
|
||||
| (.[:$maxLayers-1] | map([.])) + [ .[$maxLayers-1:] ]
|
||||
| map(select(length > 0))
|
||||
' \
|
||||
--argjson maxLayers "$availableLayers" > store_layers.json
|
||||
|
||||
# The index on $store_layers is necessary because the --slurpfile
|
||||
# automatically reads the file as an array.
|
||||
cat ${baseJson} | jq '
|
||||
jq '
|
||||
. + {
|
||||
"store_dir": $store_dir,
|
||||
"from_image": $from_image,
|
||||
@@ -1101,7 +1067,7 @@ rec {
|
||||
}
|
||||
' --arg store_dir "${storeDir}" \
|
||||
--argjson from_image ${if fromImage == null then "null" else "'\"${fromImage}\"'"} \
|
||||
--slurpfile store_layers store_layers.json \
|
||||
--slurpfile store_layers "$layersJsonFile" \
|
||||
--arg customisation_layer ${customisationLayer} \
|
||||
--arg repo_tag "$imageName:$imageTag" \
|
||||
--arg created "$created" \
|
||||
@@ -1109,8 +1075,9 @@ rec {
|
||||
--arg uid "$uid" \
|
||||
--arg gid "$gid" \
|
||||
--arg uname "$uname" \
|
||||
--arg gname "$gname" |
|
||||
tee $out
|
||||
--arg gname "$gname" \
|
||||
${baseJson} \
|
||||
| tee $out
|
||||
'';
|
||||
|
||||
result = runCommand "stream-${baseName}"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
coreutils,
|
||||
flattenReferencesGraph,
|
||||
lib,
|
||||
jq,
|
||||
runCommand,
|
||||
}:
|
||||
{
|
||||
closureRoots,
|
||||
excludePaths ? [ ],
|
||||
# This could be a path to (or a derivation producing a path to)
|
||||
# a json file containing the pipeline
|
||||
pipeline ? [ ],
|
||||
debug ? false,
|
||||
}:
|
||||
if closureRoots == [ ] then
|
||||
builtins.toFile "docker-layers-empty" "[]"
|
||||
else
|
||||
runCommand "docker-layers"
|
||||
{
|
||||
__structuredAttrs = true;
|
||||
# graph, exclude_paths and pipeline are expected by the
|
||||
# flatten_references_graph executable.
|
||||
exportReferencesGraph.graph = closureRoots;
|
||||
exclude_paths = excludePaths;
|
||||
inherit pipeline;
|
||||
nativeBuildInputs = [
|
||||
coreutils
|
||||
flattenReferencesGraph
|
||||
jq
|
||||
];
|
||||
}
|
||||
''
|
||||
. .attrs.sh
|
||||
|
||||
flatten_references_graph_arg=.attrs.json
|
||||
|
||||
echo "pipeline: $pipeline"
|
||||
|
||||
if jq -e '.pipeline | type == "string"' .attrs.json; then
|
||||
jq '. + { "pipeline": $pipeline[0] }' \
|
||||
--slurpfile pipeline "$pipeline" \
|
||||
.attrs.json > flatten_references_graph_arg.json
|
||||
|
||||
flatten_references_graph_arg=flatten_references_graph_arg.json
|
||||
fi
|
||||
|
||||
${lib.optionalString debug "export DEBUG=True"}
|
||||
flatten_references_graph "$flatten_references_graph_arg" > ''${outputs[out]}
|
||||
''
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
lib,
|
||||
runCommand,
|
||||
jq,
|
||||
}:
|
||||
{
|
||||
maxLayers,
|
||||
fromImage ? null,
|
||||
}:
|
||||
runCommand "popularity-contest-layering-pipeline.json" { inherit maxLayers; } ''
|
||||
# Compute the number of layers that are already used by a potential
|
||||
# 'fromImage' as well as the customization layer. Ensure that there is
|
||||
# still at least one layer available to store the image contents.
|
||||
# one layer will be taken up by the customisation layer
|
||||
usedLayers=1
|
||||
|
||||
${lib.optionalString (fromImage != null) ''
|
||||
# subtract number of base image layers
|
||||
baseImageLayersCount=$(tar -xOf "${fromImage}" manifest.json | ${lib.getExe jq} '.[0].Layers | length')
|
||||
|
||||
(( usedLayers += baseImageLayersCount ))
|
||||
''}
|
||||
|
||||
if ! (( $usedLayers < $maxLayers )); then
|
||||
echo >&2 "Error: usedLayers $usedLayers layers to store 'fromImage' and" \
|
||||
"'extraCommands', but only maxLayers=$maxLayers were" \
|
||||
"allowed. At least 1 layer is required to store contents."
|
||||
exit 1
|
||||
fi
|
||||
availableLayers=$(( maxLayers - usedLayers ))
|
||||
|
||||
# Produce pipeline which uses popularity_contest algo.
|
||||
echo '[["popularity_contest"],["limit_layers",'$availableLayers']]' > $out
|
||||
''
|
||||
@@ -18,14 +18,14 @@
|
||||
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "alpaca";
|
||||
version = "2.8.0";
|
||||
version = "2.9.0";
|
||||
pyproject = false; # Built with meson
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Jeffser";
|
||||
repo = "Alpaca";
|
||||
rev = "refs/tags/${version}";
|
||||
hash = "sha256-Wk7a5/fO87yl/TolzoonSRLkc2TGEZKgVCgXcUuhjQI=";
|
||||
hash = "sha256-ionioPA69haDIyXjqU84nuTNtI32jOnhd6oCTRI6vcA=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
python3Packages,
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
}:
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "cruft";
|
||||
version = "2.15.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "cruft";
|
||||
repo = "cruft";
|
||||
rev = version;
|
||||
hash = "sha256-qIVyNMoI3LsoOV/6XPa60Y1vTRvkezesF7wF9WVSLGk=";
|
||||
};
|
||||
|
||||
build-system = with python3Packages; [
|
||||
poetry-core
|
||||
];
|
||||
|
||||
nativeCheckInputs = with python3Packages; [
|
||||
pytest7CheckHook
|
||||
];
|
||||
|
||||
dependencies = with python3Packages; [
|
||||
click
|
||||
cookiecutter
|
||||
gitpython
|
||||
typer
|
||||
];
|
||||
|
||||
pythonImportsCheck = "cruft";
|
||||
|
||||
# Unfortunately, some tests require internet access to fully clone
|
||||
# https://github.com/cruft/cookiecutter-test (including all branches)
|
||||
# which is possible to package, but is annoying and may be not always pure
|
||||
#
|
||||
# See https://discourse.nixos.org/t/keep-git-folder-in-when-fetching-a-git-repo/8590/6
|
||||
#
|
||||
# There are only 13 tests which do not require internet access on moment of the writing.
|
||||
# But some tests are better than none, right?
|
||||
disabledTests = [
|
||||
"test_get_diff_with_add"
|
||||
"test_get_diff_with_delete"
|
||||
"test_get_diff_with_unicode"
|
||||
];
|
||||
|
||||
disabledTestPaths = [
|
||||
"tests/test_api.py" # only 2 tests pass, and 24 fail. I am going to ignore entire file
|
||||
"tests/test_cli.py"
|
||||
];
|
||||
|
||||
meta = {
|
||||
changelog = "https://github.com/cruft/cruft/blob/${version}/CHANGELOG.md";
|
||||
description = "Allows you to maintain all the necessary boilerplate for building projects";
|
||||
homepage = "https://github.com/cruft/cruft";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "cruft";
|
||||
maintainers = with lib.maintainers; [ perchun ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
cwtch,
|
||||
fetchgit,
|
||||
flutter,
|
||||
lib,
|
||||
tor,
|
||||
zenity,
|
||||
}:
|
||||
let
|
||||
runtimeBinDependencies = [
|
||||
tor
|
||||
zenity
|
||||
];
|
||||
in
|
||||
flutter.buildFlutterApplication rec {
|
||||
pname = "cwtch-ui";
|
||||
version = "1.15.1";
|
||||
# This Gitea instance has archive downloads disabled, so: fetchgit
|
||||
src = fetchgit {
|
||||
url = "https://git.openprivacy.ca/cwtch.im/cwtch-ui";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-+UtWhQMhm0UjY0kx3B5TwcBFhUfJma3rpeYls4XWy7I=";
|
||||
};
|
||||
|
||||
# NOTE: The included pubspec.json does not exactly match the upstream
|
||||
# pubspec.lock. With Flutter 3.24, a newer version of material_color_utilities
|
||||
# is required than the upstream locked version. From a checkout of cwtch-ui
|
||||
# 1.15.1, I ran `flutter pub upgrade material_color_utilities` on 2024-10-17.
|
||||
# This upgraded material_color_utilities and its dependencies.
|
||||
pubspecLock = lib.importJSON ./pubspec.json;
|
||||
gitHashes = {
|
||||
flutter_gherkin = "sha256-Y8tR84kkczQPBwh7cGhPFAAqrMZKRfGp/02huPaaQZg=";
|
||||
};
|
||||
|
||||
flutterBuildFlags = [
|
||||
"--dart-define"
|
||||
"BUILD_VER=${version}"
|
||||
"--dart-define"
|
||||
"BUILD_DATE=1980-01-01-00:00"
|
||||
];
|
||||
|
||||
# These things are added to LD_LIBRARY_PATH, but not PATH
|
||||
runtimeDependencies = [ cwtch ];
|
||||
|
||||
extraWrapProgramArgs = "--prefix PATH : ${lib.makeBinPath runtimeBinDependencies}";
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/applications
|
||||
substitute linux/cwtch.template.desktop "$out/share/applications/cwtch.desktop" \
|
||||
--replace-fail PREFIX "$out"
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Messaging app built on the cwtch decentralized, privacy-preserving, multi-party messaging protocol";
|
||||
homepage = "https://cwtch.im/";
|
||||
changelog = "https://docs.cwtch.im/changelog";
|
||||
license = lib.licenses.mit;
|
||||
mainProgram = "cwtch";
|
||||
platforms = [ "x86_64-linux" ];
|
||||
maintainers = [ lib.maintainers.gmacon ];
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
{
|
||||
buildGoModule,
|
||||
fetchgit,
|
||||
lib,
|
||||
}:
|
||||
buildGoModule rec {
|
||||
pname = "libcwtch";
|
||||
version = "0.1.3";
|
||||
# This Gitea instance has archive downloads disabled, so: fetchgit
|
||||
src = fetchgit {
|
||||
url = "https://git.openprivacy.ca/cwtch.im/autobindings.git";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-Gp6JnyjKChB7w0fUHkreu81QRXQ3YdMb2ReD/VCkVOE=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-UgG/yjupUFrfjPZ4E+OM1VsWi9MuMuHxcZ4yvz+q/Y0=";
|
||||
overrideModAttrs = (
|
||||
old: {
|
||||
preBuild = ''
|
||||
make lib.go
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
postPatch = ''
|
||||
substituteInPlace Makefile \
|
||||
--replace-fail '$(shell git describe --tags)' v${version} \
|
||||
--replace-fail '$(shell git log -1 --format=%cd --date=format:%G-%m-%d-%H-%M)' 1980-01-01-00-00
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
make linux
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
install -D build/linux/libCwtch.h -t $out/include
|
||||
install -D build/linux/libCwtch.*.so $out/lib/libCwtch.so
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Decentralized, privacy-preserving, multi-party messaging protocol";
|
||||
homepage = "https://cwtch.im/";
|
||||
changelog = "https://docs.cwtch.im/changelog";
|
||||
license = lib.licenses.mit;
|
||||
platforms = lib.platforms.linux;
|
||||
maintainers = [ lib.maintainers.gmacon ];
|
||||
};
|
||||
}
|
||||
@@ -27,7 +27,7 @@ buildNpmPackage rec {
|
||||
homepage = "https://github.com/olrtg/emmet-language-server";
|
||||
changelog = "https://github.com/olrtg/emmet-language-server/releases/tag/v${version}";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [ stnley ];
|
||||
maintainers = [ ];
|
||||
mainProgram = "emmet-language-server";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ buildGoModule {
|
||||
# Enabling silkworm also breaks the build as it requires dynamically linked libraries.
|
||||
# If we need it in the future, we should consider packaging silkworm and silkworm-go
|
||||
# as depenedencies explicitly.
|
||||
tags = "-tags=nosqlite,noboltdb,nosilkworm";
|
||||
tags = [ "nosqlite" "noboltdb" "nosilkworm" ];
|
||||
|
||||
passthru.updateScript = nix-update-script { };
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Start this shell with:
|
||||
# nix-shell path/to/root/of/nixpkgs -A flattenReferencesGraph.dev-shell
|
||||
{
|
||||
mkShell,
|
||||
callPackage,
|
||||
python3Packages,
|
||||
}:
|
||||
let
|
||||
helpers = callPackage (import ./helpers.nix) { };
|
||||
in
|
||||
mkShell {
|
||||
inputsFrom = [ (callPackage (import ./package.nix) { }) ];
|
||||
buildInputs = [
|
||||
helpers.format
|
||||
helpers.lint
|
||||
helpers.unittest
|
||||
# This is needed to plot graphs when DEBUG_PLOT is set to True.
|
||||
python3Packages.pycairo
|
||||
# This can be used on linux to display the graphs.
|
||||
# On other platforms the image viewer needs to be set with
|
||||
# DEBUG_PLOT_IMAGE_VIEWER env var.
|
||||
# pkgs.gwenview
|
||||
];
|
||||
shellHook = ''
|
||||
echo '
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
|
||||
Commands useful for development (should be executed from scr dir):
|
||||
|
||||
|
||||
format
|
||||
* formats all files in place using autopep8
|
||||
|
||||
lint
|
||||
* lints all files using flake8
|
||||
|
||||
unittest
|
||||
* runs all unit tests
|
||||
|
||||
following env vars can be set to enable extra output in tests:
|
||||
- DEBUG=True - enable debug logging
|
||||
- DEBUG_PLOT=True - plot graphs processed by split_paths.py and
|
||||
subcomponent.py
|
||||
- DEBUG_PLOT_IMAGE_VIEWER=$PATH_OF_IMAGE_VIEWER_APP - app used to
|
||||
display plots (default: gwenview)
|
||||
- DEBUG_PLOT_SAVE_BASE_NAME=$SOME_NAME - if set, plots will be saved
|
||||
to files instead of displayed with image viewer
|
||||
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
'
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
bash,
|
||||
writers,
|
||||
python3Packages,
|
||||
}:
|
||||
let
|
||||
writeCheckedBashBin =
|
||||
name:
|
||||
let
|
||||
interpreter = "${bash}/bin/bash";
|
||||
in
|
||||
writers.makeScriptWriter {
|
||||
inherit interpreter;
|
||||
check = "${interpreter} -n $1";
|
||||
} "/bin/${name}";
|
||||
|
||||
# Helpers used during build/development.
|
||||
lint = writeCheckedBashBin "lint" ''
|
||||
${python3Packages.flake8}/bin/flake8 --show-source ''${@}
|
||||
'';
|
||||
|
||||
unittest = writeCheckedBashBin "unittest" ''
|
||||
if [ "$#" -eq 0 ]; then
|
||||
set -- discover -p '*_test.py'
|
||||
fi
|
||||
|
||||
${python3Packages.python}/bin/python -m unittest "''${@}"
|
||||
'';
|
||||
|
||||
format = writeCheckedBashBin "format" ''
|
||||
${python3Packages.autopep8}/bin/autopep8 -r -i . "''${@}"
|
||||
'';
|
||||
in
|
||||
{
|
||||
inherit format lint unittest;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
callPackage,
|
||||
lib,
|
||||
python3Packages,
|
||||
}:
|
||||
let
|
||||
inherit (lib) fileset;
|
||||
helpers = callPackage ./helpers.nix { };
|
||||
pythonPackages = python3Packages;
|
||||
in
|
||||
pythonPackages.buildPythonApplication {
|
||||
version = "0.1.0";
|
||||
pname = "flatten-references-graph";
|
||||
|
||||
src = fileset.toSource {
|
||||
root = ./src;
|
||||
fileset = fileset.unions [
|
||||
./src/.flake8
|
||||
./src/flatten_references_graph
|
||||
./src/setup.py
|
||||
];
|
||||
};
|
||||
|
||||
propagatedBuildInputs = with pythonPackages; [
|
||||
igraph
|
||||
toolz
|
||||
];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
checkPhase = ''
|
||||
${helpers.unittest}/bin/unittest
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
dev-shell = callPackage ./dev-shell.nix { };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
[flake8]
|
||||
max-line-length = 80
|
||||
[pep8]
|
||||
aggressive = 1
|
||||
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
@@ -0,0 +1,48 @@
|
||||
import json as json
|
||||
import sys as sys
|
||||
|
||||
from .lib import debug, load_json
|
||||
from .flatten_references_graph import flatten_references_graph
|
||||
|
||||
|
||||
def main_impl(file_path):
|
||||
debug(f"loading json from {file_path}")
|
||||
|
||||
data = load_json(file_path)
|
||||
|
||||
# These are required
|
||||
references_graph = data["graph"]
|
||||
pipeline = data["pipeline"]
|
||||
|
||||
# This is optional
|
||||
exclude_paths = data.get("exclude_paths")
|
||||
|
||||
debug("references_graph", references_graph)
|
||||
debug("pipeline", pipeline)
|
||||
debug("exclude_paths", exclude_paths)
|
||||
|
||||
result = flatten_references_graph(
|
||||
references_graph,
|
||||
pipeline,
|
||||
exclude_paths=exclude_paths
|
||||
)
|
||||
|
||||
debug("result", result)
|
||||
|
||||
return json.dumps(
|
||||
result,
|
||||
# For reproducibility.
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
# Avoid tailing whitespaces.
|
||||
separators=(",", ": ")
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
file_path = sys.argv[1]
|
||||
print(main_impl(file_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,52 @@
|
||||
import unittest
|
||||
import inspect as inspect
|
||||
|
||||
from .__main__ import main_impl
|
||||
from .lib import path_relative_to_file
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class TestMain(unittest.TestCase):
|
||||
|
||||
def test_main_impl(self):
|
||||
|
||||
file_path = path_relative_to_file(
|
||||
__file__,
|
||||
"__test_fixtures/flatten-references-graph-main-input.json"
|
||||
)
|
||||
|
||||
result = main_impl(file_path)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
inspect.cleandoc(
|
||||
"""
|
||||
[
|
||||
[
|
||||
"B"
|
||||
],
|
||||
[
|
||||
"C"
|
||||
],
|
||||
[
|
||||
"A"
|
||||
]
|
||||
]
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
def test_main_impl2(self):
|
||||
file_path = path_relative_to_file(
|
||||
__file__,
|
||||
"__test_fixtures/flatten-references-graph-main-input-no-paths.json"
|
||||
)
|
||||
|
||||
result = main_impl(file_path)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
inspect.cleandoc("[]")
|
||||
)
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"closureSize": 1,
|
||||
"narHash": "sha256:a",
|
||||
"narSize": 2,
|
||||
"path": "A",
|
||||
"references": [
|
||||
"A",
|
||||
"B",
|
||||
"C"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 3,
|
||||
"narHash": "sha256:b",
|
||||
"narSize": 4,
|
||||
"path": "B",
|
||||
"references": [
|
||||
"C"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 5,
|
||||
"narHash": "sha256:c",
|
||||
"narSize": 6,
|
||||
"path": "C",
|
||||
"references": [
|
||||
"C"
|
||||
]
|
||||
}
|
||||
]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"graph": [
|
||||
{
|
||||
"closureSize": 168,
|
||||
"narHash": "sha256:0dl4kfhb493yz8a5wgh0d2z3kr61z65gp85vx33rqwa1m1lnymy8",
|
||||
"narSize": 168,
|
||||
"path": "/nix/store/fakehash000000000000000000000000-no-store-paths-base.json",
|
||||
"references": []
|
||||
}
|
||||
],
|
||||
"pipeline": [
|
||||
[
|
||||
"popularity_contest"
|
||||
],
|
||||
[
|
||||
"limit_layers",
|
||||
99
|
||||
]
|
||||
],
|
||||
"exclude_paths": [
|
||||
"/nix/store/fakehash000000000000000000000000-no-store-paths-base.json"
|
||||
]
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"graph": [
|
||||
{
|
||||
"closureSize": 1,
|
||||
"narHash": "sha256:a",
|
||||
"narSize": 2,
|
||||
"path": "A",
|
||||
"references": [
|
||||
"A",
|
||||
"B",
|
||||
"C"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 3,
|
||||
"narHash": "sha256:b",
|
||||
"narSize": 4,
|
||||
"path": "B",
|
||||
"references": [
|
||||
"C"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 5,
|
||||
"narHash": "sha256:c",
|
||||
"narSize": 6,
|
||||
"path": "C",
|
||||
"references": [
|
||||
"C"
|
||||
]
|
||||
}
|
||||
],
|
||||
"pipeline": [
|
||||
["split_paths", ["B"]]
|
||||
]
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
from toolz import curried as tlz
|
||||
|
||||
from .lib import (
|
||||
flatten,
|
||||
over,
|
||||
references_graph_to_igraph
|
||||
)
|
||||
|
||||
from .pipe import pipe
|
||||
|
||||
MAX_LAYERS = 127
|
||||
|
||||
|
||||
def create_list_of_lists_of_strings(deeply_nested_lists_or_dicts_of_graphs):
|
||||
list_of_graphs = flatten(deeply_nested_lists_or_dicts_of_graphs)
|
||||
|
||||
return list(
|
||||
filter(
|
||||
# remove empty layers
|
||||
lambda xs: len(xs) > 0,
|
||||
tlz.map(
|
||||
lambda g: g.vs["name"],
|
||||
list_of_graphs
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def flatten_references_graph(references_graph, pipeline, exclude_paths=None):
|
||||
if exclude_paths is not None:
|
||||
exclude_paths = frozenset(exclude_paths)
|
||||
references_graph = tlz.compose(
|
||||
tlz.map(over(
|
||||
"references",
|
||||
lambda xs: frozenset(xs).difference(exclude_paths)
|
||||
)),
|
||||
tlz.remove(lambda node: node["path"] in exclude_paths)
|
||||
)(references_graph)
|
||||
|
||||
igraph_graph = references_graph_to_igraph(references_graph)
|
||||
|
||||
return create_list_of_lists_of_strings(pipe(
|
||||
pipeline,
|
||||
igraph_graph
|
||||
))
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import unittest
|
||||
from .flatten_references_graph import flatten_references_graph
|
||||
# from .lib import path_relative_to_file, load_json
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
references_graph = [
|
||||
{
|
||||
"closureSize": 1,
|
||||
"narHash": "sha256:a",
|
||||
"narSize": 2,
|
||||
"path": "A",
|
||||
"references": [
|
||||
"A",
|
||||
"C",
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 3,
|
||||
"narHash": "sha256:b",
|
||||
"narSize": 4,
|
||||
"path": "B",
|
||||
"references": [
|
||||
"C",
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 5,
|
||||
"narHash": "sha256:c",
|
||||
"narSize": 6,
|
||||
"path": "C",
|
||||
"references": [
|
||||
"C"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 7,
|
||||
"narHash": "sha256:d",
|
||||
"narSize": 8,
|
||||
"path": "D",
|
||||
"references": [
|
||||
"D"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_flatten_references_graph(self):
|
||||
pipeline = [
|
||||
["split_paths", ["B"]],
|
||||
]
|
||||
|
||||
result = flatten_references_graph(references_graph, pipeline)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
[
|
||||
# B and it's exclusive deps
|
||||
["B", "D"],
|
||||
# Common deps
|
||||
["C"],
|
||||
# Rest (without common deps)
|
||||
["A"]
|
||||
]
|
||||
)
|
||||
|
||||
pipeline = [
|
||||
["split_paths", ["B"]],
|
||||
["over", "main", ["subcomponent_in", ["B"]]],
|
||||
]
|
||||
|
||||
result = flatten_references_graph(references_graph, pipeline)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
[
|
||||
["B"],
|
||||
["D"],
|
||||
["C"],
|
||||
["A"]
|
||||
]
|
||||
)
|
||||
|
||||
def test_flatten_references_graph_exclude_paths(self):
|
||||
pipeline = [
|
||||
["split_paths", ["B"]],
|
||||
]
|
||||
|
||||
result = flatten_references_graph(
|
||||
references_graph,
|
||||
pipeline,
|
||||
exclude_paths=["A"]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
[
|
||||
# A was excluded so there is no "rest" or "common" layer
|
||||
["B", "C", "D"]
|
||||
]
|
||||
)
|
||||
|
||||
result = flatten_references_graph(
|
||||
references_graph,
|
||||
pipeline,
|
||||
exclude_paths=["D"]
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
result,
|
||||
[
|
||||
# D removed from this layer
|
||||
["B"],
|
||||
["C"],
|
||||
["A"]
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,329 @@
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
from toolz import curried as tlz
|
||||
from toolz import curry
|
||||
import igraph as igraph
|
||||
import itertools as itertools
|
||||
import json as json
|
||||
import os as os
|
||||
import re as re
|
||||
import sys
|
||||
|
||||
DEBUG = os.environ.get("DEBUG", False) == "True"
|
||||
DEBUG_PLOT = os.environ.get("DEBUG_PLOT", False) == "True"
|
||||
# If this is set, the plots will be saved to files instead of being displayed
|
||||
# with default image viewer.
|
||||
DEBUG_PLOT_SAVE_BASE_NAME = os.environ.get("DEBUG_PLOT_SAVE_BASE_NAME")
|
||||
|
||||
c = igraph.configuration.init()
|
||||
# App used to open the plots when DEBUG_PLOT_SAVE_BASE_NAME is not set.
|
||||
c["apps.image_viewer"] = os.environ.get("DEBUG_PLOT_IMAGE_VIEWER", "gwenview")
|
||||
|
||||
|
||||
def debug(*args, **kwargs):
|
||||
if DEBUG:
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def debug_plot(graph, name, **kwargs):
|
||||
if not DEBUG_PLOT:
|
||||
return
|
||||
|
||||
vertex_label = [
|
||||
# remove /nix/store/HASH- prefix from labels
|
||||
re.split("^/nix/store/[a-z0-9]{32}-", name)[-1]
|
||||
for name in graph.vs["name"]
|
||||
]
|
||||
|
||||
save_as = (
|
||||
None if DEBUG_PLOT_SAVE_BASE_NAME is None
|
||||
else DEBUG_PLOT_SAVE_BASE_NAME + name + ".png"
|
||||
)
|
||||
|
||||
igraph.plot(
|
||||
graph,
|
||||
save_as,
|
||||
vertex_label=vertex_label,
|
||||
**(tlz.merge(
|
||||
{
|
||||
# "bbox": (3840, 2160),
|
||||
"bbox": (800, 600),
|
||||
"margin": 100,
|
||||
"vertex_label_dist": -5,
|
||||
"edge_color": "orange",
|
||||
"vertex_size": 20,
|
||||
"vertex_label_size": 30,
|
||||
"edge_arrow_size": 2
|
||||
},
|
||||
kwargs
|
||||
)),
|
||||
)
|
||||
|
||||
|
||||
def debug_plot_with_highligth(g, vs, layout):
|
||||
debug_plot(
|
||||
g,
|
||||
layout=layout,
|
||||
# layout=Layout(new_coords),
|
||||
vertex_color=[
|
||||
"green" if v.index in vs else "red"
|
||||
for v in g.vs
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@curry
|
||||
def pick_keys(keys, d):
|
||||
return {
|
||||
key: d[key] for key in keys if key in d
|
||||
}
|
||||
|
||||
|
||||
def unnest_iterable(xs):
|
||||
return itertools.chain.from_iterable(xs)
|
||||
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@curry
|
||||
def sorted_by(key, xs):
|
||||
return sorted(xs, key=lambda x: x[key])
|
||||
|
||||
|
||||
@curry
|
||||
def find_vertex_by_name_or_none(graph, name):
|
||||
try:
|
||||
# NOTE: find by name is constant time.
|
||||
return graph.vs.find(name)
|
||||
# This will be thrown if vertex with given name is not found.
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def subcomponent_multi(graph, vertices, mode="out"):
|
||||
"""Return concatenated subcomponents generated by the given list of
|
||||
vertices.
|
||||
"""
|
||||
return tlz.mapcat(
|
||||
lambda vertex: graph.subcomponent(vertex, mode=mode),
|
||||
vertices
|
||||
)
|
||||
|
||||
|
||||
@curry
|
||||
def edges_for_reference_graph_node(path_to_size_dict, reference_graph_node):
|
||||
source = reference_graph_node["path"]
|
||||
return map(
|
||||
lambda x: {"source": source, "target": x},
|
||||
sorted(
|
||||
filter(
|
||||
# references might contain source
|
||||
lambda x: x != source,
|
||||
reference_graph_node["references"]
|
||||
),
|
||||
key=lambda x: 1 * path_to_size_dict[x]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
reference_graph_node_keys_to_keep = [
|
||||
"closureSize",
|
||||
"narSize"
|
||||
]
|
||||
|
||||
pick_reference_graph_node_keys = pick_keys(reference_graph_node_keys_to_keep)
|
||||
|
||||
|
||||
def vertex_from_reference_graph_node(reference_graph_node):
|
||||
return tlz.merge(
|
||||
{"name": reference_graph_node["path"]},
|
||||
pick_reference_graph_node_keys(reference_graph_node)
|
||||
)
|
||||
|
||||
|
||||
def references_graph_to_igraph(references_graph):
|
||||
"""
|
||||
Converts result of exportReferencesGraph into an igraph directed graph.
|
||||
Uses paths as igraph node names, and sets closureSize and narSize as
|
||||
properties of igraph nodes.
|
||||
"""
|
||||
debug('references_graph', references_graph)
|
||||
references_graph = sorted(references_graph, key=lambda x: 1 * x["narSize"])
|
||||
|
||||
# Short circuit since DictList throws an error if first argument (vertices)
|
||||
# contains no elements.
|
||||
# The error is: KeyError: 'name'
|
||||
# here: https://github.com/igraph/python-igraph/blob/da7484807f5152a2c18c55dd4154653de2c7f5f7/src/igraph/__init__.py#L3091 # noqa: E501
|
||||
# This looks like a bug.
|
||||
if len(references_graph) == 0:
|
||||
return empty_directed_graph()
|
||||
|
||||
path_to_size_dict = {
|
||||
node["path"]: node["narSize"] for node in references_graph
|
||||
}
|
||||
|
||||
debug('path_to_size_dict', path_to_size_dict)
|
||||
|
||||
return igraph.Graph.DictList(
|
||||
map(vertex_from_reference_graph_node, references_graph),
|
||||
unnest_iterable(map(
|
||||
edges_for_reference_graph_node(path_to_size_dict),
|
||||
references_graph
|
||||
)),
|
||||
directed=True
|
||||
)
|
||||
|
||||
|
||||
@curry
|
||||
def graph_vertex_index_to_name(graph, index):
|
||||
return graph.vs[index]["name"]
|
||||
|
||||
|
||||
def igraph_to_reference_graph(igraph_instance):
|
||||
return [
|
||||
tlz.merge(
|
||||
{
|
||||
"path": v["name"],
|
||||
"references": list(map(
|
||||
graph_vertex_index_to_name(igraph_instance),
|
||||
igraph_instance.successors(v.index)
|
||||
))
|
||||
},
|
||||
pick_reference_graph_node_keys(v.attributes())
|
||||
)
|
||||
for v in igraph_instance.vs
|
||||
]
|
||||
|
||||
|
||||
def load_closure_graph(file_path):
|
||||
return references_graph_to_igraph(load_json(file_path))
|
||||
|
||||
|
||||
def path_relative_to_file(file_path_from, file_path):
|
||||
dir_path = Path(file_path_from).parent
|
||||
return dir_path / file_path
|
||||
|
||||
|
||||
def is_None(x):
|
||||
return x is None
|
||||
|
||||
|
||||
def not_None(x):
|
||||
return x is not None
|
||||
|
||||
|
||||
def print_layers(layers):
|
||||
debug("\n::::LAYERS:::::")
|
||||
for index, layer in enumerate(layers):
|
||||
debug("")
|
||||
debug("layer index:", index)
|
||||
debug("[")
|
||||
for v in layer.vs["name"]:
|
||||
debug(" ", v)
|
||||
debug("]")
|
||||
|
||||
|
||||
def print_vs(graph):
|
||||
for v in graph.vs:
|
||||
debug(v)
|
||||
|
||||
|
||||
def directed_graph(edges, vertices=None, vertex_attrs=[]):
|
||||
graph = igraph.Graph.TupleList(edges, directed=True)
|
||||
|
||||
# Add detached vertices (without edges) if any.
|
||||
if vertices is not None:
|
||||
graph = graph + vertices
|
||||
|
||||
# Add vertex attributes if any.
|
||||
for (name, attrs_dict) in vertex_attrs:
|
||||
vertex = graph.vs.find(name)
|
||||
|
||||
for (k, v) in attrs_dict.items():
|
||||
vertex[k] = v
|
||||
|
||||
return graph
|
||||
|
||||
|
||||
def empty_directed_graph():
|
||||
return directed_graph([])
|
||||
|
||||
|
||||
def graph_is_empty(graph):
|
||||
return len(graph.vs) == 0
|
||||
|
||||
|
||||
def pick_attrs(attrs, x):
|
||||
return {attr: getattr(x, attr) for attr in attrs}
|
||||
|
||||
|
||||
def merge_graphs(graphs):
|
||||
return tlz.reduce(lambda acc, g: acc + g, graphs, empty_directed_graph())
|
||||
|
||||
|
||||
# Functions below can be used in user defined pipeline (see pipe.py).
|
||||
# All functions need to be curried, and the user needs to be able to
|
||||
# provide values for all arguments apart from the last one from nix code.
|
||||
@curry
|
||||
def over(prop_name, func, dictionary):
|
||||
value = dictionary[prop_name]
|
||||
return tlz.assoc(dictionary, prop_name, func(value))
|
||||
|
||||
|
||||
# One argument functions also need to be curried to simplify processing of the
|
||||
# pipeline.
|
||||
@curry
|
||||
def flatten(xs):
|
||||
xs = xs.values() if isinstance(xs, dict) else xs
|
||||
for x in xs:
|
||||
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
|
||||
yield from flatten(x)
|
||||
else:
|
||||
yield x
|
||||
|
||||
|
||||
@curry
|
||||
def split_every(count, graph):
|
||||
vs = graph.vs
|
||||
return [
|
||||
graph.induced_subgraph(vs[x:x + count])
|
||||
for x in range(0, len(vs), count)
|
||||
]
|
||||
|
||||
|
||||
@curry
|
||||
def limit_layers(max_count, graphs):
|
||||
assert max_count > 0, "max count needs to > 0"
|
||||
|
||||
graphs_iterator = iter(graphs)
|
||||
|
||||
return tlz.concat([
|
||||
tlz.take(max_count - 1, graphs_iterator),
|
||||
# Merges all graphs remaining in the iterator, after initial
|
||||
# max_count - 1 have been taken.
|
||||
(lambda: (yield merge_graphs(graphs_iterator)))()
|
||||
])
|
||||
|
||||
|
||||
@curry
|
||||
def remove_paths(paths, graph):
|
||||
# Allow passing a single path.
|
||||
if isinstance(paths, str):
|
||||
paths = [paths]
|
||||
|
||||
indices_to_remove = tlz.compose(
|
||||
list,
|
||||
tlz.map(lambda v: v.index),
|
||||
tlz.remove(is_None),
|
||||
tlz.map(find_vertex_by_name_or_none(graph))
|
||||
)(paths)
|
||||
|
||||
return graph - indices_to_remove if len(indices_to_remove) > 0 else graph
|
||||
|
||||
|
||||
@curry
|
||||
def reverse(iterator):
|
||||
return reversed(list(iterator))
|
||||
@@ -0,0 +1,199 @@
|
||||
import unittest
|
||||
|
||||
from toolz import curried as tlz
|
||||
|
||||
from . import test_helpers as th
|
||||
|
||||
from .lib import (
|
||||
directed_graph,
|
||||
igraph_to_reference_graph,
|
||||
limit_layers,
|
||||
pick_keys,
|
||||
references_graph_to_igraph,
|
||||
reference_graph_node_keys_to_keep
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
references_graph = [
|
||||
{
|
||||
"closureSize": 3,
|
||||
"narHash": "sha256:d",
|
||||
"narSize": 0,
|
||||
"path": "D",
|
||||
"references": [
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 3,
|
||||
"narHash": "sha256:b",
|
||||
"narSize": 4,
|
||||
"path": "B",
|
||||
"references": [
|
||||
"B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 3,
|
||||
"narHash": "sha256:e",
|
||||
"narSize": 5,
|
||||
"path": "E",
|
||||
"references": [
|
||||
"E"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 1,
|
||||
"narHash": "sha256:a",
|
||||
"narSize": 10,
|
||||
"path": "A",
|
||||
"references": [
|
||||
# most of the time references contain self path, but not always.
|
||||
"C",
|
||||
"B",
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 5,
|
||||
"narHash": "sha256:c",
|
||||
"narSize": 6,
|
||||
"path": "C",
|
||||
"references": [
|
||||
"C",
|
||||
"E",
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"closureSize": 5,
|
||||
"narHash": "sha256:f",
|
||||
"narSize": 2,
|
||||
"path": "F",
|
||||
"references": [
|
||||
"F"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class TestLib(unittest.TestCase, th.CustomAssertions):
|
||||
|
||||
def test_references_graph_to_igraph(self):
|
||||
|
||||
graph = references_graph_to_igraph(references_graph)
|
||||
|
||||
pick_preserved_keys = pick_keys(reference_graph_node_keys_to_keep)
|
||||
|
||||
self.assertGraphEqual(
|
||||
graph,
|
||||
directed_graph(
|
||||
[
|
||||
("A", "B"),
|
||||
("A", "C"),
|
||||
("C", "E"),
|
||||
("C", "D"),
|
||||
],
|
||||
["F"],
|
||||
# Add "narSize" and "closureSize" attributes to each node.
|
||||
map(
|
||||
lambda node: (node["path"], pick_preserved_keys(node)),
|
||||
references_graph
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def test_references_graph_to_igraph_one_node(self):
|
||||
|
||||
references_graph = [
|
||||
{
|
||||
'closureSize': 168,
|
||||
'narHash': 'sha256:0dl4',
|
||||
'narSize': 168,
|
||||
'path': 'A',
|
||||
'references': []
|
||||
}
|
||||
]
|
||||
|
||||
graph = references_graph_to_igraph(references_graph)
|
||||
|
||||
pick_preserved_keys = pick_keys(reference_graph_node_keys_to_keep)
|
||||
|
||||
self.assertGraphEqual(
|
||||
graph,
|
||||
directed_graph(
|
||||
[],
|
||||
["A"],
|
||||
# Add "narSize" and "closureSize" attributes to each node.
|
||||
map(
|
||||
lambda node: (node["path"], pick_preserved_keys(node)),
|
||||
references_graph
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def test_references_graph_to_igraph_zero_nodes(self):
|
||||
|
||||
references_graph = []
|
||||
|
||||
graph = references_graph_to_igraph(references_graph)
|
||||
|
||||
self.assertGraphEqual(
|
||||
graph,
|
||||
directed_graph(
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
def test_igraph_to_reference_graph(self):
|
||||
|
||||
graph = references_graph_to_igraph(references_graph)
|
||||
|
||||
nodes_by_path = {
|
||||
node["path"]: node for node in references_graph
|
||||
}
|
||||
|
||||
result = igraph_to_reference_graph(graph)
|
||||
|
||||
self.assertEqual(
|
||||
len(result),
|
||||
len(references_graph)
|
||||
)
|
||||
|
||||
pick_preserved_keys = pick_keys([
|
||||
"path",
|
||||
*reference_graph_node_keys_to_keep
|
||||
])
|
||||
|
||||
for node in result:
|
||||
original_node = nodes_by_path[node["path"]]
|
||||
|
||||
self.assertDictEqual(
|
||||
pick_preserved_keys(original_node),
|
||||
pick_preserved_keys(node)
|
||||
)
|
||||
|
||||
revove_self_ref = tlz.remove(lambda a: a == node["path"])
|
||||
|
||||
self.assertListEqual(
|
||||
sorted(node["references"]),
|
||||
sorted(revove_self_ref(original_node["references"]))
|
||||
)
|
||||
|
||||
def test_limit_layers_nothing_to_do(self):
|
||||
graph = references_graph_to_igraph(references_graph)
|
||||
|
||||
layers = [graph]
|
||||
result = limit_layers(1, layers)
|
||||
result_list = list(result)
|
||||
|
||||
self.assertEqual(
|
||||
len(result_list),
|
||||
1
|
||||
)
|
||||
|
||||
self.assertGraphEqual(graph, result_list[0])
|
||||
@@ -0,0 +1,80 @@
|
||||
from toolz import curried as tlz
|
||||
from toolz import curry
|
||||
|
||||
from . import lib as lib
|
||||
from . import subcomponent as subcomponent
|
||||
from .popularity_contest import popularity_contest
|
||||
from .split_paths import split_paths
|
||||
|
||||
from .lib import (
|
||||
# references_graph_to_igraph
|
||||
debug,
|
||||
pick_attrs
|
||||
)
|
||||
|
||||
funcs = tlz.merge(
|
||||
pick_attrs(
|
||||
[
|
||||
"flatten",
|
||||
"over",
|
||||
"split_every",
|
||||
"limit_layers",
|
||||
"remove_paths",
|
||||
"reverse"
|
||||
],
|
||||
lib
|
||||
),
|
||||
pick_attrs(
|
||||
[
|
||||
"subcomponent_in",
|
||||
"subcomponent_out",
|
||||
],
|
||||
subcomponent
|
||||
),
|
||||
{
|
||||
"split_paths": split_paths,
|
||||
"popularity_contest": popularity_contest,
|
||||
"map": tlz.map
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@curry
|
||||
def nth_or_none(index, xs):
|
||||
try:
|
||||
return xs[index]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
def preapply_func(func_call_data):
|
||||
[func_name, *args] = func_call_data
|
||||
debug("func_name", func_name)
|
||||
debug("args", args)
|
||||
debug('func_name in ["over"]', func_name in ["over"])
|
||||
|
||||
# TODO: these could be handled in more generic way by defining, for each
|
||||
# function, which of the args are expected to be functions which need
|
||||
# pre-applying.
|
||||
if func_name == "over":
|
||||
[first_arg, second_arg] = args
|
||||
args = [first_arg, preapply_func(second_arg)]
|
||||
|
||||
elif func_name == "map":
|
||||
args = [preapply_func(args[0])]
|
||||
|
||||
return funcs[func_name](*args)
|
||||
|
||||
|
||||
@curry
|
||||
def pipe(pipeline, data):
|
||||
debug("pipeline", pipeline)
|
||||
partial_funcs = list(tlz.map(preapply_func, pipeline))
|
||||
debug('partial_funcs', partial_funcs)
|
||||
return tlz.pipe(
|
||||
data,
|
||||
*partial_funcs
|
||||
)
|
||||
|
||||
|
||||
funcs["pipe"] = pipe
|
||||
@@ -0,0 +1,153 @@
|
||||
import unittest
|
||||
from .pipe import pipe
|
||||
|
||||
from . import test_helpers as th
|
||||
|
||||
from .lib import (
|
||||
directed_graph,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
def make_test_graph():
|
||||
edges = [
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("A", "C"),
|
||||
("B", "D"),
|
||||
("B", "E"),
|
||||
("E", "F"),
|
||||
("B", "G"),
|
||||
("Root2", "B"),
|
||||
("Root3", "C"),
|
||||
]
|
||||
|
||||
return directed_graph(edges)
|
||||
|
||||
|
||||
class CustomAssertions:
|
||||
def runAndAssertResult(self, graph, pipeline, expected_graph_args):
|
||||
result = list(pipe(pipeline, graph))
|
||||
|
||||
for (index, expected_graph_arg) in enumerate(expected_graph_args):
|
||||
|
||||
self.assertGraphEqual(
|
||||
directed_graph(*expected_graph_arg),
|
||||
result[index]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class Test(
|
||||
unittest.TestCase,
|
||||
CustomAssertions,
|
||||
th.CustomAssertions
|
||||
):
|
||||
|
||||
def test_1(self):
|
||||
pipeline = [
|
||||
["split_paths", ["B"]],
|
||||
[
|
||||
"over",
|
||||
"main",
|
||||
[
|
||||
"pipe",
|
||||
[
|
||||
["subcomponent_in", ["B"]],
|
||||
[
|
||||
"over",
|
||||
"rest",
|
||||
["popularity_contest"]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
["flatten"],
|
||||
["map", ["remove_paths", "Root3"]],
|
||||
["limit_layers", 5],
|
||||
]
|
||||
|
||||
expected_graph_args = [
|
||||
# "B"" separated from the rest by "split_paths" and
|
||||
# "subcomponent_in' stages.
|
||||
([], ["B"]),
|
||||
# Deps of "B", split into individual layers by "popularity_contest",
|
||||
# with "F" being most popular
|
||||
([], ["F"]),
|
||||
([], ["D"]),
|
||||
([], ["E"]),
|
||||
# "rest" output of "split_paths" stage with "G" merged into it by
|
||||
# "limit_layers" stage.
|
||||
(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("A", "C")
|
||||
],
|
||||
["Root2", "G"]
|
||||
)
|
||||
]
|
||||
|
||||
self.runAndAssertResult(
|
||||
make_test_graph(),
|
||||
pipeline,
|
||||
expected_graph_args
|
||||
)
|
||||
|
||||
def test_2(self):
|
||||
graph = directed_graph(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
],
|
||||
["Root2"]
|
||||
)
|
||||
self.runAndAssertResult(
|
||||
graph,
|
||||
[
|
||||
["popularity_contest"],
|
||||
],
|
||||
[
|
||||
# Ordered from most to least popular
|
||||
([], ["B"]),
|
||||
([], ["A"]),
|
||||
([], ["Root1"]),
|
||||
([], ["Root2"])
|
||||
]
|
||||
)
|
||||
|
||||
self.runAndAssertResult(
|
||||
graph,
|
||||
[
|
||||
["popularity_contest"],
|
||||
["limit_layers", 3],
|
||||
],
|
||||
[
|
||||
# Most popular first
|
||||
([], ["B"]),
|
||||
([], ["A"]),
|
||||
# Least popular combined
|
||||
([], ["Root1", "Root2"]),
|
||||
]
|
||||
)
|
||||
|
||||
self.runAndAssertResult(
|
||||
graph,
|
||||
[
|
||||
["popularity_contest"],
|
||||
["reverse"],
|
||||
["limit_layers", 3],
|
||||
],
|
||||
[
|
||||
# Least popular first
|
||||
([], ["Root2"]),
|
||||
([], ["Root1"]),
|
||||
# Most popular first
|
||||
([], ["A", "B"])
|
||||
]
|
||||
)
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
# Using a simple algorithm, convert the references to a path in to a
|
||||
# sorted list of dependent paths based on how often they're referenced
|
||||
# and how deep in the tree they live. Equally-"popular" paths are then
|
||||
# sorted by name.
|
||||
#
|
||||
# The existing writeReferencesToFile prints the paths in a simple
|
||||
# ascii-based sorting of the paths.
|
||||
#
|
||||
# Sorting the paths by graph improves the chances that the difference
|
||||
# between two builds appear near the end of the list, instead of near
|
||||
# the beginning. This makes a difference for Nix builds which export a
|
||||
# closure for another program to consume, if that program implements its
|
||||
# own level of binary diffing.
|
||||
#
|
||||
# For an example, Docker Images. If each store path is a separate layer
|
||||
# then Docker Images can be very efficiently transfered between systems,
|
||||
# and we get very good cache reuse between images built with the same
|
||||
# version of Nixpkgs. However, since Docker only reliably supports a
|
||||
# small number of layers (42) it is important to pick the individual
|
||||
# layers carefully. By storing very popular store paths in the first 40
|
||||
# layers, we improve the chances that the next Docker image will share
|
||||
# many of those layers.*
|
||||
#
|
||||
# Given the dependency tree:
|
||||
#
|
||||
# A - B - C - D -\
|
||||
# \ \ \ \
|
||||
# \ \ \ \
|
||||
# \ \ - E ---- F
|
||||
# \- G
|
||||
#
|
||||
# Nodes which have multiple references are duplicated:
|
||||
#
|
||||
# A - B - C - D - F
|
||||
# \ \ \
|
||||
# \ \ \- E - F
|
||||
# \ \
|
||||
# \ \- E - F
|
||||
# \
|
||||
# \- G
|
||||
#
|
||||
# Each leaf node is now replaced by a counter defaulted to 1:
|
||||
#
|
||||
# A - B - C - D - (F:1)
|
||||
# \ \ \
|
||||
# \ \ \- E - (F:1)
|
||||
# \ \
|
||||
# \ \- E - (F:1)
|
||||
# \
|
||||
# \- (G:1)
|
||||
#
|
||||
# Then each leaf counter is merged with its parent node, replacing the
|
||||
# parent node with a counter of 1, and each existing counter being
|
||||
# incremented by 1. That is to say `- D - (F:1)` becomes `- (D:1, F:2)`:
|
||||
#
|
||||
# A - B - C - (D:1, F:2)
|
||||
# \ \ \
|
||||
# \ \ \- (E:1, F:2)
|
||||
# \ \
|
||||
# \ \- (E:1, F:2)
|
||||
# \
|
||||
# \- (G:1)
|
||||
#
|
||||
# Then each leaf counter is merged with its parent node again, merging
|
||||
# any counters, then incrementing each:
|
||||
#
|
||||
# A - B - (C:1, D:2, E:2, F:5)
|
||||
# \ \
|
||||
# \ \- (E:1, F:2)
|
||||
# \
|
||||
# \- (G:1)
|
||||
#
|
||||
# And again:
|
||||
#
|
||||
# A - (B:1, C:2, D:3, E:4, F:8)
|
||||
# \
|
||||
# \- (G:1)
|
||||
#
|
||||
# And again:
|
||||
#
|
||||
# (A:1, B:2, C:3, D:4, E:5, F:9, G:2)
|
||||
#
|
||||
# and then paths have the following "popularity":
|
||||
#
|
||||
# A 1
|
||||
# B 2
|
||||
# C 3
|
||||
# D 4
|
||||
# E 5
|
||||
# F 9
|
||||
# G 2
|
||||
#
|
||||
# and the popularity contest would result in the paths being printed as:
|
||||
#
|
||||
# F
|
||||
# E
|
||||
# D
|
||||
# C
|
||||
# B
|
||||
# G
|
||||
# A
|
||||
#
|
||||
# * Note: People who have used a Dockerfile before assume Docker's
|
||||
# Layers are inherently ordered. However, this is not true -- Docker
|
||||
# layers are content-addressable and are not explicitly layered until
|
||||
# they are composed in to an Image.
|
||||
|
||||
import igraph as igraph
|
||||
|
||||
from collections import defaultdict
|
||||
from operator import eq
|
||||
from toolz import curried as tlz
|
||||
from toolz import curry
|
||||
|
||||
from .lib import (
|
||||
debug,
|
||||
directed_graph,
|
||||
igraph_to_reference_graph,
|
||||
over,
|
||||
pick_keys,
|
||||
reference_graph_node_keys_to_keep
|
||||
)
|
||||
|
||||
eq = curry(eq)
|
||||
|
||||
pick_keys_to_keep = pick_keys(reference_graph_node_keys_to_keep)
|
||||
|
||||
|
||||
# Find paths in the original dataset which are never referenced by
|
||||
# any other paths
|
||||
def find_roots(closures):
|
||||
debug('closures', closures)
|
||||
roots = []
|
||||
|
||||
for closure in closures:
|
||||
path = closure['path']
|
||||
if not any_refer_to(path, closures):
|
||||
roots.append(path)
|
||||
|
||||
return roots
|
||||
|
||||
|
||||
def any_refer_to(path, closures):
|
||||
for closure in closures:
|
||||
if path != closure['path']:
|
||||
if path in closure['references']:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def all_paths(closures):
|
||||
paths = []
|
||||
for closure in closures:
|
||||
paths.append(closure['path'])
|
||||
paths.extend(closure['references'])
|
||||
paths.sort()
|
||||
return list(set(paths))
|
||||
|
||||
|
||||
# Convert:
|
||||
#
|
||||
# [
|
||||
# { path: /nix/store/foo, references: [ /nix/store/foo, /nix/store/bar, /nix/store/baz ] }, # noqa: E501
|
||||
# { path: /nix/store/bar, references: [ /nix/store/bar, /nix/store/baz ] },
|
||||
# { path: /nix/store/baz, references: [ /nix/store/baz, /nix/store/tux ] },
|
||||
# { path: /nix/store/tux, references: [ /nix/store/tux ] }
|
||||
# ]
|
||||
#
|
||||
# To:
|
||||
# {
|
||||
# /nix/store/foo: [ /nix/store/bar, /nix/store/baz ],
|
||||
# /nix/store/bar: [ /nix/store/baz ],
|
||||
# /nix/store/baz: [ /nix/store/tux ] },
|
||||
# /nix/store/tux: [ ]
|
||||
# }
|
||||
#
|
||||
# Note that it drops self-references to avoid loops.
|
||||
|
||||
|
||||
def make_lookup(closures):
|
||||
return {
|
||||
# remove self reference
|
||||
node["path"]: over("references", tlz.remove(eq(node["path"])), node)
|
||||
for node in closures
|
||||
}
|
||||
|
||||
|
||||
# Convert:
|
||||
#
|
||||
# /nix/store/foo with
|
||||
# {
|
||||
# /nix/store/foo: [ /nix/store/bar, /nix/store/baz ],
|
||||
# /nix/store/bar: [ /nix/store/baz ],
|
||||
# /nix/store/baz: [ /nix/store/tux ] },
|
||||
# /nix/store/tux: [ ]
|
||||
# }
|
||||
#
|
||||
# To:
|
||||
#
|
||||
# {
|
||||
# /nix/store/bar: {
|
||||
# /nix/store/baz: {
|
||||
# /nix/store/tux: {}
|
||||
# }
|
||||
# },
|
||||
# /nix/store/baz: {
|
||||
# /nix/store/tux: {}
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
def make_graph_segment_from_root(subgraphs_cache, root, lookup):
|
||||
children = {}
|
||||
for ref in lookup[root]:
|
||||
# make_graph_segment_from_root is a pure function, and will
|
||||
# always return the same result based on a given input. Thus,
|
||||
# cache computation.
|
||||
#
|
||||
# Python's assignment will use a pointer, preventing memory
|
||||
# bloat for large graphs.
|
||||
if ref not in subgraphs_cache:
|
||||
debug("Subgraph Cache miss on {}".format(ref))
|
||||
subgraphs_cache[ref] = make_graph_segment_from_root(
|
||||
subgraphs_cache, ref, lookup
|
||||
)
|
||||
else:
|
||||
debug("Subgraph Cache hit on {}".format(ref))
|
||||
children[ref] = subgraphs_cache[ref]
|
||||
return children
|
||||
|
||||
|
||||
# Convert a graph segment in to a popularity-counted dictionary:
|
||||
#
|
||||
# From:
|
||||
# {
|
||||
# /nix/store/foo: {
|
||||
# /nix/store/bar: {
|
||||
# /nix/store/baz: {
|
||||
# /nix/store/tux: {}
|
||||
# }
|
||||
# }
|
||||
# /nix/store/baz: {
|
||||
# /nix/store/tux: {}
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# to:
|
||||
# [
|
||||
# /nix/store/foo: 1
|
||||
# /nix/store/bar: 2
|
||||
# /nix/store/baz: 4
|
||||
# /nix/store/tux: 6
|
||||
# ]
|
||||
|
||||
def graph_popularity_contest(popularity_cache, full_graph):
|
||||
popularity = defaultdict(int)
|
||||
for path, subgraph in full_graph.items():
|
||||
popularity[path] += 1
|
||||
# graph_popularity_contest is a pure function, and will
|
||||
# always return the same result based on a given input. Thus,
|
||||
# cache computation.
|
||||
#
|
||||
# Python's assignment will use a pointer, preventing memory
|
||||
# bloat for large graphs.
|
||||
if path not in popularity_cache:
|
||||
debug("Popularity Cache miss on", path)
|
||||
popularity_cache[path] = graph_popularity_contest(
|
||||
popularity_cache, subgraph
|
||||
)
|
||||
else:
|
||||
debug("Popularity Cache hit on", path)
|
||||
|
||||
subcontest = popularity_cache[path]
|
||||
for subpath, subpopularity in subcontest.items():
|
||||
debug("Calculating popularity for", subpath)
|
||||
popularity[subpath] += subpopularity + 1
|
||||
|
||||
return popularity
|
||||
|
||||
# Emit a list of packages by popularity, most first:
|
||||
#
|
||||
# From:
|
||||
# [
|
||||
# /nix/store/foo: 1
|
||||
# /nix/store/bar: 1
|
||||
# /nix/store/baz: 2
|
||||
# /nix/store/tux: 2
|
||||
# ]
|
||||
#
|
||||
# To:
|
||||
# [ /nix/store/baz /nix/store/tux /nix/store/bar /nix/store/foo ]
|
||||
|
||||
|
||||
def order_by_popularity(paths):
|
||||
paths_by_popularity = defaultdict(list)
|
||||
popularities = []
|
||||
for path, popularity in paths.items():
|
||||
popularities.append(popularity)
|
||||
paths_by_popularity[popularity].append(path)
|
||||
|
||||
popularities = sorted(set(popularities))
|
||||
|
||||
flat_ordered = []
|
||||
for popularity in popularities:
|
||||
paths = paths_by_popularity[popularity]
|
||||
paths.sort(key=package_name)
|
||||
|
||||
flat_ordered.extend(reversed(paths))
|
||||
return list(reversed(flat_ordered))
|
||||
|
||||
|
||||
def package_name(path):
|
||||
parts = path.split('-')
|
||||
start = parts.pop(0)
|
||||
# don't throw away any data, so the order is always the same.
|
||||
# even in cases where only the hash at the start has changed.
|
||||
parts.append(start)
|
||||
return '-'.join(parts)
|
||||
|
||||
|
||||
@curry
|
||||
def popularity_contest(graph):
|
||||
# Data comes in as an igraph directed graph or in the format produced
|
||||
# by nix's exportReferencesGraph:
|
||||
# [
|
||||
# { path: /nix/store/foo, references: [ /nix/store/foo, /nix/store/bar, /nix/store/baz ] }, # noqa: E501
|
||||
# { path: /nix/store/bar, references: [ /nix/store/bar, /nix/store/baz ] }, # noqa: E501
|
||||
# { path: /nix/store/baz, references: [ /nix/store/baz, /nix/store/tux ] }, # noqa: E501
|
||||
# { path: /nix/store/tux, references: [ /nix/store/tux ] }
|
||||
# ]
|
||||
#
|
||||
# We want to get out a list of paths ordered by how universally,
|
||||
# important they are, ie: tux is referenced by every path, transitively
|
||||
# so it should be #1
|
||||
#
|
||||
# [
|
||||
# /nix/store/tux,
|
||||
# /nix/store/baz,
|
||||
# /nix/store/bar,
|
||||
# /nix/store/foo,
|
||||
# ]
|
||||
#
|
||||
# NOTE: the output is actually a list of igraph graphs with a single vertex
|
||||
# with v["name"] == path, and some properties (defined in
|
||||
# reference_graph_node_keys_to_keep) from the nodes of the input graph
|
||||
# copied as vertex attributes.
|
||||
debug('graph', graph)
|
||||
|
||||
if isinstance(graph, igraph.Graph):
|
||||
graph = igraph_to_reference_graph(graph)
|
||||
|
||||
debug("Finding roots")
|
||||
roots = find_roots(graph)
|
||||
|
||||
debug("Making lookup")
|
||||
lookup = make_lookup(graph)
|
||||
|
||||
full_graph = {}
|
||||
subgraphs_cache = {}
|
||||
for root in roots:
|
||||
debug("Making full graph for", root)
|
||||
full_graph[root] = make_graph_segment_from_root(
|
||||
subgraphs_cache,
|
||||
root,
|
||||
tlz.valmap(
|
||||
tlz.get("references"),
|
||||
lookup
|
||||
)
|
||||
)
|
||||
|
||||
debug("Running contest")
|
||||
contest = graph_popularity_contest({}, full_graph)
|
||||
|
||||
debug("Ordering by popularity")
|
||||
ordered = order_by_popularity(contest)
|
||||
|
||||
debug("Checking for missing paths")
|
||||
missing = []
|
||||
|
||||
for path in all_paths(graph):
|
||||
if path not in ordered:
|
||||
missing.append(path)
|
||||
|
||||
ordered.extend(missing)
|
||||
|
||||
return map(
|
||||
# Turn each path into a graph with 1 vertex.
|
||||
lambda path: directed_graph(
|
||||
# No edges
|
||||
[],
|
||||
# One vertex, with name=path
|
||||
[path],
|
||||
# Setting desired attributes on the vertex.
|
||||
[(path, pick_keys_to_keep(lookup[path]))]
|
||||
),
|
||||
ordered
|
||||
)
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
import unittest
|
||||
from toolz import curry
|
||||
from toolz import curried as tlz
|
||||
|
||||
from . import test_helpers as th
|
||||
|
||||
from .popularity_contest import (
|
||||
all_paths,
|
||||
any_refer_to,
|
||||
find_roots,
|
||||
graph_popularity_contest,
|
||||
make_graph_segment_from_root,
|
||||
make_lookup,
|
||||
popularity_contest,
|
||||
order_by_popularity
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
directed_graph,
|
||||
igraph_to_reference_graph,
|
||||
over
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class CustomAssertions:
|
||||
@curry
|
||||
def assertResultKeys(self, keys, result):
|
||||
self.assertListEqual(
|
||||
list(result.keys()),
|
||||
keys
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Test(
|
||||
unittest.TestCase,
|
||||
CustomAssertions,
|
||||
th.CustomAssertions
|
||||
):
|
||||
|
||||
def test_empty_graph(self):
|
||||
def test_empty(graph):
|
||||
self.assertListEqual(
|
||||
list(popularity_contest(graph)),
|
||||
[]
|
||||
)
|
||||
|
||||
# popularity_contest works with igraph graph or refurence_graph in
|
||||
# form a list of dicts (as returned by nix's exportReferencesGraph)
|
||||
test_empty(directed_graph([]))
|
||||
test_empty([])
|
||||
|
||||
def test_popularity_contest(self):
|
||||
# Making sure vertex attrs are preserved.
|
||||
vertex_props_dict = {
|
||||
"Root1": {"narSize": 1, "closureSize": 2},
|
||||
"B": {"narSize": 3, "closureSize": 4},
|
||||
"X": {"narSize": 5, "closureSize": 6},
|
||||
}
|
||||
edges = [
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("A", "D"),
|
||||
("D", "E"),
|
||||
("B", "D"),
|
||||
("B", "F"),
|
||||
("Root2", "B"),
|
||||
("Root3", "C")
|
||||
]
|
||||
detached_vertices = ["X"]
|
||||
vertex_props = vertex_props_dict.items()
|
||||
|
||||
def test(graph):
|
||||
result = list(popularity_contest(graph))
|
||||
|
||||
expected_paths = [
|
||||
'E',
|
||||
'D',
|
||||
'F',
|
||||
'B',
|
||||
'A',
|
||||
'C',
|
||||
'Root1',
|
||||
'Root2',
|
||||
'Root3',
|
||||
'X'
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
len(result),
|
||||
len(expected_paths)
|
||||
)
|
||||
|
||||
for (index, path) in enumerate(expected_paths):
|
||||
path_props = vertex_props_dict.get(path) or {}
|
||||
|
||||
self.assertGraphEqual(
|
||||
result[index],
|
||||
directed_graph([], [path], [(path, path_props)])
|
||||
)
|
||||
|
||||
graph = directed_graph(edges, detached_vertices, vertex_props)
|
||||
|
||||
test(graph)
|
||||
test(igraph_to_reference_graph(graph))
|
||||
|
||||
|
||||
class TestFindRoots(unittest.TestCase):
|
||||
def test_find_roots(self):
|
||||
self.assertCountEqual(
|
||||
find_roots([
|
||||
{
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/foo",
|
||||
"/nix/store/bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/nix/store/bar",
|
||||
"references": [
|
||||
"/nix/store/bar",
|
||||
"/nix/store/tux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/nix/store/hello",
|
||||
"references": [
|
||||
]
|
||||
}
|
||||
]),
|
||||
["/nix/store/foo", "/nix/store/hello"]
|
||||
)
|
||||
|
||||
|
||||
class TestAnyReferTo(unittest.TestCase):
|
||||
def test_has_references(self):
|
||||
self.assertTrue(
|
||||
any_refer_to(
|
||||
"/nix/store/bar",
|
||||
[
|
||||
{
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/bar"
|
||||
]
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def test_no_references(self):
|
||||
self.assertFalse(
|
||||
any_refer_to(
|
||||
"/nix/store/foo",
|
||||
[
|
||||
{
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/foo",
|
||||
"/nix/store/bar"
|
||||
]
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestAllPaths(unittest.TestCase):
|
||||
def test_returns_all_paths(self):
|
||||
self.assertCountEqual(
|
||||
all_paths([
|
||||
{
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/foo",
|
||||
"/nix/store/bar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/nix/store/bar",
|
||||
"references": [
|
||||
"/nix/store/bar",
|
||||
"/nix/store/tux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/nix/store/hello",
|
||||
"references": [
|
||||
]
|
||||
}
|
||||
]),
|
||||
["/nix/store/foo", "/nix/store/bar",
|
||||
"/nix/store/hello", "/nix/store/tux", ]
|
||||
)
|
||||
|
||||
def test_no_references(self):
|
||||
self.assertFalse(
|
||||
any_refer_to(
|
||||
"/nix/store/foo",
|
||||
[
|
||||
{
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/foo",
|
||||
"/nix/store/bar"
|
||||
]
|
||||
},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class TestMakeLookup(unittest.TestCase):
|
||||
def test_returns_lookp(self):
|
||||
self.assertDictEqual(
|
||||
# "references" in the result are iterators so we need
|
||||
# to convert them to a list before asserting.
|
||||
tlz.valmap(over("references", list), make_lookup([
|
||||
{
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/foo",
|
||||
"/nix/store/bar",
|
||||
"/nix/store/hello"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/nix/store/bar",
|
||||
"references": [
|
||||
"/nix/store/bar",
|
||||
"/nix/store/tux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/nix/store/hello",
|
||||
"references": [
|
||||
]
|
||||
}
|
||||
])),
|
||||
{
|
||||
"/nix/store/foo": {
|
||||
"path": "/nix/store/foo",
|
||||
"references": [
|
||||
"/nix/store/bar",
|
||||
"/nix/store/hello"
|
||||
]
|
||||
},
|
||||
"/nix/store/bar": {
|
||||
"path": "/nix/store/bar",
|
||||
"references": [
|
||||
"/nix/store/tux"
|
||||
]
|
||||
},
|
||||
"/nix/store/hello": {
|
||||
"path": "/nix/store/hello",
|
||||
"references": [
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TestMakeGraphSegmentFromRoot(unittest.TestCase):
|
||||
def test_returns_graph(self):
|
||||
self.assertDictEqual(
|
||||
make_graph_segment_from_root({}, "/nix/store/foo", {
|
||||
"/nix/store/foo": ["/nix/store/bar"],
|
||||
"/nix/store/bar": ["/nix/store/tux"],
|
||||
"/nix/store/tux": [],
|
||||
"/nix/store/hello": [],
|
||||
}),
|
||||
{
|
||||
"/nix/store/bar": {
|
||||
"/nix/store/tux": {}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def test_returns_graph_tiny(self):
|
||||
self.assertDictEqual(
|
||||
make_graph_segment_from_root({}, "/nix/store/tux", {
|
||||
"/nix/store/foo": ["/nix/store/bar"],
|
||||
"/nix/store/bar": ["/nix/store/tux"],
|
||||
"/nix/store/tux": [],
|
||||
}),
|
||||
{}
|
||||
)
|
||||
|
||||
|
||||
class TestGraphPopularityContest(unittest.TestCase):
|
||||
def test_counts_popularity(self):
|
||||
self.assertDictEqual(
|
||||
graph_popularity_contest({}, {
|
||||
"/nix/store/foo": {
|
||||
"/nix/store/bar": {
|
||||
"/nix/store/baz": {
|
||||
"/nix/store/tux": {}
|
||||
}
|
||||
},
|
||||
"/nix/store/baz": {
|
||||
"/nix/store/tux": {}
|
||||
}
|
||||
}
|
||||
}),
|
||||
{
|
||||
"/nix/store/foo": 1,
|
||||
"/nix/store/bar": 2,
|
||||
"/nix/store/baz": 4,
|
||||
"/nix/store/tux": 6,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TestOrderByPopularity(unittest.TestCase):
|
||||
def test_returns_in_order(self):
|
||||
self.assertEqual(
|
||||
order_by_popularity({
|
||||
"/nix/store/foo": 1,
|
||||
"/nix/store/bar": 1,
|
||||
"/nix/store/baz": 2,
|
||||
"/nix/store/tux": 2,
|
||||
}),
|
||||
[
|
||||
"/nix/store/baz",
|
||||
"/nix/store/tux",
|
||||
"/nix/store/bar",
|
||||
"/nix/store/foo"
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,227 @@
|
||||
from toolz import curried as tlz
|
||||
from toolz import curry
|
||||
|
||||
from .lib import (
|
||||
debug,
|
||||
debug_plot,
|
||||
DEBUG_PLOT,
|
||||
find_vertex_by_name_or_none,
|
||||
graph_is_empty,
|
||||
is_None,
|
||||
subcomponent_multi,
|
||||
unnest_iterable
|
||||
)
|
||||
|
||||
|
||||
@curry
|
||||
def coerce_to_singly_rooted_graph(fake_root_name, graph):
|
||||
"""Add single root to the graph connected to all existing roots.
|
||||
|
||||
If graph has only one root, return the graph unchanged and the name
|
||||
of the root vertex.
|
||||
|
||||
Otherwise return a modified graph (copy) and a name of the added root
|
||||
vertex.
|
||||
"""
|
||||
roots = graph.vs.select(lambda v: len(graph.predecessors(v)) == 0)
|
||||
root_names = roots["name"]
|
||||
|
||||
if len(root_names) == 1:
|
||||
return graph, root_names[0]
|
||||
else:
|
||||
edges = [(fake_root_name, v) for v in root_names]
|
||||
graph_with_root = graph + fake_root_name + edges
|
||||
return graph_with_root, fake_root_name
|
||||
|
||||
|
||||
@curry
|
||||
def remove_vertex(vertex_name, graph):
|
||||
"""Remove vertex with given name, returning copy of input graph if vertex
|
||||
with given name is found in the graph
|
||||
"""
|
||||
vertex = find_vertex_by_name_or_none(graph)(vertex_name)
|
||||
|
||||
return graph - vertex_name if vertex else graph
|
||||
|
||||
|
||||
def get_children_of(graph, vertex_names):
|
||||
return unnest_iterable(map(
|
||||
graph.successors,
|
||||
tlz.remove(
|
||||
is_None,
|
||||
map(
|
||||
find_vertex_by_name_or_none(graph),
|
||||
vertex_names
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
|
||||
def as_list(x):
|
||||
return x if isinstance(x, list) else [x]
|
||||
|
||||
|
||||
@curry
|
||||
def split_path_spec_to_indices(graph, split_path_spec):
|
||||
debug("split_path_spec", split_path_spec)
|
||||
if isinstance(split_path_spec, dict):
|
||||
if "children_of" in split_path_spec:
|
||||
children_of = split_path_spec["children_of"]
|
||||
|
||||
return get_children_of(graph, as_list(children_of))
|
||||
else:
|
||||
raise Exception(
|
||||
"Unexpected split path spec: dict with invalid keys."
|
||||
"Valid: [\"children_of\"]"
|
||||
)
|
||||
else:
|
||||
vertex = find_vertex_by_name_or_none(graph)(split_path_spec)
|
||||
return [] if is_None(vertex) else [vertex.index]
|
||||
|
||||
|
||||
call_count = 0
|
||||
|
||||
|
||||
@curry
|
||||
def split_paths(split_paths, graph_in):
|
||||
debug("____")
|
||||
debug("split_paths:", split_paths)
|
||||
debug("graph_in:", graph_in)
|
||||
|
||||
if DEBUG_PLOT:
|
||||
global call_count
|
||||
graph_name_prefix = f"split_paths_{call_count}_"
|
||||
call_count += 1
|
||||
|
||||
# Convert list of split_paths into list of vertex indices. Ignores
|
||||
# split_paths which don"t match any vertices in the graph.
|
||||
# All edges pointing at the indices will be deleted from the graph.
|
||||
split_path_indices = list(unnest_iterable(map(
|
||||
split_path_spec_to_indices(graph_in),
|
||||
split_paths
|
||||
)))
|
||||
|
||||
debug("split_path_indices:", split_path_indices)
|
||||
|
||||
# Short circuit if there is nothing to do (split_paths didn"t match any
|
||||
# vertices in the graph).
|
||||
if len(split_path_indices) == 0:
|
||||
if DEBUG_PLOT:
|
||||
layout = graph_in.layout('tree')
|
||||
debug_plot(graph_in, f"{graph_name_prefix}input", layout=layout)
|
||||
debug_plot(graph_in, f"{graph_name_prefix}result", layout=layout)
|
||||
|
||||
return {"rest": graph_in}
|
||||
|
||||
# If graph has multiple roots, add a single one connecting all existing
|
||||
# roots to make it easy to split the graph into 2 sets of vertices after
|
||||
# deleting edges pointing at split_path_indices.
|
||||
fake_root_name = "__root__"
|
||||
graph, root_name = coerce_to_singly_rooted_graph(fake_root_name, graph_in)
|
||||
|
||||
debug("root_name", root_name)
|
||||
|
||||
if (
|
||||
find_vertex_by_name_or_none(graph)(root_name).index
|
||||
in split_path_indices
|
||||
):
|
||||
if DEBUG_PLOT:
|
||||
layout = graph_in.layout('tree')
|
||||
debug_plot(graph_in, f"{graph_name_prefix}input", layout=layout)
|
||||
debug_plot(
|
||||
graph_in,
|
||||
f"{graph_name_prefix}result",
|
||||
layout=layout,
|
||||
vertex_color="green"
|
||||
)
|
||||
|
||||
return {"main": graph_in}
|
||||
|
||||
# Copy graph if coerce_to_singly_rooted_graph has not already created
|
||||
# a copy, since we are going to mutate the graph and don"t want to
|
||||
# mutate a function argument.
|
||||
graph = graph if graph is not graph_in else graph.copy()
|
||||
|
||||
if DEBUG_PLOT:
|
||||
layout = graph.layout('tree')
|
||||
debug_plot(graph, f"{graph_name_prefix}input", layout=layout)
|
||||
|
||||
# Get incidences of all vertices which can be reached split_path_indices
|
||||
# (including split_path_indices). This is a set of all split_paths and their
|
||||
# dependencies.
|
||||
split_off_vertex_indices = frozenset(
|
||||
subcomponent_multi(graph, split_path_indices))
|
||||
debug("split_off_vertex_indices", split_off_vertex_indices)
|
||||
|
||||
# Delete edges which point at any of the vertices in split_path_indices.
|
||||
graph.delete_edges(_target_in=split_path_indices)
|
||||
|
||||
if DEBUG_PLOT:
|
||||
debug_plot(graph, f"{graph_name_prefix}deleted_edges", layout=layout)
|
||||
|
||||
# Get incidences of all vertices which can be reached from the root. Since
|
||||
# edges pointing at split_path_indices have been deleted, none of the
|
||||
# split_path_indices will be included. Dependencies of rest_with_common will
|
||||
# only be included if they can be reached from any vertex which is itself
|
||||
# not in split_off_vertex_indices.
|
||||
rest_with_common = frozenset(graph.subcomponent(root_name, mode="out"))
|
||||
debug("rest_with_common", rest_with_common)
|
||||
|
||||
# Get a set of all dependencies common to split_path_indices and the rest
|
||||
# of the graph.
|
||||
common = split_off_vertex_indices.intersection(rest_with_common)
|
||||
debug("common", common)
|
||||
|
||||
# Get a set of vertices which cannot be reached from split_path_indices.
|
||||
rest_without_common = rest_with_common.difference(common)
|
||||
debug("rest_without_common", rest_without_common)
|
||||
|
||||
# Get a set of split_path_indices and their dependencies which cannot be
|
||||
# reached from the rest of the graph.
|
||||
split_off_without_common = split_off_vertex_indices.difference(common)
|
||||
debug("split_off_without_common", split_off_without_common)
|
||||
|
||||
if DEBUG_PLOT:
|
||||
def choose_color(index):
|
||||
if (index in split_off_without_common):
|
||||
return "green"
|
||||
elif (index in rest_without_common):
|
||||
return "red"
|
||||
else:
|
||||
return "purple"
|
||||
|
||||
vertex_color = [choose_color(v.index) for v in graph.vs]
|
||||
|
||||
debug_plot(
|
||||
graph,
|
||||
f"{graph_name_prefix}result",
|
||||
layout=layout,
|
||||
vertex_color=vertex_color
|
||||
)
|
||||
|
||||
# Return subgraphs based on calculated sets of vertices.
|
||||
|
||||
result_keys = ["main", "common", "rest"]
|
||||
result_values = [
|
||||
# Split paths and their deps (unreachable from rest of the graph).
|
||||
graph.induced_subgraph(split_off_without_common),
|
||||
# Dependencies of split paths which can be reached from the rest of the
|
||||
# graph.
|
||||
graph.induced_subgraph(common),
|
||||
# Rest of the graph (without dependencies common with split paths).
|
||||
graph.induced_subgraph(rest_without_common),
|
||||
]
|
||||
|
||||
debug('result_values', result_values[0].vs["name"])
|
||||
|
||||
return tlz.valfilter(
|
||||
tlz.complement(graph_is_empty),
|
||||
dict(zip(
|
||||
result_keys,
|
||||
(
|
||||
result_values if root_name != fake_root_name
|
||||
# If root was added, remove it
|
||||
else tlz.map(remove_vertex(fake_root_name), result_values)
|
||||
)
|
||||
))
|
||||
)
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
import unittest
|
||||
from toolz import curry
|
||||
|
||||
from . import test_helpers as th
|
||||
|
||||
from .split_paths import (
|
||||
split_paths
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
directed_graph,
|
||||
pick_keys
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
# Making sure vertex attrs are preserved.
|
||||
vertex_props_dict = {
|
||||
"Root1": {"a": 1, "b": 1},
|
||||
"B": {"b": 2},
|
||||
"X": {"x": 3}
|
||||
}
|
||||
|
||||
|
||||
def make_test_graph():
|
||||
edges = [
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("A", "D"),
|
||||
("D", "E"),
|
||||
("B", "D"),
|
||||
("B", "F"),
|
||||
("Root2", "B"),
|
||||
("Root3", "C")
|
||||
]
|
||||
|
||||
detached_vertices = ["X"]
|
||||
|
||||
vertex_props = vertex_props_dict.items()
|
||||
|
||||
return directed_graph(edges, detached_vertices, vertex_props)
|
||||
|
||||
|
||||
class CustomAssertions:
|
||||
@curry
|
||||
def assertResultKeys(self, keys, result):
|
||||
self.assertListEqual(
|
||||
list(result.keys()),
|
||||
keys
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Test(
|
||||
unittest.TestCase,
|
||||
CustomAssertions,
|
||||
th.CustomAssertions
|
||||
):
|
||||
|
||||
def test_empty_paths(self):
|
||||
input_graph = make_test_graph()
|
||||
|
||||
result = self.assertResultKeys(
|
||||
["rest"],
|
||||
split_paths([], input_graph)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
input_graph
|
||||
)
|
||||
|
||||
def test_empty_graph(self):
|
||||
empty_graph = directed_graph([])
|
||||
|
||||
def test_empty(paths):
|
||||
result = self.assertResultKeys(
|
||||
["rest"],
|
||||
split_paths(paths, empty_graph)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
empty_graph
|
||||
)
|
||||
|
||||
test_empty([])
|
||||
test_empty(["B"])
|
||||
|
||||
def test_split_paths_single(self):
|
||||
result = self.assertResultKeys(
|
||||
["main", "common", "rest"],
|
||||
split_paths(["B"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph(
|
||||
[
|
||||
("B", "F")
|
||||
],
|
||||
None,
|
||||
pick_keys(["B"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("Root3", "C")
|
||||
],
|
||||
["Root2", "X"],
|
||||
pick_keys(["Root1", "X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["common"],
|
||||
directed_graph([("D", "E")])
|
||||
)
|
||||
|
||||
def test_split_paths_multi(self):
|
||||
result = self.assertResultKeys(
|
||||
["main", "common", "rest"],
|
||||
split_paths(["B", "Root3"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph(
|
||||
[
|
||||
("B", "F"),
|
||||
("Root3", "C")
|
||||
],
|
||||
None,
|
||||
pick_keys(["B"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[("Root1", "A")],
|
||||
["Root2", "X"],
|
||||
pick_keys(["Root1", "X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["common"],
|
||||
directed_graph([("D", "E")])
|
||||
)
|
||||
|
||||
def test_split_no_common(self):
|
||||
result = self.assertResultKeys(
|
||||
["main", "rest"],
|
||||
split_paths(["D"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph([("D", "E")])
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("B", "F"),
|
||||
("Root2", "B"),
|
||||
("Root3", "C"),
|
||||
],
|
||||
["X"],
|
||||
pick_keys(["Root1", "B", "X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
from toolz import curry
|
||||
from toolz import curried as tlz
|
||||
from operator import attrgetter
|
||||
|
||||
from .lib import (
|
||||
debug,
|
||||
debug_plot,
|
||||
DEBUG_PLOT,
|
||||
find_vertex_by_name_or_none,
|
||||
is_None,
|
||||
subcomponent_multi
|
||||
)
|
||||
|
||||
|
||||
call_counts = {
|
||||
"in": 0,
|
||||
"out": 0
|
||||
}
|
||||
|
||||
|
||||
@curry
|
||||
def subcomponent(mode, paths, graph):
|
||||
if DEBUG_PLOT:
|
||||
global call_counts
|
||||
graph_name_prefix = f"subcomponent_{mode}_{call_counts[mode]}_"
|
||||
call_counts[mode] += 1
|
||||
|
||||
layout = graph.layout('tree')
|
||||
debug_plot(graph, f"{graph_name_prefix}input", layout=layout)
|
||||
|
||||
path_indices = tlz.compose(
|
||||
tlz.map(attrgetter('index')),
|
||||
tlz.remove(is_None),
|
||||
tlz.map(find_vertex_by_name_or_none(graph))
|
||||
)(paths)
|
||||
|
||||
debug("path_indices", path_indices)
|
||||
|
||||
main_indices = list(subcomponent_multi(graph, path_indices, mode))
|
||||
|
||||
debug('main_indices', main_indices)
|
||||
|
||||
if DEBUG_PLOT:
|
||||
def choose_color(index):
|
||||
if (index in main_indices):
|
||||
return "green"
|
||||
else:
|
||||
return "red"
|
||||
|
||||
vertex_color = [choose_color(v.index) for v in graph.vs]
|
||||
|
||||
debug_plot(
|
||||
graph,
|
||||
f"{graph_name_prefix}result",
|
||||
layout=layout,
|
||||
vertex_color=vertex_color
|
||||
)
|
||||
|
||||
return {
|
||||
"main": graph.induced_subgraph(main_indices),
|
||||
"rest": graph - main_indices
|
||||
}
|
||||
|
||||
|
||||
subcomponent_in = subcomponent("in")
|
||||
|
||||
subcomponent_out = subcomponent("out")
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
import unittest
|
||||
|
||||
from . import test_helpers as th
|
||||
|
||||
from .subcomponent import (
|
||||
subcomponent_out,
|
||||
subcomponent_in
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
pick_keys,
|
||||
directed_graph,
|
||||
empty_directed_graph
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
# Making sure vertex attrs are preserved.
|
||||
vertex_props_dict = {
|
||||
"Root1": {"a": 1, "b": 1},
|
||||
"B": {"b": 2},
|
||||
"X": {"x": 3}
|
||||
}
|
||||
|
||||
|
||||
def make_test_graph():
|
||||
edges = [
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("A", "C"),
|
||||
("B", "D"),
|
||||
("B", "E"),
|
||||
("Root2", "B"),
|
||||
("Root3", "C"),
|
||||
]
|
||||
|
||||
detached_vertices = ["X"]
|
||||
|
||||
vertex_props = vertex_props_dict.items()
|
||||
|
||||
return directed_graph(edges, detached_vertices, vertex_props)
|
||||
|
||||
|
||||
class CustomAssertions:
|
||||
def assertResultKeys(self, result):
|
||||
self.assertListEqual(
|
||||
list(result.keys()),
|
||||
["main", "rest"]
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Test(
|
||||
unittest.TestCase,
|
||||
CustomAssertions,
|
||||
th.CustomAssertions
|
||||
):
|
||||
|
||||
def test_empty_paths(self):
|
||||
def test(func):
|
||||
input_graph = make_test_graph()
|
||||
|
||||
result = self.assertResultKeys(
|
||||
func([], input_graph)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
empty_directed_graph()
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
input_graph
|
||||
)
|
||||
|
||||
test(subcomponent_out)
|
||||
test(subcomponent_in)
|
||||
|
||||
def test_empty_graph(self):
|
||||
def test(func):
|
||||
empty_graph = empty_directed_graph()
|
||||
|
||||
def test_empty(paths):
|
||||
result = self.assertResultKeys(
|
||||
func(paths, empty_graph)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
empty_graph
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
empty_graph
|
||||
)
|
||||
|
||||
test_empty([])
|
||||
test_empty(["B"])
|
||||
|
||||
test(subcomponent_out)
|
||||
test(subcomponent_in)
|
||||
|
||||
def test_subcomponent_out(self):
|
||||
result = self.assertResultKeys(
|
||||
subcomponent_out(["B"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph(
|
||||
[
|
||||
("B", "D"),
|
||||
("B", "E")
|
||||
],
|
||||
None,
|
||||
pick_keys(["B"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("A", "C"),
|
||||
("Root3", "C")
|
||||
],
|
||||
["Root2", "X"],
|
||||
pick_keys(["Root1", "X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
def test_subcomponent_out_multi(self):
|
||||
result = self.assertResultKeys(
|
||||
subcomponent_out(["B", "Root3"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph(
|
||||
[
|
||||
("B", "D"),
|
||||
("B", "E"),
|
||||
("Root3", "C")
|
||||
],
|
||||
None,
|
||||
pick_keys(["B"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[("Root1", "A")],
|
||||
["Root2", "X"],
|
||||
pick_keys(["Root1", "X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
def test_subcomponent_in(self):
|
||||
result = self.assertResultKeys(
|
||||
subcomponent_in(["B"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("Root2", "B")
|
||||
],
|
||||
None,
|
||||
pick_keys(["Root1", "B"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[("Root3", "C")],
|
||||
["D", "E", "X"],
|
||||
pick_keys(["X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
|
||||
def test_subcomponent_in_multi(self):
|
||||
result = self.assertResultKeys(
|
||||
subcomponent_in(["B", "Root3"], make_test_graph())
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["main"],
|
||||
directed_graph(
|
||||
[
|
||||
("Root1", "A"),
|
||||
("A", "B"),
|
||||
("Root2", "B"),
|
||||
],
|
||||
["Root3"],
|
||||
pick_keys(["Root1", "B"], vertex_props_dict).items()
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
self.assertGraphEqual(
|
||||
result["rest"],
|
||||
directed_graph(
|
||||
[],
|
||||
["C", "D", "E", "X"],
|
||||
pick_keys(["X"], vertex_props_dict).items()
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
from toolz import curried as tlz
|
||||
|
||||
from .lib import (
|
||||
not_None,
|
||||
graph_vertex_index_to_name
|
||||
)
|
||||
|
||||
|
||||
def edges_as_set(graph):
|
||||
return frozenset(
|
||||
(
|
||||
graph_vertex_index_to_name(graph, e.source),
|
||||
graph_vertex_index_to_name(graph, e.target)
|
||||
) for e in graph.es
|
||||
)
|
||||
|
||||
|
||||
class CustomAssertions:
|
||||
def assertGraphEqual(self, g1, g2):
|
||||
self.assertSetEqual(
|
||||
frozenset(g1.vs["name"]),
|
||||
frozenset(g2.vs["name"])
|
||||
)
|
||||
|
||||
self.assertSetEqual(
|
||||
edges_as_set(g1),
|
||||
edges_as_set(g2)
|
||||
)
|
||||
|
||||
for name in g1.vs["name"]:
|
||||
def get_vertex_attrs(g):
|
||||
return tlz.valfilter(not_None, g.vs.find(name).attributes())
|
||||
|
||||
self.assertDictEqual(
|
||||
get_vertex_attrs(g1),
|
||||
get_vertex_attrs(g2),
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="flatten_references_graph",
|
||||
version="0.1.0",
|
||||
author="Adrian Gierakowski",
|
||||
packages=["flatten_references_graph"],
|
||||
install_requires=[
|
||||
"igraph",
|
||||
"toolz"
|
||||
],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"flatten_references_graph=flatten_references_graph.__main__:main"
|
||||
]
|
||||
}
|
||||
)
|
||||
@@ -6,23 +6,23 @@
|
||||
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "git-graph";
|
||||
version = "unstable-2023-01-14";
|
||||
version = "0.6.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mlange-42";
|
||||
repo = pname;
|
||||
rev = "9bd54eb0aed6f108364bce9ad0bdff12077038fc";
|
||||
hash = "sha256-tMM/mpt9yzZYSpnOGBuGLM0XTJiiyChfUrERMuyn3mQ=";
|
||||
rev = "refs/tags/${version}";
|
||||
hash = "sha256-xYUpLujePO1MS0c25UJX5rRdmPzkaFgF5zJonzQOJqM=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-ZLF/l2HnIbmkayWXhjYr01M6lGaGiK2UYyp654ncxgo=";
|
||||
cargoHash = "sha256-y5tVjWj/LczblkL793878vzDG0Gtj3kIo2jZlRA6GJE=";
|
||||
|
||||
meta = with lib; {
|
||||
description = "Command line tool to show clear git graphs arranged for your branching model";
|
||||
homepage = "https://github.com/mlange-42/git-graph";
|
||||
license = licenses.mit;
|
||||
broken = stdenv.hostPlatform.isDarwin;
|
||||
maintainers = with maintainers; [ cafkafk ];
|
||||
maintainers = with maintainers; [ cafkafk matthiasbeyer ];
|
||||
mainProgram = "git-graph";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
{ python3Packages
|
||||
, lib
|
||||
, fetchFromGitLab
|
||||
, meson
|
||||
, pkg-config
|
||||
, glib
|
||||
, ninja
|
||||
, desktop-file-utils
|
||||
, gobject-introspection
|
||||
, gtk3
|
||||
, libnotify
|
||||
, dbus
|
||||
, wrapGAppsHook3
|
||||
}:
|
||||
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "gkraken";
|
||||
version = "1.2.0";
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "leinardi";
|
||||
repo = "gkraken";
|
||||
rev = version;
|
||||
sha256 = "0hxlh0319rl28iba02917z3n6d5cq2qcgpj2ng31bkjjhlvvfm2g";
|
||||
};
|
||||
|
||||
format = "other";
|
||||
|
||||
postPatch = ''
|
||||
patchShebangs scripts/meson_post_install.py
|
||||
'';
|
||||
|
||||
nativeBuildInputs = [
|
||||
meson
|
||||
pkg-config
|
||||
gobject-introspection
|
||||
glib
|
||||
ninja
|
||||
gtk3
|
||||
desktop-file-utils
|
||||
wrapGAppsHook3
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
glib
|
||||
gtk3
|
||||
libnotify
|
||||
dbus
|
||||
];
|
||||
|
||||
propagatedBuildInputs = with python3Packages; [
|
||||
pygobject3
|
||||
peewee
|
||||
rx
|
||||
injector
|
||||
liquidctl
|
||||
pyxdg
|
||||
requests
|
||||
matplotlib
|
||||
dbus-python
|
||||
];
|
||||
|
||||
dontWrapGApps = true;
|
||||
|
||||
# Extract udev rules from python code
|
||||
postInstall = ''
|
||||
mkdir -p $out/lib/udev/rules.d
|
||||
sed -e '/\s*\(from\|@singleton\|@inject\)/d' $src/gkraken/interactor/udev_interactor.py > udev_interactor.py
|
||||
python -c 'from udev_interactor import _UDEV_RULE; print(_UDEV_RULE)' > $out/lib/udev/rules.d/60-gkraken.rules
|
||||
'';
|
||||
|
||||
preFixup = ''
|
||||
makeWrapperArgs+=(
|
||||
"''${gappsWrapperArgs[@]}"
|
||||
)
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "GUI that allows to control the cooling (fan and/or pump profiles) of NZXT Kraken AIO liquid coolers from Linux";
|
||||
homepage = "https://gitlab.com/leinardi/gkraken";
|
||||
changelog = "https://gitlab.com/leinardi/gkraken/-/tags/${version}";
|
||||
license = licenses.gpl3Plus;
|
||||
maintainers = with maintainers; [ OPNA2608 ];
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "gkraken";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
buildGoModule,
|
||||
buildNpmPackage,
|
||||
nix-update-script,
|
||||
versionCheckHook,
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
}:
|
||||
let
|
||||
version = "0.8.0";
|
||||
rev = "a1e92b4bcdaf81d5b5e8f3edab60e33549e9009b";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
repo = "gose";
|
||||
owner = "stv0g";
|
||||
inherit rev;
|
||||
hash = "sha256-Vcp9gMJ296KS8OFOIcWvvYrS+LrOVlN3KN20yJxoo5U=";
|
||||
};
|
||||
|
||||
frontend = buildNpmPackage {
|
||||
pname = "gose-frontend";
|
||||
inherit version;
|
||||
src = "${src}/frontend";
|
||||
|
||||
npmDepsHash = "sha256-p24s2SgCL8E9vUoZEyWSrd15IdkprneAXS7dwb7UbyA=";
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
find ./dist
|
||||
mkdir $out
|
||||
cp -r dist/* $out/
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in
|
||||
buildGoModule {
|
||||
pname = "gose";
|
||||
inherit version;
|
||||
inherit src;
|
||||
|
||||
vendorHash = "sha256-U/umJ6McCuD0HARVMj1JXHOpVxcph16z7Y7i47Nf3cg=";
|
||||
|
||||
CGO_ENABLED = 0;
|
||||
|
||||
postInstall = ''
|
||||
mv $out/bin/cmd $out/bin/gose
|
||||
'';
|
||||
|
||||
tags = [ "embed" ];
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X main.version=${version}"
|
||||
"-X main.commit=${rev}"
|
||||
"-X main.builtBy=Nix"
|
||||
];
|
||||
|
||||
# Skipping test which relies on internet services.
|
||||
checkFlags = "-skip TestShortener";
|
||||
|
||||
nativeInstallCheckInputs = [
|
||||
versionCheckHook
|
||||
];
|
||||
versionCheckProgramArg = "-version";
|
||||
doInstallCheck = true;
|
||||
|
||||
prePatch = ''
|
||||
cp -r ${frontend} frontend/dist
|
||||
'';
|
||||
|
||||
passthru.updateScript = nix-update-script { };
|
||||
|
||||
meta = {
|
||||
description = "Modern and scalable file-uploader focusing on scalability and simplicity";
|
||||
homepage = "https://github.com/stv0g/gose";
|
||||
license = lib.licenses.asl20;
|
||||
maintainers = with lib.maintainers; [ stv0g ];
|
||||
mainProgram = "gose";
|
||||
};
|
||||
}
|
||||
@@ -27,13 +27,13 @@ let
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "melonDS";
|
||||
version = "0.9.5-unstable-2024-09-29";
|
||||
version = "1.0rc-unstable-2024-11-27";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "melonDS-emu";
|
||||
repo = "melonDS";
|
||||
rev = "e9446fa9dc3ba64c22f8e5bb611ad394b5729eda";
|
||||
hash = "sha256-0YNF9NPKaaKzC3d8cXG4Od3AUc1zxVtuB5u2Q8ruwr0=";
|
||||
rev = "cba838dd52a8d163c59e312d75f451a04cea9b48";
|
||||
hash = "sha256-0Pv2ARbQkYaGDQ7AMLlMdK1MjnsctPIIWxGz2XNTNmI=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -29,6 +29,10 @@ python3Packages.buildPythonApplication rec {
|
||||
pyqt5
|
||||
];
|
||||
|
||||
postFixup = ''
|
||||
mv $out/${python3Packages.python.sitePackages}/usr/share $out/share
|
||||
'';
|
||||
|
||||
pythonImportsCheck = [ "meteo_qt" ];
|
||||
|
||||
makeWrapperArgs = [ "\${qtWrapperArgs[@]}" ];
|
||||
|
||||
Generated
-5005
File diff suppressed because it is too large
Load Diff
@@ -92,13 +92,8 @@ rustPlatform.buildRustPackage rec {
|
||||
hash = "sha256-aflzpJZ48AFBqNTssZl2KxkspQb662nGkEU6COIluxk=";
|
||||
};
|
||||
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
outputHashes = {
|
||||
"bindgen_cuda-0.1.6" = "sha256-OWGcQxT+x5HyIFskNVWpPr6Qfkh6Mv/g4PVSm5oA27g=";
|
||||
"candle-core-0.7.2" = "sha256-OovBzD1gEYToa3HT8oQtbY6sDy0heRwAH2cK7gz5Jm0=";
|
||||
};
|
||||
};
|
||||
useFetchCargoVendor = true;
|
||||
cargoHash = "sha256-USp8siEXVjtkPoCHfQjDYPtgLfNHcy02LSUNdwDbxgs=";
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
|
||||
@@ -17,7 +17,7 @@ buildGoModule rec {
|
||||
};
|
||||
|
||||
modRoot = "./cli";
|
||||
tags = "extension";
|
||||
tags = [ "extension" ];
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "ooniprobe-cli";
|
||||
version = "3.23.0";
|
||||
version = "3.24.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "ooni";
|
||||
repo = "probe-cli";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-ey71XA/ZtN6AI/DyjqFGcY5XW9H0RoOjrmjDEIgJMkM=";
|
||||
hash = "sha256-jg09yIX3jHMp0PwSwy2RMwYhZm0haJFTmpwcyTc5I0A=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-wuCqyr7JuzRHJE8ct1DRi9aFJWeHdF1sxUjvQiBMa6I=";
|
||||
vendorHash = "sha256-QH5PyCvfJWWDhjH6Jo8O3wBq3MwfpHWKSuYMmpaqjWk=";
|
||||
|
||||
subPackages = [ "cmd/ooniprobe" ];
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "pappl";
|
||||
version = "1.4.7";
|
||||
version = "1.4.8";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "michaelrsweet";
|
||||
repo = "pappl";
|
||||
rev = "refs/tags/v${version}";
|
||||
hash = "sha256-Npry3H+QbAH19hoqAZuOwjpZwCPhOLewD8uKZlo4gdQ=";
|
||||
hash = "sha256-vp0DQvsQzuiXb5QAXVEcXNvyDJeRM99aO9/baZw9QwA=";
|
||||
};
|
||||
|
||||
outputs = [
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
lib,
|
||||
fetchFromGitHub,
|
||||
rustPlatform,
|
||||
versionCheckHook,
|
||||
|
||||
openssl,
|
||||
pkg-config,
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "rattler-build";
|
||||
version = "0.29.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "prefix-dev";
|
||||
repo = "rattler-build";
|
||||
rev = "refs/tags/v${version}";
|
||||
hash = "sha256-MTCtDE0oX9vUIwO62sKkCGleooHU28Lq8eXzwiNPDMo=";
|
||||
};
|
||||
|
||||
useFetchCargoVendor = true;
|
||||
cargoHash = "sha256-QONzLC1LvKkbslXZQTqxb6RHgsoUX21MXXyLVtTSvxc=";
|
||||
|
||||
doCheck = false; # test requires network access
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
openssl
|
||||
];
|
||||
|
||||
doInstallCheck = true;
|
||||
|
||||
nativeInstallCheckInputs = [
|
||||
versionCheckHook
|
||||
];
|
||||
|
||||
versionCheckProgramArg = [ "-V" ];
|
||||
|
||||
meta = {
|
||||
description = "Universal package builder for Windows, macOS and Linux";
|
||||
homepage = "https://github.com/prefix-dev/rattler-build";
|
||||
changelog = "https://github.com/prefix-dev/rattler-build/releases/tag/v${version}";
|
||||
license = lib.licenses.bsd3;
|
||||
maintainers = with lib.maintainers; [ genga898 ];
|
||||
mainProgram = "rattler-build";
|
||||
};
|
||||
}
|
||||
@@ -8,17 +8,17 @@
|
||||
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "typstyle";
|
||||
version = "0.12.4";
|
||||
version = "0.12.5";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Enter-tainer";
|
||||
repo = "typstyle";
|
||||
rev = "refs/tags/v${version}";
|
||||
hash = "sha256-PMx7vo0HPknrZUQooehuFYRTfiWSpLYw03zcfVuNNEc=";
|
||||
hash = "sha256-28+B7lov2sUBDZUGHKBL1XzOM9q43idllQ0CPlZEdOw=";
|
||||
};
|
||||
|
||||
useFetchCargoVendor = true;
|
||||
cargoHash = "sha256-ughfZ9+oR7vjlBW7jl0w99FM8eEUYODh6jJ0no8tNvI=";
|
||||
cargoHash = "sha256-rSPXfGZXwrnCeG9RgjQdKQkacI4phzRF3AwfKu17Yvc=";
|
||||
|
||||
# Disabling tests requiring network access
|
||||
checkFlags = [
|
||||
|
||||
@@ -28,7 +28,7 @@ stdenv.mkDerivation rec {
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/AbiWord/${pname}/releases/download/v${version}/${pname}-${version}.tar.gz";
|
||||
url = "https://github.com/rrthomas/${pname}/releases/download/v${version}/${pname}-${version}.tar.gz";
|
||||
hash = "sha256-2aWhDcmzikOzoPoix27W67fgnrU1r/YpVK/NvUDv/2s=";
|
||||
};
|
||||
|
||||
@@ -75,7 +75,7 @@ stdenv.mkDerivation rec {
|
||||
|
||||
meta = with lib; {
|
||||
description = "Generic spell checking library";
|
||||
homepage = "https://abiword.github.io/enchant/";
|
||||
homepage = "https://rrthomas.github.io/enchant/";
|
||||
license = licenses.lgpl21Plus; # with extra provision for non-free checkers
|
||||
maintainers = with maintainers; [ jtojnar ];
|
||||
platforms = platforms.unix;
|
||||
|
||||
@@ -462,6 +462,7 @@ mapAliases {
|
||||
topGit = top-git;
|
||||
}; # Added 2021-01-14
|
||||
|
||||
gkraken = throw "'gkraken' has been deprecated by upstream. Consider using the replacement 'coolercontrol' instead."; # Added 2024-11-22
|
||||
glew-egl = lib.warn "'glew-egl' is now provided by 'glew' directly" glew; # Added 2024-08-11
|
||||
glfw-wayland = glfw; # Added 2024-04-19
|
||||
glfw-wayland-minecraft = glfw3-minecraft; # Added 2024-05-08
|
||||
|
||||
@@ -779,6 +779,8 @@ with pkgs;
|
||||
|
||||
referencesByPopularity = callPackage ../build-support/references-by-popularity { };
|
||||
|
||||
dockerMakeLayers = callPackage ../build-support/docker/make-layers.nix { };
|
||||
|
||||
removeReferencesTo = callPackage ../build-support/remove-references-to {
|
||||
inherit (darwin) signingUtils;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user