1 // Copyright 2016 Mozilla Foundation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use crate::errors::*;
16 use clap::{App, AppSettings, Arg};
17 use std::env;
18 use std::ffi::OsString;
19 use std::path::PathBuf;
20 use which::which_in;
21 
22 arg_enum! {
23     #[derive(Debug)]
24     #[allow(non_camel_case_types)]
25     pub enum StatsFormat {
26         text,
27         json
28     }
29 }
30 
31 /// A specific command to run.
32 pub enum Command {
33     /// Show cache statistics and exit.
34     ShowStats(StatsFormat),
35     /// Run background server.
36     InternalStartServer,
37     /// Start background server as a subprocess.
38     StartServer,
39     /// Stop background server.
40     StopServer,
41     /// Zero cache statistics and exit.
42     ZeroStats,
43     /// Show the status of the distributed client.
44     DistStatus,
45     /// Perform a login to authenticate for distributed compilation.
46     DistAuth,
47     /// Package a toolchain for distributed compilation (executable, out)
48     PackageToolchain(PathBuf, PathBuf),
49     /// Run a compiler command.
50     Compile {
51         /// The binary to execute.
52         exe: OsString,
53         /// The commandline arguments to pass to `exe`.
54         cmdline: Vec<OsString>,
55         /// The directory in which to execute the command.
56         cwd: PathBuf,
57         /// The environment variables to use for execution.
58         env_vars: Vec<(OsString, OsString)>,
59     },
60 }
61 
62 /// Get the `App` used for argument parsing.
get_app<'a, 'b>() -> App<'a, 'b>63 pub fn get_app<'a, 'b>() -> App<'a, 'b> {
64     App::new(env!("CARGO_PKG_NAME"))
65         .version(env!("CARGO_PKG_VERSION"))
66         .setting(AppSettings::TrailingVarArg)
67         .after_help(concat!(
68                 "Enabled features:\n",
69                 "    S3:        ", cfg!(feature = "s3"), "\n",
70                 "    Redis:     ", cfg!(feature = "redis"), "\n",
71                 "    Memcached: ", cfg!(feature = "memcached"), "\n",
72                 "    GCS:       ", cfg!(feature = "gcs"), "\n",
73                 "    Azure:     ", cfg!(feature = "azure"), "\n")
74                 )
75         .args_from_usage(
76             "-s --show-stats  'show cache statistics'
77              --start-server   'start background server'
78              --stop-server    'stop background server'
79              -z, --zero-stats 'zero statistics counters'
80              --dist-auth      'authenticate for distributed compilation'
81              --dist-status    'show status of the distributed client'"
82                 )
83         .arg(Arg::from_usage("--package-toolchain <executable> <out> 'package toolchain for distributed compilation'")
84              .required(false))
85         .arg(Arg::from_usage("--stats-format  'set output format of statistics'")
86              .possible_values(&StatsFormat::variants())
87              .default_value("text"))
88         .arg(
89             Arg::with_name("cmd")
90                 .multiple(true)
91                 .use_delimiter(false)
92                 )
93 }
94 
95 /// Parse the commandline into a `Command` to execute.
parse() -> Result<Command>96 pub fn parse() -> Result<Command> {
97     trace!("parse");
98     let cwd =
99         env::current_dir().context("sccache: Couldn't determine current working directory")?;
100     // The internal start server command is passed in the environment.
101     let internal_start_server = match env::var("SCCACHE_START_SERVER") {
102         Ok(val) => val == "1",
103         Err(_) => false,
104     };
105     let mut args: Vec<_> = env::args_os().collect();
106     if !internal_start_server {
107         if let Ok(exe) = env::current_exe() {
108             match exe
109                 .file_stem()
110                 .and_then(|s| s.to_str())
111                 .map(|s| s.to_lowercase())
112             {
113                 // If the executable has its standard name, do nothing.
114                 Some(ref e) if e == env!("CARGO_PKG_NAME") => {}
115                 // Otherwise, if it was copied/hardlinked under a different $name, act
116                 // as if it were invoked with `sccache $name`, but avoid $name resolving
117                 // to ourselves again if it's in the PATH.
118                 _ => {
119                     if let (Some(path), Some(exe_filename)) = (env::var_os("PATH"), exe.file_name())
120                     {
121                         match which_in(exe_filename, Some(&path), &cwd) {
122                             Ok(ref full_path)
123                                 if full_path.canonicalize()? == exe.canonicalize()? =>
124                             {
125                                 if let Some(dir) = full_path.parent() {
126                                     let path = env::join_paths(
127                                         env::split_paths(&path).filter(|p| p != dir),
128                                     )
129                                     .ok();
130                                     if let Ok(full_path) = which_in(exe_filename, path, &cwd) {
131                                         args[0] = full_path.into();
132                                     }
133                                 }
134                             }
135                             Ok(full_path) => args[0] = full_path.into(),
136                             Err(_) => {}
137                         }
138                         args.insert(0, env!("CARGO_PKG_NAME").into());
139                     }
140                 }
141             }
142         }
143     }
144     let matches = get_app().get_matches_from(args);
145 
146     let show_stats = matches.is_present("show-stats");
147     let start_server = matches.is_present("start-server");
148     let stop_server = matches.is_present("stop-server");
149     let zero_stats = matches.is_present("zero-stats");
150     let dist_auth = matches.is_present("dist-auth");
151     let dist_status = matches.is_present("dist-status");
152     let package_toolchain = matches.is_present("package-toolchain");
153     let cmd = matches.values_of_os("cmd");
154     // Ensure that we've only received one command to run.
155     fn is_some<T>(x: &Option<T>) -> bool {
156         x.is_some()
157     }
158     if [
159         internal_start_server,
160         show_stats,
161         start_server,
162         stop_server,
163         zero_stats,
164         package_toolchain,
165         is_some(&cmd),
166     ]
167     .iter()
168     .filter(|&&x| x)
169     .count()
170         > 1
171     {
172         bail!("Too many commands specified");
173     }
174     if internal_start_server {
175         Ok(Command::InternalStartServer)
176     } else if show_stats {
177         let fmt =
178             value_t!(matches.value_of("stats-format"), StatsFormat).unwrap_or_else(|e| e.exit());
179         Ok(Command::ShowStats(fmt))
180     } else if start_server {
181         Ok(Command::StartServer)
182     } else if stop_server {
183         Ok(Command::StopServer)
184     } else if zero_stats {
185         Ok(Command::ZeroStats)
186     } else if dist_auth {
187         Ok(Command::DistAuth)
188     } else if dist_status {
189         Ok(Command::DistStatus)
190     } else if package_toolchain {
191         let mut values = matches
192             .values_of_os("package-toolchain")
193             .expect("Parsed package-toolchain but no values");
194         assert!(values.len() == 2);
195         let (executable, out) = (
196             values.next().expect("package-toolchain missing value 1"),
197             values.next().expect("package-toolchain missing value 2"),
198         );
199         Ok(Command::PackageToolchain(executable.into(), out.into()))
200     } else if let Some(mut args) = cmd {
201         if let Some(exe) = args.next() {
202             let cmdline = args.map(|s| s.to_owned()).collect::<Vec<_>>();
203             let mut env_vars = env::vars_os().collect::<Vec<_>>();
204 
205             // If we're running under rr, avoid the `LD_PRELOAD` bits, as it will
206             // almost surely do the wrong thing, as the compiler gets executed
207             // in a different process tree.
208             //
209             // FIXME: Maybe we should strip out `LD_PRELOAD` always?
210             if env::var_os("RUNNING_UNDER_RR").is_some() {
211                 env_vars.retain(|(k, _v)| k != "LD_PRELOAD" && k != "RUNNING_UNDER_RR");
212             }
213 
214             Ok(Command::Compile {
215                 exe: exe.to_owned(),
216                 cmdline,
217                 cwd,
218                 env_vars,
219             })
220         } else {
221             bail!("No compile command");
222         }
223     } else {
224         bail!("No command specified");
225     }
226 }
227