Skip to content

Support distributed cross-compilation on Windows#271

Merged
luser merged 7 commits intomozilla:masterfrom
aidanhs:aphs-dist-sccache-cross-windows
Aug 24, 2018
Merged

Support distributed cross-compilation on Windows#271
luser merged 7 commits intomozilla:masterfrom
aidanhs:aphs-dist-sccache-cross-windows

Conversation

@aidanhs
Copy link
Contributor

@aidanhs aidanhs commented Aug 6, 2018

For review after #249

@aidanhs aidanhs force-pushed the aphs-dist-sccache-cross-windows branch from 43c36ea to 6a4e9a3 Compare August 6, 2018 17:32
}
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purposes of distributed compilation it's not sufficient to just 'pass through' any -Xclang options as suggested in #262 - it's important to filter out the preprocessor-only args, which the dependency stuff falls under.

I'm not sure what the best solution long-term is - by the time you've created a clang (or rather, gcc) ParseArguments you've lost a lot of information (e.g. "there is another input file" turns into "here's an arg") which reduces our accuracy. I think you almost want an interim LosslessParsedArgs so you can inspect it e.g. within this function to see how it affects the msvc compilation.

For now it just grabs the pieces that can be easily partitioned into common or preprocessor args without any side effects and bails otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a reasonable way to handle things for now. It might be worth refactoring things in the future so that we could use something less leaky like parse_arguments, but I won't lose any sleep over it. clang-cl is an odd duck, and dealing with -Xclang is even odder. I'd probably want to read the clang-cl source to figure things out before anyone spent any more time on this.

depfile: depfile.map(|d| d.into()),
outputs: outputs,
preprocessor_args: vec!(),
preprocessor_args: preprocessor_args,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a little unclear why all of these used to go in common args - it's pessimistic so wouldn't be a problem, and I couldn't see anything in the git history indicating why it's like this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure this out either, TBH. This is definitely better, though.

take_arg!("-Xclang", String, Separated, PassThrough),
// TODO: should be extracted and reprocessed, though bear in mind some
// flags are not valid under a -Xclang
take_arg!("-Xclang", String, Separated, TooHard),
Copy link
Contributor Author

@aidanhs aidanhs Aug 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It felt a bit dubious to me to just pass these through blindly, though there is one test below that depended on it working. This isn't necessary for this PR, but I stumbled across it and thought I might tweak it, particularly given the work that msvc.rs now does indicates there could be all kinds of things hidden in here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize this was even here, honestly. I don't think I've ever seen this used outside of clang-cl.

@aidanhs aidanhs force-pushed the aphs-dist-sccache-cross-windows branch 3 times, most recently from e0fe8db to ad4ea62 Compare August 12, 2018 14:20
@aidanhs aidanhs changed the title Support distributed cross-compilation on Windows, add some authorization Support distributed cross-compilation on Windows Aug 12, 2018
@aidanhs aidanhs force-pushed the aphs-dist-sccache-cross-windows branch from ad4ea62 to 2707dfd Compare August 16, 2018 20:06
pub archive: PathBuf,
pub archive_compiler_executable: String,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the way you specify a cross compiler toolchain - the compiler executable you want to override, an archive and a path within the archive you use to override the path of the compiler executable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, we can see how people like this in practice. I don't suppose it's worse than the current way people create cross-toolchains for icecream.

@aidanhs
Copy link
Contributor Author

aidanhs commented Aug 16, 2018

Oh, I hadn't realised this needed a rebase - odd, I was already rebasing on top of the last PR.

enum Void {}

// Only supported on x86_64 Linux machines
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to control this at the cargo level so that we don't build it on other platforms?

