ink_e2e/
contract_build.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::log_info;
16use contract_build::{
17    BuildArtifacts,
18    BuildMode,
19    ExecuteArgs,
20    Features,
21    ImageVariant,
22    ManifestPath,
23    MetadataSpec,
24    Network,
25    OutputType,
26    UnstableFlags,
27    Verbosity,
28};
29use itertools::Itertools;
30use std::{
31    collections::{
32        hash_map::Entry,
33        HashMap,
34    },
35    path::{
36        Path,
37        PathBuf,
38    },
39    sync::{
40        Mutex,
41        OnceLock,
42    },
43};
44
45/// Builds the "root" contract (the contract in which the E2E tests are defined) together
46/// with any contracts which are a dependency of the root contract.
47pub fn build_root_and_contract_dependencies() -> Vec<PathBuf> {
48    let contract_project = ContractProject::new();
49    let contract_manifests = contract_project.root_with_contract_dependencies();
50    build_contracts(&contract_manifests)
51}
52
53/// Access manifest paths of contracts which are part of the project in which the E2E
54/// tests are defined.
55struct ContractProject {
56    root_package: Option<PathBuf>,
57    contract_dependencies: Vec<PathBuf>,
58}
59
60impl ContractProject {
61    fn new() -> Self {
62        let cmd = cargo_metadata::MetadataCommand::new();
63        let metadata = cmd
64            .exec()
65            .unwrap_or_else(|err| panic!("Error invoking `cargo metadata`: {}", err));
66
67        fn maybe_contract_package(package: &cargo_metadata::Package) -> Option<PathBuf> {
68            package
69                .features
70                .iter()
71                .any(|(feat, _)| {
72                    feat == "ink-as-dependency"
73                        && !package.name.eq("ink")
74                        && !package.name.eq("ink_env")
75                })
76                .then(|| package.manifest_path.clone().into_std_path_buf())
77        }
78
79        let root_package = metadata
80            .resolve
81            .as_ref()
82            .and_then(|resolve| resolve.root.as_ref())
83            .and_then(|root_package_id| {
84                metadata
85                    .packages
86                    .iter()
87                    .find(|package| &package.id == root_package_id)
88            })
89            .and_then(maybe_contract_package);
90        log_info(&format!("found root package: {:?}", root_package));
91
92        let contract_dependencies: Vec<PathBuf> = metadata
93            .packages
94            .iter()
95            .filter_map(maybe_contract_package)
96            .collect();
97
98        log_info(&format!(
99            "found those contract dependencies: {:?}",
100            contract_dependencies
101        ));
102        Self {
103            root_package,
104            contract_dependencies,
105        }
106    }
107
108    fn root_with_additional_contracts<P>(
109        &self,
110        additional_contracts: impl IntoIterator<Item = P>,
111    ) -> Vec<PathBuf>
112    where
113        PathBuf: From<P>,
114    {
115        let mut all_manifests: Vec<_> = self.root_package.iter().cloned().collect();
116        let mut additional_contracts: Vec<_> = additional_contracts
117            .into_iter()
118            .map(PathBuf::from)
119            .collect();
120        all_manifests.append(&mut additional_contracts);
121        all_manifests.into_iter().unique().collect()
122    }
123
124    fn root_with_contract_dependencies(&self) -> Vec<PathBuf> {
125        self.root_with_additional_contracts(&self.contract_dependencies)
126    }
127}
128
129/// Build all the of the contracts of the supplied `contract_manifests`.
130///
131/// Only attempts to build a contract at the given path once only per test run, to avoid
132/// the attempt for different tests to build the same contract concurrently.
133fn build_contracts(contract_manifests: &[PathBuf]) -> Vec<PathBuf> {
134    static CONTRACT_BUILD_JOBS: OnceLock<Mutex<HashMap<PathBuf, PathBuf>>> =
135        OnceLock::new();
136    let mut contract_build_jobs = CONTRACT_BUILD_JOBS
137        .get_or_init(|| Mutex::new(HashMap::new()))
138        .lock()
139        .unwrap();
140
141    let mut blob_paths = Vec::new();
142    for manifest in contract_manifests {
143        let contract_binary_path = match contract_build_jobs.entry(manifest.clone()) {
144            Entry::Occupied(entry) => entry.get().clone(),
145            Entry::Vacant(entry) => {
146                let contract_binary_path = build_contract(manifest);
147                entry.insert(contract_binary_path.clone());
148                contract_binary_path
149            }
150        };
151        blob_paths.push(contract_binary_path);
152    }
153    blob_paths
154}
155
156/// Builds the contract at `manifest_path`, returns the path to the contract
157/// PolkaVM build artifact.
158fn build_contract(path_to_cargo_toml: &Path) -> PathBuf {
159    let manifest_path = ManifestPath::new(path_to_cargo_toml).unwrap_or_else(|err| {
160        panic!(
161            "Invalid manifest path {}: {err}",
162            path_to_cargo_toml.display()
163        )
164    });
165    let args = ExecuteArgs {
166        manifest_path,
167        verbosity: Verbosity::Default,
168        build_mode: BuildMode::Release, // todo change back to Debug
169        features: Features::default(),
170        network: Network::Online,
171        build_artifact: BuildArtifacts::All,
172        unstable_flags: UnstableFlags::default(),
173        keep_debug_symbols: false,
174        extra_lints: false,
175        output_type: OutputType::HumanReadable,
176        skip_clippy_and_linting: false,
177        image: ImageVariant::Default,
178        metadata_spec: MetadataSpec::Ink,
179    };
180
181    match contract_build::execute(args) {
182        Ok(build_result) => {
183            build_result
184                .dest_binary
185                .expect("PolkaVM code artifact not generated")
186                .canonicalize()
187                .expect("Invalid dest bundle path")
188        }
189        Err(err) => {
190            panic!(
191                "contract build for {} failed: {err}",
192                path_to_cargo_toml.display()
193            )
194        }
195    }
196}