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}