1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
use std::ffi::{OsStr, OsString};
use std::path::Path;

use crate::command::Command;
use crate::env::env_var;
use crate::path_helpers::cwd;
use crate::util::set_host_rpath;
use crate::{is_darwin, is_msvc, is_windows, uname};

/// Construct a new `rustc` invocation. This will automatically set the library
/// search path as `-L cwd()`. Use [`bare_rustc`] to avoid this.
#[track_caller]
pub fn rustc() -> Rustc {
    Rustc::new()
}

/// Construct a plain `rustc` invocation with no flags set. Note that [`set_host_rpath`]
/// still presets the environment variable `HOST_RPATH_DIR` by default.
#[track_caller]
pub fn bare_rustc() -> Rustc {
    Rustc::bare()
}

/// Construct a new `rustc` aux-build invocation.
#[track_caller]
pub fn aux_build() -> Rustc {
    Rustc::new_aux_build()
}

/// A `rustc` invocation builder.
#[derive(Debug)]
#[must_use]
pub struct Rustc {
    cmd: Command,
}

crate::macros::impl_common_helpers!(Rustc);

#[track_caller]
fn setup_common() -> Command {
    let rustc = env_var("RUSTC");
    let mut cmd = Command::new(rustc);
    set_host_rpath(&mut cmd);
    cmd
}

impl Rustc {
    // `rustc` invocation constructor methods

    /// Construct a new `rustc` invocation. This will automatically set the library
    /// search path as `-L cwd()`. Use [`bare_rustc`] to avoid this.
    #[track_caller]
    pub fn new() -> Self {
        let mut cmd = setup_common();
        cmd.arg("-L").arg(cwd());
        Self { cmd }
    }

    /// Construct a bare `rustc` invocation with no flags set.
    #[track_caller]
    pub fn bare() -> Self {
        let cmd = setup_common();
        Self { cmd }
    }

    /// Construct a new `rustc` invocation with `aux_build` preset (setting `--crate-type=lib`).
    #[track_caller]
    pub fn new_aux_build() -> Self {
        let mut cmd = setup_common();
        cmd.arg("--crate-type=lib");
        Self { cmd }
    }

    // Argument provider methods

    /// Configure the compilation environment.
    pub fn cfg(&mut self, s: &str) -> &mut Self {
        self.cmd.arg("--cfg");
        self.cmd.arg(s);
        self
    }

    /// Specify default optimization level `-O` (alias for `-C opt-level=2`).
    pub fn opt(&mut self) -> &mut Self {
        self.cmd.arg("-O");
        self
    }

    /// Specify a specific optimization level.
    pub fn opt_level(&mut self, option: &str) -> &mut Self {
        self.cmd.arg(format!("-Copt-level={option}"));
        self
    }

    /// Incorporate a hashed string to mangled symbols.
    pub fn metadata(&mut self, meta: &str) -> &mut Self {
        self.cmd.arg(format!("-Cmetadata={meta}"));
        self
    }

    /// Add a suffix in each output filename.
    pub fn extra_filename(&mut self, suffix: &str) -> &mut Self {
        self.cmd.arg(format!("-Cextra-filename={suffix}"));
        self
    }

    /// Specify type(s) of output files to generate.
    pub fn emit<S: AsRef<str>>(&mut self, kinds: S) -> &mut Self {
        let kinds = kinds.as_ref();
        self.cmd.arg(format!("--emit={kinds}"));
        self
    }

    /// Specify where an external library is located.
    pub fn extern_<P: AsRef<Path>>(&mut self, crate_name: &str, path: P) -> &mut Self {
        assert!(
            !crate_name.contains(|c: char| c.is_whitespace() || c == '\\' || c == '/'),
            "crate name cannot contain whitespace or path separators"
        );

        let path = path.as_ref().to_string_lossy();

        self.cmd.arg("--extern");
        self.cmd.arg(format!("{crate_name}={path}"));

        self
    }