fn into_inputs_creator(self: Box<Self>) -> Result<Box<FnMut(&mut io::Write)>> {
fn into_dist_inputs_creator(self: Box<Self>, path_transformer: &mut dist::PathTransformer) -> Result<Box<FnMut(&mut io::Write)>> {
let input_path = self.cwd.join(&self.parsed_args.input);
let dist_input_path = path_transformer.to_dist(&input_path).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason this has an unwrap?

let input_path = self.cwd.join(&self.parsed_args.input);
let dist_input_path = path_transformer.to_dist(&input_path).unwrap();
// tar-rs imposes that `set_path` takes a relative path
assert!(dist_input_path.starts_with("/"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These shouldn't be actual assert!s, should they? Those will take down the whole server.

info!("Packaging C compiler");
// TODO: write our own, since this is GPL
let curdir = env::current_dir().unwrap();
env::set_current_dir("/tmp").unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be cleaner to set the cwd of the Command instead?

let (compile_cmd, _dist_compile_cmd, cacheable) = compilation.generate_compile_commands().unwrap();

let mut path_transformer = dist::PathTransformer::new();
let (compile_cmd, _dist_compile_cmd, cacheable) = compilation.generate_compile_commands(&mut path_transformer).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this previously, but this should propagate errors instead of using unwrap.

let compile_out_pretty = out_pretty.clone();
let compile_out_pretty2 = out_pretty.clone();
let (compile_cmd, dist_compile_cmd, cacheable) = compilation.generate_compile_commands().unwrap();
let compile_out_pretty3 = out_pretty.clone();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would be nice to figure out something smarter here, eh?

let dist_output_paths = compilation.outputs()
.map(|(_key, path)| path_transformer.to_dist_abs(&cwd.join(path)))
.collect::<Option<_>>()
.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another unwrap.

.and_then(move |(path_transformer, mut dist_compile_cmd, dist_inputs_creator, dist_output_paths)| {
debug!("[{}]: Identifying toolchain", compile_out_pretty2);
let toolchain_creator_cb = BoxFnOnce::from(move |f| toolchain_creator.write_pkg(f));
// TODO: put on a thread
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't have a fix for this TODO in a later patch, can you file a follow-up issue for it so we don't forget?


let test = b"#if defined(_MSC_VER)
let test = b"#if defined(_MSC_VER) && defined(__clang__)
msvc-clang
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use clang-cl here since that's how it's generally referred to.

flag!("-Zi", DebugInfo),
flag!("-c", DoCompilation),
take_arg!("-deps", Path, Concatenated, DepFile),
take_arg!("-o", Path, Separated, Output), // Deprecated but valid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed for something in particular?

}
}

// TODO: doing this here reorders the arguments, hopefully that doesn't affect the meaning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd probably have to look at the clang-cl source to figure this out, but hopefully it's not an issue. Maybe we can just make a note of it in the docs somewhere?


let dist_command = (|| {
// http://releases.llvm.org/6.0.0/tools/clang/docs/UsersManual.html#clang-cl
// TODO: Use /T... for language?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a useful followup. It looks like you could just replace -c with -Tc or -Tp:
https://docs.microsoft.com/en-us/cpp/build/reference/tc-tp-tc-tp-specify-source-file-type

use std::fs::File;

pub trait CompilerPackager {
fn write_pkg(self: Box<Self>, f: File) -> io::Result<()>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason this uses io::Result instead of our custom Error/Result?

use errors::*;

#[derive(Clone, Debug)]
pub struct CustomToolchain {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a little unfortunate to have two CustomToolchain structs that differ by only one member. I gather that since this one gets stored in a HashMap the other member is the key, but it's still not great.

let mut custom_toolchain_paths = HashMap::new();
for ct in config_custom_toolchains.into_iter() {
if custom_toolchain_paths.contains_key(&ct.compiler_executable) {
panic!("Multiple toolchains for {:?}", ct.compiler_executable)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem good.

Err(e) => panic!("{}", e),
// TODO: by this point the toolchain should be known to exist
pub fn get_toolchain(&self, tc: &Toolchain) -> Option<Vec<u8>> {
// TODO: be more relaxed about path casing and slashes on Windows
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File a followup for this? Maybe just running the paths through fs::canonicalize would be good enough.

match self.cache.lock().unwrap().get(tc) {
Ok(rdr) => rdr,
Err(LruError::FileNotInCache) => return None,
Err(e) => panic!("{}", e),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also not good.

rdr.read_to_end(&mut ret).unwrap();
Some(ret)
}
// TODO: It's more correct to have a FnBox or Box<FnOnce> here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you can remove this TODO now!

pub fn put_toolchain(&self, compiler_path: &Path, weak_key: &str, create: BoxFnOnce<(fs::File,), io::Result<()>>) -> Result<(Toolchain, Option<String>)> {
if let Some(tc_and_compiler_path) = self.get_custom_toolchain(compiler_path) {
debug!("Using custom toolchain for {:?}", compiler_path);
let (tc, compiler_path) = tc_and_compiler_path.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another unwrap.

Err(e) => return Some(Err(e)),
};
let tc = Toolchain { archive_id };
*maybe_tc = Some(tc.clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be better written using the entry API?

mod test;

// TODO: paths (particularly outputs, which are accessed by an unsandboxed program)
// should be some pre-sanitised AbsPath type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe as a first pass you could just have a newtype around PathBuf that implements AsRef?

impl PathTransformer {
pub fn new() -> Self { PathTransformer }
pub fn to_dist_abs(&mut self, p: &Path) -> Option<String> {
if !p.is_absolute() { panic!("non absolute path {:?}", p) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't panic.

}
}
pub fn to_dist_abs(&mut self, p: &Path) -> Option<String> {
if !p.is_absolute() { panic!("non absolute path {:?}", p) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't panic.

Component::Prefix(_) |
Component::RootDir => panic!("unexpected part in path {:?}", p),
Component::Normal(osstr) => osstr.to_str()?,
// TODO: should be forbidden
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you run into cases where these were necessary in practice? If not, it seems like it'd be better to forbid them now before anyone relies on this working.

Copy link
Contributor

@luser luser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall everything in here looks sensible. I'd like to see all of the panic / assert / unwrap usage outside of tests cleaned up, but that can be done after everything lands.

@luser luser merged commit d502791 into mozilla:master Aug 24, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants