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#[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 fn triple(&self) -> &str {
53 match *self {
54 CompilationMode::Cross(ref target) => target.triple(),
55 CompilationMode::Native(ref triple) => triple,
56 }
57 }
58
59 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
129pub 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 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 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}