iota_move_build/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5extern crate move_ir_types;
6
7use std::{
8    collections::{BTreeMap, BTreeSet, HashSet},
9    io::Write,
10    path::Path,
11    str::FromStr,
12};
13
14use anyhow::bail;
15use fastcrypto::encoding::Base64;
16use iota_package_management::{
17    PublishedAtError, resolve_published_id,
18    system_package_versions::{SYSTEM_GIT_REPO, SystemPackagesVersion},
19};
20use iota_types::{
21    IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS, MOVE_STDLIB_ADDRESS, STARDUST_ADDRESS,
22    base_types::ObjectID,
23    error::{IotaError, IotaResult},
24    is_system_package,
25    move_package::{FnInfo, FnInfoKey, FnInfoMap, MovePackage},
26};
27use iota_verifier::verifier as iota_bytecode_verifier;
28use move_binary_format::{
29    CompiledModule,
30    normalized::{self, Type},
31};
32use move_bytecode_utils::{Modules, layout::SerdeLayoutBuilder, module_cache::GetModule};
33use move_compiler::{
34    compiled_unit::AnnotatedCompiledModule,
35    diagnostics::{Diagnostics, report_diagnostics_to_buffer, report_warnings},
36    editions::Edition,
37    linters::LINT_WARNING_PREFIX,
38    shared::files::MappedFiles,
39};
40use move_core_types::{
41    account_address::AccountAddress,
42    language_storage::{ModuleId, StructTag, TypeTag},
43};
44use move_package::{
45    BuildConfig as MoveBuildConfig, LintFlag,
46    compilation::{
47        build_plan::BuildPlan, compiled_package::CompiledPackage as MoveCompiledPackage,
48    },
49    package_hooks::{PackageHooks, PackageIdentifier},
50    resolution::{dependency_graph::DependencyGraph, resolution_graph::ResolvedGraph},
51    source_package::parsed_manifest::{
52        Dependencies, Dependency, DependencyKind, GitInfo, InternalDependency, OnChainInfo,
53        PackageName, SourceManifest,
54    },
55};
56use move_symbol_pool::Symbol;
57use serde_reflection::Registry;
58
59#[cfg(test)]
60#[path = "unit_tests/build_tests.rs"]
61mod build_tests;
62
63pub mod test_utils {
64    use std::path::PathBuf;
65
66    use crate::{BuildConfig, CompiledPackage, IotaPackageHooks};
67
68    pub fn compile_basics_package() -> CompiledPackage {
69        compile_example_package("../../examples/move/basics")
70    }
71
72    pub fn compile_managed_coin_package() -> CompiledPackage {
73        compile_example_package("../../crates/iota-core/src/unit_tests/data/managed_coin")
74    }
75
76    pub fn compile_example_package(relative_path: &str) -> CompiledPackage {
77        move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
78        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
79        path.push(relative_path);
80
81        BuildConfig::new_for_testing().build(&path).unwrap()
82    }
83}
84
85/// Wrapper around the core Move `CompiledPackage` with some IOTA-specific
86/// traits and info
87#[derive(Debug, Clone)]
88pub struct CompiledPackage {
89    pub package: MoveCompiledPackage,
90    /// Address the package is recorded as being published at.
91    pub published_at: Result<ObjectID, PublishedAtError>,
92    /// The dependency IDs of this package
93    pub dependency_ids: PackageDependencies,
94    /// The bytecode modules that this package depends on (both directly and
95    /// transitively), i.e. on-chain dependencies.
96    pub bytecode_deps: Vec<(PackageName, CompiledModule)>,
97    /// Transitive dependency graph of a Move package
98    pub dependency_graph: DependencyGraph,
99}
100
101/// Wrapper around the core Move `BuildConfig` with some IOTA-specific info
102#[derive(Clone)]
103pub struct BuildConfig {
104    pub config: MoveBuildConfig,
105    /// If true, run the Move bytecode verifier on the bytecode from a
106    /// successful build
107    pub run_bytecode_verifier: bool,
108    /// If true, print build diagnostics to stderr--no printing if false
109    pub print_diags_to_stderr: bool,
110    /// The chain ID that compilation is with respect to (e.g., required to
111    /// resolve published dependency IDs from the `Move.lock`).
112    pub chain_id: Option<String>,
113}
114
115impl BuildConfig {
116    pub fn new_for_testing() -> Self {
117        move_package::package_hooks::register_package_hooks(Box::new(IotaPackageHooks));
118
119        let install_dir = tempfile::tempdir().unwrap().keep();
120        let config = MoveBuildConfig {
121            default_flavor: Some(move_compiler::editions::Flavor::Iota),
122            lock_file: Some(install_dir.join("Move.lock")),
123            install_dir: Some(install_dir),
124            lint_flag: LintFlag::LEVEL_NONE,
125            // TODO: in the future, we may want to provide local implicit dependencies to tests
126            implicit_dependencies: Dependencies::new(),
127            silence_warnings: true,
128            ..MoveBuildConfig::default()
129        };
130        BuildConfig {
131            config,
132            run_bytecode_verifier: true,
133            print_diags_to_stderr: false,
134            chain_id: None,
135        }
136    }
137
138    pub fn new_for_testing_replace_addresses<I, S>(dep_original_addresses: I) -> Self
139    where
140        I: IntoIterator<Item = (S, ObjectID)>,
141        S: Into<String>,
142    {
143        let mut build_config = Self::new_for_testing();
144        for (addr_name, obj_id) in dep_original_addresses {
145            build_config
146                .config
147                .additional_named_addresses
148                .insert(addr_name.into(), AccountAddress::from(obj_id));
149        }
150        build_config
151    }
152
153    fn fn_info(units: &[AnnotatedCompiledModule]) -> FnInfoMap {
154        let mut fn_info_map = BTreeMap::new();
155        for u in units {
156            let mod_addr = u.named_module.address.into_inner();
157            let mod_is_test = u.attributes.is_test_or_test_only();
158            for (_, s, info) in &u.function_infos {
159                let fn_name = s.as_str().to_string();
160                let is_test = mod_is_test || info.attributes.is_test_or_test_only();
161                fn_info_map.insert(FnInfoKey { fn_name, mod_addr }, FnInfo { is_test });
162            }
163        }
164
165        fn_info_map
166    }
167
168    fn compile_package<W: Write>(
169        resolution_graph: &ResolvedGraph,
170        writer: &mut W,
171    ) -> anyhow::Result<(MoveCompiledPackage, FnInfoMap)> {
172        let build_plan = BuildPlan::create(resolution_graph)?;
173        let mut fn_info = None;
174        let compiled_pkg = build_plan.compile_with_driver(writer, |compiler| {
175            let (files, units_res) = compiler.build()?;
176            match units_res {
177                Ok((units, warning_diags)) => {
178                    decorate_warnings(warning_diags, Some(&files));
179                    fn_info = Some(Self::fn_info(&units));
180                    Ok((files, units))
181                }
182                Err(error_diags) => {
183                    // with errors present don't even try decorating warnings output to avoid
184                    // clutter
185                    assert!(!error_diags.is_empty());
186                    let diags_buf =
187                        report_diagnostics_to_buffer(&files, error_diags, /* color */ true);
188                    if let Err(err) = std::io::stderr().write_all(&diags_buf) {
189                        anyhow::bail!("Cannot output compiler diagnostics: {}", err);
190                    }
191                    anyhow::bail!("Compilation error");
192                }
193            }
194        })?;
195        Ok((compiled_pkg, fn_info.unwrap()))
196    }
197
198    /// Given a `path` and a `build_config`, build the package in that path,
199    /// including its dependencies. If we are building the IOTA framework,
200    /// we skip the check that the addresses should be 0
201    pub fn build(self, path: &Path) -> IotaResult<CompiledPackage> {
202        let print_diags_to_stderr = self.print_diags_to_stderr;
203        let run_bytecode_verifier = self.run_bytecode_verifier;
204        let chain_id = self.chain_id.clone();
205        let resolution_graph = self.resolution_graph(path, chain_id.clone())?;
206        build_from_resolution_graph(
207            resolution_graph,
208            run_bytecode_verifier,
209            print_diags_to_stderr,
210            chain_id,
211        )
212    }
213
214    pub fn resolution_graph(
215        mut self,
216        path: &Path,
217        chain_id: Option<String>,
218    ) -> IotaResult<ResolvedGraph> {
219        if let Some(err_msg) = set_iota_flavor(&mut self.config) {
220            return Err(IotaError::ModuleBuildFailure { error: err_msg });
221        }
222
223        if self.print_diags_to_stderr {
224            self.config
225                .resolution_graph_for_package(path, chain_id, &mut std::io::stderr())
226        } else {
227            self.config
228                .resolution_graph_for_package(path, chain_id, &mut std::io::sink())
229        }
230        .map_err(|err| IotaError::ModuleBuildFailure {
231            error: format!("{err:?}"),
232        })
233    }
234}
235
236/// There may be additional information that needs to be displayed after
237/// diagnostics are reported (optionally report diagnostics themselves if files
238/// argument is provided).
239pub fn decorate_warnings(warning_diags: Diagnostics, files: Option<&MappedFiles>) {
240    let any_linter_warnings = warning_diags.any_with_prefix(LINT_WARNING_PREFIX);
241    let (filtered_diags_num, unique) =
242        warning_diags.filtered_source_diags_with_prefix(LINT_WARNING_PREFIX);
243    if let Some(f) = files {
244        report_warnings(f, warning_diags);
245    }
246    if any_linter_warnings {
247        eprintln!(
248            "Please report feedback on the linter warnings at https://github.com/iotaledger/iota/issues\n"
249        );
250    }
251    if filtered_diags_num > 0 {
252        eprintln!(
253            "Total number of linter warnings suppressed: {filtered_diags_num} (unique lints: {unique})"
254        );
255    }
256}
257
258/// Sets build config's default flavor to `Flavor::Iota`. Returns error message
259/// if the flavor was previously set to something else than `Flavor::Iota`.
260pub fn set_iota_flavor(build_config: &mut MoveBuildConfig) -> Option<String> {
261    use move_compiler::editions::Flavor;
262
263    let flavor = build_config.default_flavor.get_or_insert(Flavor::Iota);
264    if flavor != &Flavor::Iota {
265        return Some(format!(
266            "The flavor of the Move compiler cannot be overridden with anything but \
267                 \"{}\", but the default override was set to: \"{flavor}\"",
268            Flavor::Iota,
269        ));
270    }
271    None
272}
273
274pub fn build_from_resolution_graph(
275    resolution_graph: ResolvedGraph,
276    run_bytecode_verifier: bool,
277    print_diags_to_stderr: bool,
278    chain_id: Option<String>,
279) -> IotaResult<CompiledPackage> {
280    let (published_at, dependency_ids) = gather_published_ids(&resolution_graph, chain_id);
281
282    // collect bytecode dependencies as these are not returned as part of core
283    // `CompiledPackage`
284    let bytecode_deps = collect_bytecode_deps(&resolution_graph)?;
285
286    // compile!
287    let result = if print_diags_to_stderr {
288        BuildConfig::compile_package(&resolution_graph, &mut std::io::stderr())
289    } else {
290        BuildConfig::compile_package(&resolution_graph, &mut std::io::sink())
291    };
292
293    let (package, fn_info) = result.map_err(|error| IotaError::ModuleBuildFailure {
294        // Use [Debug] formatting to capture [anyhow] error context
295        error: format!("{error:?}"),
296    })?;
297
298    if run_bytecode_verifier {
299        verify_bytecode(&package, &fn_info)?;
300    }
301
302    Ok(CompiledPackage {
303        package,
304        published_at,
305        dependency_ids,
306        bytecode_deps,
307        dependency_graph: resolution_graph.graph,
308    })
309}
310
311/// Returns the deps from `resolution_graph` that have no source code
312fn collect_bytecode_deps(
313    resolution_graph: &ResolvedGraph,
314) -> IotaResult<Vec<(Symbol, CompiledModule)>> {
315    let mut bytecode_deps = vec![];
316    for (name, pkg) in resolution_graph.package_table.iter() {
317        if !pkg
318            .get_sources(&resolution_graph.build_options)
319            .unwrap()
320            .is_empty()
321        {
322            continue;
323        }
324        let modules =
325            pkg.get_bytecodes_bytes()
326                .map_err(|error| IotaError::ModuleDeserializationFailure {
327                    error: format!(
328                        "Deserializing bytecode dependency for package {name}: {error:?}"
329                    ),
330                })?;
331        for module in modules {
332            let module =
333                CompiledModule::deserialize_with_defaults(module.as_ref()).map_err(|error| {
334                    IotaError::ModuleDeserializationFailure {
335                        error: format!(
336                            "Deserializing bytecode dependency for package {name}: {error:?}"
337                        ),
338                    }
339                })?;
340            bytecode_deps.push((*name, module));
341        }
342    }
343
344    Ok(bytecode_deps)
345}
346
347/// Check that the compiled modules in `package` are valid
348fn verify_bytecode(package: &MoveCompiledPackage, fn_info: &FnInfoMap) -> IotaResult<()> {
349    let compiled_modules = package.root_modules_map();
350    for m in compiled_modules.iter_modules() {
351        move_bytecode_verifier::verify_module_unmetered(m).map_err(|err| {
352            IotaError::ModuleVerificationFailure {
353                error: err.to_string(),
354            }
355        })?;
356        iota_bytecode_verifier::iota_verify_module_unmetered(m, fn_info)?;
357    }
358    // Don't change the link components to iota. It is correct as it is.
359    // TODO(https://github.com/MystenLabs/sui/issues/69): Run Move linker
360    Ok(())
361}
362
363impl CompiledPackage {
364    /// Return all of the bytecode modules in this package (not including direct
365    /// or transitive deps) Note: these are not topologically sorted by
366    /// dependency--use `get_dependency_sorted_modules` to produce a list of
367    /// modules suitable for publishing or static analysis
368    pub fn get_modules(&self) -> impl Iterator<Item = &CompiledModule> {
369        self.package.root_modules().map(|m| &m.unit.module)
370    }
371
372    /// Return all of the bytecode modules in this package (not including direct
373    /// or transitive deps) Note: these are not topologically sorted by
374    /// dependency--use `get_dependency_sorted_modules` to produce a list of
375    /// modules suitable for publishing or static analysis
376    pub fn into_modules(self) -> Vec<CompiledModule> {
377        self.package
378            .root_compiled_units
379            .into_iter()
380            .map(|m| m.unit.module)
381            .collect()
382    }
383
384    /// Return all of the bytecode modules that this package depends on (both
385    /// directly and transitively) Note: these are not topologically sorted
386    /// by dependency.
387    pub fn get_dependent_modules(&self) -> impl Iterator<Item = &CompiledModule> {
388        self.package
389            .deps_compiled_units
390            .iter()
391            .map(|(_, m)| &m.unit.module)
392            .chain(self.bytecode_deps.iter().map(|(_, m)| m))
393    }
394
395    /// Return all of the bytecode modules in this package and the modules of
396    /// its direct and transitive dependencies. Note: these are not
397    /// topologically sorted by dependency.
398    pub fn get_modules_and_deps(&self) -> impl Iterator<Item = &CompiledModule> {
399        self.package
400            .all_modules()
401            .map(|m| &m.unit.module)
402            .chain(self.bytecode_deps.iter().map(|(_, m)| m))
403    }
404
405    /// Return the bytecode modules in this package, topologically sorted in
406    /// dependency order. Optionally include dependencies that have not been
407    /// published (are at address 0x0), if `with_unpublished_deps` is true.
408    /// This is the function to call if you would like to publish
409    /// or statically analyze the modules.
410    pub fn get_dependency_sorted_modules(
411        &self,
412        with_unpublished_deps: bool,
413    ) -> Vec<CompiledModule> {
414        let all_modules = Modules::new(self.get_modules_and_deps());
415
416        // SAFETY: package built successfully
417        let modules = all_modules.compute_topological_order().unwrap();
418
419        if with_unpublished_deps {
420            // For each transitive dependent module, if they are not to be published, they
421            // must have a non-zero address (meaning they are already published
422            // on-chain).
423            modules
424                .filter(|module| module.address() == &AccountAddress::ZERO)
425                .cloned()
426                .collect()
427        } else {
428            // Collect all module IDs from the current package to be published (module names
429            // are not sufficient as we may have modules with the same names in
430            // user code and in IOTA framework which would result in the latter
431            // being pulled into a set of modules to be published).
432            let self_modules: HashSet<_> = self
433                .package
434                .root_modules_map()
435                .iter_modules()
436                .iter()
437                .map(|m| m.self_id())
438                .collect();
439
440            modules
441                .filter(|module| self_modules.contains(&module.self_id()))
442                .cloned()
443                .collect()
444        }
445    }
446
447    /// Return the set of Object IDs corresponding to this package's transitive
448    /// dependencies' storage package IDs (where to load those packages
449    /// on-chain).
450    pub fn get_dependency_storage_package_ids(&self) -> Vec<ObjectID> {
451        self.dependency_ids.published.values().copied().collect()
452    }
453
454    /// Return a digest of the bytecode modules in this package.
455    pub fn get_package_digest(&self, with_unpublished_deps: bool) -> [u8; 32] {
456        MovePackage::compute_digest_for_modules_and_deps(
457            &self.get_package_bytes(with_unpublished_deps),
458            self.dependency_ids.published.values(),
459        )
460    }
461
462    /// Return a serialized representation of the bytecode modules in this
463    /// package, topologically sorted in dependency order
464    pub fn get_package_bytes(&self, with_unpublished_deps: bool) -> Vec<Vec<u8>> {
465        self.get_dependency_sorted_modules(with_unpublished_deps)
466            .iter()
467            .map(|m| {
468                let mut bytes = Vec::new();
469                m.serialize_with_version(m.version, &mut bytes).unwrap(); // safe because package built successfully
470                bytes
471            })
472            .collect()
473    }
474
475    /// Return the base64-encoded representation of the bytecode modules in this
476    /// package, topologically sorted in dependency order
477    pub fn get_package_base64(&self, with_unpublished_deps: bool) -> Vec<Base64> {
478        self.get_package_bytes(with_unpublished_deps)
479            .iter()
480            .map(|b| Base64::from_bytes(b))
481            .collect()
482    }
483
484    /// Get bytecode modules from the IOTA System that are used by this package
485    pub fn get_iota_system_modules(&self) -> impl Iterator<Item = &CompiledModule> {
486        self.get_modules_and_deps()
487            .filter(|m| *m.self_id().address() == IOTA_SYSTEM_ADDRESS)
488    }
489
490    /// Get bytecode modules from the IOTA Framework that are used by this
491    /// package
492    pub fn get_iota_framework_modules(&self) -> impl Iterator<Item = &CompiledModule> {
493        self.get_modules_and_deps()
494            .filter(|m| *m.self_id().address() == IOTA_FRAMEWORK_ADDRESS)
495    }
496
497    /// Get bytecode modules from the Move stdlib that are used by this package
498    pub fn get_stdlib_modules(&self) -> impl Iterator<Item = &CompiledModule> {
499        self.get_modules_and_deps()
500            .filter(|m| *m.self_id().address() == MOVE_STDLIB_ADDRESS)
501    }
502
503    /// Get bytecode modules from Stardust that are used by this package
504    pub fn get_stardust_modules(&self) -> impl Iterator<Item = &CompiledModule> {
505        self.get_modules_and_deps()
506            .filter(|m| *m.self_id().address() == STARDUST_ADDRESS)
507    }
508
509    /// Generate layout schemas for all types declared by this package, as well
510    /// as all struct types passed into `entry` functions declared by
511    /// modules in this package (either directly or by reference).
512    /// These layout schemas can be consumed by clients (e.g., the TypeScript
513    /// SDK) to enable BCS serialization/deserialization of the package's
514    /// objects, tx arguments, and events.
515    pub fn generate_struct_layouts(&self) -> Registry {
516        let mut package_types = BTreeSet::new();
517        for m in self.get_modules() {
518            let normalized_m = normalized::Module::new(m);
519            // 1. generate struct layouts for all declared types
520            'structs: for (name, s) in normalized_m.structs {
521                let mut dummy_type_parameters = Vec::new();
522                for t in &s.type_parameters {
523                    if t.is_phantom {
524                        // if all of t's type parameters are phantom, we can generate a type layout
525                        // we make this happen by creating a StructTag with dummy `type_params`,
526                        // since the layout generator won't look at them. we
527                        // need to do this because SerdeLayoutBuilder will refuse to generate a
528                        // layout for any open StructTag, but phantom types
529                        // cannot affect the layout of a struct, so we just use dummy values
530                        dummy_type_parameters.push(TypeTag::Signer)
531                    } else {
532                        // open type--do not attempt to generate a layout
533                        // TODO: handle generating layouts for open types?
534                        continue 'structs;
535                    }
536                }
537                debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
538                package_types.insert(StructTag {
539                    address: *m.address(),
540                    module: m.name().to_owned(),
541                    name,
542                    type_params: dummy_type_parameters,
543                });
544            }
545            // 2. generate struct layouts for all parameters of `entry` funs
546            for (_name, f) in normalized_m.functions {
547                if f.is_entry {
548                    for t in f.parameters {
549                        let tag_opt = match t.clone() {
550                            Type::Address
551                            | Type::Bool
552                            | Type::Signer
553                            | Type::TypeParameter(_)
554                            | Type::U8
555                            | Type::U16
556                            | Type::U32
557                            | Type::U64
558                            | Type::U128
559                            | Type::U256
560                            | Type::Vector(_) => continue,
561                            Type::Reference(t) | Type::MutableReference(t) => t.into_struct_tag(),
562                            s @ Type::Struct { .. } => s.into_struct_tag(),
563                        };
564                        if let Some(tag) = tag_opt {
565                            package_types.insert(tag);
566                        }
567                    }
568                }
569            }
570        }
571        let mut layout_builder = SerdeLayoutBuilder::new(self);
572        for typ in &package_types {
573            layout_builder.build_data_layout(typ).unwrap();
574        }
575        layout_builder.into_registry()
576    }
577
578    /// Checks whether this package corresponds to a built-in framework
579    pub fn is_system_package(&self) -> bool {
580        // System packages always have "published-at" addresses
581        let Ok(published_at) = self.published_at else {
582            return false;
583        };
584
585        is_system_package(published_at)
586    }
587
588    /// Checks for root modules with non-zero package addresses.  Returns an
589    /// arbitrary one, if one can be found, otherwise returns `None`.
590    pub fn published_root_module(&self) -> Option<&CompiledModule> {
591        self.package.root_compiled_units.iter().find_map(|unit| {
592            if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
593                Some(&unit.unit.module)
594            } else {
595                None
596            }
597        })
598    }
599
600    pub fn verify_unpublished_dependencies(
601        &self,
602        unpublished_deps: &BTreeSet<Symbol>,
603    ) -> IotaResult<()> {
604        if unpublished_deps.is_empty() {
605            return Ok(());
606        }
607
608        let errors = self
609            .package
610            .deps_compiled_units
611            .iter()
612            .filter_map(|(p, m)| {
613                if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
614                {
615                    return None;
616                }
617                Some(format!(
618                    " - {}::{} in dependency {}",
619                    m.unit.module.address(),
620                    m.unit.name,
621                    p
622                ))
623            })
624            .collect::<Vec<String>>();
625
626        if errors.is_empty() {
627            return Ok(());
628        }
629
630        let mut error_message = vec![];
631        error_message.push(
632            "The following modules in package dependencies set a non-zero self-address:".into(),
633        );
634        error_message.extend(errors);
635        error_message.push(
636            "If these packages really are unpublished, their self-addresses should be set \
637	     to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
638	     are already published, ensure they specify the address in the `published-at` of \
639	     their Move.toml manifest."
640                .into(),
641        );
642
643        Err(IotaError::ModulePublishFailure {
644            error: error_message.join("\n"),
645        })
646    }
647
648    pub fn get_published_dependencies_ids(&self) -> Vec<ObjectID> {
649        self.dependency_ids.published.values().cloned().collect()
650    }
651
652    /// Find the map of packages that are immediate dependencies of the root
653    /// modules, joined with the set of bytecode dependencies.
654    pub fn find_immediate_deps_pkgs_to_keep(
655        &self,
656        with_unpublished_deps: bool,
657    ) -> Result<BTreeMap<Symbol, ObjectID>, anyhow::Error> {
658        // Start from the root modules (or all modules if with_unpublished_deps is true
659        // as we need to include modules with 0x0 address)
660        let root_modules: Vec<_> = if with_unpublished_deps {
661            self.package
662                .all_compiled_units_with_source()
663                .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
664                .map(|x| x.unit.clone())
665                .collect()
666        } else {
667            self.package
668                .root_modules()
669                .map(|x| x.unit.clone())
670                .collect()
671        };
672
673        // Find the immediate dependencies for each root module and store the package
674        // name in the pkgs_to_keep set. This basically prunes the packages that
675        // are not used based on the modules information.
676        let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
677        let module_to_pkg_name: BTreeMap<_, _> = self
678            .package
679            .all_modules()
680            .map(|m| (m.unit.module.self_id(), m.unit.package_name))
681            .collect();
682
683        for module in &root_modules {
684            let immediate_deps = module.module.immediate_dependencies();
685            for dep in immediate_deps {
686                if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
687                    let Some(pkg_name) = pkg_name else {
688                        bail!("Expected a package name but it's None")
689                    };
690                    pkgs_to_keep.insert(*pkg_name);
691                }
692            }
693        }
694
695        // If a package depends on another published package that has only bytecode
696        // without source code available, we need to include also that package
697        // as dep.
698        pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
699
700        // Finally, filter out packages that are published and exist in the manifest at
701        // the compilation time but are not referenced in the source code.
702        Ok(self
703            .dependency_ids
704            .clone()
705            .published
706            .into_iter()
707            .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
708            .collect())
709    }
710}
711
712/// Create a set of [Dependencies] from a [SystemPackagesVersion]; the
713/// dependencies are override git dependencies to the specific revision given by
714/// the [SystemPackagesVersion]
715pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
716    packages
717        .packages
718        .iter()
719        .map(|package| {
720            (
721                package.package_name.clone().into(),
722                Dependency::Internal(InternalDependency {
723                    kind: DependencyKind::Git(GitInfo {
724                        git_url: SYSTEM_GIT_REPO.into(),
725                        git_rev: packages.git_revision.clone().into(),
726                        subdir: package.repo_path.clone().into(),
727                    }),
728                    subst: None,
729                    digest: None,
730                    dep_override: true,
731                }),
732            )
733        })
734        .collect()
735}
736
737impl GetModule for CompiledPackage {
738    type Error = anyhow::Error;
739    // TODO: return ref here for better efficiency? Borrow checker +
740    // all_modules_map() make it hard to do this
741    type Item = CompiledModule;
742
743    fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
744        Ok(self.package.all_modules_map().get_module(id).ok().cloned())
745    }
746}
747
748pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
749
750pub struct IotaPackageHooks;
751
752impl PackageHooks for IotaPackageHooks {
753    fn custom_package_info_fields(&self) -> Vec<String> {
754        vec![
755            PUBLISHED_AT_MANIFEST_FIELD.to_string(),
756            // TODO: remove this once version fields are removed from all manifests
757            "version".to_string(),
758        ]
759    }
760
761    fn resolve_on_chain_dependency(
762        &self,
763        _dep_name: move_symbol_pool::Symbol,
764        _info: &OnChainInfo,
765    ) -> anyhow::Result<()> {
766        Ok(())
767    }
768
769    fn custom_resolve_pkg_id(
770        &self,
771        manifest: &SourceManifest,
772    ) -> anyhow::Result<PackageIdentifier> {
773        if (!cfg!(debug_assertions) || cfg!(test))
774            && manifest.package.edition == Some(Edition::DEVELOPMENT)
775        {
776            return Err(Edition::DEVELOPMENT.unknown_edition_error());
777        }
778        Ok(manifest.package.name)
779    }
780
781    fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
782        Ok(None)
783    }
784}
785
786#[derive(Debug, Clone)]
787pub struct PackageDependencies {
788    /// Set of published dependencies (name and address).
789    pub published: BTreeMap<Symbol, ObjectID>,
790    /// Set of unpublished dependencies (name).
791    pub unpublished: BTreeSet<Symbol>,
792    /// Set of dependencies with invalid `published-at` addresses.
793    pub invalid: BTreeMap<Symbol, String>,
794    /// Set of dependencies that have conflicting `published-at` addresses. The
795    /// key refers to the package, and the tuple refers to the address in
796    /// the (Move.lock, Move.toml) respectively.
797    pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
798}
799
800/// Partition packages in `resolution_graph` into one of four groups:
801/// - The ID that the package itself is published at (if it is published)
802/// - The IDs of dependencies that have been published
803/// - The names of packages that have not been published on chain.
804/// - The names of packages that have a `published-at` field that isn't filled
805///   with a valid address.
806pub fn gather_published_ids(
807    resolution_graph: &ResolvedGraph,
808    chain_id: Option<String>,
809) -> (Result<ObjectID, PublishedAtError>, PackageDependencies) {
810    let root = resolution_graph.root_package();
811
812    let mut published = BTreeMap::new();
813    let mut unpublished = BTreeSet::new();
814    let mut invalid = BTreeMap::new();
815    let mut conflicting = BTreeMap::new();
816    let mut published_at = Err(PublishedAtError::NotPresent);
817
818    for (name, package) in &resolution_graph.package_table {
819        let property = resolve_published_id(package, chain_id.clone());
820        if name == &root {
821            // Separate out the root package as a special case
822            published_at = property;
823            continue;
824        }
825
826        match property {
827            Ok(id) => {
828                published.insert(*name, id);
829            }
830            Err(PublishedAtError::NotPresent) => {
831                unpublished.insert(*name);
832            }
833            Err(PublishedAtError::Invalid(value)) => {
834                invalid.insert(*name, value);
835            }
836            Err(PublishedAtError::Conflict {
837                id_lock,
838                id_manifest,
839            }) => {
840                conflicting.insert(*name, (id_lock, id_manifest));
841            }
842        };
843    }
844
845    (
846        published_at,
847        PackageDependencies {
848            published,
849            unpublished,
850            invalid,
851            conflicting,
852        },
853    )
854}
855
856pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectID, PublishedAtError> {
857    let Some(value) = manifest
858        .package
859        .custom_properties
860        .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
861    else {
862        return Err(PublishedAtError::NotPresent);
863    };
864
865    ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
866}
867
868pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
869    if unpublished.is_empty() {
870        return Ok(());
871    };
872
873    let mut error_messages = unpublished
874        .iter()
875        .map(|name| {
876            format!(
877                "Package dependency \"{name}\" does not specify a published address \
878		 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, nor is there a 'published-id' in the Move.lock).",
879            )
880        })
881        .collect::<Vec<_>>();
882
883    error_messages.push(
884        "If this is intentional, you may use the --with-unpublished-dependencies flag to \
885             continue publishing these dependencies as part of your package (they won't be \
886             linked against existing packages on-chain)."
887            .into(),
888    );
889
890    Err(IotaError::ModulePublishFailure {
891        error: error_messages.join("\n"),
892    })
893}
894
895pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
896    if invalid.is_empty() {
897        return Ok(());
898    }
899
900    let error_messages = invalid
901        .iter()
902        .map(|(name, value)| {
903            format!(
904                "Package dependency \"{name}\" does not specify a valid published \
905		 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
906                 or 'published-id' in Move.lock file."
907            )
908        })
909        .collect::<Vec<_>>();
910
911    Err(IotaError::ModulePublishFailure {
912        error: error_messages.join("\n"),
913    })
914}