add attic push functionality

This commit is contained in:
Lucy Hochkamp 2025-10-10 17:40:25 +02:00
parent 9ca574cb94
commit f982e37fe5
No known key found for this signature in database
8 changed files with 175 additions and 58 deletions

2
.envrc
View file

@ -1 +1 @@
use nix use flake

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target /target
/result /result
.direnv

23
flake.lock generated
View file

@ -18,7 +18,28 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1760063676,
"narHash": "sha256-s5Fjh43skH2L+avOGioLmEHoYZffDbg3abV5h0gjeew=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "897deed0923cc5a1d560c5176abe0d172ec9716d",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
} }
} }
}, },

View file

@ -1,34 +1,47 @@
{ {
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = { outputs =
nixpkgs, { nixpkgs
self, , rust-overlay
}: let , self
forAllSystems = fn: ,
builtins.foldl' (attrs: system: let }:
outputs = fn system; let
outputNames = builtins.attrNames outputs; forAllSystems = fn:
in builtins.foldl'
builtins.foldl' (attrs: outName: let (attrs: system:
existing = attrs.${outName} or {}; let
new = existing // {${system} = outputs.${outName};}; outputs = fn system;
in outputNames = builtins.attrNames outputs;
attrs // {${outName} = new;}) in
attrs builtins.foldl'
outputNames) {} ["x86_64-linux" "aarch64-linux"]; (attrs: outName:
in let
existing = attrs.${outName} or { };
new = existing // { ${system} = outputs.${outName}; };
in
attrs // { ${outName} = new; })
attrs
outputNames)
{ } [ "x86_64-linux" "aarch64-linux" ];
in
{ {
overlays.default = final: prev: { overlays.default = final: prev: {
nix-ci = final.lib.flip final.callPackage {} ({ nix-ci = final.lib.flip final.callPackage { } ({ rustPlatform
rustPlatform, , lib
lib, , makeWrapper
makeWrapper, , nix-eval-jobs
nix-eval-jobs, ,
}: let }:
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); let
in cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
in
rustPlatform.buildRustPackage { rustPlatform.buildRustPackage {
pname = cargoToml.package.name; pname = cargoToml.package.name;
version = cargoToml.package.version; version = cargoToml.package.version;
@ -40,7 +53,7 @@
./Cargo.lock ./Cargo.lock
]; ];
}; };
nativeBuildInputs = [makeWrapper]; nativeBuildInputs = [ makeWrapper ];
cargoLock.lockFile = ./Cargo.lock; cargoLock.lockFile = ./Cargo.lock;
preFixup = '' preFixup = ''
wrapProgram "$out/bin/nix-ci" \ wrapProgram "$out/bin/nix-ci" \
@ -49,13 +62,35 @@
}); });
}; };
} }
// forAllSystems (system: let // forAllSystems (system:
let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [self.overlays.default]; overlays = [
self.overlays.default
rust-overlay.overlays.default
(final: prev: {
inherit (prev.lixPackageSets.stable)
nixpkgs-review
nix-eval-jobs
nix-fast-build
colmena;
})
];
};
rust = pkgs.rust-bin.stable.latest.default.override {
extensions = [
"rust-src" # for rust-analyzer
"rust-analyzer"
];
};
in
{
devShells.default = pkgs.mkShell {
nativeBuildInputs = [ pkgs.nix-eval-jobs rust pkgs.attic-client ];
}; };
in {
devShells.default = import ./shell.nix {inherit pkgs;};
packages = { packages = {
default = pkgs.nix-ci; default = pkgs.nix-ci;
nix-ci = pkgs.nix-ci; nix-ci = pkgs.nix-ci;

View file

@ -1,4 +0,0 @@
{pkgs ? import <nixpkgs> {}}:
pkgs.mkShell {
nativeBuildInputs = with pkgs; [nix-eval-jobs];
}

View file

@ -17,6 +17,9 @@ pub struct Options {
#[arg(long)] #[arg(long)]
/// store uri where build outputs will be uploaded to /// store uri where build outputs will be uploaded to
pub copy_to: Option<String>, pub copy_to: Option<String>,
#[arg(long)]
/// attic store where build outputs will be uploaded to
pub copy_to_attic: Option<String>,
#[arg(long, default_value = "4")] #[arg(long, default_value = "4")]
/// maximum number of evaluation workers /// maximum number of evaluation workers

View file

@ -51,34 +51,57 @@ pub async fn copy_loop(
continue; continue;
} }
}; };
let Some(copy_to) = &opts.copy_to else {
continue;
};
for store_path in &valid_paths { for store_path in &valid_paths {
if paths_copied.contains(store_path) { if paths_copied.contains(store_path) {
continue; continue;
} }
paths_copied.push(store_path.clone()); paths_copied.push(store_path.clone());
match copy_path(store_path, copy_to).await { if let Some(copy_to) = &opts.copy_to {
Ok(()) => { match copy_path(store_path, copy_to).await {
let _ = result_tx Ok(()) => {
.send(NixCiResult { let _ = result_tx
r#type: NixCiResultType::Copy, .send(NixCiResult {
path: store_path.clone(), r#type: NixCiResultType::Copy,
success: true, path: store_path.clone(),
}) success: true,
.await; })
tracing::info!("copied path {}", store_path); .await;
tracing::info!("copied path {}", store_path);
}
Err(e) => {
let _ = result_tx
.send(NixCiResult {
r#type: NixCiResultType::Copy,
path: store_path.clone(),
success: false,
})
.await;
tracing::error!("failed to copy path {}: {}", store_path, e);
}
} }
Err(e) => { }
let _ = result_tx if let Some(copy_to_attic) = &opts.copy_to_attic {
.send(NixCiResult { match copy_path_attic(store_path, copy_to_attic).await {
r#type: NixCiResultType::Copy, Ok(()) => {
path: store_path.clone(), let _ = result_tx
success: false, .send(NixCiResult {
}) r#type: NixCiResultType::Copy,
.await; path: store_path.clone(),
tracing::error!("failed to copy path {}: {}", store_path, e); success: true,
})
.await;
tracing::info!("copied path {}", store_path);
}
Err(e) => {
let _ = result_tx
.send(NixCiResult {
r#type: NixCiResultType::Copy,
path: store_path.clone(),
success: false,
})
.await;
tracing::error!("failed to copy path {}: {}", store_path, e);
}
} }
} }
} }
@ -141,6 +164,30 @@ async fn check_path_validity(store_path: &str) -> anyhow::Result<bool> {
Ok(cmd.status.success()) Ok(cmd.status.success())
} }
async fn copy_path_attic(store_path: &str, attic_store: &str) -> anyhow::Result<()> {
let mut cmd = WrappedChild::new(
Command::new("attic").args(&["push", attic_store, store_path]),
Some(format!("attic push {} {}", attic_store, store_path)),
)?;
loop {
match cmd.next_line().await? {
ChildOutput::Finished => break,
ChildOutput::Stderr(line) | ChildOutput::Stdout(line) => {
tracing::info!("{}", line);
}
}
}
let exit_status = cmd.exit_status().unwrap();
if exit_status.success() {
Ok(())
} else {
Err(anyhow::anyhow!(
"nix copy exited with non-success {}",
exit_status
))
}
}
async fn copy_path(store_path: &str, copy_to: &str) -> anyhow::Result<()> { async fn copy_path(store_path: &str, copy_to: &str) -> anyhow::Result<()> {
let mut cmd = WrappedChild::new( let mut cmd = WrappedChild::new(
Command::new("nix").args(&["copy", "--to", copy_to, store_path]), Command::new("nix").args(&["copy", "--to", copy_to, store_path]),

View file

@ -63,6 +63,20 @@ async fn main() -> anyhow::Result<()> {
std::process::exit(1); std::process::exit(1);
} }
} }
if let Some(store) = &opts.copy_to_attic {
let cmd = Command::new("attic")
.args(&["cache", "info", store])
.output()
.await?;
if !cmd.status.success() {
tracing::error!(
"{:?} is not a valid attic store: {}",
store,
String::from_utf8_lossy(&cmd.stderr),
);
std::process::exit(1);
}
}
tracing::debug!("running with options {:?}", opts); tracing::debug!("running with options {:?}", opts);
let opts = Arc::new(opts); let opts = Arc::new(opts);