contract_build/
args.rs

1// Copyright (C) Use Ink (UK) Ltd.
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
17use std::{
18    convert::TryFrom,
19    fmt,
20    fs,
21    fs::File,
22    io::Write,
23    path::Path,
24};
25
26use anyhow::Result;
27use clap::Args;
28use polkavm_linker::TARGET_JSON_64_BIT as POLKAVM_TARGET_JSON_64_BIT;
29use rustversion::{
30    before,
31    since,
32};
33
34use crate::CrateMetadata;
35
36/// Name of the rustc/LLVM custom target spec for PolkaVM.
37const POLKAVM_TARGET_NAME: &str = "riscv64emac-unknown-none-polkavm";
38
39#[derive(Default, Clone, Copy, Debug, Args)]
40pub struct VerbosityFlags {
41    /// No output printed to stdout
42    #[clap(long)]
43    quiet: bool,
44    /// Use verbose output
45    #[clap(long)]
46    verbose: bool,
47}
48
49impl TryFrom<&VerbosityFlags> for Verbosity {
50    type Error = anyhow::Error;
51
52    fn try_from(value: &VerbosityFlags) -> Result<Self, Self::Error> {
53        match (value.quiet, value.verbose) {
54            (false, false) => Ok(Verbosity::Default),
55            (true, false) => Ok(Verbosity::Quiet),
56            (false, true) => Ok(Verbosity::Verbose),
57            (true, true) => anyhow::bail!("Cannot pass both --quiet and --verbose flags"),
58        }
59    }
60}
61
62/// Denotes if output should be printed to stdout.
63#[derive(
64    Clone, Copy, Default, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug,
65)]
66pub enum Verbosity {
67    /// Use default output
68    #[default]
69    Default,
70    /// No output printed to stdout
71    Quiet,
72    /// Use verbose output
73    Verbose,
74}
75
76impl Verbosity {
77    /// Returns `true` if output should be printed (i.e. verbose output is set).
78    pub fn is_verbose(&self) -> bool {
79        match self {
80            Verbosity::Quiet => false,
81            Verbosity::Default | Verbosity::Verbose => true,
82        }
83    }
84}
85
86/// Use network connection to build contracts and generate metadata or use cached
87/// dependencies only.
88#[derive(Eq, PartialEq, Copy, Clone, Debug, Default, serde::Serialize)]
89pub enum Network {
90    /// Use network
91    #[default]
92    Online,
93    /// Use cached dependencies.
94    Offline,
95}
96
97impl Network {
98    /// If `Network::Offline` append the `--offline` flag for cargo invocations.
99    pub fn append_to_args(&self, args: &mut Vec<String>) {
100        match self {
101            Self::Online => (),
102            Self::Offline => args.push("--offline".to_owned()),
103        }
104    }
105}
106
107/// Describes which artifacts to generate
108#[derive(
109    Copy,
110    Clone,
111    Default,
112    Eq,
113    PartialEq,
114    Debug,
115    clap::ValueEnum,
116    serde::Serialize,
117    serde::Deserialize,
118)]
119#[clap(name = "build-artifacts")]
120pub enum BuildArtifacts {
121    /// Generate the contract binary (`<name>.polkavm`), the metadata and a bundled
122    /// `<name>.contract` file.
123    #[clap(name = "all")]
124    #[default]
125    All,
126    /// Only the contract binary (`<name>.polkavm`) is created, generation of metadata
127    /// and a bundled `<name>.contract` file is skipped.
128    #[clap(name = "code-only")]
129    CodeOnly,
130    /// No artifacts produced: runs the `cargo check` command for the PolkaVM target,
131    /// only checks for compilation errors.
132    #[clap(name = "check-only")]
133    CheckOnly,
134}
135
136impl BuildArtifacts {
137    /// Returns the number of steps required to complete a build artifact.
138    /// Used as output on the cli.
139    pub fn steps(&self) -> usize {
140        match self {
141            BuildArtifacts::All => 5,
142            BuildArtifacts::CodeOnly => 4,
143            BuildArtifacts::CheckOnly => 1,
144        }
145    }
146}
147
148#[derive(Default, Copy, Clone)]
149pub struct Target;
150
151impl Target {
152    /// The target string to be passed to rustc in order to build for this target.
153    pub fn llvm_target(crate_metadata: &CrateMetadata) -> String {
154        // With Rust 1.91, the `target-pointer-width` field became an Integer
155        // instead of a String. Depending on the toolchain this crate is compiled
156        // with, we transform the value in the PolkaVM JSON into the correct format.
157        //
158        // See <https://github.com/rust-lang/rust/pull/144443> for more details.
159        #[since(1.91)]
160        fn target_spec() -> String {
161            POLKAVM_TARGET_JSON_64_BIT.to_string().replace(
162                r#"target-pointer-width": "64""#,
163                r#"target-pointer-width": 64"#,
164            )
165        }
166        #[before(1.91)]
167        fn target_spec() -> String {
168            POLKAVM_TARGET_JSON_64_BIT.to_string().replace(
169                r#"target-pointer-width": 64"#,
170                r#"target-pointer-width": "64""#,
171            )
172        }
173
174        // Instead of a target literal we use a JSON file with a more complex
175        // target configuration here. The path to the file is passed for the
176        // `rustc --target` argument. We write this file to the `target/` folder.
177        let target_dir = crate_metadata.artifact_directory.to_string_lossy();
178        let path = format!("{target_dir}/{POLKAVM_TARGET_NAME}.json");
179        if !Path::exists(Path::new(&path)) {
180            fs::create_dir_all(&crate_metadata.artifact_directory).unwrap_or_else(|e| {
181                panic!("unable to create target dir {target_dir:?}: {e:?}")
182            });
183            let mut file = File::create(&path).unwrap();
184            file.write_all(target_spec().as_bytes()).unwrap();
185        }
186        path
187    }
188
189    /// The name used for the target folder inside the `target/` folder.
190    pub fn llvm_target_alias() -> &'static str {
191        POLKAVM_TARGET_NAME
192    }
193
194    /// Target specific flags to be set to `CARGO_ENCODED_RUSTFLAGS` while building.
195    pub fn rustflags() -> Option<&'static str> {
196        // Substrate has the `cfg` `substrate_runtime` to distinguish if e.g. `sp-io`
197        // is being build for `std` or for a Wasm/RISC-V runtime.
198        Some("--cfg\x1fsubstrate_runtime")
199    }
200
201    /// The file extension that is used by rustc when outputting the binary.
202    pub fn source_extension() -> &'static str {
203        ""
204    }
205
206    // The file extension that is used to store the post processed binary.
207    pub fn dest_extension() -> &'static str {
208        "polkavm"
209    }
210}
211
212/// The mode to build the contract in.
213#[derive(
214    Eq, PartialEq, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize,
215)]
216pub enum BuildMode {
217    /// Functionality to output debug messages is build into the contract.
218    #[default]
219    Debug,
220    /// The contract is built without any debugging functionality.
221    Release,
222    /// the contract is built in release mode and in a deterministic environment.
223    Verifiable,
224}
225
226impl fmt::Display for BuildMode {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match self {
229            Self::Debug => write!(f, "debug"),
230            Self::Release => write!(f, "release"),
231            Self::Verifiable => write!(f, "verifiable"),
232        }
233    }
234}
235
236/// The type of output to display at the end of a build.
237#[derive(Clone, Debug, Default)]
238pub enum OutputType {
239    /// Output build results in a human readable format.
240    #[default]
241    HumanReadable,
242    /// Output the build results JSON formatted.
243    Json,
244}
245
246#[derive(Default, Clone, Debug, Args)]
247pub struct UnstableOptions {
248    /// Use the original manifest (Cargo.toml), do not modify for build optimizations
249    #[clap(long = "unstable-options", short = 'Z', number_of_values = 1)]
250    options: Vec<String>,
251}
252
253#[derive(Clone, Default)]
254pub struct UnstableFlags {
255    pub original_manifest: bool,
256}
257
258impl TryFrom<&UnstableOptions> for UnstableFlags {
259    type Error = anyhow::Error;
260
261    fn try_from(value: &UnstableOptions) -> Result<Self, Self::Error> {
262        let valid_flags = ["original-manifest"];
263        let invalid_flags = value
264            .options
265            .iter()
266            .filter(|o| !valid_flags.contains(&o.as_str()))
267            .collect::<Vec<_>>();
268        if !invalid_flags.is_empty() {
269            anyhow::bail!("Unknown unstable-options {invalid_flags:?}")
270        }
271        Ok(UnstableFlags {
272            original_manifest: value.options.contains(&"original-manifest".to_owned()),
273        })
274    }
275}
276
277/// Define the standard `cargo` features args to be passed through.
278#[derive(Default, Clone, Debug, Args)]
279pub struct Features {
280    /// Space or comma separated list of features to activate
281    #[clap(long, value_delimiter = ',')]
282    features: Vec<String>,
283}
284
285impl From<Vec<String>> for Features {
286    fn from(features: Vec<String>) -> Self {
287        Self { features }
288    }
289}
290
291impl Features {
292    /// Appends a feature.
293    pub fn push(&mut self, feature: String) {
294        self.features.push(feature)
295    }
296
297    /// Appends the raw features args to pass through to the `cargo` invocation.
298    pub fn append_to_args(&self, args: &mut Vec<String>) {
299        if !self.features.is_empty() {
300            args.push("--features".to_string());
301            let features = if self.features.len() == 1 {
302                self.features[0].clone()
303            } else {
304                self.features.join(",")
305            };
306            args.push(features);
307        }
308    }
309}
310
311/// Specification to use for contract metadata.
312#[derive(
313    Debug,
314    Default,
315    Clone,
316    Copy,
317    PartialEq,
318    Eq,
319    clap::ValueEnum,
320    serde::Serialize,
321    serde::Deserialize,
322)]
323#[serde(rename_all = "lowercase")]
324pub enum MetadataSpec {
325    /// ink!
326    #[clap(name = "ink")]
327    #[serde(rename = "ink!")]
328    #[default]
329    Ink,
330    /// Solidity
331    #[clap(name = "solidity")]
332    Solidity,
333}
334
335impl fmt::Display for MetadataSpec {
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        match self {
338            Self::Ink => write!(f, "ink"),
339            Self::Solidity => write!(f, "solidity"),
340        }
341    }
342}