-
Notifications
You must be signed in to change notification settings - Fork 572
Sccache support for building Rust on Windows #294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/commands.rs
Outdated
} | ||
#[cfg(not(feature = "dist-client"))] | ||
Command::PackageToolchain(_executable, _out) => { | ||
bail!("Toolchain packaging not compiled in") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be slightly better if it mentioned that you need to build the dist-client
feature.
ccc48d9
to
433782f
Compare
@@ -595,6 +686,7 @@ static ARGS: [ArgInfo<ArgData>; 33] = [ | |||
take_arg!("--out-dir", PathBuf, CanBeSeparated('='), OutDir), | |||
take_arg!("--pretty", OsString, CanBeSeparated('='), NotCompilation), | |||
take_arg!("--print", OsString, CanBeSeparated('='), NotCompilation), | |||
take_arg!("--remap-path-prefix", OsString, CanBeSeparated('='), TooHard), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remapping path prefixes was already a failure case (since it would read incorrect things from the dep file), this addition just makes it graceful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably figure out how to deal with this longer-term but it's probably fine for now. Was it harmful in the non-distributed case, or only in the distributed case?
Fixed the dep file generation - essentially, remapping path prefixes only applies to inputs, but some of the paths in a .d file are outputs so aren't mapped. I hadn't noticed it before because nothing actually uses the created .d files. The chosen solution does mean that the .d files retrieved from remote compilation use forward slashes (i.e. |
let packager = compiler.map(|c| c.get_toolchain_packager()); | ||
let res = packager.and_then(|p| p.write_pkg(out_file)); | ||
core.run(res)? | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a followup improvement, I think it would be nice if this command printed a status message when it started (since it could presumably take a little bit of time to write the package) and also printed the toolchain hash afterwards, for ease of configuring cross-compiling clients.
We also might want to audit potential error messages in the methods in use here, since most of them were only used internally in the server previously and this exposes them directly to command-line usage.
@@ -402,6 +402,9 @@ pub fn generate_compile_commands(path_transformer: &mut dist::PathTransformer, | |||
env_vars: &[(OsString, OsString)]) | |||
-> Result<(CompileCommand, Option<dist::CompileCommand>, Cacheable)> | |||
{ | |||
#[cfg(not(feature = "dist-client"))] | |||
let _ = path_transformer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love statement-level #[cfg]
usage like this, but I don't know that there's a better pattern. Maybe using cfg-if
would make it nicer?
use tempdir::TempDir; | ||
use util::{fmt_duration_as_secs, run_input_output, Digest}; | ||
use util::{HashToDigest, OsStrExt, ref_env}; | ||
|
||
use errors::*; | ||
|
||
/// Can dylibs (like proc macros) be distributed on this platform? | ||
#[cfg(all(feature = "dist-client", target_os = "linux", target_arch = "x86_64"))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are proc macro shared libraries portable enough that you can run them on different Linux systems? I guess we'll find out in practice once more people start using this code.
|
||
#[cfg(feature = "dist-client")] | ||
const RLIB_PREFIX: &str = "lib"; | ||
#[cfg(feature = "dist-client")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consts like these would probably be fine with just #[allow(unused)]
, but it's not a big deal.
let rlib_dep_reader = match rlib_dep_reader { | ||
Ok(r) => Some(Arc::new(r)), | ||
Err(e) => { | ||
warn!("Failed to initialise RlibDepDecoder, distributed compiles will be inefficient: {}", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're going to need to figure out a way to surface these sorts of things to the client at some point.
})) | ||
})); | ||
|
||
#[cfg(not(feature = "dist-client"))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this would be more readable if you created a do-nothing future like f_ok(None)
for the #[cfg(not(feature = "dist-client"))]
branch, and then joined into the same code from either version?
@@ -927,10 +1021,13 @@ impl<T> CompilerHasher<T> for RustHasher | |||
(o, p) | |||
}) | |||
.collect::<HashMap<_, _>>(); | |||
if let Some(dep_info) = dep_info { | |||
let dep_info = if let Some(dep_info) = dep_info { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like it'd be better written as dep_info.map(|dep_info| ...)
.
} | ||
} | ||
// OUT_DIR was changed during transformation, check if this compilation is relying on anything | ||
// inside it - if so, disallow distributed compilation (there are sometimes hardcoded paths present) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would happen for things like "my build script compiled some C code" or "my build script generated Rust code", right? Is it too hard to ship those additional files across the wire in those cases, or are there other cases I'm missing?
|
||
// TODO: we do end up with slashes facing the wrong way, but Windows is agnostic so it's | ||
// mostly ok. We currently don't get mappings for every single path because it means we need to | ||
// figure out all prefixes and send them over the wire. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is OK, sccache really only strives to handle the common cases of rustc invoked by cargo, and it seems unlikely that files will wind up on different drives there.
// have been passed on the command line, allowing us to scan them all and find the | ||
// complete list of crates we might need. | ||
// If it's not a cargo build, we can't to extract the `extern crate` statements and | ||
// so have no way to build a list of necessary crates - send all rlibs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice that you have fallback support here, but it would have been fine to just not support distributed compilation for the non-cargo case, honestly.
for (_dep_info_dist_path, dep_info_local_path) in output_paths { | ||
trace!("Comparing with {}", dep_info_local_path.display()); | ||
if dep_info == dep_info_local_path { | ||
error!("Replacing using the transformer {:?}", path_transformer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like an error
.
|
||
// TODO: unfortunately there is exactly nothing you can do with the k given the | ||
// current trait bounds. Just use some kind of sane value; | ||
//let k_size = mem::size_of::<PathBuf>() + k.capacity(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was mostly to account for the memory usage case where you would want to measure both the keys and values. If you only care about the values this is fine.
let cache = lru_cache::LruCache::with_meter(CACHE_SIZE, DepsSize); | ||
|
||
let rlib_dep_reader = RlibDepReader { cache: Mutex::new(cache), executable }; | ||
if let Err(e) = rlib_dep_reader.discover_rlib_deps(env_vars, &temp_rlib) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just a sanity check to ensure that -Z ls
works at all with the current rustc?
cmd.args(&["-Z", "ls"]).arg(&rlib) | ||
.env_clear() | ||
.envs(ref_env(env_vars)) | ||
.env("RUSTC_BOOTSTRAP", "1"); // TODO: this is fairly naughty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we expect will ever become stable? If not it's probably a necessary evil, but it's a pretty minor thing.
|
||
let process::Output { status, stdout, stderr } = cmd.output()?; | ||
|
||
if !status.success() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do have run_input_output
which handles a few of these things for you, but it returns a Future
so you'd have to refactor this a bit:
Line 142 in dda88a6
pub fn run_input_output<C>(mut command: C, input: Option<Vec<u8>>) |
assert!(line_splits.next().is_none()); | ||
|
||
let mut libstring_splits = libstring.rsplitn(2, '-'); | ||
// Rustc prints strict hash value (rather than extra filename as it likely should be) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You filed this upstream, right? If so it might be good to link the issue in this comment.
Obviously a bunch of minor comments there but nothing that felt serious enough to block merging. |
The RlibDepDecoder is the most important piece here - in short, you can't distribute dylibs cross-platform (without something like Wine), so we either need to be pessimistic (and disallow distributed compilation if there's every a dylib anywhere in the deps folder, which is almost certain after first compilation) or have some way of telling what is a dependency of a particular crate rlib (we know the direct rlib dependencies of the thing we're compiling as they're passed in explicitly, we're interested in going a step down).
-Z ls
does this.As a nice side effect, it makes other compiles more efficient too as it reduces what you send over the wire.
I did some informal measurements distributing over Wireless G from Windows 4core i3 non-SSD laptop to an 8core i7 SSD laptop: Firefox clobber build went from ~100mins to ~55mins. YMMV.
Unfortunately, while I was playing I saw an oddity with generated
.d
files - it looks like remapping prefixes is perhaps not working in some situations. The firefox build works, so I need to look at the implications.