gear_wasm_optimizer/
cargo_command.rs

1// This file is part of Gear.
2
3// Copyright (C) 2022-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::cargo_toolchain::Toolchain;
20use anyhow::{anyhow, ensure, Context, Result};
21use std::{env, path::PathBuf, process::Command};
22
23/// Helper to deal with the `cargo` command.
24#[derive(Clone)]
25pub struct CargoCommand {
26    path: String,
27    manifest_path: PathBuf,
28    profile: String,
29    rustc_flags: Vec<&'static str>,
30    target_dir: PathBuf,
31    features: Vec<String>,
32    toolchain: Toolchain,
33    check_recommended_toolchain: bool,
34    force_recommended_toolchain: bool,
35}
36
37impl CargoCommand {
38    /// Initialize new cargo command.
39    pub fn new() -> CargoCommand {
40        let toolchain = Toolchain::try_from_rustup().expect("Failed to get toolchain from rustup");
41
42        CargoCommand {
43            path: "rustup".to_string(),
44            manifest_path: "Cargo.toml".into(),
45            profile: "dev".to_string(),
46            // TODO: enable `-C linker-plugin-lto` (https://github.com/rust-lang/rust/issues/130604)
47            rustc_flags: vec!["-C", "link-arg=--import-memory"],
48            target_dir: "target".into(),
49            features: vec![],
50            toolchain,
51            check_recommended_toolchain: false,
52            force_recommended_toolchain: false,
53        }
54    }
55}
56
57impl Default for CargoCommand {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl CargoCommand {
64    /// Set path to the `Cargo.toml` file.
65    pub fn set_manifest_path(&mut self, path: PathBuf) {
66        self.manifest_path = path;
67    }
68
69    /// Set path to the `target` directory.
70    pub fn set_target_dir(&mut self, path: PathBuf) {
71        self.target_dir = path;
72    }
73
74    /// Set profile.
75    ///
76    /// Possible values: `dev`, `release`.
77    pub fn set_profile(&mut self, profile: String) {
78        self.profile = profile;
79    }
80
81    /// Set features.
82    pub fn set_features(&mut self, features: &[String]) {
83        self.features = features.into();
84    }
85
86    /// Sets whether to check the version of the recommended toolchain.
87    pub fn set_check_recommended_toolchain(&mut self, check_recommended_toolchain: bool) {
88        self.check_recommended_toolchain = check_recommended_toolchain;
89    }
90
91    /// Sets whether to force the version of the recommended toolchain.
92    pub fn set_force_recommended_toolchain(&mut self, force_recommended_toolchain: bool) {
93        self.force_recommended_toolchain = force_recommended_toolchain;
94    }
95
96    /// Execute the `cargo` command with invoking supplied arguments.
97    pub fn run(&self) -> Result<()> {
98        if self.check_recommended_toolchain {
99            self.toolchain.check_recommended_toolchain()?;
100        }
101
102        let toolchain = if self.force_recommended_toolchain {
103            Toolchain::recommended_nightly()
104        } else {
105            self.toolchain.clone()
106        };
107
108        let mut cargo = Command::new(&self.path);
109        if self.force_recommended_toolchain {
110            self.clean_up_environment(&mut cargo);
111        }
112        cargo
113            .arg("run")
114            .arg(toolchain.raw_toolchain_str().as_ref())
115            .arg("cargo")
116            .arg("rustc")
117            .arg("--target=wasm32v1-none")
118            .arg("--color=always")
119            .arg(format!("--manifest-path={}", self.manifest_path.display()))
120            .arg("--profile")
121            .arg(&self.profile);
122
123        if !self.features.is_empty() {
124            cargo.arg("--features");
125            cargo.arg(self.features.join(","));
126        }
127
128        cargo
129            .arg("--")
130            .args(&self.rustc_flags)
131            .env("CARGO_TARGET_DIR", &self.target_dir)
132            .env("__GEAR_WASM_BUILDER_NO_BUILD", "1"); // Don't build the original crate recursively
133
134        self.remove_cargo_encoded_rustflags(&mut cargo);
135
136        cargo.env("CARGO_ENCODED_RUSTFLAGS", "-Ctarget-feature=+sign-ext");
137
138        let status = cargo.status().context("unable to execute cargo command")?;
139        ensure!(
140            status.success(),
141            anyhow!("cargo command run failed: {status}")
142        );
143
144        Ok(())
145    }
146
147    fn clean_up_environment(&self, command: &mut Command) {
148        // Inherited build script environment variables must be removed
149        // so that they cannot change the behavior of the cargo package manager.
150
151        // https://doc.rust-lang.org/cargo/reference/environment-variables.html
152        // `RUSTC_WRAPPER` and `RUSTC_WORKSPACE_WRAPPER` are not removed due to tools like sccache.
153        const INHERITED_ENV_VARS: &[&str] = &[
154            "CARGO",
155            "CARGO_MANIFEST_DIR",
156            "CARGO_MANIFEST_LINKS",
157            "CARGO_MAKEFLAGS",
158            "OUT_DIR",
159            "TARGET",
160            "HOST",
161            "NUM_JOBS",
162            "OPT_LEVEL",
163            "PROFILE",
164            "RUSTC",
165            "RUSTDOC",
166            "RUSTC_LINKER",
167            "CARGO_ENCODED_RUSTFLAGS",
168        ];
169
170        for env_var in INHERITED_ENV_VARS {
171            command.env_remove(env_var);
172        }
173
174        const INHERITED_ENV_VARS_WITH_PREFIX: &[&str] =
175            &["CARGO_FEATURE_", "CARGO_CFG_", "DEP_", "CARGO_PKG_"];
176
177        for (env_var, _) in env::vars() {
178            for prefix in INHERITED_ENV_VARS_WITH_PREFIX {
179                if env_var.starts_with(prefix) {
180                    command.env_remove(&env_var);
181                }
182            }
183        }
184    }
185
186    fn remove_cargo_encoded_rustflags(&self, command: &mut Command) {
187        // substrate's wasm-builder removes these vars so do we
188        // check its source for details
189        command.env_remove("CARGO_ENCODED_RUSTFLAGS");
190        command.env_remove("RUSTFLAGS");
191    }
192}