repro_env/
args.rs

1use crate::errors::*;
2use crate::lockfile::Lockfile;
3use crate::manifest::Manifest;
4use clap::{ArgAction, CommandFactory, Parser, Subcommand};
5use clap_complete::Shell;
6use std::collections::HashSet;
7use std::env;
8use std::io;
9use std::path::Path;
10use std::path::PathBuf;
11
12#[derive(Debug, Parser)]
13#[command(version)]
14pub struct Args {
15    /// Increase logging output (can be used multiple times)
16    #[arg(short, long, global = true, action(ArgAction::Count))]
17    pub verbose: u8,
18    /// Change the current directory to this path before executing the subcommand
19    #[arg(short = 'C', long)]
20    pub context: Option<PathBuf>,
21    #[command(subcommand)]
22    pub subcommand: SubCommand,
23}
24
25#[derive(Debug, Subcommand)]
26pub enum SubCommand {
27    Build(Build),
28    Update(Update),
29    Fetch(Fetch),
30    Completions(Completions),
31}
32
33/// Run a build in a reproducible environment
34#[derive(Debug, Parser)]
35pub struct Build {
36    /// The dependency lockfile to use
37    #[arg(short, long)]
38    pub file: Option<PathBuf>,
39    /// Do not delete the build container, wait for ctrl-c
40    #[arg(short, long)]
41    pub keep: bool,
42    /// Pass environment variables into the build container (FOO=bar or just FOO to lookup the value)
43    #[arg(short, long)]
44    pub env: Vec<String>,
45    /// The command to execute inside the build container
46    #[arg(required = true)]
47    pub cmd: Vec<String>,
48}
49
50impl Build {
51    pub fn validate(&self) -> Result<()> {
52        let mut env_keys = HashSet::new();
53        for env in &self.env {
54            let key = if let Some((key, _value)) = env.split_once('=') {
55                key
56            } else if env::var(env).is_ok() {
57                env
58            } else {
59                bail!("Referenced environment variables does not exist: {env:?}");
60            };
61
62            if !env_keys.insert(key) {
63                bail!("Can not set environment multiple times: {key:?}");
64            }
65        }
66        Ok(())
67    }
68
69    pub async fn load_files(&self) -> Result<(Option<Manifest>, Lockfile)> {
70        let path = self.file.as_deref().unwrap_or(Path::new("repro-env.lock"));
71        let lockfile = Lockfile::read_from_file(path).await?;
72
73        let manifest = if self.file.is_none() {
74            Some(Manifest::read_from_file("repro-env.toml").await?)
75        } else {
76            None
77        };
78
79        Ok((manifest, lockfile))
80    }
81}
82
83/// Update all dependencies of the reproducible environment
84#[derive(Debug, Parser)]
85pub struct Update {
86    /// Do not attempt to pull the container tag from registry before resolving it
87    #[arg(long)]
88    pub no_pull: bool,
89    /// Do not delete the build container, wait for ctrl-c
90    #[arg(short, long)]
91    pub keep: bool,
92}
93
94/// Fetch dependencies into the local cache
95#[derive(Debug, Parser)]
96pub struct Fetch {
97    /// The dependency lockfile to use
98    #[arg(short, long)]
99    pub file: Option<PathBuf>,
100    /// Do not attempt to pull the container tag from registry
101    #[arg(long)]
102    pub no_pull: bool,
103}
104
105/// Generate shell completions
106#[derive(Debug, Parser)]
107pub struct Completions {
108    pub shell: Shell,
109}
110
111impl Completions {
112    pub fn generate<W: io::Write>(&self, mut w: W) -> Result<()> {
113        clap_complete::generate(self.shell, &mut Args::command(), "repro-env", &mut w);
114        Ok(())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_zsh_completions() {
124        Completions { shell: Shell::Zsh }
125            .generate(io::sink())
126            .unwrap();
127    }
128}