1use 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
45pub const DEFAULT_PORT: u16 = 4226;
47
48const SERVER_STARTUP_TIMEOUT: Duration = Duration::from_millis(10000);
50
51fn 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
66fn 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 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#[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 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 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 unsafe {
145 SetStdHandle(STD_ERROR_HANDLE, f.into_raw_handle() as _);
146 }
147}
148
149fn 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
168fn redirect_error_log(f: File) -> Result<()> {
170 debug!("redirecting stderr into {:?}", f);
171 redirect_stderr(f);
172 Ok(())
173}
174
175#[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 let runtime = Runtime::new()?;
194 let pipe_name = &format!(r"\\.\pipe\{}", Uuid::new_v4().as_simple());
195
196 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 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(); 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
302fn 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 match run_server_process(startup_timeout)? {
318 ServerStartup::Ok { addr: actual_addr } => {
319 if addr.to_string() != actual_addr {
320 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
342pub 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
355pub 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
368pub 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
381pub fn request_shutdown(mut conn: ServerConnection) -> Result<ServerInfo> {
383 debug!("request_shutdown");
384 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
395fn 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 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#[cfg(unix)]
428#[allow(dead_code)]
429fn status_signal(status: process::ExitStatus) -> Option<i32> {
430 status.signal()
431}
432
433#[cfg(not(unix))]
435#[allow(dead_code)]
436fn status_signal(_status: process::ExitStatus) -> Option<i32> {
437 None
438}
439
440fn 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 let dumb_term = env::var("TERM").map(|v| v == "dumb").unwrap_or(false);
457 if color_mode == ColorMode::On
461 || (!dumb_term && stream.is_terminal() && color_mode != ColorMode::Off)
462 {
463 writer.write_all(data)?;
464 } else {
465 let mut writer = Writer::new(writer);
467 writer.write_all(data)?;
468 }
469 Ok(())
470 }
471 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#[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 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 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 2
583 }))
584}
585
586#[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
615pub fn run_command(cmd: Command) -> Result<i32> {
617 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 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 daemonize()?;
664 redirect_error_log(f)?;
665 } else {
666 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}