    /// Remap source path prefixes in all output.
    pub fn remap_path_prefix<P: AsRef<Path>, P2: AsRef<Path>>(
        &mut self,
        from: P,
        to: P2,
    ) -> &mut Self {
        let from = from.as_ref().to_string_lossy();
        let to = to.as_ref().to_string_lossy();

        self.cmd.arg("--remap-path-prefix");
        self.cmd.arg(format!("{from}={to}"));

        self
    }

    /// Specify path to the input file.
    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        self.cmd.arg(path.as_ref());
        self
    }

    //Adjust the backtrace level, displaying more detailed information at higher levels.
    pub fn set_backtrace_level<R: AsRef<OsStr>>(&mut self, level: R) -> &mut Self {
        self.cmd.env("RUST_BACKTRACE", level);
        self
    }

    /// Specify path to the output file. Equivalent to `-o`` in rustc.
    pub fn output<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        self.cmd.arg("-o");
        self.cmd.arg(path.as_ref());
        self
    }

    /// Specify path to the output directory. Equivalent to `--out-dir`` in rustc.
    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        self.cmd.arg("--out-dir");
        self.cmd.arg(path.as_ref());
        self
    }

    /// This flag defers LTO optimizations to the linker.
    pub fn linker_plugin_lto(&mut self, option: &str) -> &mut Self {
        self.cmd.arg(format!("-Clinker-plugin-lto={option}"));
        self
    }

    /// Specify what happens when the code panics.
    pub fn panic(&mut self, option: &str) -> &mut Self {
        self.cmd.arg(format!("-Cpanic={option}"));
        self
    }

    /// Specify number of codegen units
    pub fn codegen_units(&mut self, units: usize) -> &mut Self {
        self.cmd.arg(format!("-Ccodegen-units={units}"));
        self
    }

    /// Specify directory path used for incremental cache
    pub fn incremental<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        let mut arg = OsString::new();
        arg.push("-Cincremental=");
        arg.push(path.as_ref());
        self.cmd.arg(&arg);
        self
    }

    /// Specify directory path used for profile generation
    pub fn profile_generate<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        let mut arg = OsString::new();
        arg.push("-Cprofile-generate=");
        arg.push(path.as_ref());
        self.cmd.arg(&arg);
        self
    }

    /// Specify directory path used for profile usage
    pub fn profile_use<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        let mut arg = OsString::new();
        arg.push("-Cprofile-use=");
        arg.push(path.as_ref());
        self.cmd.arg(&arg);
        self
    }

    /// Specify error format to use
    pub fn error_format(&mut self, format: &str) -> &mut Self {
        self.cmd.arg(format!("--error-format={format}"));
        self
    }

    /// Specify json messages printed by the compiler
    pub fn json(&mut self, items: &str) -> &mut Self {
        self.cmd.arg(format!("--json={items}"));
        self
    }

    /// Specify the target triple, or a path to a custom target json spec file.
    pub fn target<S: AsRef<str>>(&mut self, target: S) -> &mut Self {
        let target = target.as_ref();
        self.cmd.arg(format!("--target={target}"));
        self
    }

    /// Specify the crate type.
    pub fn crate_type(&mut self, crate_type: &str) -> &mut Self {
        self.cmd.arg("--crate-type");
        self.cmd.arg(crate_type);
        self
    }

    /// Add a directory to the library search path. Equivalent to `-L` in rustc.
    pub fn library_search_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        self.cmd.arg("-L");
        self.cmd.arg(path.as_ref());
        self
    }

    /// Add a directory to the library search path with a restriction, where `kind` is a dependency
    /// type. Equivalent to `-L KIND=PATH` in rustc.
    pub fn specific_library_search_path<P: AsRef<Path>>(
        &mut self,
        kind: &str,
        path: P,
    ) -> &mut Self {
        assert!(["dependency", "native", "all", "framework", "crate"].contains(&kind));
        let path = path.as_ref().to_string_lossy();
        self.cmd.arg(format!("-L{kind}={path}"));
        self
    }

    /// Override the system root. Equivalent to `--sysroot` in rustc.
    pub fn sysroot<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
        self.cmd.arg("--sysroot");
        self.cmd.arg(path.as_ref());
        self
    }

    /// Specify the edition year.
    pub fn edition(&mut self, edition: &str) -> &mut Self {
        self.cmd.arg("--edition");
        self.cmd.arg(edition);
        self
    }

    /// Specify the print request.
    pub fn print(&mut self, request: &str) -> &mut Self {
        self.cmd.arg("--print");
        self.cmd.arg(request);
        self
    }

    /// Add an extra argument to the linker invocation, via `-Clink-arg`.
    pub fn link_arg(&mut self, link_arg: &str) -> &mut Self {
        self.cmd.arg(format!("-Clink-arg={link_arg}"));
        self
    }

    /// Add multiple extra arguments to the linker invocation, via `-Clink-args`.
    pub fn link_args(&mut self, link_args: &str) -> &mut Self {
        self.cmd.arg(format!("-Clink-args={link_args}"));
        self
    }

    /// Specify a stdin input
    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
        self.cmd.stdin(input);
        self
    }

    /// Specify the crate name.
    pub fn crate_name<S: AsRef<OsStr>>(&mut self, name: S) -> &mut Self {
        self.cmd.arg("--crate-name");
        self.cmd.arg(name.as_ref());
        self
    }

    /// Specify the linker
    pub fn linker(&mut self, linker: &str) -> &mut Self {
        self.cmd.arg(format!("-Clinker={linker}"));
        self
    }

    /// Specify the linker flavor
    pub fn linker_flavor(&mut self, linker_flavor: &str) -> &mut Self {
        self.cmd.arg(format!("-Clinker-flavor={linker_flavor}"));
        self
    }

    /// `EXTRARSCXXFLAGS`
    pub fn extra_rs_cxx_flags(&mut self) -> &mut Self {
        // Adapted from tools.mk (trimmed):
        //
        // ```makefile
        // ifdef IS_WINDOWS
        //     ifdef IS_MSVC
        //     else
        //         EXTRARSCXXFLAGS := -lstatic:-bundle=stdc++
        //     endif
        // else
        //     ifeq ($(UNAME),Darwin)
        //         EXTRARSCXXFLAGS := -lc++
        //     else
        //         ifeq ($(UNAME),FreeBSD)
        //         else
        //             ifeq ($(UNAME),SunOS)
        //             else
        //                 ifeq ($(UNAME),OpenBSD)
        //                 else
        //                     EXTRARSCXXFLAGS := -lstdc++
        //                 endif
        //             endif
        //         endif
        //     endif
        // endif
        // ```
        let flag = if is_windows() {
            // So this is a bit hacky: we can't use the DLL version of libstdc++ because
            // it pulls in the DLL version of libgcc, which means that we end up with 2
            // instances of the DW2 unwinding implementation. This is a problem on
            // i686-pc-windows-gnu because each module (DLL/EXE) needs to register its
            // unwind information with the unwinding implementation, and libstdc++'s
            // __cxa_throw won't see the unwinding info we registered with our statically
            // linked libgcc.
            //
            // Now, simply statically linking libstdc++ would fix this problem, except
            // that it is compiled with the expectation that pthreads is dynamically
            // linked as a DLL and will fail to link with a statically linked libpthread.
            //
            // So we end up with the following hack: we link use static:-bundle to only
            // link the parts of libstdc++ that we actually use, which doesn't include
            // the dependency on the pthreads DLL.
            if is_msvc() { None } else { Some("-lstatic:-bundle=stdc++") }
        } else if is_darwin() {
            Some("-lc++")
        } else {
            match &uname()[..] {
                "FreeBSD" | "SunOS" | "OpenBSD" => None,
                _ => Some("-lstdc++"),
            }
        };
        if let Some(flag) = flag {
            self.cmd.arg(flag);
        }
        self
    }
}