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 pool = &mut normalized::RcPool::new();
517        let mut package_types = BTreeSet::new();
518        for m in self.get_modules() {
519            let normalized_m = normalized::Module::new(pool, m, /* include code */ false);
520            // 1. generate struct layouts for all declared types
521            'structs: for (name, s) in normalized_m.structs {
522                let mut dummy_type_parameters = Vec::new();
523                for t in &s.type_parameters {
524                    if t.is_phantom {
525                        // if all of t's type parameters are phantom, we can generate a type layout
526                        // we make this happen by creating a StructTag with dummy `type_params`,
527                        // since the layout generator won't look at them. we
528                        // need to do this because SerdeLayoutBuilder will refuse to generate a
529                        // layout for any open StructTag, but phantom types
530                        // cannot affect the layout of a struct, so we just use dummy values
531                        dummy_type_parameters.push(TypeTag::Signer)
532                    } else {
533                        // open type--do not attempt to generate a layout
534                        // TODO: handle generating layouts for open types?
535                        continue 'structs;
536                    }
537                }
538                debug_assert!(dummy_type_parameters.len() == s.type_parameters.len());
539                package_types.insert(StructTag {
540                    address: *m.address(),
541                    module: m.name().to_owned(),
542                    name: name.as_ident_str().to_owned(),
543                    type_params: dummy_type_parameters,
544                });
545            }
546            // 2. generate struct layouts for all parameters of `entry` funs
547            for (_name, f) in normalized_m.functions {
548                if f.is_entry {
549                    for t in &*f.parameters {
550                        let tag_opt = match &**t {
551                            Type::Address
552                            | Type::Bool
553                            | Type::Signer
554                            | Type::TypeParameter(_)
555                            | Type::U8
556                            | Type::U16
557                            | Type::U32
558                            | Type::U64
559                            | Type::U128
560                            | Type::U256
561                            | Type::Vector(_) => continue,
562                            Type::Reference(_, inner) => inner.to_struct_tag(pool),
563                            Type::Datatype(_) => t.to_struct_tag(pool),
564                        };
565                        if let Some(tag) = tag_opt {
566                            package_types.insert(tag);
567                        }
568                    }
569                }
570            }
571        }
572        let mut layout_builder = SerdeLayoutBuilder::new(self);
573        for tag in &package_types {
574            layout_builder.build_data_layout(tag).unwrap();
575        }
576        layout_builder.into_registry()
577    }
578
579    /// Checks whether this package corresponds to a built-in framework
580    pub fn is_system_package(&self) -> bool {
581        // System packages always have "published-at" addresses
582        let Ok(published_at) = self.published_at else {
583            return false;
584        };
585
586        is_system_package(published_at)
587    }
588
589    /// Checks for root modules with non-zero package addresses.  Returns an
590    /// arbitrary one, if one can be found, otherwise returns `None`.
591    pub fn published_root_module(&self) -> Option<&CompiledModule> {
592        self.package.root_compiled_units.iter().find_map(|unit| {
593            if unit.unit.module.self_id().address() != &AccountAddress::ZERO {
594                Some(&unit.unit.module)
595            } else {
596                None
597            }
598        })
599    }
600
601    pub fn verify_unpublished_dependencies(
602        &self,
603        unpublished_deps: &BTreeSet<Symbol>,
604    ) -> IotaResult<()> {
605        if unpublished_deps.is_empty() {
606            return Ok(());
607        }
608
609        let errors = self
610            .package
611            .deps_compiled_units
612            .iter()
613            .filter_map(|(p, m)| {
614                if !unpublished_deps.contains(p) || m.unit.module.address() == &AccountAddress::ZERO
615                {
616                    return None;
617                }
618                Some(format!(
619                    " - {}::{} in dependency {}",
620                    m.unit.module.address(),
621                    m.unit.name,
622                    p
623                ))
624            })
625            .collect::<Vec<String>>();
626
627        if errors.is_empty() {
628            return Ok(());
629        }
630
631        let mut error_message = vec![];
632        error_message.push(
633            "The following modules in package dependencies set a non-zero self-address:".into(),
634        );
635        error_message.extend(errors);
636        error_message.push(
637            "If these packages really are unpublished, their self-addresses should be set \
638	     to \"0x0\" in the [addresses] section of the manifest when publishing. If they \
639	     are already published, ensure they specify the address in the `published-at` of \
640	     their Move.toml manifest."
641                .into(),
642        );
643
644        Err(IotaError::ModulePublishFailure {
645            error: error_message.join("\n"),
646        })
647    }
648
649    pub fn get_published_dependencies_ids(&self) -> Vec<ObjectID> {
650        self.dependency_ids.published.values().cloned().collect()
651    }
652
653    /// Find the map of packages that are immediate dependencies of the root
654    /// modules, joined with the set of bytecode dependencies.
655    pub fn find_immediate_deps_pkgs_to_keep(
656        &self,
657        with_unpublished_deps: bool,
658    ) -> Result<BTreeMap<Symbol, ObjectID>, anyhow::Error> {
659        // Start from the root modules (or all modules if with_unpublished_deps is true
660        // as we need to include modules with 0x0 address)
661        let root_modules: Vec<_> = if with_unpublished_deps {
662            self.package
663                .all_compiled_units_with_source()
664                .filter(|m| m.unit.address.into_inner() == AccountAddress::ZERO)
665                .map(|x| x.unit.clone())
666                .collect()
667        } else {
668            self.package
669                .root_modules()
670                .map(|x| x.unit.clone())
671                .collect()
672        };
673
674        // Find the immediate dependencies for each root module and store the package
675        // name in the pkgs_to_keep set. This basically prunes the packages that
676        // are not used based on the modules information.
677        let mut pkgs_to_keep: BTreeSet<Symbol> = BTreeSet::new();
678        let module_to_pkg_name: BTreeMap<_, _> = self
679            .package
680            .all_modules()
681            .map(|m| (m.unit.module.self_id(), m.unit.package_name))
682            .collect();
683
684        for module in &root_modules {
685            let immediate_deps = module.module.immediate_dependencies();
686            for dep in immediate_deps {
687                if let Some(pkg_name) = module_to_pkg_name.get(&dep) {
688                    let Some(pkg_name) = pkg_name else {
689                        bail!("Expected a package name but it's None")
690                    };
691                    pkgs_to_keep.insert(*pkg_name);
692                }
693            }
694        }
695
696        // If a package depends on another published package that has only bytecode
697        // without source code available, we need to include also that package
698        // as dep.
699        pkgs_to_keep.extend(self.bytecode_deps.iter().map(|(name, _)| *name));
700
701        // Finally, filter out packages that are published and exist in the manifest at
702        // the compilation time but are not referenced in the source code.
703        Ok(self
704            .dependency_ids
705            .clone()
706            .published
707            .into_iter()
708            .filter(|(pkg_name, _)| pkgs_to_keep.contains(pkg_name))
709            .collect())
710    }
711}
712
713/// Create a set of [Dependencies] from a [SystemPackagesVersion]; the
714/// dependencies are override git dependencies to the specific revision given by
715/// the [SystemPackagesVersion]
716pub fn implicit_deps(packages: &SystemPackagesVersion) -> Dependencies {
717    packages
718        .packages
719        .iter()
720        .map(|package| {
721            (
722                package.package_name.clone().into(),
723                Dependency::Internal(InternalDependency {
724                    kind: DependencyKind::Git(GitInfo {
725                        git_url: SYSTEM_GIT_REPO.into(),
726                        git_rev: packages.git_revision.clone().into(),
727                        subdir: package.repo_path.clone().into(),
728                    }),
729                    subst: None,
730                    digest: None,
731                    dep_override: true,
732                }),
733            )
734        })
735        .collect()
736}
737
738impl GetModule for CompiledPackage {
739    type Error = anyhow::Error;
740    // TODO: return ref here for better efficiency? Borrow checker +
741    // all_modules_map() make it hard to do this
742    type Item = CompiledModule;
743
744    fn get_module_by_id(&self, id: &ModuleId) -> Result<Option<Self::Item>, Self::Error> {
745        Ok(self.package.all_modules_map().get_module(id).ok().cloned())
746    }
747}
748
749pub const PUBLISHED_AT_MANIFEST_FIELD: &str = "published-at";
750
751pub struct IotaPackageHooks;
752
753impl PackageHooks for IotaPackageHooks {
754    fn custom_package_info_fields(&self) -> Vec<String> {
755        vec![
756            PUBLISHED_AT_MANIFEST_FIELD.to_string(),
757            // TODO: remove this once version fields are removed from all manifests
758            "version".to_string(),
759        ]
760    }
761
762    fn resolve_on_chain_dependency(
763        &self,
764        _dep_name: move_symbol_pool::Symbol,
765        _info: &OnChainInfo,
766    ) -> anyhow::Result<()> {
767        Ok(())
768    }
769
770    fn custom_resolve_pkg_id(
771        &self,
772        manifest: &SourceManifest,
773    ) -> anyhow::Result<PackageIdentifier> {
774        if (!cfg!(debug_assertions) || cfg!(test))
775            && manifest.package.edition == Some(Edition::DEVELOPMENT)
776        {
777            return Err(Edition::DEVELOPMENT.unknown_edition_error());
778        }
779        Ok(manifest.package.name)
780    }
781
782    fn resolve_version(&self, _: &SourceManifest) -> anyhow::Result<Option<Symbol>> {
783        Ok(None)
784    }
785}
786
787#[derive(Debug, Clone)]
788pub struct PackageDependencies {
789    /// Set of published dependencies (name and address).
790    pub published: BTreeMap<Symbol, ObjectID>,
791    /// Set of unpublished dependencies (name).
792    pub unpublished: BTreeSet<Symbol>,
793    /// Set of dependencies with invalid `published-at` addresses.
794    pub invalid: BTreeMap<Symbol, String>,
795    /// Set of dependencies that have conflicting `published-at` addresses. The
796    /// key refers to the package, and the tuple refers to the address in
797    /// the (Move.lock, Move.toml) respectively.
798    pub conflicting: BTreeMap<Symbol, (ObjectID, ObjectID)>,
799}
800
801/// Partition packages in `resolution_graph` into one of four groups:
802/// - The ID that the package itself is published at (if it is published)
803/// - The IDs of dependencies that have been published
804/// - The names of packages that have not been published on chain.
805/// - The names of packages that have a `published-at` field that isn't filled
806///   with a valid address.
807pub fn gather_published_ids(
808    resolution_graph: &ResolvedGraph,
809    chain_id: Option<String>,
810) -> (Result<ObjectID, PublishedAtError>, PackageDependencies) {
811    let root = resolution_graph.root_package();
812
813    let mut published = BTreeMap::new();
814    let mut unpublished = BTreeSet::new();
815    let mut invalid = BTreeMap::new();
816    let mut conflicting = BTreeMap::new();
817    let mut published_at = Err(PublishedAtError::NotPresent);
818
819    for (name, package) in &resolution_graph.package_table {
820        let property = resolve_published_id(package, chain_id.clone());
821        if name == &root {
822            // Separate out the root package as a special case
823            published_at = property;
824            continue;
825        }
826
827        match property {
828            Ok(id) => {
829                published.insert(*name, id);
830            }
831            Err(PublishedAtError::NotPresent) => {
832                unpublished.insert(*name);
833            }
834            Err(PublishedAtError::Invalid(value)) => {
835                invalid.insert(*name, value);
836            }
837            Err(PublishedAtError::Conflict {
838                id_lock,
839                id_manifest,
840            }) => {
841                conflicting.insert(*name, (id_lock, id_manifest));
842            }
843        };
844    }
845
846    (
847        published_at,
848        PackageDependencies {
849            published,
850            unpublished,
851            invalid,
852            conflicting,
853        },
854    )
855}
856
857pub fn published_at_property(manifest: &SourceManifest) -> Result<ObjectID, PublishedAtError> {
858    let Some(value) = manifest
859        .package
860        .custom_properties
861        .get(&Symbol::from(PUBLISHED_AT_MANIFEST_FIELD))
862    else {
863        return Err(PublishedAtError::NotPresent);
864    };
865
866    ObjectID::from_str(value.as_str()).map_err(|_| PublishedAtError::Invalid(value.to_owned()))
867}
868
869pub fn check_unpublished_dependencies(unpublished: &BTreeSet<Symbol>) -> Result<(), IotaError> {
870    if unpublished.is_empty() {
871        return Ok(());
872    };
873
874    let mut error_messages = unpublished
875        .iter()
876        .map(|name| {
877            format!(
878                "Package dependency \"{name}\" does not specify a published address \
879		 (the Move.toml manifest for \"{name}\" does not contain a 'published-at' field, nor is there a 'published-id' in the Move.lock).",
880            )
881        })
882        .collect::<Vec<_>>();
883
884    error_messages.push(
885        "If this is intentional, you may use the --with-unpublished-dependencies flag to \
886             continue publishing these dependencies as part of your package (they won't be \
887             linked against existing packages on-chain)."
888            .into(),
889    );
890
891    Err(IotaError::ModulePublishFailure {
892        error: error_messages.join("\n"),
893    })
894}
895
896pub fn check_invalid_dependencies(invalid: &BTreeMap<Symbol, String>) -> Result<(), IotaError> {
897    if invalid.is_empty() {
898        return Ok(());
899    }
900
901    let error_messages = invalid
902        .iter()
903        .map(|(name, value)| {
904            format!(
905                "Package dependency \"{name}\" does not specify a valid published \
906		 address: could not parse value \"{value}\" for 'published-at' field in Move.toml \
907                 or 'published-id' in Move.lock file."
908            )
909        })
910        .collect::<Vec<_>>();
911
912    Err(IotaError::ModulePublishFailure {
913        error: error_messages.join("\n"),
914    })
915}