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#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)]
22pub struct AssetManifest {
23 pub assets: HashMap<PathBuf, BundledAsset>,
25}
26
27impl AssetManifest {
28 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 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 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 fn add_from_archive_file(&mut self, archive: &ArchiveFile, data: &[u8]) -> object::Result<()> {
80 for member in archive.members() {
84 let member = member?;
85 let name = String::from_utf8_lossy(member.name()).to_string();
86
87 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 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 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}