contract_build/util/
rustc_wrapper.rs

1// Copyright (C) ink! contributors.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Utilities for generating and setting a `rustc` wrapper executable for `cargo`
18//! commands.
19//!
20//! # Motivation
21//!
22//! The custom `rustc` wrapper passes extra compiler flags to `rustc`.
23//! This is useful in cases where `cargo` won't pass compiler flags to `rustc`
24//! for some compiler invocations
25//! (e.g. `cargo` doesn't pass `rustc` flags to proc macros and build scripts
26//! when the `--target` flag is set).
27//!
28//! Ref: <https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags>
29//!
30//! Ref: <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads>
31
32use std::{
33    env,
34    fs,
35    path::Path,
36};
37
38use anyhow::{
39    Context,
40    Result,
41};
42
43use crate::{
44    CrateMetadata,
45    Verbosity,
46    util,
47    util::EnvVars,
48};
49
50/// Generates a `rustc` wrapper executable and returns its path.
51///
52/// See [`crate::rustc_wrapper`] module docs for motivation.
53pub fn generate<P: AsRef<Path>>(target_dir: P) -> Result<String> {
54    let dir = target_dir.as_ref().join("rustc_wrapper");
55    fs::create_dir_all(&dir)?;
56    tracing::debug!("Generating `rustc` wrapper executable in {}", dir.display());
57
58    // Creates `rustc` wrapper project.
59    let cargo_toml = include_str!("../../templates/rustc_wrapper/_Cargo.toml");
60    let main_rs = include_str!("../../templates/rustc_wrapper/main.rs");
61    let manifest_path = dir.join("Cargo.toml");
62    fs::write(&manifest_path, cargo_toml)?;
63    fs::write(dir.join("main.rs"), main_rs)?;
64
65    // Compiles `rustc` wrapper.
66    let args = [
67        format!("--manifest-path={}", manifest_path.display()),
68        "--release".to_string(),
69        // JSON output is easier to parse.
70        "--message-format=json".to_string(),
71    ];
72    let cmd = util::cargo_cmd("build", args, Some(&dir), Verbosity::Quiet, Vec::new());
73    let output = cmd.stdout_capture().stderr_capture().run()?;
74    if !output.status.success() {
75        let error_msg = "Failed to generate `rustc` wrapper";
76        if output.stderr.is_empty() {
77            anyhow::bail!(error_msg)
78        } else {
79            anyhow::bail!("{}: {}", error_msg, String::from_utf8_lossy(&output.stderr))
80        }
81    }
82
83    // Parses JSON output for path to executable.
84    // Ref: <https://doc.rust-lang.org/cargo/reference/external-tools.html#artifact-messages>
85    // Ref: <https://doc.rust-lang.org/rustc/json.html>
86    let stdout = String::from_utf8_lossy(&output.stdout);
87    let exec_path_str = stdout.lines().find_map(|line| {
88        if !line.contains("\"compiler-artifact\"") {
89            return None;
90        }
91        let json: serde_json::Value = serde_json::from_str(line).ok()?;
92        let reason = json.get("reason")?;
93        if reason != "compiler-artifact" {
94            return None;
95        }
96        let exec = json.get("executable")?;
97        exec.as_str().map(ToString::to_string)
98    });
99    exec_path_str.context("Failed to generate `rustc` wrapper")
100}
101
102/// Returns a list of env vars required to set a custom `rustc` wrapper
103/// and ABI `cfg` flags (if necessary).
104///
105/// # Note
106///
107/// See [`crate::rustc_wrapper`] module docs for motivation.
108///
109/// The `rustc` wrapper is set via cargo's `RUSTC_WRAPPER` env var.
110///
111/// The extra compiler flags to pass are specified via the `RUSTC_WRAPPER_ENCODED_FLAGS`
112/// env var.
113pub fn env_vars(crate_metadata: &CrateMetadata) -> Result<Option<EnvVars<'_>>> {
114    if let Some(abi) = crate_metadata.abi {
115        let rustc_wrapper = env::var("INK_RUSTC_WRAPPER")
116            .context("Failed to retrieve `rustc` wrapper from environment")
117            .or_else(|_| generate(&crate_metadata.artifact_directory))?;
118        if env::var("INK_RUSTC_WRAPPER").is_err() {
119            // SAFETY: The `rustc` wrapper is safe to reuse across all threads.
120            unsafe { env::set_var("INK_RUSTC_WRAPPER", &rustc_wrapper) };
121        }
122        return Ok(Some(vec![
123            ("RUSTC_WRAPPER", Some(rustc_wrapper)),
124            (
125                "RUSTC_WRAPPER_ENCODED_FLAGS",
126                Some(abi.cargo_encoded_rustflag()),
127            ),
128        ]))
129    }
130
131    Ok(None)
132}