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}