Compare commits
20 Commits
606832f505
...
verify
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6386febdb8
|
||
|
|
3aad35950e
|
||
|
|
83f05b3677
|
||
|
|
0addf91edf
|
||
|
|
59fdbf41ef
|
||
|
|
f59e45998e
|
||
|
|
3bf962de00
|
||
|
|
64471269f2
|
||
|
|
0b2de9d40c
|
||
|
|
9281ba7e10
|
||
|
|
b2e2ef2b24
|
||
|
|
b61f0e0364
|
||
|
|
fab5c2c634
|
||
|
|
8c4f0868a4
|
||
|
|
1b09c77492
|
||
|
|
fba8203cda
|
||
|
|
55c2785c0e
|
||
|
|
4ea47a3459
|
||
|
|
25c0ccad0c
|
||
|
|
d341228f5b
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
|||||||
/work
|
/work
|
||||||
/example_logs
|
/example_logs
|
||||||
TODO.org
|
TODO.org
|
||||||
|
/result
|
||||||
|
/.envrc
|
||||||
|
/.direnv/
|
||||||
|
|||||||
53
Cargo.lock
generated
53
Cargo.lock
generated
@@ -609,6 +609,12 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hamming"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1047,6 +1053,7 @@ dependencies = [
|
|||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry-otlp",
|
"opentelemetry-otlp",
|
||||||
"opentelemetry-semantic-conventions",
|
"opentelemetry-semantic-conventions",
|
||||||
|
"primal",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
@@ -1339,6 +1346,52 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1e5f354948532e6017fc91f9a5ff5ba1be0dabd3a0c9e9c417969cd4c1ad6e8"
|
||||||
|
dependencies = [
|
||||||
|
"primal-check",
|
||||||
|
"primal-estimate",
|
||||||
|
"primal-sieve",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-bit"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "252429dbb8aeacc3233df500dc3a6a367bf28eb3a711272884d7540a7b636055"
|
||||||
|
dependencies = [
|
||||||
|
"hamming",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-check"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-estimate"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a432100a0b3a61085e75b5f89e9f42de73c0acb7dea5038b893697918105d822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-sieve"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e982796d82203351983d3602a8d6372d1d7894e86960047ba0d4b7426a5edd3"
|
||||||
|
dependencies = [
|
||||||
|
"primal-bit",
|
||||||
|
"primal-estimate",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.105"
|
version = "1.0.105"
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ tracing-opentelemetry = { version = "0.20.0", optional = true }
|
|||||||
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
||||||
url = { version = "2.5.8", default-features = false, features = ["std"] }
|
url = { version = "2.5.8", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
primal = { version = "0.3.3", default-features = false, features = [] }
|
||||||
|
|
||||||
# Optimized build for any sort of release.
|
# Optimized build for any sort of release.
|
||||||
[profile.release-lto]
|
[profile.lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = true
|
lto = true
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1771008912,
|
"lastModified": 1778869304,
|
||||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
"narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
"rev": "d233902339c02a9c334e7e593de68855ad26c4cb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1771211437,
|
"lastModified": 1779506148,
|
||||||
"narHash": "sha256-lcNK438i4DGtyA+bPXXyVLHVmJjYpVKmpux9WASa3ro=",
|
"narHash": "sha256-OffE5yuMrSW71zIJNLvtl9MCO6WTAHEjlFPUCvkd/QM=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "c62195b3d6e1bb11e0c2fb2a494117d3b55d410f",
|
"rev": "d9973e2ab49747fada06ebbe26cda27eb0220cf1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
65
flake.nix
65
flake.nix
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
description = "nix_builder development environment";
|
description = "nix_builder nix package builder";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
@@ -62,5 +62,68 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
packages = forAllSystems (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
overlays = [ (import rust-overlay) ];
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system overlays;
|
||||||
|
};
|
||||||
|
cargoToml = (pkgs.lib.importTOML ./Cargo.toml);
|
||||||
|
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||||
|
rustPlatform = pkgs.makeRustPlatform {
|
||||||
|
cargo = rustToolchain;
|
||||||
|
rustc = rustToolchain;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
app = rustPlatform.buildRustPackage {
|
||||||
|
pname = "nix_builder";
|
||||||
|
version = cargoToml.package.version;
|
||||||
|
src = pkgs.lib.cleanSource ./.;
|
||||||
|
|
||||||
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
|
||||||
|
buildType = "lto";
|
||||||
|
# RUSTFLAGS = "-C target-cpu=x86-64-v3";
|
||||||
|
|
||||||
|
meta = with pkgs.lib; {
|
||||||
|
description = "A builder of nix configs for a build server.";
|
||||||
|
homepage = "https://code.fizz.buzz/talexander/nix_builder";
|
||||||
|
license = licenses.bsd0;
|
||||||
|
maintainers = [ ];
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
wrapProgram $out/bin/nix-builder --prefix PATH : ${
|
||||||
|
pkgs.lib.makeBinPath [
|
||||||
|
pkgs.git
|
||||||
|
pkgs.nix
|
||||||
|
pkgs.nixos-rebuild
|
||||||
|
]
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# nativeBuildInputs = [
|
||||||
|
# pkgs.pkg-config
|
||||||
|
# ];
|
||||||
|
# buildInputs = [
|
||||||
|
# pkgs.openssl
|
||||||
|
# ];
|
||||||
|
};
|
||||||
|
docker_env = pkgs.buildEnv {
|
||||||
|
name = "nix_builder";
|
||||||
|
paths = with pkgs; [
|
||||||
|
app
|
||||||
|
# bash
|
||||||
|
# uutils-coreutils-noprefix
|
||||||
|
# toybox # Smaller than uutils-coreutils?
|
||||||
|
];
|
||||||
|
};
|
||||||
|
default = app;
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
2
migrations/20260725025954_flake_inputs.down.sql
Normal file
2
migrations/20260725025954_flake_inputs.down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DROP INDEX ix_flake_inputs_build_id;
|
||||||
|
DROP TABLE flake_inputs;
|
||||||
9
migrations/20260725025954_flake_inputs.up.sql
Normal file
9
migrations/20260725025954_flake_inputs.up.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE flake_inputs (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
build_id INTEGER NOT NULL,
|
||||||
|
input_name TEXT NOT NULL,
|
||||||
|
rev TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(build_id) REFERENCES build(id)
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE INDEX ix_flake_inputs_build_id ON flake_inputs(build_id);
|
||||||
181
python_experiments/depth_first_search/depth_first_search.py
Executable file
181
python_experiments/depth_first_search/depth_first_search.py
Executable file
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
activity_tree = ActivityTree()
|
||||||
|
activity_tree.activities[0] = Activity(id=0, parent=0, text="*")
|
||||||
|
activity_tree.activities[1] = Activity(id=1, parent=0, text="A")
|
||||||
|
activity_tree.activities[2] = Activity(id=2, parent=1, text="B")
|
||||||
|
activity_tree.activities[3] = Activity(id=3, parent=2, text="C")
|
||||||
|
activity_tree.activities[4] = Activity(id=4, parent=2, text="D")
|
||||||
|
activity_tree.activities[5] = Activity(id=5, parent=4, text="E")
|
||||||
|
activity_tree.activities[6] = Activity(id=6, parent=4, text="F")
|
||||||
|
activity_tree.activities[7] = Activity(id=7, parent=1, text="G")
|
||||||
|
activity_tree.activities[8] = Activity(id=8, parent=1, text="H")
|
||||||
|
activity_tree.activities[9] = Activity(id=9, parent=8, text="I")
|
||||||
|
activity_tree.activities[10] = Activity(id=10, parent=9, text="J")
|
||||||
|
|
||||||
|
match_iter = MatchIter(
|
||||||
|
activity_tree,
|
||||||
|
lambda act: act.id in (2, 5, 10),
|
||||||
|
lambda act: act.id in (4,),
|
||||||
|
lambda act: act.id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
while (depth_and_node := match_iter.get_next()) is not None:
|
||||||
|
depth, matched, transparent, node = depth_and_node
|
||||||
|
print("\t".join((str(depth), str(matched), str(transparent), node.text)))
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# print("\n\n\nvvvvvvvvvv\n\n")
|
||||||
|
# node = match_iter.get_next()
|
||||||
|
# if node is None:
|
||||||
|
# break
|
||||||
|
# print(node.text)
|
||||||
|
# print("\n\n")
|
||||||
|
# match_iter.print_stack()
|
||||||
|
# print("\n\n\n^^^^^^^^^^\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityTree:
|
||||||
|
activities: dict[int, Activity]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.activities = {}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Activity:
|
||||||
|
id: int
|
||||||
|
parent: int
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
class MatchIter:
|
||||||
|
activity_tree: ActivityTree
|
||||||
|
stack: deque[StackEntry]
|
||||||
|
is_match: Callable[[Activity], bool]
|
||||||
|
is_transparent: Callable[[Activity], bool]
|
||||||
|
is_alive: Callable[[Activity], bool]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
activity_tree: ActivityTree,
|
||||||
|
is_match: Callable[[Activity], bool],
|
||||||
|
is_transparent: Callable[[Activity], bool],
|
||||||
|
is_alive: Callable[[Activity], bool],
|
||||||
|
) -> None:
|
||||||
|
self.activity_tree = activity_tree
|
||||||
|
self.stack = deque([StackEntry(0)])
|
||||||
|
self.is_match = is_match
|
||||||
|
self.is_transparent = is_transparent
|
||||||
|
self.is_alive = is_alive
|
||||||
|
|
||||||
|
def get_next(self) -> tuple[int, bool, bool, Activity] | None:
|
||||||
|
if (next_announce := self.get_next_announce()) is not None:
|
||||||
|
next_announce_depth, next_announce_entry = next_announce
|
||||||
|
next_announce_entry.announced = True
|
||||||
|
return (
|
||||||
|
next_announce_depth,
|
||||||
|
next_announce_entry.matched,
|
||||||
|
next_announce_entry.transparent,
|
||||||
|
self.activity_tree.activities[next_announce_entry.node_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
while len(self.stack) > 0:
|
||||||
|
stack_entry = self.stack[-1]
|
||||||
|
node = self.activity_tree.activities[stack_entry.node_id]
|
||||||
|
|
||||||
|
if stack_entry.visited:
|
||||||
|
self.stack.pop()
|
||||||
|
continue
|
||||||
|
# print(f"Checking {stack_entry.text(self.activity_tree)}")
|
||||||
|
|
||||||
|
if self.is_match(node):
|
||||||
|
stack_entry.should_announce = True
|
||||||
|
stack_entry.matched = True
|
||||||
|
for parent in self.walk_up_stack(stack_entry):
|
||||||
|
if parent.node_id != 0 and not parent.should_announce:
|
||||||
|
parent.should_announce = True
|
||||||
|
|
||||||
|
stack_entry.visited = True
|
||||||
|
stack_entry.transparent = self.is_transparent(node)
|
||||||
|
|
||||||
|
children = [
|
||||||
|
c
|
||||||
|
for c in self.activity_tree.activities.values()
|
||||||
|
if c.parent == node.id and self.is_alive(c)
|
||||||
|
]
|
||||||
|
# for c in children:
|
||||||
|
for c in reversed(children):
|
||||||
|
if c.id != 0:
|
||||||
|
self.stack.append(StackEntry(c.id))
|
||||||
|
|
||||||
|
if (
|
||||||
|
stack_entry.should_announce
|
||||||
|
and (next_announce := self.get_next_announce()) is not None
|
||||||
|
):
|
||||||
|
next_announce_depth, next_announce_entry = next_announce
|
||||||
|
next_announce_entry.announced = True
|
||||||
|
return (
|
||||||
|
next_announce_depth,
|
||||||
|
next_announce_entry.matched,
|
||||||
|
next_announce_entry.transparent,
|
||||||
|
self.activity_tree.activities[next_announce_entry.node_id],
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def print_stack(self) -> None:
|
||||||
|
for stack_entry in self.stack:
|
||||||
|
print(stack_entry.text(self.activity_tree))
|
||||||
|
|
||||||
|
def get_next_announce(self) -> tuple[int, StackEntry] | None:
|
||||||
|
for entry in self.stack:
|
||||||
|
if entry.should_announce and not entry.announced:
|
||||||
|
depth = len(
|
||||||
|
list(
|
||||||
|
entry
|
||||||
|
for entry in self.walk_up_stack(entry)
|
||||||
|
if not entry.transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (depth, entry)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def walk_up_stack(self, base: StackEntry) -> Iterable[StackEntry]:
|
||||||
|
found_start = False
|
||||||
|
for i in range(len(self.stack)):
|
||||||
|
idx = -1 - i
|
||||||
|
stack_entry = self.stack[idx]
|
||||||
|
if not found_start:
|
||||||
|
if stack_entry is base:
|
||||||
|
found_start = True
|
||||||
|
continue
|
||||||
|
if stack_entry.visited:
|
||||||
|
yield stack_entry
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StackEntry:
|
||||||
|
node_id: int
|
||||||
|
announced: bool = False
|
||||||
|
should_announce: bool = False
|
||||||
|
visited: bool = False
|
||||||
|
matched: bool = False
|
||||||
|
transparent: bool = False
|
||||||
|
|
||||||
|
def text(self, activity_tree: ActivityTree) -> str:
|
||||||
|
node = activity_tree.activities[self.node_id]
|
||||||
|
return f"{node.text}\t{"annced" if self.announced else "-"}\t{"should" if self.should_announce else "-"}\t{"visited" if self.visited else "-"}\t{"matched" if self.matched else "-"}\t{"trans" if self.transparent else "-"}"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
157
python_experiments/depth_first_search/inverted_tree.py
Executable file
157
python_experiments/depth_first_search/inverted_tree.py
Executable file
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
activity_tree = ActivityTree()
|
||||||
|
activity_tree.activities[0] = Activity(id=0, parent=0, text="*")
|
||||||
|
activity_tree.activities[1] = Activity(id=1, parent=0, text="A")
|
||||||
|
activity_tree.activities[2] = Activity(id=2, parent=1, text="B")
|
||||||
|
activity_tree.activities[3] = Activity(id=3, parent=2, text="C")
|
||||||
|
activity_tree.activities[4] = Activity(id=4, parent=2, text="D")
|
||||||
|
activity_tree.activities[5] = Activity(id=5, parent=4, text="E")
|
||||||
|
activity_tree.activities[6] = Activity(id=6, parent=4, text="F")
|
||||||
|
activity_tree.activities[7] = Activity(id=7, parent=1, text="G")
|
||||||
|
activity_tree.activities[8] = Activity(id=8, parent=1, text="H")
|
||||||
|
activity_tree.activities[9] = Activity(id=9, parent=8, text="I")
|
||||||
|
activity_tree.activities[10] = Activity(id=10, parent=9, text="J")
|
||||||
|
|
||||||
|
match_iter = MatchIter(
|
||||||
|
activity_tree,
|
||||||
|
lambda act: act.id in (2, 5, 10),
|
||||||
|
lambda act: act.id in (4,),
|
||||||
|
lambda act: act.id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
while (depth_and_node := match_iter.get_next()) is not None:
|
||||||
|
depth, matched, transparent, node = depth_and_node
|
||||||
|
print("\t".join((str(depth), str(matched), str(transparent), node.text)))
|
||||||
|
|
||||||
|
# while True:
|
||||||
|
# print("\n\n\nvvvvvvvvvv\n\n")
|
||||||
|
# node = match_iter.get_next()
|
||||||
|
# if node is None:
|
||||||
|
# break
|
||||||
|
# print(node.text)
|
||||||
|
# print("\n\n")
|
||||||
|
# match_iter.print_stack()
|
||||||
|
# print("\n\n\n^^^^^^^^^^\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityTree:
|
||||||
|
activities: dict[int, Activity]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.activities = {}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Activity:
|
||||||
|
id: int
|
||||||
|
parent: int
|
||||||
|
text: str
|
||||||
|
|
||||||
|
|
||||||
|
class MatchIter:
|
||||||
|
activity_tree: ActivityTree
|
||||||
|
stack: deque[StackEntry]
|
||||||
|
is_match: Callable[[Activity], bool]
|
||||||
|
is_transparent: Callable[[Activity], bool]
|
||||||
|
is_alive: Callable[[Activity], bool]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
activity_tree: ActivityTree,
|
||||||
|
is_match: Callable[[Activity], bool],
|
||||||
|
is_transparent: Callable[[Activity], bool],
|
||||||
|
is_alive: Callable[[Activity], bool],
|
||||||
|
) -> None:
|
||||||
|
self.activity_tree = activity_tree
|
||||||
|
self.stack = deque([StackEntry(0)])
|
||||||
|
self.is_match = is_match
|
||||||
|
self.is_transparent = is_transparent
|
||||||
|
self.is_alive = is_alive
|
||||||
|
|
||||||
|
def get_next(self) -> tuple[int, bool, bool, Activity] | None:
|
||||||
|
while len(self.stack) > 0:
|
||||||
|
stack_entry = self.stack[-1]
|
||||||
|
node = self.activity_tree.activities[stack_entry.node_id]
|
||||||
|
|
||||||
|
if stack_entry.visited:
|
||||||
|
if stack_entry.should_announce:
|
||||||
|
stack_entry_depth = len(
|
||||||
|
list(
|
||||||
|
entry
|
||||||
|
for entry in self.walk_up_stack(stack_entry)
|
||||||
|
if not entry.transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.stack.pop()
|
||||||
|
return (
|
||||||
|
stack_entry_depth,
|
||||||
|
stack_entry.matched,
|
||||||
|
stack_entry.transparent,
|
||||||
|
node,
|
||||||
|
)
|
||||||
|
self.stack.pop()
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.is_match(node):
|
||||||
|
stack_entry.should_announce = True
|
||||||
|
stack_entry.matched = True
|
||||||
|
for parent in self.walk_up_stack(stack_entry):
|
||||||
|
if parent.node_id != 0 and not parent.should_announce:
|
||||||
|
parent.should_announce = True
|
||||||
|
|
||||||
|
stack_entry.visited = True
|
||||||
|
stack_entry.transparent = self.is_transparent(node)
|
||||||
|
|
||||||
|
children = [
|
||||||
|
c
|
||||||
|
for c in self.activity_tree.activities.values()
|
||||||
|
if c.parent == node.id and self.is_alive(c)
|
||||||
|
]
|
||||||
|
for c in children:
|
||||||
|
if c.id != 0:
|
||||||
|
self.stack.append(StackEntry(c.id))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def print_stack(self) -> None:
|
||||||
|
for stack_entry in self.stack:
|
||||||
|
print(stack_entry.text(self.activity_tree))
|
||||||
|
|
||||||
|
def walk_up_stack(self, base: StackEntry) -> Iterable[StackEntry]:
|
||||||
|
found_start = False
|
||||||
|
for i in range(len(self.stack)):
|
||||||
|
idx = -1 - i
|
||||||
|
stack_entry = self.stack[idx]
|
||||||
|
if not found_start:
|
||||||
|
if stack_entry is base:
|
||||||
|
found_start = True
|
||||||
|
continue
|
||||||
|
if stack_entry.visited:
|
||||||
|
yield stack_entry
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StackEntry:
|
||||||
|
node_id: int
|
||||||
|
announced: bool = False
|
||||||
|
should_announce: bool = False
|
||||||
|
visited: bool = False
|
||||||
|
matched: bool = False
|
||||||
|
transparent: bool = False
|
||||||
|
|
||||||
|
def text(self, activity_tree: ActivityTree) -> str:
|
||||||
|
node = activity_tree.activities[self.node_id]
|
||||||
|
return f"{node.text}\t{"annced" if self.announced else "-"}\t{"should" if self.should_announce else "-"}\t{"visited" if self.visited else "-"}\t{"matched" if self.matched else "-"}\t{"trans" if self.transparent else "-"}"
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
39
python_experiments/flip_exponent_for_tests/flip.py
Executable file
39
python_experiments/flip_exponent_for_tests/flip.py
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
from math import log10
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for x, y, z, num_nodes in sorted(list(get_matches(90, 90)), key=lambda val: val[3]):
|
||||||
|
if num_nodes < 1000000:
|
||||||
|
print(f"{x}**{y} == {y}**{z} == {num_nodes}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_matches(max_x: int, max_y: int):
|
||||||
|
for x in range(2, max_x):
|
||||||
|
for y in range(2, max_y):
|
||||||
|
diff, y_exp = check(x, y)
|
||||||
|
if diff == 0:
|
||||||
|
yield (x, y, y_exp, x**y)
|
||||||
|
|
||||||
|
|
||||||
|
def check(x: int, y: int):
|
||||||
|
num_nodes = x**y
|
||||||
|
# y**z = x**y
|
||||||
|
# zlogy = ylogx
|
||||||
|
# z = ylogx/logy
|
||||||
|
|
||||||
|
# flipped:
|
||||||
|
exp_for_y = y * log10(x) / log10(y)
|
||||||
|
# Round the exp for y to be a plain integer
|
||||||
|
exp_for_y = round(exp_for_y)
|
||||||
|
flipped_num_nodes = y**exp_for_y
|
||||||
|
|
||||||
|
# compare the num nodes after rounding to find a good fit with integers.
|
||||||
|
diff = abs(num_nodes - flipped_num_nodes)
|
||||||
|
|
||||||
|
return diff, exp_for_y
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "stable"
|
channel = "nightly-2026-05-23"
|
||||||
profile = "default"
|
profile = "default"
|
||||||
components = ["clippy", "rustfmt"]
|
components = ["clippy", "rustfmt"]
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
use sqlx::Row;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::cli::parameters::BuildArgs;
|
use crate::cli::parameters::BuildArgs;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -7,7 +10,11 @@ use crate::fs_util::assert_directory;
|
|||||||
use crate::fs_util::is_git_repo;
|
use crate::fs_util::is_git_repo;
|
||||||
use crate::git_util::git_force_into_state;
|
use crate::git_util::git_force_into_state;
|
||||||
use crate::git_util::git_init_at_rev;
|
use crate::git_util::git_init_at_rev;
|
||||||
|
use crate::nix_util::FlakeLock;
|
||||||
|
use crate::nix_util::nix_flake_update;
|
||||||
|
use crate::nix_util::nix_store_verify_repair_check_contents;
|
||||||
use crate::nix_util::nixos_build_target;
|
use crate::nix_util::nixos_build_target;
|
||||||
|
use crate::nix_util::parse_flake_lock;
|
||||||
|
|
||||||
pub(crate) async fn run_build(args: BuildArgs) -> Result<()> {
|
pub(crate) async fn run_build(args: BuildArgs) -> Result<()> {
|
||||||
println!("{:?}", args);
|
println!("{:?}", args);
|
||||||
@@ -27,6 +34,8 @@ pub(crate) async fn run_build(args: BuildArgs) -> Result<()> {
|
|||||||
|
|
||||||
let db_handle = DbHandle::new(Some(database_path)).await?;
|
let db_handle = DbHandle::new(Some(database_path)).await?;
|
||||||
|
|
||||||
|
verify_nix_store().await?;
|
||||||
|
|
||||||
for target_name in args.target {
|
for target_name in args.target {
|
||||||
let target_config = {
|
let target_config = {
|
||||||
let target_config = config.get_target_config(&target_name)?;
|
let target_config = config.get_target_config(&target_name)?;
|
||||||
@@ -38,6 +47,9 @@ pub(crate) async fn run_build(args: BuildArgs) -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
prepare_flake_repo(&config, target_config).await?;
|
prepare_flake_repo(&config, target_config).await?;
|
||||||
|
if target_config.get_update() {
|
||||||
|
run_nix_update(&config, target_config).await?;
|
||||||
|
}
|
||||||
build_target(&db_handle, &config, target_config).await?;
|
build_target(&db_handle, &config, target_config).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +88,20 @@ async fn prepare_flake_repo(config_root: &Config, target_config: &TargetConfig)
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn verify_nix_store() -> Result<()> {
|
||||||
|
nix_store_verify_repair_check_contents().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_nix_update(config_root: &Config, target_config: &TargetConfig) -> Result<()> {
|
||||||
|
let flake_directory = target_config.get_flake_directory(config_root)?;
|
||||||
|
|
||||||
|
nix_flake_update(flake_directory).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn build_target(
|
async fn build_target(
|
||||||
db_handle: &DbHandle,
|
db_handle: &DbHandle,
|
||||||
config_root: &Config,
|
config_root: &Config,
|
||||||
@@ -89,14 +115,61 @@ async fn build_target(
|
|||||||
build_directory.to_string_lossy()
|
build_directory.to_string_lossy()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let target_name = target_config.get_name()?;
|
||||||
|
|
||||||
|
let build_id: i64 = sqlx::query(
|
||||||
|
r#"INSERT INTO build (start_time, target) SELECT unixepoch('now'), ? RETURNING id"#,
|
||||||
|
)
|
||||||
|
.bind(target_name)
|
||||||
|
.fetch_one(&db_handle.conn)
|
||||||
|
.await?
|
||||||
|
.try_get("id")?;
|
||||||
|
|
||||||
|
let flake_lock = parse_flake_lock(&flake_directory).await?;
|
||||||
|
write_input_revs_to_db(db_handle, &flake_lock, build_id).await?;
|
||||||
|
|
||||||
nixos_build_target(
|
nixos_build_target(
|
||||||
db_handle,
|
db_handle,
|
||||||
build_directory,
|
build_directory,
|
||||||
flake_directory,
|
flake_directory,
|
||||||
target_config.get_attr()?,
|
target_config.get_attr()?,
|
||||||
target_config.get_name()?,
|
target_name,
|
||||||
|
build_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn write_input_revs_to_db(
|
||||||
|
db_handle: &DbHandle,
|
||||||
|
flake_lock: &FlakeLock,
|
||||||
|
build_id: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let root_node = flake_lock.get_root_node()?;
|
||||||
|
let direct_inputs = flake_lock.get_inputs(root_node)?;
|
||||||
|
|
||||||
|
let mut tx = db_handle.conn.begin().await?;
|
||||||
|
for (input, node) in direct_inputs.iter() {
|
||||||
|
let locked = match &node.locked {
|
||||||
|
Some(locked) => locked,
|
||||||
|
None => {
|
||||||
|
info!(
|
||||||
|
"Not recording rev for input {} because it lacks a locked section.",
|
||||||
|
input
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let rev = &locked.rev;
|
||||||
|
info!("input {} {}", input, rev);
|
||||||
|
sqlx::query(r#"INSERT INTO flake_inputs (build_id, input_name, rev) VALUES (?, ?, ?);"#)
|
||||||
|
.bind(build_id)
|
||||||
|
.bind(input)
|
||||||
|
.bind(rev)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
tx.commit().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::nix_util::RunningBuild;
|
|||||||
|
|
||||||
pub(crate) async fn feed_logs(args: FeedLogArgs) -> Result<()> {
|
pub(crate) async fn feed_logs(args: FeedLogArgs) -> Result<()> {
|
||||||
let db_handle = DbHandle::new::<String>(None).await?;
|
let db_handle = DbHandle::new::<String>(None).await?;
|
||||||
let mut running_build = RunningBuild::new(&db_handle)?;
|
let mut running_build = RunningBuild::new(&db_handle, -1)?;
|
||||||
let file_stream = FileStream::new(args.input).await?;
|
let file_stream = FileStream::new(args.input).await?;
|
||||||
let mut nix_output_stream = NixOutputStream::new(file_stream);
|
let mut nix_output_stream = NixOutputStream::new(file_stream);
|
||||||
while let Some(message) = nix_output_stream.next().await? {
|
while let Some(message) = nix_output_stream.next().await? {
|
||||||
|
|||||||
@@ -126,6 +126,13 @@ impl TargetConfig {
|
|||||||
pub(crate) fn get_name(&'_ self) -> Result<&String, CustomError> {
|
pub(crate) fn get_name(&'_ self) -> Result<&String, CustomError> {
|
||||||
Ok(&self.name)
|
Ok(&self.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this job should update the flake inputs before the build.
|
||||||
|
///
|
||||||
|
/// This is used for proactively building updated packages so we can quickly update machines without waiting.
|
||||||
|
pub(crate) fn get_update(&self) -> bool {
|
||||||
|
self.update.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hex_string(data: &[u8]) -> String {
|
fn hex_string(data: &[u8]) -> String {
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ pub(crate) enum CustomError {
|
|||||||
SystemTime(#[allow(dead_code)] SystemTimeError),
|
SystemTime(#[allow(dead_code)] SystemTimeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for CustomError {}
|
||||||
|
|
||||||
impl Display for CustomError {
|
impl Display for CustomError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#![feature(test)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|||||||
@@ -126,26 +126,6 @@ impl Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_transparent(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Activity::Root(_activity_root) => true,
|
|
||||||
Activity::Unknown(_activity_unknown) => false,
|
|
||||||
Activity::CopyPath(_activity_copy_path) => false,
|
|
||||||
Activity::FileTransfer(_activity_file_transfer) => false,
|
|
||||||
Activity::Realize(_activity_realize) => true,
|
|
||||||
Activity::CopyPaths(_activity_copy_paths) => true,
|
|
||||||
Activity::Builds(_activity_builds) => true,
|
|
||||||
Activity::Build(_activity_build) => false,
|
|
||||||
Activity::OptimizeStore(_activity_optimize_store) => false,
|
|
||||||
Activity::VerifyPaths(_activity_verify_paths) => false,
|
|
||||||
Activity::Substitute(_activity_substitute) => false,
|
|
||||||
Activity::QueryPathInfo(_activity_query_path_info) => false,
|
|
||||||
Activity::PostBuildHook(_activity_post_build_hook) => false,
|
|
||||||
Activity::BuildWaiting(_activity_build_waiting) => true,
|
|
||||||
Activity::FetchTree(_activity_fetch_tree) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn display_name(&self) -> Option<Cow<'_, str>> {
|
pub(crate) fn display_name(&self) -> Option<Cow<'_, str>> {
|
||||||
match self {
|
match self {
|
||||||
Activity::Root(_activity_root) => {
|
Activity::Root(_activity_root) => {
|
||||||
@@ -204,63 +184,51 @@ impl Activity {
|
|||||||
|
|
||||||
pub(crate) fn get_progress_text(&self) -> Option<Cow<'_, str>> {
|
pub(crate) fn get_progress_text(&self) -> Option<Cow<'_, str>> {
|
||||||
match self {
|
match self {
|
||||||
Activity::Root(_activity_root) => {
|
Activity::Root(_activity_root) => None,
|
||||||
// TODO
|
|
||||||
panic!("Attempted to get_progress_text of a root activity.");
|
|
||||||
}
|
|
||||||
Activity::Unknown(_activity_unknown) => None,
|
Activity::Unknown(_activity_unknown) => None,
|
||||||
Activity::CopyPath(activity_copy_path) => Some(Cow::Owned(format!(
|
Activity::CopyPath(activity_copy_path) => {
|
||||||
"[{}/{}]",
|
get_progress_bar(activity_copy_path.done, activity_copy_path.expected).or_else(
|
||||||
activity_copy_path.done, activity_copy_path.expected
|
|| get_progress_text(activity_copy_path.done, activity_copy_path.expected),
|
||||||
))),
|
)
|
||||||
Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!(
|
}
|
||||||
"[{}/{}]",
|
Activity::FileTransfer(activity_file_transfer) => {
|
||||||
activity_file_transfer.done, activity_file_transfer.expected
|
get_progress_bar(activity_file_transfer.done, activity_file_transfer.expected)
|
||||||
))),
|
.or_else(|| {
|
||||||
Activity::Realize(_activity_realize) => {
|
get_progress_text(
|
||||||
// TODO
|
activity_file_transfer.done,
|
||||||
panic!("Attempted to get_progress_text of a realize activity.");
|
activity_file_transfer.expected,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Activity::Realize(_activity_realize) => None,
|
||||||
|
Activity::CopyPaths(activity_copy_paths) => {
|
||||||
|
get_progress_bar(activity_copy_paths.done, activity_copy_paths.expected).or_else(
|
||||||
|
|| get_progress_text(activity_copy_paths.done, activity_copy_paths.expected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Activity::Builds(activity_builds) => {
|
||||||
|
get_progress_bar(activity_builds.done, activity_builds.expected)
|
||||||
|
.or_else(|| get_progress_text(activity_builds.done, activity_builds.expected))
|
||||||
}
|
}
|
||||||
Activity::CopyPaths(activity_copy_paths) => Some(Cow::Owned(format!(
|
|
||||||
"[{}/{}]",
|
|
||||||
activity_copy_paths.done, activity_copy_paths.expected
|
|
||||||
))),
|
|
||||||
Activity::Builds(activity_builds) => Some(Cow::Owned(format!(
|
|
||||||
"[{}/{}]",
|
|
||||||
activity_builds.done, activity_builds.expected
|
|
||||||
))),
|
|
||||||
Activity::Build(activity_build) => activity_build
|
Activity::Build(activity_build) => activity_build
|
||||||
.phase
|
.phase
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|phase| Cow::Owned(format!("[{}]", phase))),
|
.map(|phase| Cow::Owned(format!("[{}]", phase))),
|
||||||
Activity::OptimizeStore(_activity_optimize_store) => {
|
Activity::OptimizeStore(_activity_optimize_store) => None,
|
||||||
// TODO
|
Activity::VerifyPaths(activity_verify_paths) => {
|
||||||
panic!("Attempted to get_progress_text of a optimize store activity.");
|
get_progress_bar(activity_verify_paths.done, activity_verify_paths.expected)
|
||||||
}
|
.or_else(|| {
|
||||||
Activity::VerifyPaths(_activity_verify_paths) => {
|
get_progress_text(
|
||||||
// TODO
|
activity_verify_paths.done,
|
||||||
panic!("Attempted to get_progress_text of a verify paths activity.");
|
activity_verify_paths.expected,
|
||||||
}
|
)
|
||||||
Activity::Substitute(_activity_substitute) => {
|
})
|
||||||
// TODO
|
|
||||||
panic!("Attempted to get_progress_text of a substitute activity.");
|
|
||||||
}
|
|
||||||
Activity::QueryPathInfo(_activity_query_path_info) => {
|
|
||||||
// TODO
|
|
||||||
panic!("Attempted to get_progress_text of a query path info activity.");
|
|
||||||
}
|
|
||||||
Activity::PostBuildHook(_activity_post_build_hook) => {
|
|
||||||
// TODO
|
|
||||||
panic!("Attempted to get_progress_text of a post build hook activity.");
|
|
||||||
}
|
|
||||||
Activity::BuildWaiting(_activity_build_waiting) => {
|
|
||||||
// TODO
|
|
||||||
panic!("Attempted to get_progress_text of a build waiting activity.");
|
|
||||||
}
|
|
||||||
Activity::FetchTree(_activity_fetch_tree) => {
|
|
||||||
// TODO
|
|
||||||
panic!("Attempted to get_progress_text of a fetch tree activity.");
|
|
||||||
}
|
}
|
||||||
|
Activity::Substitute(_activity_substitute) => None,
|
||||||
|
Activity::QueryPathInfo(_activity_query_path_info) => None,
|
||||||
|
Activity::PostBuildHook(_activity_post_build_hook) => None,
|
||||||
|
Activity::BuildWaiting(_activity_build_waiting) => None,
|
||||||
|
Activity::FetchTree(_activity_fetch_tree) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,8 +331,11 @@ impl Activity {
|
|||||||
Activity::OptimizeStore(_activity_optimize_store) => {
|
Activity::OptimizeStore(_activity_optimize_store) => {
|
||||||
panic!("Attempted to set the progress of an optimize store activity.");
|
panic!("Attempted to set the progress of an optimize store activity.");
|
||||||
}
|
}
|
||||||
Activity::VerifyPaths(_activity_verify_paths) => {
|
Activity::VerifyPaths(activity_verify_paths) => {
|
||||||
panic!("Attempted to set the progress of a verify paths activity.");
|
activity_verify_paths.done = done;
|
||||||
|
activity_verify_paths.expected = expected;
|
||||||
|
activity_verify_paths.running = running;
|
||||||
|
activity_verify_paths.failed = failed;
|
||||||
}
|
}
|
||||||
Activity::Substitute(_activity_substitute) => {
|
Activity::Substitute(_activity_substitute) => {
|
||||||
panic!("Attempted to set the progress of a substitute activity.");
|
panic!("Attempted to set the progress of a substitute activity.");
|
||||||
@@ -491,6 +462,10 @@ pub(crate) struct ActivityOptimizeStore {
|
|||||||
}
|
}
|
||||||
pub(crate) struct ActivityVerifyPaths {
|
pub(crate) struct ActivityVerifyPaths {
|
||||||
pub(crate) state: ActivityState,
|
pub(crate) state: ActivityState,
|
||||||
|
pub(crate) done: u64,
|
||||||
|
pub(crate) expected: u64,
|
||||||
|
pub(crate) running: u64,
|
||||||
|
pub(crate) failed: u64,
|
||||||
}
|
}
|
||||||
pub(crate) struct ActivitySubstitute {
|
pub(crate) struct ActivitySubstitute {
|
||||||
pub(crate) state: ActivityState,
|
pub(crate) state: ActivityState,
|
||||||
@@ -531,3 +506,82 @@ impl Default for ActivityState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_progress_bar(done: u64, expected: u64) -> Option<Cow<'static, str>> {
|
||||||
|
if expected == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// ○◔◑◕●
|
||||||
|
// ○◎◉●
|
||||||
|
// ▁▂▃▄▅▆▇█
|
||||||
|
// ▏▎▍▌▋▊▉█
|
||||||
|
// ░▒▓█
|
||||||
|
//
|
||||||
|
// 🮷 download
|
||||||
|
// 🮸 upload
|
||||||
|
// 🮵
|
||||||
|
// 🮶
|
||||||
|
get_progress_bar_clockwise_circle(done, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_progress_bar_fade_in(done: u64, expected: u64) -> Option<Cow<'static, str>> {
|
||||||
|
if expected == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let percent = done as f32 / expected as f32;
|
||||||
|
// ○◔◑◕●
|
||||||
|
// ○◎◉●
|
||||||
|
// ▁▂▃▄▅▆▇█
|
||||||
|
// ▏▎▍▌▋▊▉█
|
||||||
|
// ░▒▓█
|
||||||
|
//
|
||||||
|
// 🮷 download
|
||||||
|
// 🮸 upload
|
||||||
|
// 🮵
|
||||||
|
// 🮶
|
||||||
|
if percent < 0.33 {
|
||||||
|
Some(Cow::Borrowed("░"))
|
||||||
|
} else if percent < 0.66 {
|
||||||
|
Some(Cow::Borrowed("▒"))
|
||||||
|
} else if percent < 1.0 {
|
||||||
|
Some(Cow::Borrowed("▓"))
|
||||||
|
} else if percent >= 1.0 {
|
||||||
|
Some(Cow::Borrowed("█"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_progress_bar_clockwise_circle(done: u64, expected: u64) -> Option<Cow<'static, str>> {
|
||||||
|
if expected == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let percent = done as f32 / expected as f32;
|
||||||
|
// ○◔◑◕●
|
||||||
|
// ○◎◉●
|
||||||
|
// ▁▂▃▄▅▆▇█
|
||||||
|
// ▏▎▍▌▋▊▉█
|
||||||
|
// ░▒▓█
|
||||||
|
//
|
||||||
|
// 🮷 download
|
||||||
|
// 🮸 upload
|
||||||
|
// 🮵
|
||||||
|
// 🮶
|
||||||
|
if percent < 0.25 {
|
||||||
|
Some(Cow::Borrowed("○"))
|
||||||
|
} else if percent < 0.5 {
|
||||||
|
Some(Cow::Borrowed("◔"))
|
||||||
|
} else if percent < 0.75 {
|
||||||
|
Some(Cow::Borrowed("◑"))
|
||||||
|
} else if percent < 1.0 {
|
||||||
|
Some(Cow::Borrowed("◕"))
|
||||||
|
} else if percent >= 1.0 {
|
||||||
|
Some(Cow::Borrowed("●"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_progress_text(done: u64, expected: u64) -> Option<Cow<'static, str>> {
|
||||||
|
Some(Cow::Owned(format!("[{}/{}]", done, expected)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use std::ops::Deref;
|
|||||||
use super::activity::Activity;
|
use super::activity::Activity;
|
||||||
use super::activity::ActivityRoot;
|
use super::activity::ActivityRoot;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct ActivityId {
|
pub(crate) struct ActivityId {
|
||||||
id: u64,
|
id: u64,
|
||||||
}
|
}
|
||||||
|
|||||||
278
src/nix_util/activity_tree_stream.rs
Normal file
278
src/nix_util/activity_tree_stream.rs
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
use super::activity::Activity;
|
||||||
|
use super::activity::ActivityBuild;
|
||||||
|
use super::activity::ActivityBuildWaiting;
|
||||||
|
use super::activity::ActivityBuilds;
|
||||||
|
use super::activity::ActivityCopyPath;
|
||||||
|
use super::activity::ActivityCopyPaths;
|
||||||
|
use super::activity::ActivityFetchTree;
|
||||||
|
use super::activity::ActivityFileTransfer;
|
||||||
|
use super::activity::ActivityOptimizeStore;
|
||||||
|
use super::activity::ActivityPostBuildHook;
|
||||||
|
use super::activity::ActivityQueryPathInfo;
|
||||||
|
use super::activity::ActivityRealize;
|
||||||
|
use super::activity::ActivityState;
|
||||||
|
use super::activity::ActivitySubstitute;
|
||||||
|
use super::activity::ActivityUnknown;
|
||||||
|
use super::activity::ActivityVerifyPaths;
|
||||||
|
use super::activity_tree::ActivityTree;
|
||||||
|
use super::nix_output_stream::ActivityResultMessage;
|
||||||
|
use super::nix_output_stream::ActivityStartMessage;
|
||||||
|
use super::nix_output_stream::NixAction;
|
||||||
|
use super::nix_output_stream::NixMessage;
|
||||||
|
|
||||||
|
pub(crate) struct ActivityTreeStream {
|
||||||
|
activity_tree: ActivityTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivityTreeStream {
|
||||||
|
pub(crate) fn new() -> ActivityTreeStream {
|
||||||
|
ActivityTreeStream {
|
||||||
|
activity_tree: ActivityTree::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_tree(&self) -> &ActivityTree {
|
||||||
|
&self.activity_tree
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_tree_mut(&mut self) -> &mut ActivityTree {
|
||||||
|
&mut self.activity_tree
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_message(&mut self, message: &NixMessage) -> Result<()> {
|
||||||
|
let message = match message {
|
||||||
|
NixMessage::ParseFailure(_) | NixMessage::Generic(_, _) => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
NixMessage::Action(nix_action) => nix_action,
|
||||||
|
};
|
||||||
|
|
||||||
|
match message {
|
||||||
|
NixAction::Msg(_msg_message) => {
|
||||||
|
// if msg_message.msg.contains("nix log") {
|
||||||
|
// eprintln!("{}", msg_message.msg);
|
||||||
|
// }
|
||||||
|
// if msg_message.level == 0 {
|
||||||
|
// eprintln!("{}", msg_message.msg);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
NixAction::Start(activity_start_message) => {
|
||||||
|
match activity_start_message {
|
||||||
|
ActivityStartMessage::Unknown(activity_start_unknown) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_unknown.id,
|
||||||
|
activity_start_unknown.parent,
|
||||||
|
Activity::Unknown(ActivityUnknown {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
text: activity_start_unknown.text.clone(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::CopyPath(activity_start_copy_path) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_copy_path.id,
|
||||||
|
activity_start_copy_path.parent,
|
||||||
|
Activity::CopyPath(ActivityCopyPath {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
missing_path: activity_start_copy_path.missing_path.clone(),
|
||||||
|
source: activity_start_copy_path.source.clone(),
|
||||||
|
destination: activity_start_copy_path.destination.clone(),
|
||||||
|
done: 0,
|
||||||
|
expected: 0,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::FileTransfer(activity_start_file_transfer) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_file_transfer.id,
|
||||||
|
activity_start_file_transfer.parent,
|
||||||
|
Activity::FileTransfer(ActivityFileTransfer {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
url: activity_start_file_transfer.url.clone(),
|
||||||
|
done: 0,
|
||||||
|
expected: 0,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::Realize(activity_start_realize) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_realize.id,
|
||||||
|
activity_start_realize.parent,
|
||||||
|
Activity::Realize(ActivityRealize {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::CopyPaths(activity_start_copy_paths) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_copy_paths.id,
|
||||||
|
activity_start_copy_paths.parent,
|
||||||
|
Activity::CopyPaths(ActivityCopyPaths {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
text: activity_start_copy_paths.text.clone(),
|
||||||
|
done: 0,
|
||||||
|
expected: 0,
|
||||||
|
running: 0,
|
||||||
|
failed: 0,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::Builds(activity_start_builds) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_builds.id,
|
||||||
|
activity_start_builds.parent,
|
||||||
|
Activity::Builds(ActivityBuilds {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
done: 0,
|
||||||
|
expected: 0,
|
||||||
|
running: 0,
|
||||||
|
failed: 0,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::Build(activity_start_build) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_build.id,
|
||||||
|
activity_start_build.parent,
|
||||||
|
Activity::Build(ActivityBuild {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
drv_path: activity_start_build.drv_path.clone(),
|
||||||
|
machine_name: if activity_start_build.machine_name.len() > 0 {
|
||||||
|
Some(activity_start_build.machine_name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
phase: None,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_optimize_store.id,
|
||||||
|
activity_start_optimize_store.parent,
|
||||||
|
Activity::OptimizeStore(ActivityOptimizeStore {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::VerifyPaths(activity_start_verify_paths) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_verify_paths.id,
|
||||||
|
activity_start_verify_paths.parent,
|
||||||
|
Activity::VerifyPaths(ActivityVerifyPaths {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
done: 0,
|
||||||
|
expected: 0,
|
||||||
|
running: 0,
|
||||||
|
failed: 0,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::Substitute(activity_start_substitute) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_substitute.id,
|
||||||
|
activity_start_substitute.parent,
|
||||||
|
Activity::Substitute(ActivitySubstitute {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::QueryPathInfo(activity_start_query_path_info) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_query_path_info.id,
|
||||||
|
activity_start_query_path_info.parent,
|
||||||
|
Activity::QueryPathInfo(ActivityQueryPathInfo {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::PostBuildHook(activity_start_post_build_hook) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_post_build_hook.id,
|
||||||
|
activity_start_post_build_hook.parent,
|
||||||
|
Activity::PostBuildHook(ActivityPostBuildHook {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::BuildWaiting(activity_start_build_waiting) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_build_waiting.id,
|
||||||
|
0,
|
||||||
|
Activity::BuildWaiting(ActivityBuildWaiting {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
text: activity_start_build_waiting.text.clone(),
|
||||||
|
drv_path: activity_start_build_waiting.drv_path.clone(),
|
||||||
|
path_resolved: activity_start_build_waiting.path_resolved.clone(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ActivityStartMessage::FetchTree(activity_start_fetch_tree) => {
|
||||||
|
self.activity_tree.add_activity(
|
||||||
|
activity_start_fetch_tree.id,
|
||||||
|
activity_start_fetch_tree.parent,
|
||||||
|
Activity::FetchTree(ActivityFetchTree {
|
||||||
|
state: ActivityState::default(),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
NixAction::Stop(stop_message) => {
|
||||||
|
let activity = self
|
||||||
|
.activity_tree
|
||||||
|
.get_activity_id(stop_message.id)
|
||||||
|
.map(|activity_id| self.activity_tree.get_mut(&activity_id))?;
|
||||||
|
activity.get_mut_activity().stop();
|
||||||
|
// println!("{}", serde_json::to_string(&message)?);
|
||||||
|
}
|
||||||
|
NixAction::Result(activity_result_message) => {
|
||||||
|
match activity_result_message {
|
||||||
|
ActivityResultMessage::FileLinked(_activity_result_file_linked) => {}
|
||||||
|
ActivityResultMessage::BuildLogLine(_activity_result_build_log_line) => {
|
||||||
|
// These are the output from the actual build (as opposed to the output from nix).
|
||||||
|
}
|
||||||
|
ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {}
|
||||||
|
ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {}
|
||||||
|
ActivityResultMessage::SetPhase(activity_result_set_phase) => {
|
||||||
|
let activity_id = self
|
||||||
|
.activity_tree
|
||||||
|
.get_activity_id(activity_result_set_phase.id)?;
|
||||||
|
let activity = self.activity_tree.get_mut(&activity_id);
|
||||||
|
activity
|
||||||
|
.get_mut_activity()
|
||||||
|
.set_phase(Some(activity_result_set_phase.phase.clone()));
|
||||||
|
}
|
||||||
|
ActivityResultMessage::Progress(activity_result_progress) => {
|
||||||
|
let activity_id = self
|
||||||
|
.activity_tree
|
||||||
|
.get_activity_id(activity_result_progress.id)?;
|
||||||
|
let activity = self.activity_tree.get_mut(&activity_id);
|
||||||
|
activity.get_mut_activity().set_progress(
|
||||||
|
activity_result_progress.done,
|
||||||
|
activity_result_progress.expected,
|
||||||
|
activity_result_progress.running,
|
||||||
|
activity_result_progress.failed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ActivityResultMessage::SetExpected(activity_result_set_expected) => {
|
||||||
|
let activity_id = self
|
||||||
|
.activity_tree
|
||||||
|
.get_activity_id(activity_result_set_expected.id)?;
|
||||||
|
let activity = self.activity_tree.get_mut(&activity_id);
|
||||||
|
activity
|
||||||
|
.get_mut_activity()
|
||||||
|
.set_expected(activity_result_set_expected.expected);
|
||||||
|
}
|
||||||
|
ActivityResultMessage::PostBuildLogLine(
|
||||||
|
_activity_result_post_build_log_line,
|
||||||
|
) => {}
|
||||||
|
ActivityResultMessage::FetchStatus(_activity_result_fetch_status) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ use tokio::process::Command;
|
|||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::database::db_handle::DbHandle;
|
use crate::database::db_handle::DbHandle;
|
||||||
|
|
||||||
|
use super::RunningUpdate;
|
||||||
|
use super::RunningVerify;
|
||||||
use super::running_build::RunningBuild;
|
use super::running_build::RunningBuild;
|
||||||
|
|
||||||
pub(crate) async fn nixos_build_target<B, F, A, TN>(
|
pub(crate) async fn nixos_build_target<B, F, A, TN>(
|
||||||
@@ -16,6 +18,7 @@ pub(crate) async fn nixos_build_target<B, F, A, TN>(
|
|||||||
flake_path: F,
|
flake_path: F,
|
||||||
attr: A,
|
attr: A,
|
||||||
target_name: TN,
|
target_name: TN,
|
||||||
|
build_id: i64,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
B: AsRef<Path>,
|
B: AsRef<Path>,
|
||||||
@@ -54,8 +57,57 @@ where
|
|||||||
|
|
||||||
let child = command.spawn()?;
|
let child = command.spawn()?;
|
||||||
|
|
||||||
let mut running_build = RunningBuild::new(db_handle)?;
|
let mut running_build = RunningBuild::new(db_handle, build_id)?;
|
||||||
running_build.run_to_completion(child, target_name).await?;
|
running_build.run_to_completion(child, target_name).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn nix_flake_update<F>(flake_path: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let mut command = Command::new("nix");
|
||||||
|
command.current_dir(flake_path);
|
||||||
|
command.stdout(Stdio::piped());
|
||||||
|
command.stderr(Stdio::piped());
|
||||||
|
command.stdin(Stdio::null());
|
||||||
|
command.args([
|
||||||
|
"flake",
|
||||||
|
"update",
|
||||||
|
"--log-format",
|
||||||
|
"internal-json",
|
||||||
|
"-vvvvvvvvvvv",
|
||||||
|
]);
|
||||||
|
command.kill_on_drop(true);
|
||||||
|
|
||||||
|
let child = command.spawn()?;
|
||||||
|
|
||||||
|
let mut running_update = RunningUpdate::new()?;
|
||||||
|
running_update.run_to_completion(child).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn nix_store_verify_repair_check_contents() -> Result<()> {
|
||||||
|
let mut command = Command::new("nix-store");
|
||||||
|
command.stdout(Stdio::piped());
|
||||||
|
command.stderr(Stdio::piped());
|
||||||
|
command.stdin(Stdio::null());
|
||||||
|
command.args([
|
||||||
|
"--verify",
|
||||||
|
"--check-contents",
|
||||||
|
"--repair",
|
||||||
|
"--log-format",
|
||||||
|
"internal-json",
|
||||||
|
"-vvvvvvvvvvv",
|
||||||
|
]);
|
||||||
|
command.kill_on_drop(true);
|
||||||
|
|
||||||
|
let child = command.spawn()?;
|
||||||
|
|
||||||
|
let mut running_verify = RunningVerify::new()?;
|
||||||
|
running_verify.run_to_completion(child).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
153
src/nix_util/lock_parser.rs
Normal file
153
src/nix_util/lock_parser.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub(crate) struct FlakeLock {
|
||||||
|
pub(crate) root: String,
|
||||||
|
pub(crate) version: u8,
|
||||||
|
pub(crate) nodes: BTreeMap<String, FlakeLockNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub(crate) struct FlakeLockNode {
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) locked: Option<FlakeLockNodeLocked>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) original: Option<FlakeLockNodeOriginal>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) inputs: Option<BTreeMap<String, FlakeLockInputValue>>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) flake: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub(crate) struct FlakeLockNodeLocked {
|
||||||
|
#[serde(rename = "lastModified")]
|
||||||
|
pub(crate) last_modified: i64,
|
||||||
|
|
||||||
|
#[serde(rename = "narHash")]
|
||||||
|
pub(crate) nar_hash: String,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) url: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) owner: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) repo: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) git_ref: Option<String>,
|
||||||
|
|
||||||
|
pub(crate) rev: String,
|
||||||
|
|
||||||
|
#[serde(rename = "revCount", default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) rev_count: Option<i64>,
|
||||||
|
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) repo_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)]
|
||||||
|
pub(crate) enum FlakeLockNodeOriginal {
|
||||||
|
Github {
|
||||||
|
owner: String,
|
||||||
|
|
||||||
|
repo: String,
|
||||||
|
|
||||||
|
#[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
|
||||||
|
git_ref: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
rev: Option<String>,
|
||||||
|
},
|
||||||
|
Git {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
|
Tarball {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged, deny_unknown_fields)]
|
||||||
|
pub(crate) enum FlakeLockInputValue {
|
||||||
|
String(String),
|
||||||
|
List(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn parse_flake_lock<F>(flake_path: F) -> Result<FlakeLock>
|
||||||
|
where
|
||||||
|
F: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let lock_file_path = flake_path.as_ref().join("flake.lock");
|
||||||
|
let contents = tokio::fs::read_to_string(&lock_file_path).await?;
|
||||||
|
let parsed_flake_lock = serde_json::from_str(&contents)?;
|
||||||
|
Ok(parsed_flake_lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlakeLock {
|
||||||
|
pub(crate) fn get_root_node(&self) -> Result<&FlakeLockNode> {
|
||||||
|
let root_node = self.nodes.get(&self.root).ok_or("Root node not found.")?;
|
||||||
|
Ok(root_node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_inputs<'lock>(
|
||||||
|
&'lock self,
|
||||||
|
source_node: &'lock FlakeLockNode,
|
||||||
|
) -> Result<BTreeMap<&'lock String, &'lock FlakeLockNode>> {
|
||||||
|
let direct_inputs = match &source_node.inputs {
|
||||||
|
Some(inputs) => inputs,
|
||||||
|
None => {
|
||||||
|
return Ok(BTreeMap::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut inputs = BTreeMap::new();
|
||||||
|
|
||||||
|
for (input_name, input_id_or_path) in direct_inputs.iter() {
|
||||||
|
inputs.insert(input_name, self.resolve_path(input_id_or_path)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_path<'lock>(
|
||||||
|
&'lock self,
|
||||||
|
input_id_or_path: &'lock FlakeLockInputValue,
|
||||||
|
) -> Result<&'lock FlakeLockNode> {
|
||||||
|
match input_id_or_path {
|
||||||
|
FlakeLockInputValue::String(input_id) => {
|
||||||
|
// If the input value is a plain string, then it refers to a node.
|
||||||
|
let node = self.nodes.get(input_id).ok_or("Input not found.")?;
|
||||||
|
Ok(node)
|
||||||
|
}
|
||||||
|
FlakeLockInputValue::List(items) => {
|
||||||
|
// If the input value is a list, then it refers to a path to an input.
|
||||||
|
let mut current_node = self.get_root_node()?;
|
||||||
|
for step in items {
|
||||||
|
let child_input_id_or_path = current_node
|
||||||
|
.inputs
|
||||||
|
.as_ref()
|
||||||
|
.ok_or("Node without inputs while walking input tree.")?
|
||||||
|
.get(step)
|
||||||
|
.ok_or("Step not found in inputs while walking input tree")?;
|
||||||
|
current_node = self.resolve_path(child_input_id_or_path)?;
|
||||||
|
}
|
||||||
|
Ok(current_node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,22 @@
|
|||||||
mod activity;
|
mod activity;
|
||||||
mod activity_tree;
|
mod activity_tree;
|
||||||
|
mod activity_tree_stream;
|
||||||
mod high_level;
|
mod high_level;
|
||||||
|
mod lock_parser;
|
||||||
mod nix_output_stream;
|
mod nix_output_stream;
|
||||||
mod output_stream;
|
mod output_stream;
|
||||||
mod running_build;
|
mod running_build;
|
||||||
mod transparent_iter;
|
mod running_update;
|
||||||
|
mod running_verify;
|
||||||
|
mod tree_iter;
|
||||||
pub(crate) use activity_tree::ActivityIdAlreadyInTreeError;
|
pub(crate) use activity_tree::ActivityIdAlreadyInTreeError;
|
||||||
pub(crate) use activity_tree::ActivityIdNotInTreeError;
|
pub(crate) use activity_tree::ActivityIdNotInTreeError;
|
||||||
pub(crate) use high_level::*;
|
pub(crate) use high_level::*;
|
||||||
|
pub(crate) use lock_parser::FlakeLock;
|
||||||
|
pub(crate) use lock_parser::parse_flake_lock;
|
||||||
pub(crate) use nix_output_stream::NixOutputStream;
|
pub(crate) use nix_output_stream::NixOutputStream;
|
||||||
pub(crate) use output_stream::OutputLine;
|
pub(crate) use output_stream::OutputLine;
|
||||||
pub(crate) use output_stream::OutputLineStream;
|
pub(crate) use output_stream::OutputLineStream;
|
||||||
pub(crate) use running_build::RunningBuild;
|
pub(crate) use running_build::RunningBuild;
|
||||||
|
pub(crate) use running_update::RunningUpdate;
|
||||||
|
pub(crate) use running_verify::RunningVerify;
|
||||||
|
|||||||
@@ -9,46 +9,33 @@ use tracing::error;
|
|||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::database::db_handle::DbHandle;
|
use crate::database::db_handle::DbHandle;
|
||||||
use crate::nix_util::nix_output_stream::ActivityStartMessage;
|
|
||||||
use crate::nix_util::nix_output_stream::NixAction;
|
use crate::nix_util::nix_output_stream::NixAction;
|
||||||
use crate::nix_util::nix_output_stream::NixOutputStream;
|
use crate::nix_util::nix_output_stream::NixOutputStream;
|
||||||
use crate::nix_util::output_stream::OutputStream;
|
use crate::nix_util::output_stream::OutputStream;
|
||||||
use crate::nix_util::transparent_iter::TransparentIter;
|
|
||||||
|
|
||||||
use super::activity::Activity;
|
use super::activity::Activity;
|
||||||
use super::activity::ActivityBuild;
|
|
||||||
use super::activity::ActivityBuildWaiting;
|
|
||||||
use super::activity::ActivityBuilds;
|
|
||||||
use super::activity::ActivityCopyPath;
|
|
||||||
use super::activity::ActivityCopyPaths;
|
|
||||||
use super::activity::ActivityFetchTree;
|
|
||||||
use super::activity::ActivityFileTransfer;
|
|
||||||
use super::activity::ActivityOptimizeStore;
|
|
||||||
use super::activity::ActivityPostBuildHook;
|
|
||||||
use super::activity::ActivityQueryPathInfo;
|
|
||||||
use super::activity::ActivityRealize;
|
|
||||||
use super::activity::ActivityState;
|
|
||||||
use super::activity::ActivitySubstitute;
|
|
||||||
use super::activity::ActivityUnknown;
|
|
||||||
use super::activity::ActivityVerifyPaths;
|
|
||||||
use super::activity_tree::ActivityId;
|
|
||||||
use super::activity_tree::ActivityTree;
|
use super::activity_tree::ActivityTree;
|
||||||
use super::activity_tree::ActivityTreeEntry;
|
use super::activity_tree::ActivityTreeEntry;
|
||||||
|
use super::activity_tree_stream::ActivityTreeStream;
|
||||||
use super::nix_output_stream::ActivityResultMessage;
|
use super::nix_output_stream::ActivityResultMessage;
|
||||||
use super::nix_output_stream::NixMessage;
|
use super::nix_output_stream::NixMessage;
|
||||||
|
use super::tree_iter::DrawDagEntry;
|
||||||
|
use super::tree_iter::ReverseTreeIter;
|
||||||
|
|
||||||
pub(crate) struct RunningBuild<'db> {
|
pub(crate) struct RunningBuild<'db> {
|
||||||
db_handle: &'db DbHandle,
|
db_handle: &'db DbHandle,
|
||||||
activity_tree: ActivityTree,
|
activity_tree: ActivityTreeStream,
|
||||||
last_announce: Option<Instant>,
|
last_announce: Option<Instant>,
|
||||||
|
build_id: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> RunningBuild<'db> {
|
impl<'db> RunningBuild<'db> {
|
||||||
pub(crate) fn new(db_handle: &'db DbHandle) -> Result<Self> {
|
pub(crate) fn new(db_handle: &'db DbHandle, build_id: i64) -> Result<Self> {
|
||||||
Ok(RunningBuild {
|
Ok(RunningBuild {
|
||||||
db_handle,
|
db_handle,
|
||||||
activity_tree: ActivityTree::new(),
|
activity_tree: ActivityTreeStream::new(),
|
||||||
last_announce: None,
|
last_announce: None,
|
||||||
|
build_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,16 +47,6 @@ impl<'db> RunningBuild<'db> {
|
|||||||
where
|
where
|
||||||
TN: AsRef<str>,
|
TN: AsRef<str>,
|
||||||
{
|
{
|
||||||
let foo = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
|
|
||||||
let now = Instant::now();
|
|
||||||
let build_id: i64 = sqlx::query(
|
|
||||||
r#"INSERT INTO build (start_time, target) SELECT unixepoch('now'), ? RETURNING id"#,
|
|
||||||
)
|
|
||||||
.bind(target_name.as_ref())
|
|
||||||
.fetch_one(&self.db_handle.conn)
|
|
||||||
.await?
|
|
||||||
.try_get("id")?;
|
|
||||||
|
|
||||||
let output_stream = OutputStream::from_child(&mut child)?;
|
let output_stream = OutputStream::from_child(&mut child)?;
|
||||||
let mut nix_output_stream: NixOutputStream<OutputStream> =
|
let mut nix_output_stream: NixOutputStream<OutputStream> =
|
||||||
NixOutputStream::new(output_stream);
|
NixOutputStream::new(output_stream);
|
||||||
@@ -96,7 +73,7 @@ impl<'db> RunningBuild<'db> {
|
|||||||
.code()
|
.code()
|
||||||
.expect("Process should have an exit code."),
|
.expect("Process should have an exit code."),
|
||||||
)
|
)
|
||||||
.bind(build_id)
|
.bind(self.build_id)
|
||||||
.execute(&self.db_handle.conn)
|
.execute(&self.db_handle.conn)
|
||||||
.await?
|
.await?
|
||||||
.rows_affected();
|
.rows_affected();
|
||||||
@@ -106,6 +83,8 @@ impl<'db> RunningBuild<'db> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
||||||
|
self.activity_tree.handle_message(&message)?;
|
||||||
|
|
||||||
let message = match message {
|
let message = match message {
|
||||||
NixMessage::ParseFailure(line) => {
|
NixMessage::ParseFailure(line) => {
|
||||||
error!("FAIL PARSE: {line}");
|
error!("FAIL PARSE: {line}");
|
||||||
@@ -119,178 +98,19 @@ impl<'db> RunningBuild<'db> {
|
|||||||
};
|
};
|
||||||
match message {
|
match message {
|
||||||
NixAction::Msg(msg_message) => {
|
NixAction::Msg(msg_message) => {
|
||||||
if msg_message.msg.contains("nix log") {
|
if msg_message.level > 0
|
||||||
eprintln!("{}", msg_message.msg);
|
&& msg_message.level < 5
|
||||||
|
&& !(msg_message.level == 4 && msg_message.msg.starts_with("linking "))
|
||||||
|
&& !(msg_message.level == 4 && msg_message.msg.starts_with("evaluating file "))
|
||||||
|
&& !(msg_message.level == 3 && msg_message.msg.starts_with("checking path "))
|
||||||
|
{
|
||||||
|
eprintln!("LOG MESSAGE {}: {}", msg_message.level, msg_message.msg);
|
||||||
}
|
}
|
||||||
// if msg_message.level == 0 {
|
|
||||||
// eprintln!("{}", msg_message.msg);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
NixAction::Start(activity_start_message) => {
|
NixAction::Start(_activity_start_message) => {
|
||||||
match activity_start_message {
|
|
||||||
ActivityStartMessage::Unknown(activity_start_unknown) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_unknown.id,
|
|
||||||
activity_start_unknown.parent,
|
|
||||||
Activity::Unknown(ActivityUnknown {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
text: activity_start_unknown.text,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::CopyPath(activity_start_copy_path) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_copy_path.id,
|
|
||||||
activity_start_copy_path.parent,
|
|
||||||
Activity::CopyPath(ActivityCopyPath {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
missing_path: activity_start_copy_path.missing_path,
|
|
||||||
source: activity_start_copy_path.source,
|
|
||||||
destination: activity_start_copy_path.destination,
|
|
||||||
done: 0,
|
|
||||||
expected: 0,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::FileTransfer(activity_start_file_transfer) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_file_transfer.id,
|
|
||||||
activity_start_file_transfer.parent,
|
|
||||||
Activity::FileTransfer(ActivityFileTransfer {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
url: activity_start_file_transfer.url,
|
|
||||||
done: 0,
|
|
||||||
expected: 0,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::Realize(activity_start_realize) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_realize.id,
|
|
||||||
activity_start_realize.parent,
|
|
||||||
Activity::Realize(ActivityRealize {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::CopyPaths(activity_start_copy_paths) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_copy_paths.id,
|
|
||||||
activity_start_copy_paths.parent,
|
|
||||||
Activity::CopyPaths(ActivityCopyPaths {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
text: activity_start_copy_paths.text,
|
|
||||||
done: 0,
|
|
||||||
expected: 0,
|
|
||||||
running: 0,
|
|
||||||
failed: 0,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::Builds(activity_start_builds) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_builds.id,
|
|
||||||
activity_start_builds.parent,
|
|
||||||
Activity::Builds(ActivityBuilds {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
done: 0,
|
|
||||||
expected: 0,
|
|
||||||
running: 0,
|
|
||||||
failed: 0,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::Build(activity_start_build) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_build.id,
|
|
||||||
activity_start_build.parent,
|
|
||||||
Activity::Build(ActivityBuild {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
drv_path: activity_start_build.drv_path,
|
|
||||||
machine_name: if activity_start_build.machine_name.len() > 0 {
|
|
||||||
Some(activity_start_build.machine_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
phase: None,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_optimize_store.id,
|
|
||||||
activity_start_optimize_store.parent,
|
|
||||||
Activity::OptimizeStore(ActivityOptimizeStore {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::VerifyPaths(activity_start_verify_paths) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_verify_paths.id,
|
|
||||||
activity_start_verify_paths.parent,
|
|
||||||
Activity::VerifyPaths(ActivityVerifyPaths {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::Substitute(activity_start_substitute) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_substitute.id,
|
|
||||||
activity_start_substitute.parent,
|
|
||||||
Activity::Substitute(ActivitySubstitute {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::QueryPathInfo(activity_start_query_path_info) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_query_path_info.id,
|
|
||||||
activity_start_query_path_info.parent,
|
|
||||||
Activity::QueryPathInfo(ActivityQueryPathInfo {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::PostBuildHook(activity_start_post_build_hook) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_post_build_hook.id,
|
|
||||||
activity_start_post_build_hook.parent,
|
|
||||||
Activity::PostBuildHook(ActivityPostBuildHook {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::BuildWaiting(activity_start_build_waiting) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_build_waiting.id,
|
|
||||||
0,
|
|
||||||
Activity::BuildWaiting(ActivityBuildWaiting {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
text: activity_start_build_waiting.text,
|
|
||||||
drv_path: activity_start_build_waiting.drv_path,
|
|
||||||
path_resolved: activity_start_build_waiting.path_resolved,
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ActivityStartMessage::FetchTree(activity_start_fetch_tree) => {
|
|
||||||
self.activity_tree.add_activity(
|
|
||||||
activity_start_fetch_tree.id,
|
|
||||||
activity_start_fetch_tree.parent,
|
|
||||||
Activity::FetchTree(ActivityFetchTree {
|
|
||||||
state: ActivityState::default(),
|
|
||||||
}),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.print_current_status();
|
self.print_current_status();
|
||||||
}
|
}
|
||||||
NixAction::Stop(stop_message) => {
|
NixAction::Stop(_stop_message) => {
|
||||||
let activity = self
|
|
||||||
.activity_tree
|
|
||||||
.get_activity_id(stop_message.id)
|
|
||||||
.map(|activity_id| self.activity_tree.get_mut(&activity_id))?;
|
|
||||||
activity.get_mut_activity().stop();
|
|
||||||
self.print_current_status();
|
self.print_current_status();
|
||||||
// println!("{}", serde_json::to_string(&message)?);
|
// println!("{}", serde_json::to_string(&message)?);
|
||||||
}
|
}
|
||||||
@@ -302,40 +122,20 @@ impl<'db> RunningBuild<'db> {
|
|||||||
}
|
}
|
||||||
ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {}
|
ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {}
|
||||||
ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {}
|
ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {}
|
||||||
ActivityResultMessage::SetPhase(activity_result_set_phase) => {
|
ActivityResultMessage::SetPhase(_activity_result_set_phase) => {
|
||||||
let activity_id = self
|
|
||||||
.activity_tree
|
|
||||||
.get_activity_id(activity_result_set_phase.id)?;
|
|
||||||
let activity = self.activity_tree.get_mut(&activity_id);
|
|
||||||
activity
|
|
||||||
.get_mut_activity()
|
|
||||||
.set_phase(Some(activity_result_set_phase.phase));
|
|
||||||
|
|
||||||
self.print_current_status();
|
self.print_current_status();
|
||||||
}
|
}
|
||||||
ActivityResultMessage::Progress(activity_result_progress) => {
|
ActivityResultMessage::Progress(activity_result_progress) => {
|
||||||
let activity_id = self
|
if activity_result_progress.done == activity_result_progress.expected
|
||||||
.activity_tree
|
&& activity_result_progress.expected > 0
|
||||||
.get_activity_id(activity_result_progress.id)?;
|
{
|
||||||
let activity = self.activity_tree.get_mut(&activity_id);
|
// If we finished the in-progress activity, always render so we show the 100% done version.
|
||||||
activity.get_mut_activity().set_progress(
|
self.print_current_status();
|
||||||
activity_result_progress.done,
|
} else {
|
||||||
activity_result_progress.expected,
|
self.maybe_print_current_status();
|
||||||
activity_result_progress.running,
|
}
|
||||||
activity_result_progress.failed,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.maybe_print_current_status();
|
|
||||||
}
|
}
|
||||||
ActivityResultMessage::SetExpected(activity_result_set_expected) => {
|
ActivityResultMessage::SetExpected(_activity_result_set_expected) => {
|
||||||
let activity_id = self
|
|
||||||
.activity_tree
|
|
||||||
.get_activity_id(activity_result_set_expected.id)?;
|
|
||||||
let activity = self.activity_tree.get_mut(&activity_id);
|
|
||||||
activity
|
|
||||||
.get_mut_activity()
|
|
||||||
.set_expected(activity_result_set_expected.expected);
|
|
||||||
|
|
||||||
self.maybe_print_current_status();
|
self.maybe_print_current_status();
|
||||||
}
|
}
|
||||||
ActivityResultMessage::PostBuildLogLine(
|
ActivityResultMessage::PostBuildLogLine(
|
||||||
@@ -365,143 +165,83 @@ impl<'db> RunningBuild<'db> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn print_current_status(&mut self) -> () {
|
fn print_current_status(&mut self) -> () {
|
||||||
let mut tree = String::new();
|
// let draw_order = get_draw_order(self.activity_tree.get_tree());
|
||||||
let draw_order = self.get_draw_order();
|
let draw_order = ReverseTreeIter::new(
|
||||||
for dag_entry in draw_order {
|
self.activity_tree.get_tree(),
|
||||||
let leading_bars = {
|
None,
|
||||||
let mut leading_bars = String::with_capacity(3 * dag_entry.depth.len());
|
is_match_predicate,
|
||||||
for leading_bar in dag_entry
|
is_transparent_predicate,
|
||||||
.depth
|
is_alive_predicate,
|
||||||
.iter()
|
)
|
||||||
.map(|depth| if *depth { " " } else { " " })
|
.get_draw_order();
|
||||||
{
|
|
||||||
leading_bars.push_str(leading_bar);
|
print_dag(self.activity_tree.get_tree(), draw_order);
|
||||||
}
|
|
||||||
leading_bars
|
|
||||||
};
|
|
||||||
let branch = if dag_entry.has_later_siblings {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
let activity = self.activity_tree.get(&dag_entry.activity_id);
|
|
||||||
let display_name = activity
|
|
||||||
.get_activity()
|
|
||||||
.display_name()
|
|
||||||
.expect("Currently we always return a display name.");
|
|
||||||
let progress_text = activity.get_activity().get_progress_text();
|
|
||||||
let (progress, progress_sep) = match progress_text {
|
|
||||||
Some(text) => (text, " "),
|
|
||||||
None => (Cow::Borrowed(""), ""),
|
|
||||||
};
|
|
||||||
tree += &format!("{leading_bars}{branch} {progress}{progress_sep}{display_name}\n");
|
|
||||||
}
|
|
||||||
if tree.is_empty() {
|
|
||||||
println!("No active activities.");
|
|
||||||
} else {
|
|
||||||
print!("\n{}\n", tree);
|
|
||||||
}
|
|
||||||
self.last_announce = Some(Instant::now());
|
self.last_announce = Some(Instant::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_draw_order(&self) -> Vec<DrawDagEntry> {
|
|
||||||
let mut draw_order: Vec<DrawDagEntry> = Vec::new();
|
|
||||||
let mut stack: Vec<DrawStackEntry> = self
|
|
||||||
.get_children_in_order(self.activity_tree.get_root_id())
|
|
||||||
.filter_map(|child| {
|
|
||||||
if child.get_activity().is_active() {
|
|
||||||
Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry {
|
|
||||||
activity_id: child.get_activity_id().clone(),
|
|
||||||
depth: Vec::new(),
|
|
||||||
has_later_siblings: true,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
if stack.len() == 0 {
|
|
||||||
return draw_order;
|
|
||||||
}
|
|
||||||
stack
|
|
||||||
.last_mut()
|
|
||||||
.expect("If-statement ensured this is Some()")
|
|
||||||
.set_no_later_siblings();
|
|
||||||
|
|
||||||
while !stack.is_empty() {
|
|
||||||
let current_entry = stack.pop().expect("While-loop ensured this is Some.");
|
|
||||||
match current_entry {
|
|
||||||
DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => {
|
|
||||||
let current_id = draw_dag_entry.activity_id.clone();
|
|
||||||
let mut current_depth = draw_dag_entry.depth.clone();
|
|
||||||
current_depth.push(draw_dag_entry.has_later_siblings);
|
|
||||||
stack.push(DrawStackEntry::VisitedChildren(draw_dag_entry));
|
|
||||||
let children: Vec<ActivityId> = self
|
|
||||||
.get_children_in_order(current_id)
|
|
||||||
.filter_map(|child| {
|
|
||||||
if child.get_activity().is_active() {
|
|
||||||
Some(child.get_activity_id().clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let has_children = !children.is_empty();
|
|
||||||
stack.extend(children.into_iter().map(|child_id| {
|
|
||||||
DrawStackEntry::HasNotVisitedChildren(DrawDagEntry {
|
|
||||||
activity_id: child_id,
|
|
||||||
depth: current_depth.clone(),
|
|
||||||
has_later_siblings: true,
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
if has_children {
|
|
||||||
stack
|
|
||||||
.last_mut()
|
|
||||||
.expect("If-statement ensured this is Some()")
|
|
||||||
.set_no_later_siblings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DrawStackEntry::VisitedChildren(draw_dag_entry) => {
|
|
||||||
draw_order.push(draw_dag_entry);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
draw_order
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_children_in_order(
|
|
||||||
&self,
|
|
||||||
parent_id: ActivityId,
|
|
||||||
) -> impl Iterator<Item = &ActivityTreeEntry> {
|
|
||||||
let parent = self.activity_tree.get(&parent_id);
|
|
||||||
parent
|
|
||||||
.get_child_ids()
|
|
||||||
.iter()
|
|
||||||
.map(|child_id| self.activity_tree.get(child_id))
|
|
||||||
.flat_map(|child| TransparentIter::new(&self.activity_tree, child))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DrawStackEntry {
|
pub(crate) fn print_dag(activity_tree: &ActivityTree, draw_order: Vec<DrawDagEntry>) -> () {
|
||||||
HasNotVisitedChildren(DrawDagEntry),
|
let mut tree = String::new();
|
||||||
VisitedChildren(DrawDagEntry),
|
for dag_entry in draw_order {
|
||||||
}
|
let leading_bars = {
|
||||||
|
let mut leading_bars = String::with_capacity(3 * dag_entry.depth.len());
|
||||||
struct DrawDagEntry {
|
for leading_bar in dag_entry
|
||||||
activity_id: ActivityId,
|
.depth
|
||||||
depth: Vec<bool>,
|
.iter()
|
||||||
has_later_siblings: bool,
|
.map(|depth| if *depth { " " } else { " " })
|
||||||
}
|
{
|
||||||
|
leading_bars.push_str(leading_bar);
|
||||||
impl DrawStackEntry {
|
|
||||||
fn set_no_later_siblings(&mut self) -> () {
|
|
||||||
match self {
|
|
||||||
DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => {
|
|
||||||
draw_dag_entry.has_later_siblings = false
|
|
||||||
}
|
|
||||||
DrawStackEntry::VisitedChildren(draw_dag_entry) => {
|
|
||||||
draw_dag_entry.has_later_siblings = false
|
|
||||||
}
|
}
|
||||||
|
leading_bars
|
||||||
};
|
};
|
||||||
|
let branch = if dag_entry.has_later_siblings {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
let activity = activity_tree.get(&dag_entry.activity_id);
|
||||||
|
let display_name = activity
|
||||||
|
.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Currently we always return a display name.");
|
||||||
|
let progress_text = activity.get_activity().get_progress_text();
|
||||||
|
let (progress, progress_sep) = match progress_text {
|
||||||
|
Some(text) => (text, " "),
|
||||||
|
None => (Cow::Borrowed(""), ""),
|
||||||
|
};
|
||||||
|
tree += &format!("{leading_bars}{branch} {progress}{progress_sep}{display_name}\n");
|
||||||
|
}
|
||||||
|
if tree.is_empty() {
|
||||||
|
println!("No active activities.");
|
||||||
|
} else {
|
||||||
|
print!("\n{}\n", tree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_match_predicate(_entry: &ActivityTreeEntry) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_transparent_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
match entry.get_activity() {
|
||||||
|
Activity::Root(_activity_root) => true,
|
||||||
|
Activity::Unknown(_activity_unknown) => false,
|
||||||
|
Activity::CopyPath(_activity_copy_path) => false,
|
||||||
|
Activity::FileTransfer(_activity_file_transfer) => false,
|
||||||
|
Activity::Realize(_activity_realize) => true,
|
||||||
|
Activity::CopyPaths(_activity_copy_paths) => true,
|
||||||
|
Activity::Builds(_activity_builds) => true,
|
||||||
|
Activity::Build(_activity_build) => false,
|
||||||
|
Activity::OptimizeStore(_activity_optimize_store) => false,
|
||||||
|
Activity::VerifyPaths(_activity_verify_paths) => false,
|
||||||
|
Activity::Substitute(_activity_substitute) => false,
|
||||||
|
Activity::QueryPathInfo(_activity_query_path_info) => false,
|
||||||
|
Activity::PostBuildHook(_activity_post_build_hook) => false,
|
||||||
|
Activity::BuildWaiting(_activity_build_waiting) => true,
|
||||||
|
Activity::FetchTree(_activity_fetch_tree) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_alive_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
entry.get_activity().is_active()
|
||||||
|
}
|
||||||
|
|||||||
178
src/nix_util/running_update.rs
Normal file
178
src/nix_util/running_update.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use tokio::process::Child;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::nix_util::NixOutputStream;
|
||||||
|
use crate::nix_util::nix_output_stream::NixAction;
|
||||||
|
use crate::nix_util::output_stream::OutputStream;
|
||||||
|
use crate::nix_util::tree_iter::ForwardTreeIter;
|
||||||
|
|
||||||
|
use super::activity_tree::ActivityTreeEntry;
|
||||||
|
use super::activity_tree_stream::ActivityTreeStream;
|
||||||
|
use super::nix_output_stream::ActivityResultMessage;
|
||||||
|
use super::nix_output_stream::NixMessage;
|
||||||
|
|
||||||
|
pub(crate) struct RunningUpdate {
|
||||||
|
activity_tree: ActivityTreeStream,
|
||||||
|
last_announce: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunningUpdate {
|
||||||
|
pub(crate) fn new() -> Result<Self> {
|
||||||
|
Ok(RunningUpdate {
|
||||||
|
activity_tree: ActivityTreeStream::new(),
|
||||||
|
last_announce: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn run_to_completion(&mut self, mut child: Child) -> Result<()> {
|
||||||
|
let output_stream = OutputStream::from_child(&mut child)?;
|
||||||
|
let mut nix_output_stream: NixOutputStream<OutputStream> =
|
||||||
|
NixOutputStream::new(output_stream);
|
||||||
|
|
||||||
|
let exit_status_handle = tokio::spawn(async move {
|
||||||
|
let status = child
|
||||||
|
.wait()
|
||||||
|
.await
|
||||||
|
.expect("nixos-rebuild encountered an error");
|
||||||
|
status
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(message) = nix_output_stream.next().await? {
|
||||||
|
self.handle_message(message)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let exit_status = exit_status_handle.await?;
|
||||||
|
println!("nix update status was: {}", exit_status);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
||||||
|
self.activity_tree.handle_message(&message)?;
|
||||||
|
|
||||||
|
let message = match message {
|
||||||
|
NixMessage::ParseFailure(line) => {
|
||||||
|
error!("FAIL PARSE: {line}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
NixMessage::Generic(_value, line) => {
|
||||||
|
error!("GENERIC PARSE: {line}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
NixMessage::Action(nix_action) => nix_action,
|
||||||
|
};
|
||||||
|
match message {
|
||||||
|
NixAction::Msg(msg_message) => {
|
||||||
|
// if msg_message.level > 0 && msg_message.level < 5 {
|
||||||
|
// eprintln!("LOG MESSAGE {}: {}", msg_message.level, msg_message.msg);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
NixAction::Start(activity_start_message) => {
|
||||||
|
// println!("START: {}", serde_json::to_string(&activity_start_message)?);
|
||||||
|
self.print_current_status();
|
||||||
|
}
|
||||||
|
NixAction::Stop(stop_message) => {
|
||||||
|
// println!("STOP: {}", serde_json::to_string(&stop_message)?);
|
||||||
|
self.print_current_status();
|
||||||
|
}
|
||||||
|
NixAction::Result(activity_result_message) => {
|
||||||
|
match activity_result_message {
|
||||||
|
ActivityResultMessage::FileLinked(_activity_result_file_linked) => {}
|
||||||
|
ActivityResultMessage::BuildLogLine(_activity_result_build_log_line) => {}
|
||||||
|
ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {}
|
||||||
|
ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {}
|
||||||
|
ActivityResultMessage::SetPhase(_activity_result_set_phase) => {}
|
||||||
|
ActivityResultMessage::Progress(activity_result_progress) => {
|
||||||
|
// if activity_result_progress.expected != 0 {
|
||||||
|
// println!(
|
||||||
|
// "PROGRESS: {}",
|
||||||
|
// serde_json::to_string(&activity_result_progress)?
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
self.maybe_print_current_status();
|
||||||
|
}
|
||||||
|
ActivityResultMessage::SetExpected(activity_result_set_expected) => {
|
||||||
|
// if activity_result_set_expected.expected != 0 {
|
||||||
|
// println!(
|
||||||
|
// "EXPECTED: {}",
|
||||||
|
// serde_json::to_string(&activity_result_set_expected)?
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
self.maybe_print_current_status();
|
||||||
|
}
|
||||||
|
ActivityResultMessage::PostBuildLogLine(
|
||||||
|
_activity_result_post_build_log_line,
|
||||||
|
) => {}
|
||||||
|
ActivityResultMessage::FetchStatus(_activity_result_fetch_status) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_print_current_status(&mut self) -> () {
|
||||||
|
let last_announce = match self.last_announce {
|
||||||
|
Some(instant) => instant,
|
||||||
|
None => {
|
||||||
|
// If we haven't announced before, always announce.
|
||||||
|
return self.print_current_status();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let time_since_last_announce = now.duration_since(last_announce);
|
||||||
|
if time_since_last_announce > Duration::new(5, 0) {
|
||||||
|
return self.print_current_status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_current_status(&mut self) -> () {
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
self.activity_tree.get_tree(),
|
||||||
|
None,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for (depth, _is_match, _is_transparent, node) in nodes {
|
||||||
|
let progress_text = node
|
||||||
|
.get_activity()
|
||||||
|
.get_progress_text()
|
||||||
|
.unwrap_or(Cow::Borrowed(""));
|
||||||
|
let name = node
|
||||||
|
.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.unwrap_or(Cow::Borrowed("null"));
|
||||||
|
out.push(format!("{depth}\t{progress_text}\t{name}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.is_empty() {
|
||||||
|
println!("No active activities.");
|
||||||
|
} else {
|
||||||
|
println!("\n");
|
||||||
|
for l in out {
|
||||||
|
println!("{l}\n");
|
||||||
|
}
|
||||||
|
println!("\n");
|
||||||
|
}
|
||||||
|
self.last_announce = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_match_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
entry.get_activity().get_progress_text().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_transparent_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_alive_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
entry.get_activity().is_active()
|
||||||
|
}
|
||||||
181
src/nix_util/running_verify.rs
Normal file
181
src/nix_util/running_verify.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use tokio::process::Child;
|
||||||
|
use tracing::error;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::Result;
|
||||||
|
use crate::nix_util::NixOutputStream;
|
||||||
|
use crate::nix_util::nix_output_stream::NixAction;
|
||||||
|
use crate::nix_util::output_stream::OutputStream;
|
||||||
|
use crate::nix_util::tree_iter::ForwardTreeIter;
|
||||||
|
|
||||||
|
use super::activity_tree::ActivityTreeEntry;
|
||||||
|
use super::activity_tree_stream::ActivityTreeStream;
|
||||||
|
use super::nix_output_stream::ActivityResultMessage;
|
||||||
|
use super::nix_output_stream::NixMessage;
|
||||||
|
|
||||||
|
pub(crate) struct RunningVerify {
|
||||||
|
activity_tree: ActivityTreeStream,
|
||||||
|
last_announce: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunningVerify {
|
||||||
|
pub(crate) fn new() -> Result<Self> {
|
||||||
|
Ok(RunningVerify {
|
||||||
|
activity_tree: ActivityTreeStream::new(),
|
||||||
|
last_announce: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn run_to_completion(&mut self, mut child: Child) -> Result<()> {
|
||||||
|
let output_stream = OutputStream::from_child(&mut child)?;
|
||||||
|
let mut nix_output_stream: NixOutputStream<OutputStream> =
|
||||||
|
NixOutputStream::new(output_stream);
|
||||||
|
|
||||||
|
info!("Verifying nix store.");
|
||||||
|
|
||||||
|
let exit_status_handle = tokio::spawn(async move {
|
||||||
|
let status = child
|
||||||
|
.wait()
|
||||||
|
.await
|
||||||
|
.expect("nixos-rebuild encountered an error");
|
||||||
|
status
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(message) = nix_output_stream.next().await? {
|
||||||
|
self.handle_message(message)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let exit_status = exit_status_handle.await?;
|
||||||
|
info!("nix store verify status was: {}", exit_status);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_message(&mut self, message: NixMessage) -> Result<()> {
|
||||||
|
self.activity_tree.handle_message(&message)?;
|
||||||
|
|
||||||
|
let message = match message {
|
||||||
|
NixMessage::ParseFailure(line) => {
|
||||||
|
error!("FAIL PARSE: {line}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
NixMessage::Generic(_value, line) => {
|
||||||
|
error!("GENERIC PARSE: {line}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
NixMessage::Action(nix_action) => nix_action,
|
||||||
|
};
|
||||||
|
match message {
|
||||||
|
NixAction::Msg(msg_message) => {
|
||||||
|
// if msg_message.level > 0 && msg_message.level < 5 {
|
||||||
|
// eprintln!("LOG MESSAGE {}: {}", msg_message.level, msg_message.msg);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
NixAction::Start(activity_start_message) => {
|
||||||
|
// println!("START: {}", serde_json::to_string(&activity_start_message)?);
|
||||||
|
self.print_current_status();
|
||||||
|
}
|
||||||
|
NixAction::Stop(stop_message) => {
|
||||||
|
// println!("STOP: {}", serde_json::to_string(&stop_message)?);
|
||||||
|
self.print_current_status();
|
||||||
|
}
|
||||||
|
NixAction::Result(activity_result_message) => {
|
||||||
|
match activity_result_message {
|
||||||
|
ActivityResultMessage::FileLinked(_activity_result_file_linked) => {}
|
||||||
|
ActivityResultMessage::BuildLogLine(_activity_result_build_log_line) => {}
|
||||||
|
ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {}
|
||||||
|
ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {}
|
||||||
|
ActivityResultMessage::SetPhase(_activity_result_set_phase) => {}
|
||||||
|
ActivityResultMessage::Progress(activity_result_progress) => {
|
||||||
|
// if activity_result_progress.expected != 0 {
|
||||||
|
// println!(
|
||||||
|
// "PROGRESS: {}",
|
||||||
|
// serde_json::to_string(&activity_result_progress)?
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
self.maybe_print_current_status();
|
||||||
|
}
|
||||||
|
ActivityResultMessage::SetExpected(activity_result_set_expected) => {
|
||||||
|
// if activity_result_set_expected.expected != 0 {
|
||||||
|
// println!(
|
||||||
|
// "EXPECTED: {}",
|
||||||
|
// serde_json::to_string(&activity_result_set_expected)?
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
self.maybe_print_current_status();
|
||||||
|
}
|
||||||
|
ActivityResultMessage::PostBuildLogLine(
|
||||||
|
_activity_result_post_build_log_line,
|
||||||
|
) => {}
|
||||||
|
ActivityResultMessage::FetchStatus(_activity_result_fetch_status) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_print_current_status(&mut self) -> () {
|
||||||
|
let last_announce = match self.last_announce {
|
||||||
|
Some(instant) => instant,
|
||||||
|
None => {
|
||||||
|
// If we haven't announced before, always announce.
|
||||||
|
return self.print_current_status();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let time_since_last_announce = now.duration_since(last_announce);
|
||||||
|
if time_since_last_announce > Duration::new(5, 0) {
|
||||||
|
return self.print_current_status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_current_status(&mut self) -> () {
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
self.activity_tree.get_tree(),
|
||||||
|
None,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for (depth, _is_match, _is_transparent, node) in nodes {
|
||||||
|
let progress_text = node
|
||||||
|
.get_activity()
|
||||||
|
.get_progress_text()
|
||||||
|
.unwrap_or(Cow::Borrowed(""));
|
||||||
|
let name = node
|
||||||
|
.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.unwrap_or(Cow::Borrowed("null"));
|
||||||
|
out.push(format!("{depth}\t{progress_text}\t{name}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.is_empty() {
|
||||||
|
println!("No active activities.");
|
||||||
|
} else {
|
||||||
|
println!("\n");
|
||||||
|
for l in out {
|
||||||
|
println!("{l}\n");
|
||||||
|
}
|
||||||
|
println!("\n");
|
||||||
|
}
|
||||||
|
self.last_announce = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_match_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
entry.get_activity().get_progress_text().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_transparent_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_alive_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
entry.get_activity().is_active()
|
||||||
|
}
|
||||||
100
src/nix_util/tree_iter/forward_tree_iter/benchmarks.rs
Normal file
100
src/nix_util/tree_iter/forward_tree_iter/benchmarks.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use crate::nix_util::activity::Activity;
|
||||||
|
use crate::nix_util::activity::ActivityState;
|
||||||
|
use crate::nix_util::activity::ActivityUnknown;
|
||||||
|
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn build_example_tree(
|
||||||
|
num_levels: u64,
|
||||||
|
num_children_per_node: u64,
|
||||||
|
) -> Result<ActivityTree, Box<dyn std::error::Error>> {
|
||||||
|
let mut activity_tree = ActivityTree::new();
|
||||||
|
let mut next_index = 1;
|
||||||
|
let mut previous_level: Vec<u64> = vec![0];
|
||||||
|
|
||||||
|
for _ in 0..num_levels {
|
||||||
|
let mut current_parents = Vec::new();
|
||||||
|
std::mem::swap(&mut previous_level, &mut current_parents);
|
||||||
|
for parent in current_parents {
|
||||||
|
for _ in 0..num_children_per_node {
|
||||||
|
activity_tree.add_activity(
|
||||||
|
next_index,
|
||||||
|
parent,
|
||||||
|
Activity::Unknown(ActivityUnknown {
|
||||||
|
state: ActivityState::Stopped,
|
||||||
|
text: format!("N{next_index}"),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
previous_level.push(next_index);
|
||||||
|
next_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(activity_tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_primes(limit: u64) -> BTreeSet<u64> {
|
||||||
|
primal::Primes::all()
|
||||||
|
.take_while(|p| *p <= (limit as usize))
|
||||||
|
.map(|p| p as u64)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with few levels but many nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_shallow_wide_tree(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 3;
|
||||||
|
let num_children_per_node: u64 = 27;
|
||||||
|
let total_nodes = num_children_per_node.pow(num_levels as u32);
|
||||||
|
|
||||||
|
let primes = get_primes(total_nodes);
|
||||||
|
// match = power of 2
|
||||||
|
// transparent = prime
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
|entry| (*entry.get_activity_id().deref()).count_ones() == 1,
|
||||||
|
|entry| primes.contains(entry.get_activity_id().deref()),
|
||||||
|
|_entry| true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for _ in nodes {}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with many levels but few nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_deep_narrow_tree(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 9;
|
||||||
|
let num_children_per_node: u64 = 3;
|
||||||
|
let total_nodes = num_children_per_node.pow(num_levels as u32);
|
||||||
|
|
||||||
|
let primes = get_primes(total_nodes);
|
||||||
|
// match = power of 2
|
||||||
|
// transparent = prime
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
|entry| (*entry.get_activity_id().deref()).count_ones() == 1,
|
||||||
|
|entry| primes.contains(entry.get_activity_id().deref()),
|
||||||
|
|_entry| true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for _ in nodes {}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
243
src/nix_util/tree_iter/forward_tree_iter/mod.rs
Normal file
243
src/nix_util/tree_iter/forward_tree_iter/mod.rs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod benchmarks;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::nix_util::activity_tree::ActivityTree;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTreeEntry;
|
||||||
|
|
||||||
|
use super::stack_entry::StackEntry;
|
||||||
|
|
||||||
|
/// An iterator over the activity tree that will return nodes in the tree matching the supplied criteria.
|
||||||
|
///
|
||||||
|
/// is_match: Return only nodes that pass this predicate and the parents of that node up to the root.
|
||||||
|
/// is_alive: If a node fails this predicate, do not return it and do not check any of its children.
|
||||||
|
/// is_transparent: Nodes passing this predicate will be returned but they will not be counted in the depth calculation.
|
||||||
|
///
|
||||||
|
/// The elements returned from this iterator are a tuple containing:
|
||||||
|
/// - The depth
|
||||||
|
/// - Whether the node passed is_match
|
||||||
|
/// - Whether the node passed is_transparent
|
||||||
|
/// - The activity itself
|
||||||
|
///
|
||||||
|
/// Using this information, you can draw a tree, using the depth information to glean which nodes are children of other nodes.
|
||||||
|
///
|
||||||
|
/// This iterator returns the earliest/highest nodes first in a depth-first-search pattern.
|
||||||
|
///
|
||||||
|
/// With the example tree:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// A - B - C
|
||||||
|
/// |\ \
|
||||||
|
/// H G D - E
|
||||||
|
/// | \
|
||||||
|
/// I F
|
||||||
|
/// |
|
||||||
|
/// J
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// With B, E, and J matching
|
||||||
|
/// and D as transparent
|
||||||
|
/// you would get the following output:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// 0 False False A
|
||||||
|
/// 1 True False B
|
||||||
|
/// 2 False True D
|
||||||
|
/// 2 True False E
|
||||||
|
/// 1 False False H
|
||||||
|
/// 2 False False I
|
||||||
|
/// 3 True False J
|
||||||
|
/// ```
|
||||||
|
pub(crate) struct ForwardTreeIter<'tree, MP, TP, AP> {
|
||||||
|
activity_tree: &'tree ActivityTree,
|
||||||
|
origin: &'tree ActivityTreeEntry,
|
||||||
|
is_match_predicate: MP,
|
||||||
|
is_transparent_predicate: TP,
|
||||||
|
is_alive_predicate: AP,
|
||||||
|
stack: Vec<StackEntry<'tree>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree, MP, TP, AP> ForwardTreeIter<'tree, MP, TP, AP>
|
||||||
|
where
|
||||||
|
MP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
TP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
AP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(
|
||||||
|
activity_tree: &'tree ActivityTree,
|
||||||
|
origin: Option<&'tree ActivityTreeEntry>,
|
||||||
|
is_match_predicate: MP,
|
||||||
|
is_transparent_predicate: TP,
|
||||||
|
is_alive_predicate: AP,
|
||||||
|
) -> ForwardTreeIter<'tree, MP, TP, AP> {
|
||||||
|
let origin_node = origin.unwrap_or_else(|| {
|
||||||
|
let root_id = activity_tree.get_root_id();
|
||||||
|
activity_tree.get(&root_id)
|
||||||
|
});
|
||||||
|
ForwardTreeIter {
|
||||||
|
activity_tree,
|
||||||
|
origin: origin_node,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
stack: vec![StackEntry::new(origin_node)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the next StackEntry that should be returned from the iterator.
|
||||||
|
fn get_next_announce<'iter>(
|
||||||
|
&'iter mut self,
|
||||||
|
) -> Option<(usize, bool, bool, &'tree ActivityTreeEntry)> {
|
||||||
|
let origin_activity_id = *self.origin.get_activity_id().deref();
|
||||||
|
let (activity, is_match, is_transparent) = match self
|
||||||
|
.stack
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|entry| {
|
||||||
|
entry.should_announce()
|
||||||
|
&& !entry.is_announced()
|
||||||
|
&& *entry.get_node().get_activity_id().deref() != origin_activity_id
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
Some(entry) => {
|
||||||
|
entry.set_announced();
|
||||||
|
let is_match = entry.matches();
|
||||||
|
let is_transparent = entry.is_transparent();
|
||||||
|
(entry.get_node(), is_match, is_transparent)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let depth = self
|
||||||
|
.walk_up_stack(activity)
|
||||||
|
.filter(|node| {
|
||||||
|
!node.is_transparent()
|
||||||
|
&& *node.get_node().get_activity_id().deref() != origin_activity_id
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
Some((depth, is_match, is_transparent, activity))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk up the stack, returning all visited nodes above the base.
|
||||||
|
///
|
||||||
|
/// This should give you the path to base (starting from the end).
|
||||||
|
fn walk_up_stack<'iter>(
|
||||||
|
&'iter mut self,
|
||||||
|
base: &'tree ActivityTreeEntry,
|
||||||
|
) -> impl Iterator<Item = &'iter mut StackEntry<'tree>> {
|
||||||
|
self.stack
|
||||||
|
.iter_mut()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|node| !std::ptr::eq(node.get_node(), base))
|
||||||
|
.skip(1)
|
||||||
|
.filter(|node| node.is_visited())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree, MP, TP, AP> Iterator for ForwardTreeIter<'tree, MP, TP, AP>
|
||||||
|
where
|
||||||
|
MP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
TP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
AP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
{
|
||||||
|
type Item = (usize, bool, bool, &'tree ActivityTreeEntry);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// If any entries in the stack are waiting to be returned, output them.
|
||||||
|
match self.get_next_announce() {
|
||||||
|
out @ Some(_) => {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Walk through stack
|
||||||
|
loop {
|
||||||
|
let mut should_check_for_output = false;
|
||||||
|
|
||||||
|
let node = {
|
||||||
|
let stack_entry = match self.stack.last_mut() {
|
||||||
|
Some(e) => e,
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let node = stack_entry.get_node();
|
||||||
|
|
||||||
|
// If we've already processed the children, remove it from the stack.
|
||||||
|
if stack_entry.is_visited() {
|
||||||
|
self.stack.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
|
};
|
||||||
|
|
||||||
|
// Otherwise, check if it matches the predicate and mark all parents if it does.
|
||||||
|
if (self.is_match_predicate)(node) {
|
||||||
|
{
|
||||||
|
let stack_entry = self
|
||||||
|
.stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("Loop condition guarantees this is Some.");
|
||||||
|
// TODO: Combine into single op.
|
||||||
|
stack_entry.set_should_announce();
|
||||||
|
stack_entry.set_matches();
|
||||||
|
should_check_for_output = true;
|
||||||
|
}
|
||||||
|
let origin_activity_id = *self.origin.get_activity_id().deref();
|
||||||
|
for parent in self.walk_up_stack(node).filter(|entry| {
|
||||||
|
!entry.should_announce()
|
||||||
|
&& *entry.get_node().get_activity_id().deref() != origin_activity_id
|
||||||
|
}) {
|
||||||
|
parent.set_should_announce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let children = {
|
||||||
|
let stack_entry = self
|
||||||
|
.stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("Loop condition guarantees this is Some.");
|
||||||
|
|
||||||
|
// Mark the node as visited
|
||||||
|
stack_entry.set_visited();
|
||||||
|
if (self.is_transparent_predicate)(node) {
|
||||||
|
stack_entry.set_transparent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all children to the stack if they pass the is_alive_predicate.
|
||||||
|
let children = node
|
||||||
|
.get_child_ids()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|id| self.activity_tree.get(id))
|
||||||
|
.filter(|node| {
|
||||||
|
*node.get_activity_id().deref() != 0 && (self.is_alive_predicate)(node)
|
||||||
|
})
|
||||||
|
.map(|node| StackEntry::new(node));
|
||||||
|
children
|
||||||
|
};
|
||||||
|
|
||||||
|
self.stack.extend(children);
|
||||||
|
|
||||||
|
// If any entries in the stack are waiting to be returned, output them.
|
||||||
|
if should_check_for_output {
|
||||||
|
match self.get_next_announce() {
|
||||||
|
out @ Some(_) => {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
151
src/nix_util/tree_iter/forward_tree_iter/tests.rs
Normal file
151
src/nix_util/tree_iter/forward_tree_iter/tests.rs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
use crate::nix_util::running_build::is_transparent_predicate;
|
||||||
|
use crate::nix_util::tree_iter::test_util::build_example_tree;
|
||||||
|
use crate::nix_util::tree_iter::test_util::is_alive_predicate;
|
||||||
|
use crate::nix_util::tree_iter::test_util::is_match_predicate;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forward_tree_order() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![
|
||||||
|
(0, false, false, "Unknown(N1)".into()),
|
||||||
|
(1, true, false, "Unknown(N2)".into()),
|
||||||
|
(2, false, true, "Realize".into()), // N4
|
||||||
|
(2, true, false, "Unknown(N5)".into()),
|
||||||
|
(1, false, false, "Unknown(N8)".into()),
|
||||||
|
(2, false, false, "Unknown(N9)".into()),
|
||||||
|
(3, true, false, "Unknown(N10)".into()),
|
||||||
|
(0, false, false, "Unknown(N11)".into()),
|
||||||
|
(1, true, false, "Unknown(N12)".into()),
|
||||||
|
(2, false, true, "Realize".into()), // N14
|
||||||
|
(2, true, false, "Unknown(N15)".into()),
|
||||||
|
(1, false, false, "Unknown(N18)".into()),
|
||||||
|
(2, false, false, "Unknown(N19)".into()),
|
||||||
|
(3, true, false, "Unknown(N20)".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_from_a() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
Some(activity_tree.get(&activity_tree.get_activity_id(1)?)),
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![
|
||||||
|
(0, true, false, "Unknown(N2)".into()),
|
||||||
|
(1, false, true, "Realize".into()), // N4
|
||||||
|
(1, true, false, "Unknown(N5)".into()),
|
||||||
|
(0, false, false, "Unknown(N8)".into()),
|
||||||
|
(1, false, false, "Unknown(N9)".into()),
|
||||||
|
(2, true, false, "Unknown(N10)".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_from_h() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
Some(activity_tree.get(&activity_tree.get_activity_id(8)?)),
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![
|
||||||
|
(0, false, false, "Unknown(N9)".into()),
|
||||||
|
(1, true, false, "Unknown(N10)".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_from_c() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ForwardTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
Some(activity_tree.get(&activity_tree.get_activity_id(3)?)),
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
14
src/nix_util/tree_iter/mod.rs
Normal file
14
src/nix_util/tree_iter/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod test_util;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
mod forward_tree_iter;
|
||||||
|
mod original_implementation;
|
||||||
|
mod reverse_tree_iter;
|
||||||
|
mod stack_entry;
|
||||||
|
|
||||||
|
pub(crate) use forward_tree_iter::ForwardTreeIter;
|
||||||
|
pub(crate) use original_implementation::DrawDagEntry;
|
||||||
|
pub(crate) use original_implementation::get_draw_order;
|
||||||
|
pub(crate) use reverse_tree_iter::ReverseTreeIter;
|
||||||
85
src/nix_util/tree_iter/original_implementation/benchmarks.rs
Normal file
85
src/nix_util/tree_iter/original_implementation/benchmarks.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use test::Bencher;
|
||||||
|
use test::black_box;
|
||||||
|
|
||||||
|
use crate::nix_util::activity::Activity;
|
||||||
|
use crate::nix_util::activity::ActivityRealize;
|
||||||
|
use crate::nix_util::activity::ActivityState;
|
||||||
|
use crate::nix_util::activity::ActivityUnknown;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTree;
|
||||||
|
|
||||||
|
use super::get_draw_order;
|
||||||
|
|
||||||
|
fn build_example_tree(
|
||||||
|
num_levels: u64,
|
||||||
|
num_children_per_node: u64,
|
||||||
|
) -> Result<ActivityTree, Box<dyn std::error::Error>> {
|
||||||
|
let mut activity_tree = ActivityTree::new();
|
||||||
|
let mut next_index = 1;
|
||||||
|
let mut previous_level: Vec<u64> = vec![0];
|
||||||
|
|
||||||
|
for _ in 0..num_levels {
|
||||||
|
let mut current_parents = Vec::new();
|
||||||
|
std::mem::swap(&mut previous_level, &mut current_parents);
|
||||||
|
for parent in current_parents {
|
||||||
|
for _ in 0..num_children_per_node {
|
||||||
|
// transparent = prime
|
||||||
|
let activity = if primal::is_prime(next_index as u64) {
|
||||||
|
Activity::Realize(ActivityRealize {
|
||||||
|
state: ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Activity::Unknown(ActivityUnknown {
|
||||||
|
state: ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
},
|
||||||
|
text: format!("N{next_index}"),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
// if next_index is prime {} else {}
|
||||||
|
activity_tree.add_activity(next_index, parent, activity)?;
|
||||||
|
previous_level.push(next_index);
|
||||||
|
next_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(activity_tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with few levels but many nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_shallow_wide_tree(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 3;
|
||||||
|
let num_children_per_node: u64 = 27;
|
||||||
|
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let _ = black_box(get_draw_order(&activity_tree));
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with many levels but few nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_deep_narrow_tree(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 9;
|
||||||
|
let num_children_per_node: u64 = 3;
|
||||||
|
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let _ = black_box(get_draw_order(&activity_tree));
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
112
src/nix_util/tree_iter/original_implementation/mod.rs
Normal file
112
src/nix_util/tree_iter/original_implementation/mod.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod benchmarks;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
mod transparent_iter;
|
||||||
|
|
||||||
|
use crate::nix_util::activity_tree::ActivityId;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTree;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTreeEntry;
|
||||||
|
|
||||||
|
use self::transparent_iter::TransparentIter;
|
||||||
|
|
||||||
|
pub(crate) fn get_draw_order(activity_tree: &ActivityTree) -> Vec<DrawDagEntry> {
|
||||||
|
let mut draw_order: Vec<DrawDagEntry> = Vec::new();
|
||||||
|
let mut stack: Vec<DrawStackEntry> =
|
||||||
|
get_children_in_order(activity_tree, activity_tree.get_root_id())
|
||||||
|
.filter_map(|child| {
|
||||||
|
if child.get_activity().is_active() {
|
||||||
|
Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry {
|
||||||
|
activity_id: child.get_activity_id().clone(),
|
||||||
|
depth: Vec::new(),
|
||||||
|
has_later_siblings: true,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if stack.len() == 0 {
|
||||||
|
return draw_order;
|
||||||
|
}
|
||||||
|
stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("If-statement ensured this is Some()")
|
||||||
|
.set_no_later_siblings();
|
||||||
|
|
||||||
|
while !stack.is_empty() {
|
||||||
|
let current_entry = stack.pop().expect("While-loop ensured this is Some.");
|
||||||
|
match current_entry {
|
||||||
|
DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => {
|
||||||
|
let current_id = draw_dag_entry.activity_id.clone();
|
||||||
|
let mut current_depth = draw_dag_entry.depth.clone();
|
||||||
|
current_depth.push(draw_dag_entry.has_later_siblings);
|
||||||
|
stack.push(DrawStackEntry::VisitedChildren(draw_dag_entry));
|
||||||
|
let children: Vec<ActivityId> = get_children_in_order(activity_tree, current_id)
|
||||||
|
.filter_map(|child| {
|
||||||
|
if child.get_activity().is_active() {
|
||||||
|
Some(child.get_activity_id().clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let has_children = !children.is_empty();
|
||||||
|
stack.extend(children.into_iter().map(|child_id| {
|
||||||
|
DrawStackEntry::HasNotVisitedChildren(DrawDagEntry {
|
||||||
|
activity_id: child_id,
|
||||||
|
depth: current_depth.clone(),
|
||||||
|
has_later_siblings: true,
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
if has_children {
|
||||||
|
stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("If-statement ensured this is Some()")
|
||||||
|
.set_no_later_siblings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DrawStackEntry::VisitedChildren(draw_dag_entry) => {
|
||||||
|
draw_order.push(draw_dag_entry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
draw_order
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children_in_order(
|
||||||
|
activity_tree: &ActivityTree,
|
||||||
|
parent_id: ActivityId,
|
||||||
|
) -> impl Iterator<Item = &ActivityTreeEntry> {
|
||||||
|
let parent = activity_tree.get(&parent_id);
|
||||||
|
parent
|
||||||
|
.get_child_ids()
|
||||||
|
.iter()
|
||||||
|
.map(|child_id| activity_tree.get(child_id))
|
||||||
|
.flat_map(|child| TransparentIter::new(activity_tree, child))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DrawStackEntry {
|
||||||
|
HasNotVisitedChildren(DrawDagEntry),
|
||||||
|
VisitedChildren(DrawDagEntry),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub(crate) struct DrawDagEntry {
|
||||||
|
pub(crate) activity_id: ActivityId,
|
||||||
|
pub(crate) depth: Vec<bool>,
|
||||||
|
pub(crate) has_later_siblings: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawStackEntry {
|
||||||
|
fn set_no_later_siblings(&mut self) -> () {
|
||||||
|
match self {
|
||||||
|
DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => {
|
||||||
|
draw_dag_entry.has_later_siblings = false
|
||||||
|
}
|
||||||
|
DrawStackEntry::VisitedChildren(draw_dag_entry) => {
|
||||||
|
draw_dag_entry.has_later_siblings = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/nix_util/tree_iter/original_implementation/tests.rs
Normal file
1
src/nix_util/tree_iter/original_implementation/tests.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::activity_tree::ActivityTree;
|
use crate::nix_util::activity_tree::ActivityTree;
|
||||||
use super::activity_tree::ActivityTreeEntry;
|
use crate::nix_util::activity_tree::ActivityTreeEntry;
|
||||||
|
use crate::nix_util::running_build::is_transparent_predicate;
|
||||||
|
|
||||||
/// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children.
|
/// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children.
|
||||||
pub(crate) struct TransparentIter<'tree> {
|
pub(crate) struct TransparentIter<'tree> {
|
||||||
@@ -19,7 +20,7 @@ impl<'tree> Iterator for TransparentIter<'tree> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !origin.get_activity().is_transparent() {
|
if !is_transparent_predicate(origin) {
|
||||||
let origin = self
|
let origin = self
|
||||||
.origin
|
.origin
|
||||||
.take()
|
.take()
|
||||||
@@ -34,7 +35,7 @@ impl<'tree> Iterator for TransparentIter<'tree> {
|
|||||||
loop {
|
loop {
|
||||||
let current_entry = self.get_current_entry();
|
let current_entry = self.get_current_entry();
|
||||||
if let Some(child) = current_entry {
|
if let Some(child) = current_entry {
|
||||||
if child.get_activity().is_transparent() {
|
if !is_transparent_predicate(child) {
|
||||||
self.child_index.push(0);
|
self.child_index.push(0);
|
||||||
} else {
|
} else {
|
||||||
let last_index = self.child_index.last_mut().expect("Stack cannot be empty.");
|
let last_index = self.child_index.last_mut().expect("Stack cannot be empty.");
|
||||||
129
src/nix_util/tree_iter/reverse_tree_iter/benchmarks.rs
Normal file
129
src/nix_util/tree_iter/reverse_tree_iter/benchmarks.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use crate::nix_util::activity::Activity;
|
||||||
|
use crate::nix_util::activity::ActivityState;
|
||||||
|
use crate::nix_util::activity::ActivityUnknown;
|
||||||
|
|
||||||
|
use test::Bencher;
|
||||||
|
use test::black_box;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn build_example_tree(
|
||||||
|
num_levels: u64,
|
||||||
|
num_children_per_node: u64,
|
||||||
|
) -> Result<ActivityTree, Box<dyn std::error::Error>> {
|
||||||
|
let mut activity_tree = ActivityTree::new();
|
||||||
|
let mut next_index = 1;
|
||||||
|
let mut previous_level: Vec<u64> = vec![0];
|
||||||
|
|
||||||
|
for _ in 0..num_levels {
|
||||||
|
// Subsequent levels, add num_children to each node of the previous level.
|
||||||
|
let mut current_parents = Vec::new();
|
||||||
|
std::mem::swap(&mut previous_level, &mut current_parents);
|
||||||
|
for parent in current_parents {
|
||||||
|
for _ in 0..num_children_per_node {
|
||||||
|
activity_tree.add_activity(
|
||||||
|
next_index,
|
||||||
|
parent,
|
||||||
|
Activity::Unknown(ActivityUnknown {
|
||||||
|
state: ActivityState::Stopped,
|
||||||
|
text: format!("N{next_index}"),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
previous_level.push(next_index);
|
||||||
|
next_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(activity_tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_primes(limit: u64) -> BTreeSet<u64> {
|
||||||
|
primal::Primes::all()
|
||||||
|
.take_while(|p| *p <= (limit as usize))
|
||||||
|
.map(|p| p as u64)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with few levels but many nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_shallow_wide_tree(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 3;
|
||||||
|
let num_children_per_node: u64 = 27;
|
||||||
|
let total_nodes = num_children_per_node.pow(num_levels as u32);
|
||||||
|
|
||||||
|
let primes = get_primes(total_nodes);
|
||||||
|
// match = power of 2
|
||||||
|
// transparent = prime
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
|entry| (*entry.get_activity_id().deref()).count_ones() == 1,
|
||||||
|
|entry| primes.contains(entry.get_activity_id().deref()),
|
||||||
|
|_entry| true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for _ in nodes {}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with few levels but many nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_shallow_wide_tree_draw(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 3;
|
||||||
|
let num_children_per_node: u64 = 27;
|
||||||
|
let total_nodes = num_children_per_node.pow(num_levels as u32);
|
||||||
|
|
||||||
|
let primes = get_primes(total_nodes);
|
||||||
|
// match = power of 2
|
||||||
|
// transparent = prime
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
|entry| (*entry.get_activity_id().deref()).count_ones() == 1,
|
||||||
|
|entry| primes.contains(entry.get_activity_id().deref()),
|
||||||
|
|_entry| true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = black_box(nodes.get_draw_order());
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test a tree with many levels but few nodes per level.
|
||||||
|
#[bench]
|
||||||
|
fn bench_deep_narrow_tree_draw(b: &mut Bencher) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let num_levels: u64 = 9;
|
||||||
|
let num_children_per_node: u64 = 3;
|
||||||
|
let total_nodes = num_children_per_node.pow(num_levels as u32);
|
||||||
|
|
||||||
|
let primes = get_primes(total_nodes);
|
||||||
|
// match = power of 2
|
||||||
|
// transparent = prime
|
||||||
|
let activity_tree = build_example_tree(num_levels, num_children_per_node)?;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
|entry| (*entry.get_activity_id().deref()).count_ones() == 1,
|
||||||
|
|entry| primes.contains(entry.get_activity_id().deref()),
|
||||||
|
|_entry| true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = black_box(nodes.get_draw_order());
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
253
src/nix_util/tree_iter/reverse_tree_iter/mod.rs
Normal file
253
src/nix_util/tree_iter/reverse_tree_iter/mod.rs
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod benchmarks;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::nix_util::activity_tree::ActivityTree;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTreeEntry;
|
||||||
|
|
||||||
|
use super::original_implementation::DrawDagEntry;
|
||||||
|
use super::stack_entry::StackEntry;
|
||||||
|
|
||||||
|
/// An iterator over the activity tree that will return nodes in the tree matching the supplied criteria.
|
||||||
|
///
|
||||||
|
/// is_match: Return only nodes that pass this predicate and the parents of that node up to the root.
|
||||||
|
/// is_alive: If a node fails this predicate, do not return it and do not check any of its children.
|
||||||
|
/// is_transparent: Nodes passing this predicate will be returned but they will not be counted in the depth calculation.
|
||||||
|
///
|
||||||
|
/// The elements returned from this iterator are a tuple containing:
|
||||||
|
/// - The depth
|
||||||
|
/// - Whether the node passed is_match
|
||||||
|
/// - Whether the node passed is_transparent
|
||||||
|
/// - The activity itself
|
||||||
|
///
|
||||||
|
/// Using this information, you can draw a tree, using the depth information to glean which nodes are children of other nodes.
|
||||||
|
///
|
||||||
|
/// This iterator returns the deepest/latest nodes first in a depth-first-search pattern.
|
||||||
|
///
|
||||||
|
/// With the example tree:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// A - B - C
|
||||||
|
/// |\ \
|
||||||
|
/// H G D - E
|
||||||
|
/// | \
|
||||||
|
/// I F
|
||||||
|
/// |
|
||||||
|
/// J
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// With B, E, and J matching
|
||||||
|
/// and D as transparent
|
||||||
|
/// you would get the following output:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// 3 True False J
|
||||||
|
/// 2 False False I
|
||||||
|
/// 1 False False H
|
||||||
|
/// 2 True False E
|
||||||
|
/// 2 False True D
|
||||||
|
/// 1 True False B
|
||||||
|
/// 0 False False A
|
||||||
|
/// ```
|
||||||
|
pub(crate) struct ReverseTreeIter<'tree, MP, TP, AP> {
|
||||||
|
activity_tree: &'tree ActivityTree,
|
||||||
|
origin: &'tree ActivityTreeEntry,
|
||||||
|
is_match_predicate: MP,
|
||||||
|
is_transparent_predicate: TP,
|
||||||
|
is_alive_predicate: AP,
|
||||||
|
stack: Vec<StackEntry<'tree>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree, MP, TP, AP> ReverseTreeIter<'tree, MP, TP, AP>
|
||||||
|
where
|
||||||
|
MP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
TP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
AP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(
|
||||||
|
activity_tree: &'tree ActivityTree,
|
||||||
|
origin: Option<&'tree ActivityTreeEntry>,
|
||||||
|
is_match_predicate: MP,
|
||||||
|
is_transparent_predicate: TP,
|
||||||
|
is_alive_predicate: AP,
|
||||||
|
) -> ReverseTreeIter<'tree, MP, TP, AP> {
|
||||||
|
let origin_node = origin.unwrap_or_else(|| {
|
||||||
|
let root_id = activity_tree.get_root_id();
|
||||||
|
activity_tree.get(&root_id)
|
||||||
|
});
|
||||||
|
ReverseTreeIter {
|
||||||
|
activity_tree,
|
||||||
|
origin: origin_node,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
stack: vec![StackEntry::new(origin_node)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the next StackEntry that should be returned from the iterator.
|
||||||
|
fn announce<'iter>(
|
||||||
|
&'iter mut self,
|
||||||
|
node: &'tree ActivityTreeEntry,
|
||||||
|
is_match: bool,
|
||||||
|
is_transparent: bool,
|
||||||
|
) -> (usize, bool, bool, &'tree ActivityTreeEntry) {
|
||||||
|
let depth = self
|
||||||
|
.walk_up_stack(node)
|
||||||
|
.filter(|node| !node.is_transparent())
|
||||||
|
.count();
|
||||||
|
(depth, is_match, is_transparent, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk up the stack, returning all visited nodes above the base.
|
||||||
|
///
|
||||||
|
/// This should give you the path to base (starting from the end).
|
||||||
|
fn walk_up_stack<'iter>(
|
||||||
|
&'iter mut self,
|
||||||
|
base: &'tree ActivityTreeEntry,
|
||||||
|
) -> impl Iterator<Item = &'iter mut StackEntry<'tree>> {
|
||||||
|
self.stack
|
||||||
|
.iter_mut()
|
||||||
|
.rev()
|
||||||
|
.skip_while(|node| !std::ptr::eq(node.get_node(), base))
|
||||||
|
.skip(1)
|
||||||
|
.filter(|node| node.is_visited())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_draw_order(self) -> Vec<DrawDagEntry> {
|
||||||
|
let mut order = Vec::new();
|
||||||
|
let mut walk_depth: Vec<usize> = Vec::new();
|
||||||
|
|
||||||
|
for (inp_depth, _is_match, _is_transparent, entry) in
|
||||||
|
self.filter(|(_inp_depth, _is_match, is_transparent, _entry)| !*is_transparent)
|
||||||
|
{
|
||||||
|
let mut depth = vec![false; inp_depth];
|
||||||
|
let mut high_water_mark = inp_depth;
|
||||||
|
for preceding_depth in walk_depth.iter().rev() {
|
||||||
|
if *preceding_depth < high_water_mark {
|
||||||
|
depth[*preceding_depth] = true;
|
||||||
|
high_water_mark = *preceding_depth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
order.push(DrawDagEntry {
|
||||||
|
activity_id: entry.get_activity_id().clone(),
|
||||||
|
depth,
|
||||||
|
has_later_siblings: walk_depth
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.take_while(|wd| **wd >= inp_depth)
|
||||||
|
.any(|wd| *wd == inp_depth),
|
||||||
|
});
|
||||||
|
walk_depth.push(inp_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree, MP, TP, AP> Iterator for ReverseTreeIter<'tree, MP, TP, AP>
|
||||||
|
where
|
||||||
|
MP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
TP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
AP: FnMut(&'tree ActivityTreeEntry) -> bool,
|
||||||
|
{
|
||||||
|
type Item = (usize, bool, bool, &'tree ActivityTreeEntry);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let origin_activity_id = *self.origin.get_activity_id().deref();
|
||||||
|
|
||||||
|
// Walk through stack
|
||||||
|
loop {
|
||||||
|
let mut should_check_for_output = false;
|
||||||
|
|
||||||
|
let (node, is_visited, should_announce, is_match, is_transparent) = {
|
||||||
|
let stack_entry = match self.stack.last_mut() {
|
||||||
|
Some(e) => e,
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let node = stack_entry.get_node();
|
||||||
|
let is_visited = stack_entry.is_visited();
|
||||||
|
let should_announce = stack_entry.should_announce();
|
||||||
|
let is_match = stack_entry.matches();
|
||||||
|
let is_transparent = stack_entry.is_transparent();
|
||||||
|
|
||||||
|
// If we've already processed the children, remove it from the stack.
|
||||||
|
if is_visited {
|
||||||
|
if should_announce && *node.get_activity_id().deref() != origin_activity_id {
|
||||||
|
let output = Some(self.announce(node, is_match, is_transparent));
|
||||||
|
self.stack.pop();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
self.stack.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
(node, is_visited, should_announce, is_match, is_transparent)
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_visited {
|
||||||
|
if should_announce && *node.get_activity_id().deref() != origin_activity_id {
|
||||||
|
let output = Some(self.announce(node, is_match, is_transparent));
|
||||||
|
self.stack.pop();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
self.stack.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, check if it matches the predicate and mark all parents if it does.
|
||||||
|
if (self.is_match_predicate)(node) {
|
||||||
|
{
|
||||||
|
let stack_entry = self
|
||||||
|
.stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("Loop condition guarantees this is Some.");
|
||||||
|
// TODO: Combine into single op.
|
||||||
|
stack_entry.set_should_announce();
|
||||||
|
stack_entry.set_matches();
|
||||||
|
should_check_for_output = true;
|
||||||
|
}
|
||||||
|
let origin_activity_id = *self.origin.get_activity_id().deref();
|
||||||
|
for parent in self.walk_up_stack(node).filter(|entry| {
|
||||||
|
!entry.should_announce()
|
||||||
|
&& *entry.get_node().get_activity_id().deref() != origin_activity_id
|
||||||
|
}) {
|
||||||
|
parent.set_should_announce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let children = {
|
||||||
|
let stack_entry = self
|
||||||
|
.stack
|
||||||
|
.last_mut()
|
||||||
|
.expect("Loop condition guarantees this is Some.");
|
||||||
|
|
||||||
|
// Mark the node as visited
|
||||||
|
stack_entry.set_visited();
|
||||||
|
if (self.is_transparent_predicate)(node) {
|
||||||
|
stack_entry.set_transparent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all children to the stack if they pass the is_alive_predicate.
|
||||||
|
let children = node
|
||||||
|
.get_child_ids()
|
||||||
|
.iter()
|
||||||
|
.map(|id| self.activity_tree.get(id))
|
||||||
|
.filter(|node| {
|
||||||
|
*node.get_activity_id().deref() != 0 && (self.is_alive_predicate)(node)
|
||||||
|
})
|
||||||
|
.map(|node| StackEntry::new(node));
|
||||||
|
children
|
||||||
|
};
|
||||||
|
|
||||||
|
self.stack.extend(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/nix_util/tree_iter/reverse_tree_iter/tests.rs
Normal file
230
src/nix_util/tree_iter/reverse_tree_iter/tests.rs
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
use crate::nix_util::running_build::is_transparent_predicate;
|
||||||
|
use crate::nix_util::tree_iter::test_util::build_example_tree;
|
||||||
|
use crate::nix_util::tree_iter::test_util::is_alive_predicate;
|
||||||
|
use crate::nix_util::tree_iter::test_util::is_match_predicate;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reverse_tree_order() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![
|
||||||
|
(3, true, false, "Unknown(N20)".into()),
|
||||||
|
(2, false, false, "Unknown(N19)".into()),
|
||||||
|
(1, false, false, "Unknown(N18)".into()),
|
||||||
|
(2, true, false, "Unknown(N15)".into()),
|
||||||
|
(2, false, true, "Realize".into()),
|
||||||
|
(1, true, false, "Unknown(N12)".into()),
|
||||||
|
(0, false, false, "Unknown(N11)".into()),
|
||||||
|
(3, true, false, "Unknown(N10)".into()),
|
||||||
|
(2, false, false, "Unknown(N9)".into()),
|
||||||
|
(1, false, false, "Unknown(N8)".into()),
|
||||||
|
(2, true, false, "Unknown(N5)".into()),
|
||||||
|
(2, false, true, "Realize".into()),
|
||||||
|
(1, true, false, "Unknown(N2)".into()),
|
||||||
|
(0, false, false, "Unknown(N1)".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_from_1() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
Some(activity_tree.get(&activity_tree.get_activity_id(1)?)),
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![
|
||||||
|
(3, true, false, "Unknown(N10)".into()),
|
||||||
|
(2, false, false, "Unknown(N9)".into()),
|
||||||
|
(1, false, false, "Unknown(N8)".into()),
|
||||||
|
(2, true, false, "Unknown(N5)".into()),
|
||||||
|
(2, false, true, "Realize".into()), // N4
|
||||||
|
(1, true, false, "Unknown(N2)".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_from_8() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
Some(activity_tree.get(&activity_tree.get_activity_id(8)?)),
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![
|
||||||
|
(2, true, false, "Unknown(N10)".into()),
|
||||||
|
(1, false, false, "Unknown(N9)".into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn start_from_3() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let nodes = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
Some(activity_tree.get(&activity_tree.get_activity_id(3)?)),
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output: Vec<_> = nodes
|
||||||
|
.map(|(depth, is_match, is_transparent, node)| {
|
||||||
|
(
|
||||||
|
depth,
|
||||||
|
is_match,
|
||||||
|
is_transparent,
|
||||||
|
node.get_activity()
|
||||||
|
.display_name()
|
||||||
|
.expect("Should have a display_name"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let expected = vec![];
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reverse_tree_draw_order() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_example_tree()?;
|
||||||
|
let expected = vec![
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(20).unwrap(),
|
||||||
|
depth: vec![false, false, false],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(19).unwrap(),
|
||||||
|
depth: vec![false, false],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(18).unwrap(),
|
||||||
|
depth: vec![false],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(15).unwrap(),
|
||||||
|
depth: vec![false, true],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(12).unwrap(),
|
||||||
|
depth: vec![false],
|
||||||
|
has_later_siblings: true,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(11).unwrap(),
|
||||||
|
depth: vec![],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(10).unwrap(),
|
||||||
|
depth: vec![true, false, false],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(9).unwrap(),
|
||||||
|
depth: vec![true, false],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(8).unwrap(),
|
||||||
|
depth: vec![true],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(5).unwrap(),
|
||||||
|
depth: vec![true, true],
|
||||||
|
has_later_siblings: false,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(2).unwrap(),
|
||||||
|
depth: vec![true],
|
||||||
|
has_later_siblings: true,
|
||||||
|
},
|
||||||
|
DrawDagEntry {
|
||||||
|
activity_id: activity_tree.get_activity_id(1).unwrap(),
|
||||||
|
depth: vec![],
|
||||||
|
has_later_siblings: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let reverse_draw_order = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
is_match_predicate,
|
||||||
|
is_transparent_predicate,
|
||||||
|
is_alive_predicate,
|
||||||
|
)
|
||||||
|
.get_draw_order();
|
||||||
|
|
||||||
|
assert_eq!(reverse_draw_order, expected);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
63
src/nix_util/tree_iter/stack_entry.rs
Normal file
63
src/nix_util/tree_iter/stack_entry.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use crate::nix_util::activity_tree::ActivityTreeEntry;
|
||||||
|
|
||||||
|
const FLAG_ANNOUNCED: u8 = 1 << 0;
|
||||||
|
const FLAG_SHOULD_ANNOUNCE: u8 = 1 << 1;
|
||||||
|
const FLAG_VISITED: u8 = 1 << 2;
|
||||||
|
const FLAG_MATCHED: u8 = 1 << 3;
|
||||||
|
const FLAG_TRANSPARENT: u8 = 1 << 4;
|
||||||
|
|
||||||
|
pub(crate) struct StackEntry<'tree> {
|
||||||
|
// TODO: node_id? node itself?
|
||||||
|
node: &'tree ActivityTreeEntry,
|
||||||
|
flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'tree> StackEntry<'tree> {
|
||||||
|
pub(crate) fn new(node: &'tree ActivityTreeEntry) -> StackEntry<'tree> {
|
||||||
|
StackEntry { node, flags: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_node(&self) -> &'tree ActivityTreeEntry {
|
||||||
|
self.node
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_announced(&self) -> bool {
|
||||||
|
self.flags & FLAG_ANNOUNCED != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_announced(&mut self) -> () {
|
||||||
|
self.flags = self.flags | FLAG_ANNOUNCED;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn should_announce(&self) -> bool {
|
||||||
|
self.flags & FLAG_SHOULD_ANNOUNCE != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_should_announce(&mut self) -> () {
|
||||||
|
self.flags = self.flags | FLAG_SHOULD_ANNOUNCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_visited(&self) -> bool {
|
||||||
|
self.flags & FLAG_VISITED != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_visited(&mut self) -> () {
|
||||||
|
self.flags = self.flags | FLAG_VISITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn matches(&self) -> bool {
|
||||||
|
self.flags & FLAG_MATCHED != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_matches(&mut self) -> () {
|
||||||
|
self.flags = self.flags | FLAG_MATCHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_transparent(&self) -> bool {
|
||||||
|
self.flags & FLAG_TRANSPARENT != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_transparent(&mut self) -> () {
|
||||||
|
self.flags = self.flags | FLAG_TRANSPARENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
392
src/nix_util/tree_iter/test_util.rs
Normal file
392
src/nix_util/tree_iter/test_util.rs
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::nix_util::activity::Activity;
|
||||||
|
use crate::nix_util::activity::ActivityRealize;
|
||||||
|
use crate::nix_util::activity::ActivityState;
|
||||||
|
use crate::nix_util::activity::ActivityUnknown;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTree;
|
||||||
|
use crate::nix_util::activity_tree::ActivityTreeEntry;
|
||||||
|
|
||||||
|
impl Activity {
|
||||||
|
pub(crate) fn start(&mut self) -> () {
|
||||||
|
match self {
|
||||||
|
Activity::Root(_activity_root) => {
|
||||||
|
panic!("Attempted to start root activity.");
|
||||||
|
}
|
||||||
|
Activity::Unknown(activity_unknown) => {
|
||||||
|
activity_unknown.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::CopyPath(activity_copy_path) => {
|
||||||
|
activity_copy_path.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::FileTransfer(activity_file_transfer) => {
|
||||||
|
activity_file_transfer.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::Realize(activity_realize) => {
|
||||||
|
activity_realize.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::CopyPaths(activity_copy_paths) => {
|
||||||
|
activity_copy_paths.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::Builds(activity_builds) => {
|
||||||
|
activity_builds.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::Build(activity_build) => {
|
||||||
|
activity_build.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::OptimizeStore(activity_optimize_store) => {
|
||||||
|
activity_optimize_store.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::VerifyPaths(activity_verify_paths) => {
|
||||||
|
activity_verify_paths.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::Substitute(activity_substitute) => {
|
||||||
|
activity_substitute.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::QueryPathInfo(activity_query_path_info) => {
|
||||||
|
activity_query_path_info.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::PostBuildHook(activity_post_build_hook) => {
|
||||||
|
activity_post_build_hook.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::BuildWaiting(activity_build_waiting) => {
|
||||||
|
activity_build_waiting.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Activity::FetchTree(activity_fetch_tree) => {
|
||||||
|
activity_fetch_tree.state = ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the following ActivityTree:
|
||||||
|
///
|
||||||
|
/// Unknown(N20)
|
||||||
|
/// Unknown(N19)
|
||||||
|
/// Unknown(N18)
|
||||||
|
/// Unknown(N15)
|
||||||
|
/// Unknown(N12)
|
||||||
|
/// Unknown(N11)
|
||||||
|
/// Unknown(N10)
|
||||||
|
/// Unknown(N9)
|
||||||
|
/// Unknown(N8)
|
||||||
|
/// Unknown(N5)
|
||||||
|
/// Unknown(N2)
|
||||||
|
/// Unknown(N1)
|
||||||
|
pub(crate) fn build_example_tree() -> Result<ActivityTree, Box<dyn std::error::Error>> {
|
||||||
|
let instructions = vec![
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 1,
|
||||||
|
parent_id: 0,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 2,
|
||||||
|
parent_id: 1,
|
||||||
|
is_match: true,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 3,
|
||||||
|
parent_id: 2,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 4,
|
||||||
|
parent_id: 2,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: true,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 5,
|
||||||
|
parent_id: 4,
|
||||||
|
is_match: true,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 6,
|
||||||
|
parent_id: 4,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 7,
|
||||||
|
parent_id: 1,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 8,
|
||||||
|
parent_id: 1,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 9,
|
||||||
|
parent_id: 8,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 10,
|
||||||
|
parent_id: 9,
|
||||||
|
is_match: true,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 11,
|
||||||
|
parent_id: 0,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 12,
|
||||||
|
parent_id: 11,
|
||||||
|
is_match: true,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 13,
|
||||||
|
parent_id: 12,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 14,
|
||||||
|
parent_id: 12,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: true,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 15,
|
||||||
|
parent_id: 14,
|
||||||
|
is_match: true,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 16,
|
||||||
|
parent_id: 14,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 17,
|
||||||
|
parent_id: 11,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 18,
|
||||||
|
parent_id: 11,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 19,
|
||||||
|
parent_id: 18,
|
||||||
|
is_match: false,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
BuildInstruction {
|
||||||
|
activity_id: 20,
|
||||||
|
parent_id: 19,
|
||||||
|
is_match: true,
|
||||||
|
is_transparent: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let activity_tree = build_activity_tree_from_instructions(&instructions, false)?;
|
||||||
|
|
||||||
|
Ok(activity_tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct BuildInstruction {
|
||||||
|
activity_id: u64,
|
||||||
|
parent_id: u64,
|
||||||
|
is_transparent: bool,
|
||||||
|
is_match: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_every_possible_addition<'old, 'new>(
|
||||||
|
old_state: &'old Vec<BuildInstruction>,
|
||||||
|
) -> Vec<Vec<BuildInstruction>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for is_transparent in [true, false] {
|
||||||
|
for is_match in [true, false] {
|
||||||
|
let next_activity_id = old_state.last().map(|bi| bi.activity_id).unwrap_or(0) + 1;
|
||||||
|
for parent_id in 0..next_activity_id {
|
||||||
|
let mut new_state = old_state.clone();
|
||||||
|
new_state.push(BuildInstruction {
|
||||||
|
activity_id: next_activity_id,
|
||||||
|
parent_id,
|
||||||
|
is_transparent,
|
||||||
|
is_match,
|
||||||
|
});
|
||||||
|
out.push(new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_activity_tree_from_instructions(
|
||||||
|
instructions: &Vec<BuildInstruction>,
|
||||||
|
start_parents: bool,
|
||||||
|
) -> Result<ActivityTree, Box<dyn std::error::Error>> {
|
||||||
|
let mut activity_tree = ActivityTree::new();
|
||||||
|
for instruct in instructions {
|
||||||
|
match (instruct.is_transparent, instruct.is_match) {
|
||||||
|
(true, true) => {
|
||||||
|
activity_tree.add_activity(
|
||||||
|
instruct.activity_id,
|
||||||
|
instruct.parent_id,
|
||||||
|
Activity::Realize(ActivityRealize {
|
||||||
|
state: ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
// Walk up tree marking parents as match
|
||||||
|
if start_parents {
|
||||||
|
let mut parent_id = instruct.parent_id;
|
||||||
|
while parent_id > 0 {
|
||||||
|
let parent =
|
||||||
|
activity_tree.get_mut(&activity_tree.get_activity_id(parent_id)?);
|
||||||
|
parent.get_mut_activity().start();
|
||||||
|
parent_id = *parent.get_parent_id().deref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(true, false) => {
|
||||||
|
activity_tree.add_activity(
|
||||||
|
instruct.activity_id,
|
||||||
|
instruct.parent_id,
|
||||||
|
Activity::Realize(ActivityRealize {
|
||||||
|
state: ActivityState::Stopped,
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
activity_tree.add_activity(
|
||||||
|
instruct.activity_id,
|
||||||
|
instruct.parent_id,
|
||||||
|
Activity::Unknown(ActivityUnknown {
|
||||||
|
state: ActivityState::Started {
|
||||||
|
progress_numerator: 0,
|
||||||
|
progress_denominator: 0,
|
||||||
|
progress_running: 0,
|
||||||
|
progress_failed: 0,
|
||||||
|
},
|
||||||
|
text: format!("N{}", instruct.activity_id),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
// Walk up tree marking parents as match
|
||||||
|
if start_parents {
|
||||||
|
let mut parent_id = instruct.parent_id;
|
||||||
|
while parent_id > 0 {
|
||||||
|
let parent =
|
||||||
|
activity_tree.get_mut(&activity_tree.get_activity_id(parent_id)?);
|
||||||
|
parent.get_mut_activity().start();
|
||||||
|
parent_id = *parent.get_parent_id().deref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(false, false) => {
|
||||||
|
activity_tree.add_activity(
|
||||||
|
instruct.activity_id,
|
||||||
|
instruct.parent_id,
|
||||||
|
Activity::Unknown(ActivityUnknown {
|
||||||
|
state: ActivityState::Stopped,
|
||||||
|
text: format!("N{}", instruct.activity_id),
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(activity_tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_match_predicate(entry: &ActivityTreeEntry) -> bool {
|
||||||
|
entry.get_activity().is_active()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_alive_predicate(_entry: &ActivityTreeEntry) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
55
src/nix_util/tree_iter/tests.rs
Normal file
55
src/nix_util/tree_iter/tests.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use crate::nix_util::running_build::is_transparent_predicate;
|
||||||
|
use crate::nix_util::running_build::print_dag;
|
||||||
|
|
||||||
|
use super::ReverseTreeIter;
|
||||||
|
use super::get_draw_order;
|
||||||
|
use super::test_util::BuildInstruction;
|
||||||
|
use super::test_util::build_activity_tree_from_instructions;
|
||||||
|
use super::test_util::get_every_possible_addition;
|
||||||
|
|
||||||
|
fn compare_single_tree(
|
||||||
|
instructions: &Vec<BuildInstruction>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let activity_tree = build_activity_tree_from_instructions(instructions, true)?;
|
||||||
|
let original_draw_order = get_draw_order(&activity_tree);
|
||||||
|
let reverse_draw_order = ReverseTreeIter::new(
|
||||||
|
&activity_tree,
|
||||||
|
None,
|
||||||
|
|entry| entry.get_activity().is_active(),
|
||||||
|
is_transparent_predicate,
|
||||||
|
|_entry| true,
|
||||||
|
)
|
||||||
|
.get_draw_order();
|
||||||
|
|
||||||
|
if original_draw_order != reverse_draw_order {
|
||||||
|
println!("Draw order mismatch!");
|
||||||
|
println!("Instructions: {:?}", instructions);
|
||||||
|
println!("Original: {:?}", original_draw_order);
|
||||||
|
println!("Reverse: {:?}", reverse_draw_order);
|
||||||
|
print_dag(&activity_tree, original_draw_order);
|
||||||
|
print_dag(&activity_tree, reverse_draw_order);
|
||||||
|
return Err("mismatch".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn reverse_tree_order_draw_order_iterative() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut prev_generation: Vec<Vec<BuildInstruction>> = Vec::new();
|
||||||
|
let mut current_generation: Vec<Vec<BuildInstruction>> = vec![vec![]];
|
||||||
|
|
||||||
|
// Iterate through every possible tree with N nodes.
|
||||||
|
for _ in 0..7 {
|
||||||
|
std::mem::swap(&mut prev_generation, &mut current_generation);
|
||||||
|
for old_state in prev_generation.drain(..) {
|
||||||
|
for new_state in get_every_possible_addition(&old_state) {
|
||||||
|
compare_single_tree(&new_state)?;
|
||||||
|
current_generation.push(new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user