Skip to main content

sccache/
commands.rs

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
15use crate::cache::storage_from_config;
16use crate::client::{ServerConnection, connect_to_server, connect_with_retry};
17use crate::cmdline::{Command, StatsFormat};
18use crate::compiler::ColorMode;
19use crate::config::{Config, default_disk_cache_dir};
20use crate::jobserver::Client;
21use crate::mock_command::{CommandChild, CommandCreatorSync, ProcessCommandCreator, RunCommand};
22use crate::protocol::{Compile, CompileFinished, CompileResponse, Request, Response};
23use crate::server::{self, DistInfo, ServerInfo, ServerStartup, ServerStats};
24use crate::util::daemonize;
25use byteorder::{BigEndian, ByteOrder};
26use fs::{File, OpenOptions};
27use fs_err as fs;
28use log::Level::Trace;
29use std::env;
30use std::ffi::{OsStr, OsString};
31use std::io::{self, IsTerminal, Write};
32#[cfg(unix)]
33use std::os::unix::process::ExitStatusExt;
34use std::path::Path;
35use std::process;
36use std::time::Duration;
37use strip_ansi_escapes::Writer;
38use tokio::io::AsyncReadExt;
39use tokio::runtime::Runtime;
40use walkdir::WalkDir;
41use which::which_in;
42
43use crate::errors::*;
44
45/// The default sccache server port.
46pub const DEFAULT_PORT: u16 = 4226;
47
48/// The number of milliseconds to wait for server startup.
49const SERVER_STARTUP_TIMEOUT: Duration = Duration::from_millis(10000);
50
51/// Get the port on which the server should listen.
52fn get_addr() -> crate::net::SocketAddr {
53    #[cfg(unix)]
54    if let Ok(addr) = env::var("SCCACHE_SERVER_UDS") {
55        if let Ok(uds) = crate::net::SocketAddr::parse_uds(&addr) {
56            return uds;
57        }
58    }
59    let port = env::var("SCCACHE_SERVER_PORT")
60        .ok()
61        .and_then(|s| s.parse().ok())
62        .unwrap_or(DEFAULT_PORT);
63    crate::net::SocketAddr::with_port(port)
64}
65
66/// Check if ignoring all response errors
67fn ignore_all_server_io_errors() -> bool {
68    match env::var("SCCACHE_IGNORE_SERVER_IO_ERROR") {
69        Ok(ignore_server_error) => ignore_server_error == "1",
70        Err(_) => false,
71    }
72}
73
74async fn read_server_startup_status<R: AsyncReadExt + Unpin>(
75    mut server: R,
76) -> Result<ServerStartup> {
77    // This is an async equivalent of ServerConnection::read_one_response
78    let mut bytes = [0u8; 4];
79    server.read_exact(&mut bytes[..]).await?;
80
81    let len = BigEndian::read_u32(&bytes);
82    let mut data = vec![0; len as usize];
83    server.read_exact(data.as_mut_slice()).await?;
84
85    Ok(bincode::deserialize(&data)?)
86}
87
88/// Re-execute the current executable as a background server, and wait
89/// for it to start up.
90#[cfg(not(windows))]
91fn run_server_process(startup_timeout: Option<Duration>) -> Result<ServerStartup> {
92    trace!("run_server_process");
93    let tempdir = tempfile::Builder::new().prefix("sccache").tempdir()?;
94    let socket_path = tempdir.path().join("sock");
95    let runtime = Runtime::new()?;
96    let exe_path = env::current_exe()?;
97    let workdir = exe_path.parent().expect("executable path has no parent?!");
98
99    // Spawn a blocking task to bind the Unix socket. Note that the socket
100    // must be bound before spawning `_child` below to avoid a race between
101    // the parent binding the socket and the child connecting to it.
102    let listener = {
103        let _guard = runtime.enter();
104        tokio::net::UnixListener::bind(&socket_path)?
105    };
106
107    let _child = process::Command::new(&exe_path)
108        .current_dir(workdir)
109        .env("SCCACHE_START_SERVER", "1")
110        .env("SCCACHE_STARTUP_NOTIFY", &socket_path)
111        .env("RUST_BACKTRACE", "1")
112        .spawn()?;
113
114    let startup = async move {
115        let (socket, _) = listener.accept().await?;
116
117        read_server_startup_status(socket).await
118    };
119
120    let timeout = startup_timeout.unwrap_or(SERVER_STARTUP_TIMEOUT);
121    runtime.block_on(async move {
122        match tokio::time::timeout(timeout, startup).await {
123            Ok(result) => result,
124            Err(_elapsed) => Ok(ServerStartup::TimedOut),
125        }
126    })
127}
128
129#[cfg(not(windows))]
130fn redirect_stderr(f: File) {
131    use libc::dup2;
132    use std::os::unix::io::IntoRawFd;
133    // Ignore errors here.
134    unsafe {
135        dup2(f.into_raw_fd(), 2);
136    }
137}
138
139#[cfg(windows)]
140fn redirect_stderr(f: File) {
141    use std::os::windows::io::IntoRawHandle;
142    use windows_sys::Win32::System::Console::{STD_ERROR_HANDLE, SetStdHandle};
143    // Ignore errors here.
144    unsafe {
145        SetStdHandle(STD_ERROR_HANDLE, f.into_raw_handle() as _);
146    }
147}
148
149/// Create the log file and return an error if cannot be created
150fn create_error_log() -> Result<File> {
151    trace!("Create the log file");
152    let name = match env::var("SCCACHE_ERROR_LOG") {
153        Ok(filename) if !filename.is_empty() => filename,
154        _ => {
155            bail!("Cannot read variable 'SCCACHE_ERROR_LOG'");
156        }
157    };
158
159    let f = match OpenOptions::new().create(true).append(true).open(&name) {
160        Ok(f) => f,
161        Err(_) => {
162            bail!("Cannot open/write log file '{}'", &name);
163        }
164    };
165    Ok(f)
166}
167
168/// If `SCCACHE_ERROR_LOG` is set, redirect stderr to it.
169fn redirect_error_log(f: File) -> Result<()> {
170    debug!("redirecting stderr into {:?}", f);
171    redirect_stderr(f);
172    Ok(())
173}
174
175/// Re-execute the current executable as a background server.
176#[cfg(windows)]
177fn run_server_process(startup_timeout: Option<Duration>) -> Result<ServerStartup> {
178    use futures::StreamExt;
179    use std::mem;
180    use std::os::windows::ffi::OsStrExt;
181    use std::ptr;
182    use tokio::net::windows::named_pipe;
183    use uuid::Uuid;
184    use windows_sys::Win32::Foundation::CloseHandle;
185    use windows_sys::Win32::System::Threading::{
186        CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, CreateProcessW,
187        PROCESS_INFORMATION, STARTUPINFOW,
188    };
189
190    trace!("run_server_process");
191
192    // Create a mini event loop and register our named pipe server
193    let runtime = Runtime::new()?;
194    let pipe_name = &format!(r"\\.\pipe\{}", Uuid::new_v4().as_simple());
195
196    // Spawn a server which should come back and connect to us
197    let exe_path = env::current_exe()?;
198    let mut exe = OsStr::new(&exe_path)
199        .encode_wide()
200        .chain(Some(0u16))
201        .collect::<Vec<u16>>();
202    let mut envp = {
203        let mut v = vec![];
204        let extra_vars = vec![
205            (OsString::from("SCCACHE_START_SERVER"), OsString::from("1")),
206            (
207                OsString::from("SCCACHE_STARTUP_NOTIFY"),
208                OsString::from(&pipe_name),
209            ),
210            (OsString::from("RUST_BACKTRACE"), OsString::from("1")),
211        ];
212        for (key, val) in env::vars_os().chain(extra_vars) {
213            v.extend(
214                key.encode_wide()
215                    .chain(Some('=' as u16))
216                    .chain(val.encode_wide())
217                    .chain(Some(0)),
218            );
219        }
220        v.push(0);
221        v
222    };
223    let workdir = exe_path
224        .parent()
225        .expect("executable path has no parent?!")
226        .as_os_str()
227        .encode_wide()
228        .chain(Some(0u16))
229        .collect::<Vec<u16>>();
230
231    // TODO: Expose `bInheritHandles` argument of `CreateProcessW` through the
232    //       standard library's `Command` type and then use that instead.
233    let mut pi = PROCESS_INFORMATION {
234        hProcess: 0,
235        hThread: 0,
236        dwProcessId: 0,
237        dwThreadId: 0,
238    };
239    let mut si: STARTUPINFOW = unsafe { mem::zeroed() };
240    si.cb = mem::size_of::<STARTUPINFOW>() as _;
241    if unsafe {
242        CreateProcessW(
243            exe.as_mut_ptr(),
244            ptr::null_mut(),
245            ptr::null_mut(),
246            ptr::null_mut(),
247            0,
248            CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW,
249            envp.as_mut_ptr().cast(),
250            workdir.as_ptr(),
251            &si,
252            &mut pi,
253        ) != 0
254    } {
255        unsafe {
256            CloseHandle(pi.hProcess);
257            CloseHandle(pi.hThread);
258        }
259    } else {
260        return Err(io::Error::last_os_error().into());
261    }
262
263    fn create_named_pipe(
264        pipe_name: &str,
265        is_first: bool,
266    ) -> io::Result<named_pipe::NamedPipeServer> {
267        named_pipe::ServerOptions::new()
268            .first_pipe_instance(is_first)
269            .reject_remote_clients(true)
270            .access_inbound(true)
271            .access_outbound(true)
272            .in_buffer_size(65536)
273            .out_buffer_size(65536)
274            .create(pipe_name)
275    }
276
277    let startup = async move {
278        let pipe = create_named_pipe(pipe_name, true)?;
279
280        let incoming = futures::stream::try_unfold(pipe, |listener| async move {
281            listener.connect().await?;
282            let new_listener = create_named_pipe(pipe_name, false)?;
283            Ok::<_, io::Error>(Some((listener, new_listener)))
284        });
285
286        futures::pin_mut!(incoming);
287        let socket = incoming.next().await;
288        let socket = socket.unwrap(); // incoming() never returns None
289
290        read_server_startup_status(socket?).await
291    };
292
293    let timeout = startup_timeout.unwrap_or(SERVER_STARTUP_TIMEOUT);
294    runtime.block_on(async move {
295        match tokio::time::timeout(timeout, startup).await {
296            Ok(result) => result,
297            Err(_elapsed) => Ok(ServerStartup::TimedOut),
298        }
299    })
300}
301
302/// Attempt to connect to an sccache server listening on `addr`, or start one if no server is running.
303fn connect_or_start_server(
304    addr: &crate::net::SocketAddr,
305    startup_timeout: Option<Duration>,
306) -> Result<ServerConnection> {
307    trace!("connect_or_start_server({addr})");
308    match connect_to_server(addr) {
309        Ok(server) => Ok(server),
310        Err(ref e)
311            if (e.kind() == io::ErrorKind::ConnectionRefused
312                || e.kind() == io::ErrorKind::TimedOut)
313                || (e.kind() == io::ErrorKind::NotFound && addr.is_unix_path()) =>
314        {
315            // If the connection was refused we probably need to start
316            // the server.
317            match run_server_process(startup_timeout)? {
318                ServerStartup::Ok { addr: actual_addr } => {
319                    if addr.to_string() != actual_addr {
320                        // bail as the next connect_with_retry will fail
321                        bail!("sccache: Listening on address {actual_addr} instead of {addr}");
322                    }
323                }
324                ServerStartup::AddrInUse => {
325                    debug!("AddrInUse: possible parallel server bootstraps, retrying..");
326                }
327                ServerStartup::TimedOut => bail!(
328                    "Timed out waiting for server startup. Maybe the remote service is unreachable?\nRun with SCCACHE_LOG=debug SCCACHE_NO_DAEMON=1 to get more information"
329                ),
330                ServerStartup::Err { reason } => bail!(
331                    "Server startup failed: {}\nRun with SCCACHE_LOG=debug SCCACHE_NO_DAEMON=1 to get more information",
332                    reason
333                ),
334            }
335            let server = connect_with_retry(addr)?;
336            Ok(server)
337        }
338        Err(e) => Err(e.into()),
339    }
340}
341
342/// Send a `ZeroStats` request to the server, and return the `ServerInfo` request if successful.
343pub fn request_zero_stats(mut conn: ServerConnection) -> Result<()> {
344    debug!("request_stats");
345    let response = conn.request(Request::ZeroStats).context(
346        "failed to send zero statistics command to server or failed to receive response",
347    )?;
348    if let Response::ZeroStats = response {
349        Ok(())
350    } else {
351        bail!("Unexpected server response!")
352    }
353}
354
355/// Send a `GetStats` request to the server, and return the `ServerInfo` request if successful.
356pub fn request_stats(mut conn: ServerConnection) -> Result<ServerInfo> {
357    debug!("request_stats");
358    let response = conn.request(Request::GetStats).context(
359        "Failed to send data to or receive data from server. Mismatch of client/server versions?",
360    )?;
361    if let Response::Stats(stats) = response {
362        Ok(*stats)
363    } else {
364        bail!("Unexpected server response!")
365    }
366}
367
368/// Send a `DistStatus` request to the server, and return `DistStatus` if successful.
369pub fn request_dist_status(mut conn: ServerConnection) -> Result<DistInfo> {
370    debug!("request_dist_status");
371    let response = conn
372        .request(Request::DistStatus)
373        .context("Failed to send data to or receive data from server")?;
374    if let Response::DistStatus(info) = response {
375        Ok(info)
376    } else {
377        bail!("Unexpected server response!")
378    }
379}
380
381/// Send a `Shutdown` request to the server, and return the `ServerInfo` contained within the response if successful.
382pub fn request_shutdown(mut conn: ServerConnection) -> Result<ServerInfo> {
383    debug!("request_shutdown");
384    //TODO: better error mapping
385    let response = conn
386        .request(Request::Shutdown)
387        .context("Failed to send data to or receive data from server")?;
388    if let Response::ShuttingDown(stats) = response {
389        Ok(*stats)
390    } else {
391        bail!("Unexpected server response!")
392    }
393}
394
395/// Send a `Compile` request to the server, and return the server response if successful.
396fn request_compile<W, X, Y>(
397    conn: &mut ServerConnection,
398    exe: W,
399    args: &[X],
400    cwd: Y,
401    env_vars: Vec<(OsString, OsString)>,
402) -> Result<CompileResponse>
403where
404    W: AsRef<Path>,
405    X: AsRef<OsStr>,
406    Y: AsRef<Path>,
407{
408    let req = Request::Compile(Compile {
409        exe: exe.as_ref().to_owned().into(),
410        cwd: cwd.as_ref().to_owned().into(),
411        args: args.iter().map(|a| a.as_ref().to_owned()).collect(),
412        env_vars,
413    });
414    trace!("request_compile: {:?}", req);
415    //TODO: better error mapping?
416    let response = conn
417        .request(req)
418        .context("Failed to send data to or receive data from server")?;
419    if let Response::Compile(response) = response {
420        Ok(response)
421    } else {
422        bail!("Unexpected response from server")
423    }
424}
425
426/// Return the signal that caused a process to exit from `status`.
427#[cfg(unix)]
428#[allow(dead_code)]
429fn status_signal(status: process::ExitStatus) -> Option<i32> {
430    status.signal()
431}
432
433/// Not implemented for non-Unix.
434#[cfg(not(unix))]
435#[allow(dead_code)]
436fn status_signal(_status: process::ExitStatus) -> Option<i32> {
437    None
438}
439
440/// Handle `response`, the output from running a compile on the server.
441/// Return the compiler exit status.
442fn handle_compile_finished(
443    response: CompileFinished,
444    stdout: &mut dyn Write,
445    stderr: &mut dyn Write,
446) -> Result<i32> {
447    trace!("handle_compile_finished");
448    fn write_output(
449        stream: impl IsTerminal,
450        writer: &mut dyn Write,
451        data: &[u8],
452        color_mode: ColorMode,
453    ) -> Result<()> {
454        // rustc uses the `termcolor` crate which explicitly checks for TERM=="dumb", so
455        // match that behavior here.
456        let dumb_term = env::var("TERM").map(|v| v == "dumb").unwrap_or(false);
457        // If the compiler options explicitly requested color output, or if this output stream
458        // is a terminal and the compiler options didn't explicitly request non-color output,
459        // then write the compiler output directly.
460        if color_mode == ColorMode::On
461            || (!dumb_term && stream.is_terminal() && color_mode != ColorMode::Off)
462        {
463            writer.write_all(data)?;
464        } else {
465            // Remove escape codes (and thus colors) while writing.
466            let mut writer = Writer::new(writer);
467            writer.write_all(data)?;
468        }
469        Ok(())
470    }
471    // It might be nice if the server sent stdout/stderr as the process
472    // ran, but then it would have to also save them in the cache as
473    // interleaved streams to really make it work.
474    write_output(
475        std::io::stdout(),
476        stdout,
477        &response.stdout,
478        response.color_mode,
479    )?;
480    write_output(
481        std::io::stderr(),
482        stderr,
483        &response.stderr,
484        response.color_mode,
485    )?;
486
487    if let Some(ret) = response.retcode {
488        trace!("compiler exited with status {}", ret);
489        Ok(ret)
490    } else if let Some(signal) = response.signal {
491        println!("sccache: Compiler killed by signal {}", signal);
492        Ok(-2)
493    } else {
494        println!("sccache: Missing compiler exit status!");
495        Ok(-3)
496    }
497}
498
499/// Handle `response`, the response from sending a `Compile` request to the server. Return the compiler exit status.
500///
501/// If the server returned `CompileStarted`, wait for a `CompileFinished` and
502/// print the results.
503///
504/// If the server returned `UnhandledCompile`, run the compilation command
505/// locally using `creator` and return the result.
506#[allow(clippy::too_many_arguments)]
507fn handle_compile_response<T>(
508    mut creator: T,
509    runtime: &mut Runtime,
510    conn: &mut ServerConnection,
511    response: CompileResponse,
512    exe: &Path,
513    cmdline: Vec<OsString>,
514    cwd: &Path,
515    stdout: &mut dyn Write,
516    stderr: &mut dyn Write,
517) -> Result<i32>
518where
519    T: CommandCreatorSync,
520{
521    match response {
522        CompileResponse::CompileStarted => {
523            debug!("Server sent CompileStarted");
524            // Wait for CompileFinished.
525            match conn.read_one_response() {
526                Ok(Response::CompileFinished(result)) => {
527                    return handle_compile_finished(result, stdout, stderr);
528                }
529                Ok(_) => bail!("unexpected response from server"),
530                Err(e) => {
531                    match e.downcast_ref::<io::Error>() {
532                        Some(io_e) if io_e.kind() == io::ErrorKind::UnexpectedEof => {
533                            eprintln!(
534                                "sccache: warning: The server looks like it shut down \
535                                 unexpectedly, compiling locally instead"
536                            );
537                        }
538                        _ => {
539                            //TODO: something better here?
540                            if ignore_all_server_io_errors() {
541                                eprintln!(
542                                    "sccache: warning: error reading compile response from server \
543                                     compiling locally instead"
544                                );
545                            } else {
546                                return Err(e)
547                                    .context("error reading compile response from server");
548                            }
549                        }
550                    }
551                }
552            }
553        }
554        CompileResponse::UnsupportedCompiler(s) => {
555            debug!("Server sent UnsupportedCompiler: {:?}", s);
556            bail!("Compiler not supported: {:?}", s);
557        }
558        CompileResponse::UnhandledCompile => {
559            debug!("Server sent UnhandledCompile");
560        }
561    }
562
563    let mut cmd = creator.new_command_sync(exe);
564    cmd.args(&cmdline).current_dir(cwd);
565    if log_enabled!(Trace) {
566        trace!("running command: {:?}", cmd);
567    }
568
569    let status = runtime.block_on(async move {
570        let child = cmd.spawn().await?;
571        child
572            .wait()
573            .await
574            .with_context(|| "failed to wait for a child")
575    })?;
576
577    Ok(status.code().unwrap_or_else(|| {
578        if let Some(sig) = status_signal(status) {
579            println!("sccache: Compile terminated by signal {}", sig);
580        }
581        // Arbitrary.
582        2
583    }))
584}
585
586/// Send a `Compile` request to the sccache server `conn`, and handle the response.
587///
588/// The first entry in `cmdline` will be looked up in `path` if it is not
589/// an absolute path.
590/// See `request_compile` and `handle_compile_response`.
591#[allow(clippy::too_many_arguments)]
592pub fn do_compile<T>(
593    creator: T,
594    runtime: &mut Runtime,
595    mut conn: ServerConnection,
596    exe: &Path,
597    cmdline: Vec<OsString>,
598    cwd: &Path,
599    path: Option<OsString>,
600    env_vars: Vec<(OsString, OsString)>,
601    stdout: &mut dyn Write,
602    stderr: &mut dyn Write,
603) -> Result<i32>
604where
605    T: CommandCreatorSync,
606{
607    trace!("do_compile");
608    let exe_path = which_in(exe, path, cwd)?;
609    let res = request_compile(&mut conn, &exe_path, &cmdline, cwd, env_vars)?;
610    handle_compile_response(
611        creator, runtime, &mut conn, res, &exe_path, cmdline, cwd, stdout, stderr,
612    )
613}
614
615/// Run `cmd` and return the process exit status.
616pub fn run_command(cmd: Command) -> Result<i32> {
617    // Config isn't required for all commands, but if it's broken then we should flag
618    // it early and loudly.
619    let config = &Config::load()?;
620    let startup_timeout = config.server_startup_timeout;
621
622    match cmd {
623        Command::ShowStats(fmt, advanced) => {
624            trace!("Command::ShowStats({:?})", fmt);
625            let stats = match connect_to_server(&get_addr()) {
626                Ok(srv) => request_stats(srv).context("failed to get stats from server")?,
627                // If there is no server, spawning a new server would start with zero stats
628                // anyways, so we can just return (mostly) empty stats directly.
629                Err(_) => {
630                    let runtime = Runtime::new()?;
631                    let storage = storage_from_config(config, runtime.handle()).ok();
632                    runtime.block_on(ServerInfo::new(ServerStats::default(), storage.as_deref()))?
633                }
634            };
635            match fmt {
636                StatsFormat::Text => stats.print(advanced),
637                StatsFormat::Json => serde_json::to_writer(&mut io::stdout(), &stats)?,
638            }
639        }
640        Command::DebugPreprocessorCacheEntries => {
641            trace!("Command::DebugPreprocessorCacheEntries");
642            let entries_dir = default_disk_cache_dir().join("preprocessor");
643            for entry in WalkDir::new(entries_dir).sort_by_file_name() {
644                let preprocessor_cache_entry_file = entry?;
645                let path = preprocessor_cache_entry_file.path();
646                if !path.is_file() {
647                    continue;
648                }
649                println!("=========================");
650                println!("Showing preprocessor entry file {}", &path.display());
651                let contents = std::fs::read(path)?;
652                let preprocessor_cache_entry =
653                    crate::compiler::PreprocessorCacheEntry::read(&contents)?;
654                println!("{:#?}", preprocessor_cache_entry);
655                println!("=========================");
656            }
657        }
658        Command::InternalStartServer => {
659            trace!("Command::InternalStartServer");
660            if env::var("SCCACHE_ERROR_LOG").is_ok() {
661                let f = create_error_log()?;
662                // Can't report failure here, we're already daemonized.
663                daemonize()?;
664                redirect_error_log(f)?;
665            } else {
666                // We aren't asking for a log file
667                daemonize()?;
668            }
669            server::start_server(config, &get_addr())?;
670        }
671        Command::StartServer => {
672            trace!("Command::StartServer");
673            println!("sccache: Starting the server...");
674            let startup =
675                run_server_process(startup_timeout).context("failed to start server process")?;
676            match startup {
677                ServerStartup::Ok { addr } => {
678                    println!("sccache: Listening on address {addr}");
679                }
680                ServerStartup::TimedOut => bail!("Timed out waiting for server startup"),
681                ServerStartup::AddrInUse => bail!("Server startup failed: Address in use"),
682                ServerStartup::Err { reason } => bail!("Server startup failed: {}", reason),
683            }
684        }
685        Command::StopServer => {
686            trace!("Command::StopServer");
687            println!("Stopping sccache server...");
688            let server = connect_to_server(&get_addr()).context("couldn't connect to server")?;
689            let stats = request_shutdown(server)?;
690            stats.print(false);
691        }
692        Command::ZeroStats => {
693            trace!("Command::ZeroStats");
694            let conn = connect_or_start_server(&get_addr(), startup_timeout)?;
695            request_zero_stats(conn).context("couldn't zero stats on server")?;
696            eprintln!("Statistics zeroed.");
697        }
698        #[cfg(feature = "dist-client")]
699        Command::DistAuth => {
700            use crate::config;
701            use crate::dist;
702            use url::Url;
703
704            match &config.dist.auth {
705                config::DistAuth::Token { .. } => {
706                    info!("No authentication needed for type 'token'");
707                }
708                config::DistAuth::Oauth2CodeGrantPKCE {
709                    client_id,
710                    auth_url,
711                    token_url,
712                } => {
713                    let cached_config = config::CachedConfig::load()?;
714
715                    let parsed_auth_url = Url::parse(auth_url)
716                        .map_err(|_| anyhow!("Failed to parse URL {}", auth_url))?;
717                    let token = dist::client_auth::get_token_oauth2_code_grant_pkce(
718                        client_id,
719                        parsed_auth_url,
720                        token_url,
721                    )?;
722
723                    cached_config
724                        .with_mut(|c| {
725                            c.dist.auth_tokens.insert(auth_url.to_owned(), token);
726                        })
727                        .context("Unable to save auth token")?;
728                    println!("Saved token");
729                }
730                config::DistAuth::Oauth2Implicit {
731                    client_id,
732                    auth_url,
733                } => {
734                    let cached_config = config::CachedConfig::load()?;
735
736                    let parsed_auth_url = Url::parse(auth_url)
737                        .map_err(|_| anyhow!("Failed to parse URL {}", auth_url))?;
738                    let token =
739                        dist::client_auth::get_token_oauth2_implicit(client_id, parsed_auth_url)?;
740
741                    cached_config
742                        .with_mut(|c| {
743                            c.dist.auth_tokens.insert(auth_url.to_owned(), token);
744                        })
745                        .context("Unable to save auth token")?;
746                    println!("Saved token");
747                }
748            }
749        }
750        #[cfg(not(feature = "dist-client"))]
751        Command::DistAuth => bail!(
752            "Distributed compilation not compiled in, please rebuild with the dist-client feature"
753        ),
754        Command::DistStatus => {
755            trace!("Command::DistStatus");
756            let srv = connect_or_start_server(&get_addr(), startup_timeout)?;
757            let status =
758                request_dist_status(srv).context("failed to get dist-status from server")?;
759            serde_json::to_writer(&mut io::stdout(), &status)?;
760        }
761        #[cfg(feature = "dist-client")]
762        Command::PackageToolchain(executable, out) => {
763            use crate::compiler;
764
765            trace!("Command::PackageToolchain({})", executable.display());
766            let runtime = Runtime::new()?;
767            let jobserver = Client::new();
768            let creator = ProcessCommandCreator::new(&jobserver);
769            let args: Vec<_> = env::args_os().collect();
770            let env: Vec<_> = env::vars_os().collect();
771            let out_file = File::create(out)?;
772            let cwd = env::current_dir().expect("A current working dir should exist");
773
774            let pool = runtime.handle().clone();
775            runtime.block_on(async move {
776                compiler::get_compiler_info(creator, &executable, &cwd, &args, &env, &pool, None)
777                    .await
778                    .map(|compiler| compiler.0.get_toolchain_packager())
779                    .and_then(|packager| packager.write_pkg(out_file))
780            })?;
781        }
782        #[cfg(not(feature = "dist-client"))]
783        Command::PackageToolchain(_executable, _out) => bail!(
784            "Toolchain packaging not compiled in, please rebuild with the dist-client feature"
785        ),
786        Command::Compile {
787            exe,
788            cmdline,
789            cwd,
790            env_vars,
791        } => {
792            trace!("Command::Compile {{ {:?}, {:?}, {:?} }}", exe, cmdline, cwd);
793
794            let incr_env_strs = ["CARGO_BUILD_INCREMENTAL", "CARGO_INCREMENTAL"];
795            incr_env_strs
796                .iter()
797                .for_each(|incr_str| match env::var(incr_str) {
798                    Ok(incr_val) if incr_val == "1" => {
799                        println!(
800                            "sccache: incremental compilation is prohibited: Unset {} to continue.",
801                            incr_str
802                        );
803                        std::process::exit(1);
804                    }
805                    _ => (),
806                });
807
808            let jobserver = Client::new();
809            let conn = connect_or_start_server(&get_addr(), startup_timeout)?;
810            let mut runtime = Runtime::new()?;
811            let res = do_compile(
812                ProcessCommandCreator::new(&jobserver),
813                &mut runtime,
814                conn,
815                exe.as_ref(),
816                cmdline,
817                &cwd,
818                env::var_os("PATH"),
819                env_vars,
820                &mut io::stdout(),
821                &mut io::stderr(),
822            );
823            return res.context("failed to execute compile");
824        }
825    }
826
827    Ok(0)
828}