dioxus_cli_opt/
lib.rs

1use anyhow::Context;
2use manganis_core::linker::LinkSection;
3use manganis_core::BundledAsset;
4use object::{read::archive::ArchiveFile, File as ObjectFile, Object, ObjectSection};
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7use std::{collections::HashMap, path::PathBuf};
8
9mod css;
10mod file;
11mod folder;
12mod image;
13mod js;
14mod json;
15
16pub use file::process_file_to;
17
18/// A manifest of all assets collected from dependencies
19///
20/// This will be filled in primarily by incremental compilation artifacts.
21#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
22pub struct AssetManifest {
23    /// Map of bundled asset name to the asset itself
24    pub assets: HashMap<PathBuf, BundledAsset>,
25}
26
27impl AssetManifest {
28    /// Manually add an asset to the manifest
29    pub fn register_asset(
30        &mut self,
31        asset_path: &Path,
32        options: manganis::AssetOptions,
33    ) -> anyhow::Result<BundledAsset> {
34        let hash = manganis_core::hash::AssetHash::hash_file_contents(asset_path)
35            .context("Failed to hash file")?;
36
37        let output_path_str = asset_path.to_str().ok_or(anyhow::anyhow!(
38            "Failed to convert wasm bindgen output path to string"
39        ))?;
40
41        let bundled_asset =
42            manganis::macro_helpers::create_bundled_asset(output_path_str, hash.bytes(), options);
43
44        self.assets.insert(asset_path.into(), bundled_asset);
45
46        Ok(bundled_asset)
47    }
48
49    #[allow(dead_code)]
50    pub fn load_from_file(path: &Path) -> anyhow::Result<Self> {
51        let src = std::fs::read_to_string(path)?;
52
53        serde_json::from_str(&src)
54            .with_context(|| format!("Failed to parse asset manifest from {path:?}\n{src}"))
55    }
56
57    /// Fill this manifest with a file object/rlib files, typically extracted from the linker intercepted
58    pub fn add_from_object_path(&mut self, path: &Path) -> anyhow::Result<()> {
59        let data = std::fs::read(path)?;
60
61        match path.extension().and_then(|ext| ext.to_str()) {
62            // Parse an rlib as a collection of objects
63            Some("rlib") => {
64                if let Ok(archive) = object::read::archive::ArchiveFile::parse(&*data) {
65                    self.add_from_archive_file(&archive, &data)?;
66                }
67            }
68            _ => {
69                if let Ok(object) = object::File::parse(&*data) {
70                    self.add_from_object_file(&object)?;
71                }
72            }
73        }
74
75        Ok(())
76    }
77
78    /// Fill this manifest from an rlib / ar file that contains many object files and their entries
79    fn add_from_archive_file(&mut self, archive: &ArchiveFile, data: &[u8]) -> object::Result<()> {
80        // Look through each archive member for object files.
81        // Read the archive member's binary data (we know it's an object file)
82        // And parse it with the normal `object::File::parse` to find the manganis string.
83        for member in archive.members() {
84            let member = member?;
85            let name = String::from_utf8_lossy(member.name()).to_string();
86
87            // Check if the archive member is an object file and parse it.
88            if name.ends_with(".o") {
89                let data = member.data(data)?;
90                let object = object::File::parse(data)?;
91                _ = self.add_from_object_file(&object);
92            }
93        }
94
95        Ok(())
96    }
97
98    /// Fill this manifest with whatever tables might come from the object file
99    fn add_from_object_file(&mut self, obj: &ObjectFile) -> anyhow::Result<()> {
100        for section in obj.sections() {
101            let Ok(section_name) = section.name() else {
102                continue;
103            };
104
105            // Check if the link section matches the asset section for one of the platforms we support. This may not be the current platform if the user is cross compiling
106            let matches = LinkSection::ALL
107                .iter()
108                .any(|x| x.link_section == section_name);
109
110            if !matches {
111                continue;
112            }
113
114            let bytes = section
115                .uncompressed_data()
116                .context("Could not read uncompressed data from object file")?;
117
118            let mut buffer = const_serialize::ConstReadBuffer::new(&bytes);
119            while let Some((remaining_buffer, asset)) =
120                const_serialize::deserialize_const!(BundledAsset, buffer)
121            {
122                self.assets
123                    .insert(asset.absolute_source_path().into(), asset);
124                buffer = remaining_buffer;
125            }
126        }
127
128        Ok(())
129    }
130}