346 lines
13 KiB
Nix
346 lines
13 KiB
Nix
{
|
|
description = "PhotonCloud local CI gates (Nix-first, CI-provider-agnostic)";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
|
|
flake-utils.url = "github:numtide/flake-utils";
|
|
photoncloud.url = "path:../..";
|
|
|
|
rust-overlay = {
|
|
url = "github:oxalica/rust-overlay";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
};
|
|
|
|
outputs = { self, nixpkgs, flake-utils, photoncloud, rust-overlay }:
|
|
flake-utils.lib.eachDefaultSystem (system:
|
|
let
|
|
overlays = [ (import rust-overlay) ];
|
|
pkgs = import nixpkgs { inherit system overlays; };
|
|
|
|
# Use a complete toolchain to ensure host `std` is present under Nix
|
|
# (fixes: "can't find crate for `std`").
|
|
rustToolchain = pkgs.rust-bin.stable.latest.complete;
|
|
|
|
# rust-overlay components (provide cargo-fmt / clippy-driver reliably in PATH)
|
|
rustfmtComponent = pkgs.rust-bin.stable.latest.rustfmt;
|
|
clippyComponent = pkgs.rust-bin.stable.latest.clippy;
|
|
|
|
ciInventory = builtins.fromJSON (builtins.readFile ./workspaces.json);
|
|
wsList = map (workspace: workspace.name) ciInventory.workspaces;
|
|
supportedWorkspaces = pkgs.lib.concatStringsSep ", " wsList;
|
|
|
|
gate = pkgs.writeShellApplication {
|
|
name = "photoncloud-gate";
|
|
|
|
runtimeInputs = with pkgs; [
|
|
bash
|
|
coreutils
|
|
findutils
|
|
gnugrep
|
|
gawk
|
|
git
|
|
jq
|
|
rustToolchain
|
|
rustfmtComponent
|
|
clippyComponent
|
|
protobuf
|
|
llvmPackages.libclang
|
|
llvmPackages.clang
|
|
pkg-config
|
|
openssl
|
|
qemu
|
|
rocksdb
|
|
];
|
|
|
|
text = ''
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
PhotonCloud local CI gates (provider-agnostic)
|
|
|
|
Usage:
|
|
photoncloud-gate [--tier 0|1|2] [--workspace <name>] [--shared-crates] [--no-logs] [--fix]
|
|
|
|
Tiers:
|
|
0: fmt + clippy + unit tests (lib) (fast, stable default)
|
|
1: tier0 + integration tests (--tests)
|
|
2: tier1 + ignored tests (-- --ignored)
|
|
|
|
Notes:
|
|
- Requires running inside a git checkout (uses `git rev-parse`).
|
|
- Logs are written to ./work/ci/<timestamp>/ by default (NOT .cccc/).
|
|
- Supported workspaces: ${supportedWorkspaces}
|
|
USAGE
|
|
}
|
|
|
|
tier="0"
|
|
only_ws=""
|
|
shared_crates="0"
|
|
no_logs="0"
|
|
fix="0"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--tier)
|
|
tier="$2"; shift 2;;
|
|
--workspace)
|
|
only_ws="$2"; shift 2;;
|
|
--shared-crates)
|
|
shared_crates="1"; shift 1;;
|
|
--no-logs)
|
|
no_logs="1"; shift 1;;
|
|
--fix)
|
|
fix="1"; shift 1;;
|
|
-h|--help)
|
|
usage; exit 0;;
|
|
*)
|
|
echo "[gate] ERROR: unknown arg: $1" >&2
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$tier" != "0" && "$tier" != "1" && "$tier" != "2" ]]; then
|
|
echo "[gate] ERROR: --tier must be 0, 1, or 2 (got: $tier)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
|
|
# Avoid .cccc/ (managed externally). Use work/ for local artifacts.
|
|
timestamp="$(date -u +%Y%m%d-%H%M%S)"
|
|
logdir="$repo_root/work/ci/$timestamp"
|
|
|
|
if [[ "$no_logs" == "0" ]]; then
|
|
mkdir -p "$logdir"
|
|
echo "[gate] logs: $logdir"
|
|
else
|
|
echo "[gate] logs: disabled"
|
|
fi
|
|
|
|
# Prefer Nix-provided toolchain over user's ~/.cargo binaries.
|
|
#
|
|
# `nix run` keeps the user's PATH (often including ~/.cargo/bin), which can cause
|
|
# cargo subcommands (cargo-fmt, etc.) to resolve outside Nix.
|
|
# Force PATH to Nix-provided tools only.
|
|
export PATH="${pkgs.lib.makeBinPath [
|
|
pkgs.bash
|
|
pkgs.coreutils
|
|
pkgs.findutils
|
|
pkgs.gnugrep
|
|
pkgs.gawk
|
|
pkgs.git
|
|
pkgs.jq
|
|
rustToolchain
|
|
rustfmtComponent
|
|
clippyComponent
|
|
pkgs.protobuf
|
|
pkgs.llvmPackages.libclang
|
|
pkgs.llvmPackages.clang
|
|
pkgs.pkg-config
|
|
pkgs.openssl
|
|
pkgs.qemu
|
|
]}"
|
|
|
|
CARGO="${rustToolchain}/bin/cargo"
|
|
export RUSTC="${rustToolchain}/bin/rustc"
|
|
CARGO_FMT="${rustToolchain}/bin/cargo-fmt"
|
|
CARGO_CLIPPY="${rustToolchain}/bin/cargo-clippy"
|
|
|
|
fmt_rustfmt_args="-- --check"
|
|
if [[ "$fix" == "1" ]]; then
|
|
fmt_rustfmt_args=""
|
|
fi
|
|
|
|
export LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
|
|
export PROTOC="${pkgs.protobuf}/bin/protoc"
|
|
export ROCKSDB_LIB_DIR="${pkgs.rocksdb}/lib"
|
|
|
|
manifest_has_target_kind() {
|
|
local manifest="$1"; shift
|
|
local kind="$1"; shift
|
|
|
|
"$CARGO" metadata --format-version 1 --no-deps --manifest-path "$manifest" \
|
|
| jq -e --arg kind "$kind" 'any(.packages[]?.targets[]?.kind[]?; . == $kind)' > /dev/null
|
|
}
|
|
|
|
run_cmd() {
|
|
local ws="$1"; shift
|
|
local title="$1"; shift
|
|
local cmd="$*"
|
|
|
|
echo ""
|
|
echo "================================================================================"
|
|
echo "[gate][$ws] $title"
|
|
echo "--------------------------------------------------------------------------------"
|
|
echo "[gate][$ws] $cmd"
|
|
echo "================================================================================"
|
|
|
|
if [[ "$no_logs" == "0" ]]; then
|
|
local out
|
|
out="$logdir/$ws.$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd 'a-z0-9_').log"
|
|
(cd "$repo_root/$ws" && bash -c "$cmd") 2>&1 | tee "$out"
|
|
else
|
|
(cd "$repo_root/$ws" && bash -c "$cmd")
|
|
fi
|
|
}
|
|
|
|
run_shared_crate_cmd() {
|
|
local crate="$1"; shift
|
|
local manifest="$1"; shift
|
|
local title="$1"; shift
|
|
local cmd="$*"
|
|
|
|
echo ""
|
|
echo "================================================================================"
|
|
echo "[gate][shared:$crate] $title"
|
|
echo "--------------------------------------------------------------------------------"
|
|
echo "[gate][shared:$crate] $cmd"
|
|
echo "================================================================================"
|
|
|
|
if [[ "$no_logs" == "0" ]]; then
|
|
local out
|
|
out="$logdir/shared_''${crate}.$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd 'a-z0-9_').log"
|
|
(cd "$repo_root" && bash -c "$cmd") 2>&1 | tee "$out"
|
|
else
|
|
(cd "$repo_root" && bash -c "$cmd")
|
|
fi
|
|
}
|
|
|
|
run_shared_crates() {
|
|
local manifests=()
|
|
while IFS= read -r manifest; do
|
|
manifests+=("$manifest")
|
|
done < <(find "$repo_root/crates" -mindepth 2 -maxdepth 2 -name Cargo.toml | sort)
|
|
|
|
if [[ "''${#manifests[@]}" -eq 0 ]]; then
|
|
echo "[gate] WARN: no shared crate manifests found under crates/"
|
|
return 0
|
|
fi
|
|
|
|
for manifest in "''${manifests[@]}"; do
|
|
local crate
|
|
local ran_unit_tests
|
|
crate="$(basename "$(dirname "$manifest")")"
|
|
run_shared_crate_cmd "$crate" "$manifest" "fmt" "$CARGO_FMT fmt --manifest-path \"$manifest\" $fmt_rustfmt_args"
|
|
run_shared_crate_cmd "$crate" "$manifest" "clippy" "$CARGO_CLIPPY clippy --manifest-path \"$manifest\" --all-targets -- -D warnings"
|
|
ran_unit_tests="0"
|
|
if manifest_has_target_kind "$manifest" "lib"; then
|
|
run_shared_crate_cmd "$crate" "$manifest" "test (tier0 unit lib)" "$CARGO test --manifest-path \"$manifest\" --lib"
|
|
ran_unit_tests="1"
|
|
fi
|
|
if manifest_has_target_kind "$manifest" "bin"; then
|
|
run_shared_crate_cmd "$crate" "$manifest" "test (tier0 unit bin)" "$CARGO test --manifest-path \"$manifest\" --bins"
|
|
ran_unit_tests="1"
|
|
fi
|
|
if [[ "$ran_unit_tests" == "0" ]]; then
|
|
echo "[gate][shared:$crate] WARN: no lib/bin unit test targets"
|
|
fi
|
|
|
|
if [[ "$tier" == "1" || "$tier" == "2" ]]; then
|
|
run_shared_crate_cmd "$crate" "$manifest" "test (tier1 integration)" "$CARGO test --manifest-path \"$manifest\" --tests"
|
|
fi
|
|
|
|
if [[ "$tier" == "2" ]]; then
|
|
run_shared_crate_cmd "$crate" "$manifest" "test (tier2 ignored)" "$CARGO test --manifest-path \"$manifest\" --tests -- --ignored"
|
|
fi
|
|
done
|
|
}
|
|
|
|
if [[ "$shared_crates" == "1" ]]; then
|
|
run_shared_crates
|
|
echo ""
|
|
echo "[gate] OK (tier=$tier, shared-crates)"
|
|
exit 0
|
|
fi
|
|
|
|
for ws in ${pkgs.lib.concatStringsSep " " wsList}; do
|
|
if [[ -n "$only_ws" && "$only_ws" != "$ws" ]]; then
|
|
continue
|
|
fi
|
|
|
|
if [[ ! -f "$repo_root/$ws/Cargo.toml" ]]; then
|
|
echo "[gate] WARN: skipping $ws (no Cargo.toml)"
|
|
continue
|
|
fi
|
|
|
|
workspace_manifest="$repo_root/$ws/Cargo.toml"
|
|
|
|
# Format gate: call Nix-provided `cargo-fmt` directly (avoid resolving ~/.cargo/bin/cargo-fmt).
|
|
#
|
|
# NOTE: Avoid `--all` here; with path-dependencies it may traverse outside the workspace directory.
|
|
run_cmd "$ws" "fmt" "$CARGO_FMT fmt $fmt_rustfmt_args"
|
|
# Lint gate: call Nix-provided `cargo-clippy` directly (avoid resolving ~/.cargo/bin/cargo-clippy).
|
|
run_cmd "$ws" "clippy" "$CARGO_CLIPPY clippy --workspace --all-targets -- -D warnings"
|
|
ran_unit_tests="0"
|
|
if manifest_has_target_kind "$workspace_manifest" "lib"; then
|
|
run_cmd "$ws" "test (tier0 unit lib)" "$CARGO test --workspace --lib"
|
|
ran_unit_tests="1"
|
|
fi
|
|
if manifest_has_target_kind "$workspace_manifest" "bin"; then
|
|
run_cmd "$ws" "test (tier0 unit bin)" "$CARGO test --workspace --bins"
|
|
ran_unit_tests="1"
|
|
fi
|
|
if [[ "$ran_unit_tests" == "0" ]]; then
|
|
echo "[gate][$ws] WARN: no lib/bin unit test targets"
|
|
fi
|
|
|
|
if [[ "$tier" == "1" || "$tier" == "2" ]]; then
|
|
run_cmd "$ws" "test (tier1 integration)" "$CARGO test --workspace --tests"
|
|
fi
|
|
|
|
if [[ "$tier" == "2" ]]; then
|
|
run_cmd "$ws" "test (tier2 ignored)" "$CARGO test --workspace --tests -- --ignored"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "[gate] OK (tier=$tier)"
|
|
'';
|
|
};
|
|
in
|
|
{
|
|
packages.gate = gate;
|
|
|
|
apps.gate = flake-utils.lib.mkApp {
|
|
drv = gate;
|
|
};
|
|
|
|
# CI-optimized gate (alias)
|
|
packages.gate-ci = gate;
|
|
|
|
# Checks are minimal and mirror tier0 (provider-agnostic).
|
|
checks.gate-tier0 = pkgs.runCommand "photoncloud-gate-tier0" { } ''
|
|
mkdir -p $out
|
|
${gate}/bin/photoncloud-gate --tier 0 --no-logs
|
|
touch $out/ok
|
|
'';
|
|
checks.deployer-vm-smoke = photoncloud.checks.${system}.deployer-vm-smoke;
|
|
checks.deployer-vm-rollback = photoncloud.checks.${system}.deployer-vm-rollback;
|
|
checks.deployer-bootstrap-e2e = photoncloud.checks.${system}.deployer-bootstrap-e2e;
|
|
checks.host-lifecycle-e2e = photoncloud.checks.${system}.host-lifecycle-e2e;
|
|
checks.fleet-scheduler-e2e = photoncloud.checks.${system}.fleet-scheduler-e2e;
|
|
|
|
devShells.default = pkgs.mkShell {
|
|
name = "photoncloud-ci-dev";
|
|
buildInputs = with pkgs; [
|
|
rustToolchain
|
|
protobuf
|
|
llvmPackages.libclang
|
|
llvmPackages.clang
|
|
pkg-config
|
|
openssl
|
|
rocksdb
|
|
git
|
|
];
|
|
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
|
|
PROTOC = "${pkgs.protobuf}/bin/protoc";
|
|
ROCKSDB_LIB_DIR = "${pkgs.rocksdb}/lib";
|
|
};
|
|
}
|
|
);
|
|
}
|