xargo_lib/
lib.rs

1#![cfg_attr(feature = "backtrace", feature(backtrace))]
2
3use std::hash::{Hash, Hasher};
4use std::path::{Path, PathBuf};
5use std::process::ExitStatus;
6use std::{env, process};
7
8use anyhow::{anyhow, bail, Context, Result};
9use rustc_version::Channel;
10
11use self::rustc::Target;
12
13mod cargo;
14mod cli;
15mod config;
16mod extensions;
17mod flock;
18mod rustc;
19mod sysroot;
20mod util;
21mod xargo;
22
23pub use self::cli::{Args, Verbosity};
24pub use self::config::Config;
25
26// We use a different sysroot for Native compilation to avoid file locking
27//
28// Cross compilation requires `lib/rustlib/$HOST` to match `rustc`'s sysroot,
29// whereas Native compilation wants to use a custom `lib/rustlib/$HOST`. If each
30// mode has its own sysroot then we avoid sharing that directory and thus file
31// locking it.
32#[derive(Debug)]
33pub enum CompilationMode {
34    Cross(Target),
35    Native(String),
36}
37
38impl CompilationMode {
39    fn hash<H>(&self, hasher: &mut H) -> Result<()>
40    where
41        H: Hasher,
42    {
43        match *self {
44            CompilationMode::Cross(ref target) => target.hash(hasher)?,
45            CompilationMode::Native(ref triple) => triple.hash(hasher),
46        }
47
48        Ok(())
49    }
50
51    /// Returns the condensed target triple (removes any `.json` extension and path components).
52    fn triple(&self) -> &str {
53        match *self {
54            CompilationMode::Cross(ref target) => target.triple(),
55            CompilationMode::Native(ref triple) => triple,
56        }
57    }
58
59    /// Returns the original target triple passed to xargo (perhaps with `.json` extension).
60    fn orig_triple(&self) -> &str {
61        match *self {
62            CompilationMode::Cross(ref target) => target.orig_triple(),
63            CompilationMode::Native(ref triple) => triple,
64        }
65    }
66
67    fn is_native(&self) -> bool {
68        match *self {
69            CompilationMode::Native(_) => true,
70            _ => false,
71        }
72    }
73}
74
75pub fn main_common(command_name: &str) {
76    #[cfg(feature = "backtrace")]
77    fn show_backtrace() -> bool {
78        env::var("RUST_BACKTRACE").as_ref().map(|s| &s[..]) == Ok("1")
79    }
80
81    match run(command_name) {
82        Err(e) => {
83            eprintln!("error: {}", e);
84
85            for e in e.chain().into_iter().skip(1) {
86                eprintln!("caused by: {}", e);
87            }
88
89            #[cfg(feature = "backtrace")]
90            {
91                if show_backtrace() {
92                    eprintln!("{:?}", e.backtrace());
93                } else {
94                    eprintln!("note: run with `RUST_BACKTRACE=1` for a backtrace");
95                }
96            }
97
98            process::exit(1)
99        }
100        Ok(Some(status)) => {
101            if !status.success() {
102                process::exit(status.code().unwrap_or(1))
103            }
104        }
105        Ok(None) => {}
106    }
107}
108
109fn run(command_name: &str) -> Result<Option<ExitStatus>> {
110    use cli::Command;
111
112    let (command, args) = cli::args(command_name)?;
113    match command {
114        Command::Build => Ok(Some(build(args, command_name, None)?)),
115        Command::Help => {
116            print!(include_str!("help.txt"), command_name = command_name);
117            Ok(None)
118        }
119        Command::Version => {
120            println!(
121                concat!("cargo-xbuild ", env!("CARGO_PKG_VERSION"), "{}"),
122                include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
123            );
124            Ok(None)
125        }
126    }
127}
128
129/// Execute a cargo command with cross compiled sysroot crates for custom targets.
130///
131/// If `crate_config` is provided it will override the values in the `Cargo.toml`.
132/// Otherwise the config specified in the `[package.metadata.cargo-xbuild]` section will be used.
133pub fn build(args: Args, command_name: &str, crate_config: Option<Config>) -> Result<ExitStatus> {
134    let verbose = args.verbose();
135    let quiet = args.quiet();
136    let meta = rustc::version().map_err(|e| anyhow!("getting rustc version failed: {}", e))?;
137    let cd = CurrentDirectory::get()?;
138    let config = cargo::config()?;
139
140    let mut cmd = cargo_metadata::MetadataCommand::new();
141    if let Some(manifest_path) = args.manifest_path() {
142        cmd.manifest_path(manifest_path);
143    }
144
145    let metadata = cmd
146        .exec()
147        .map_err(|e| anyhow!("cargo metadata invocation failed: {}", e))?;
148    let root = Path::new(&metadata.workspace_root);
149
150    // Fall back to manifest if config not explicitly specified
151    let crate_config = crate_config.map(Ok).unwrap_or_else(|| {
152        Config::from_metadata(&metadata, args.quiet()).map_err(|e| {
153            anyhow!(
154                "reading package.metadata.cargo-xbuild section failed: {}",
155                e
156            )
157        })
158    })?;
159
160    // We can't build sysroot with stable or beta due to unstable features
161    let sysroot = rustc::sysroot(verbose)?;
162    let src = match meta.channel {
163        Channel::Dev => rustc::Src::from_env().ok_or(anyhow!(
164            "The XARGO_RUST_SRC env variable must be set and point to the \
165             Rust source directory when working with the 'dev' channel",
166        ))?,
167        Channel::Nightly => {
168            if let Some(src) = rustc::Src::from_env() {
169                src
170            } else {
171                sysroot.src()?
172            }
173        }
174        Channel::Stable | Channel::Beta => {
175            bail!(
176                "The sysroot can't be built for the {:?} channel. \
177                 Switch to nightly.",
178                meta.channel
179            );
180        }
181    };
182
183    let cmode = if let Some(triple) = args.target() {
184        if triple == meta.host {
185            Some(CompilationMode::Native(meta.host.clone()))
186        } else {
187            Target::new(triple, &cd, verbose)?.map(CompilationMode::Cross)
188        }
189    } else {
190        if let Some(ref config) = config {
191            if let Some(triple) = config.target()? {
192                Target::new(&triple, &cd, verbose)?.map(CompilationMode::Cross)
193            } else {
194                Some(CompilationMode::Native(meta.host.clone()))
195            }
196        } else {
197            Some(CompilationMode::Native(meta.host.clone()))
198        }
199    };
200
201    if let Some(CompilationMode::Native(_)) = cmode {
202        eprintln!(
203            "WARNING: You're currently building for the host system. This is likely an \
204            error and will cause build scripts of dependencies to break.\n\n\
205
206            To build for the target system either pass a `--target` argument or \
207            set the build.target configuration key in a `.cargo/config` file.\n",
208        );
209    }
210
211    if let Some(cmode) = cmode {
212        let home = xargo::home(root, &crate_config, quiet)?;
213        let rustflags = cargo::rustflags(config.as_ref(), cmode.triple())?;
214
215        sysroot::update(
216            &cmode,
217            &home,
218            &root,
219            &crate_config,
220            &rustflags,
221            &meta,
222            &src,
223            &sysroot,
224            verbose,
225        )?;
226        return xargo::run(
227            &args,
228            &cmode,
229            rustflags,
230            &home,
231            &meta,
232            command_name,
233            verbose,
234        );
235    }
236
237    cargo::run(&args, verbose)
238}
239
240pub struct CurrentDirectory {
241    path: PathBuf,
242}
243
244impl CurrentDirectory {
245    fn get() -> Result<CurrentDirectory> {
246        env::current_dir()
247            .with_context(|| "couldn't get the current directory")
248            .map(|cd| CurrentDirectory { path: cd })
249    }
250
251    fn path(&self) -> &Path {
252        &self.path
253    }
254}