Skip to main content

agave_install/
lib.rs

1#![allow(clippy::arithmetic_side_effects)]
2use clap::{App, AppSettings, Arg, ArgMatches, SubCommand, crate_description, crate_name};
3
4mod build_env;
5mod command;
6mod config;
7mod defaults;
8mod stop_process;
9
10pub fn is_semver(semver: &str) -> Result<(), String> {
11    match semver::Version::parse(semver) {
12        Ok(_) => Ok(()),
13        Err(err) => Err(format!("{err:?}")),
14    }
15}
16
17pub fn is_release_channel(channel: &str) -> Result<(), String> {
18    match channel {
19        "edge" | "beta" | "stable" => Ok(()),
20        _ => Err(format!("Invalid release channel {channel}")),
21    }
22}
23
24pub fn is_explicit_release(string: String) -> Result<(), String> {
25    if string.starts_with('v') && is_semver(string.split_at(1).1).is_ok() {
26        return Ok(());
27    }
28    is_semver(&string).or_else(|_| is_release_channel(&string))
29}
30
31pub fn explicit_release_of(matches: &ArgMatches<'_>, name: &str) -> config::ExplicitRelease {
32    let explicit_release = matches.value_of(name).unwrap().to_string();
33    if explicit_release.starts_with('v') && is_semver(explicit_release.split_at(1).1).is_ok() {
34        config::ExplicitRelease::Semver(explicit_release.split_at(1).1.to_string())
35    } else if is_semver(&explicit_release).is_ok() {
36        config::ExplicitRelease::Semver(explicit_release.to_owned())
37    } else {
38        config::ExplicitRelease::Channel(explicit_release.to_owned())
39    }
40}
41
42fn handle_init(matches: &ArgMatches<'_>, config_file: &str) -> Result<(), String> {
43    let data_dir = matches.value_of("data_dir").unwrap();
44    let no_modify_path = matches.is_present("no_modify_path");
45    let explicit_release = explicit_release_of(matches, "explicit_release");
46
47    command::init(config_file, data_dir, no_modify_path, explicit_release)
48}
49
50pub fn main() -> Result<(), String> {
51    agave_logger::setup();
52
53    let matches = App::new(crate_name!())
54        .about(crate_description!())
55        .version(solana_version::version!())
56        .setting(AppSettings::SubcommandRequiredElseHelp)
57        .arg({
58            let arg = Arg::with_name("config_file")
59                .short("c")
60                .long("config")
61                .value_name("PATH")
62                .takes_value(true)
63                .global(true)
64                .help("Configuration file to use");
65            match *defaults::CONFIG_FILE {
66                Some(ref config_file) => arg.default_value(config_file),
67                None => arg.required(true),
68            }
69        })
70        .subcommand(
71            SubCommand::with_name("init")
72                .about("Initializes a new installation")
73                .setting(AppSettings::DisableVersion)
74                .arg({
75                    let arg = Arg::with_name("data_dir")
76                        .short("d")
77                        .long("data-dir")
78                        .value_name("PATH")
79                        .takes_value(true)
80                        .required(true)
81                        .help("Directory to store install data");
82                    match *defaults::DATA_DIR {
83                        Some(ref data_dir) => arg.default_value(data_dir),
84                        None => arg,
85                    }
86                })
87                .arg(
88                    Arg::with_name("no_modify_path")
89                        .long("no-modify-path")
90                        .help("Don't configure the PATH environment variable"),
91                )
92                .arg(
93                    Arg::with_name("explicit_release")
94                        .value_name("release")
95                        .index(1)
96                        .validator(is_explicit_release)
97                        .required(true)
98                        .help("The release version or channel to install"),
99                ),
100        )
101        .subcommand(
102            SubCommand::with_name("info")
103                .about("Displays information about the current installation")
104                .setting(AppSettings::DisableVersion)
105                .arg(
106                    Arg::with_name("local_info_only")
107                        .short("l")
108                        .long("local")
109                        .help("Only display local information, don't check for updates"),
110                )
111                .arg(
112                    Arg::with_name("eval")
113                        .long("eval")
114                        .help("Display information in a format that can be used with `eval`"),
115                ),
116        )
117        .subcommand(
118            SubCommand::with_name("gc")
119                .about("Delete older releases from the install cache to reclaim disk space")
120                .setting(AppSettings::DisableVersion),
121        )
122        .subcommand(
123            SubCommand::with_name("update")
124                .about("Checks for an update, and if available downloads and applies it")
125                .setting(AppSettings::DisableVersion),
126        )
127        .subcommand(
128            SubCommand::with_name("run")
129                .about("Runs a program while periodically checking and applying software updates")
130                .after_help("The program will be restarted upon a successful software update")
131                .setting(AppSettings::DisableVersion)
132                .arg(
133                    Arg::with_name("program_name")
134                        .index(1)
135                        .required(true)
136                        .help("program to run"),
137                )
138                .arg(
139                    Arg::with_name("program_arguments")
140                        .index(2)
141                        .multiple(true)
142                        .help("Arguments to supply to the program"),
143                ),
144        )
145        .subcommand(SubCommand::with_name("list").about("List installed versions of solana cli"))
146        .get_matches();
147
148    let config_file = matches.value_of("config_file").unwrap();
149
150    match matches.subcommand() {
151        ("init", Some(matches)) => handle_init(matches, config_file),
152        ("info", Some(matches)) => {
153            let local_info_only = matches.is_present("local_info_only");
154            let eval = matches.is_present("eval");
155            command::info(config_file, local_info_only, eval).map(|_| ())
156        }
157        ("gc", Some(_matches)) => command::gc(config_file),
158        ("update", Some(_matches)) => command::update(config_file, false).map(|_| ()),
159        ("run", Some(matches)) => {
160            let program_name = matches.value_of("program_name").unwrap();
161            let program_arguments = matches
162                .values_of("program_arguments")
163                .map(Iterator::collect)
164                .unwrap_or_else(Vec::new);
165
166            command::run(config_file, program_name, program_arguments)
167        }
168        ("list", Some(_matches)) => command::list(config_file),
169        _ => unreachable!(),
170    }
171}
172
173pub fn main_init() -> Result<(), String> {
174    agave_logger::setup();
175
176    let matches = App::new("agave-install-init")
177        .about("Initializes a new installation")
178        .version(solana_version::version!())
179        .arg({
180            let arg = Arg::with_name("config_file")
181                .short("c")
182                .long("config")
183                .value_name("PATH")
184                .takes_value(true)
185                .help("Configuration file to use");
186            match *defaults::CONFIG_FILE {
187                Some(ref config_file) => arg.default_value(config_file),
188                None => arg.required(true),
189            }
190        })
191        .arg({
192            let arg = Arg::with_name("data_dir")
193                .short("d")
194                .long("data-dir")
195                .value_name("PATH")
196                .takes_value(true)
197                .required(true)
198                .help("Directory to store install data");
199            match *defaults::DATA_DIR {
200                Some(ref data_dir) => arg.default_value(data_dir),
201                None => arg,
202            }
203        })
204        .arg(
205            Arg::with_name("no_modify_path")
206                .long("no-modify-path")
207                .help("Don't configure the PATH environment variable"),
208        )
209        .arg(
210            Arg::with_name("explicit_release")
211                .value_name("release")
212                .index(1)
213                .validator(is_explicit_release)
214                .required(true)
215                .help("The release version or channel to install"),
216        )
217        .get_matches();
218
219    let config_file = matches.value_of("config_file").unwrap();
220    handle_init(&matches, config_file)
221}