1mod artifact;
13mod clean;
14mod coverage;
15mod deploy_render;
16mod heartbeat;
17mod postgres;
18mod publish;
19mod release;
20mod watchdog;
21
22#[cfg(test)]
23mod test_support;
24
25use anyhow::{Context, Result, bail};
26use clap::{Parser, Subcommand};
27
28use artifact::Kind;
29use heartbeat::Heartbeat;
30
31#[derive(Parser)]
32#[command(about = "nomisync CI tasks")]
33struct Cli {
34 #[command(subcommand)]
35 command: Command,
36}
37
38#[derive(Subcommand)]
39enum Command {
40 Postgres {
42 #[arg(required = true, trailing_var_arg = true)]
44 argv: Vec<String>,
45 },
46 Coverage,
48 Release,
50 Finalize,
52}
53
54fn main() -> Result<()> {
55 match Cli::parse().command {
56 Command::Postgres { argv } => with_postgres(&argv),
57 Command::Coverage => produce(Kind::Coverage),
58 Command::Release => produce(Kind::Release),
59 Command::Finalize => finalize(),
60 }
61}
62
63fn finalize() -> Result<()> {
70 let identity = artifact::Identity::from_env()?;
71 let store = publish::Store::from_env()?;
72 let work =
73 tempfile::TempDir::with_prefix("nomisync-finalize").context("cannot make a temp dir")?;
74
75 let metas: Vec<artifact::Meta> = Kind::ALL
76 .iter()
77 .map(|kind| store.fetch_meta(*kind, &identity, work.path()))
78 .collect::<Result<_>>()?;
79
80 for meta in &metas {
81 let local = store.verify_object(meta, &identity, work.path())?;
82 println!(
83 ">> verified {} ({} bytes)",
84 local.file_name().unwrap_or_default().to_string_lossy(),
85 meta.bytes
86 );
87 }
88
89 let [coverage, release] = metas.as_slice() else {
90 bail!(
91 "expected exactly {} artifacts, found {}",
92 Kind::ALL.len(),
93 metas.len()
94 );
95 };
96 artifact::check_pair(coverage, release).map_err(|err| anyhow::anyhow!("{err}"))?;
99
100 store.write_manifest(&metas, &identity, work.path())?;
101 println!(
102 ">> manifest written for {} run {}",
103 identity.commit, identity.run_id
104 );
105 Ok(())
106}
107
108fn with_artifacts() -> bool {
111 std::env::var("WITH_ARTIFACTS").as_deref() == Ok("1")
112}
113
114fn produce(kind: Kind) -> Result<()> {
115 let heartbeat = Heartbeat::start();
119 heartbeat.phase("starting postgres");
120
121 let runtime = tokio::runtime::Builder::new_current_thread()
122 .enable_all()
123 .build()
124 .context("cannot build a tokio runtime")?;
125 let database = runtime.block_on(postgres::start())?;
128 let url = database.database_url();
129
130 match kind {
131 Kind::Coverage => coverage::run_coverage(&heartbeat, &url)?,
132 Kind::Release => release::build(&heartbeat, &url)?,
133 }
134 release::package(kind, &heartbeat, with_artifacts())
135}
136
137fn with_postgres(argv: &[String]) -> Result<()> {
141 let runtime = tokio::runtime::Builder::new_current_thread()
142 .enable_all()
143 .build()
144 .context("cannot build a tokio runtime")?;
145 let database = runtime.block_on(postgres::start())?;
146
147 let (program, args) = argv.split_first().context("no command given")?;
148 let status = std::process::Command::new(program)
149 .args(args)
150 .env("DATABASE_URL", database.database_url())
151 .status()
152 .with_context(|| format!("failed to run {program}"))?;
153
154 if !status.success() {
155 bail!("{program} exited with {status}");
156 }
157 Ok(())
158}