1use ar_archive_writer::{
22 write_archive_to_stream, ArchiveKind, NewArchiveMember, DEFAULT_OBJECT_READER,
23};
24use object::{
25 write::{Object, StandardSection, Symbol, SymbolSection},
26 Architecture, BinaryFormat, Endianness, SymbolFlags, SymbolKind, SymbolScope,
27};
28use std::{
29 collections::hash_map::DefaultHasher,
30 env, error,
31 fs::{self, File},
32 hash::{Hash, Hasher},
33 io::{Seek, Write},
34 path::{Path, PathBuf},
35};
36
37pub use include_blob_macros::*;
38
39type Result<T> = std::result::Result<T, Box<dyn error::Error>>;
40
41pub fn make_includable<A: AsRef<Path>>(path: A) {
48 make_includable_impl(path.as_ref()).unwrap();
49}
50
51fn make_includable_impl(path: &Path) -> Result<()> {
52 let path = path.canonicalize().unwrap_or_else(|_| {
53 panic!(
54 "could not find file '{}' (working directory is '{}')",
55 path.display(),
56 std::env::current_dir().unwrap().display(),
57 );
58 });
59 println!("cargo:rerun-if-changed={}", path.display());
60 let metadata = fs::metadata(&path)?;
61
62 if metadata.is_dir() {
63 for entry in fs::read_dir(&path)? {
64 let entry = entry?;
65 make_includable_impl(&entry.path())?;
66 }
67 Ok(())
68 } else if metadata.is_file() {
69 process_file(path, metadata)
70 } else {
71 panic!(
72 "cannot handle file type '{:?}' of '{}'",
73 metadata.file_type(),
74 path.display()
75 );
76 }
77}
78
79fn process_file(path: PathBuf, metadata: fs::Metadata) -> Result<()> {
80 let mut hasher = DefaultHasher::new();
81 path.hash(&mut hasher);
82 metadata.modified()?.hash(&mut hasher);
83 let unique_name = format!("include_blob_{:016x}", hasher.finish());
84
85 let content = fs::read(&path)?;
86
87 let (pre, post) = lib_prefix_and_suffix();
88 let out_dir = env::var("OUT_DIR")?;
89 let out_file_path = format!("{out_dir}/{pre}{unique_name}{post}");
90 let mut out_file = File::create(&out_file_path)?;
91
92 let info = TargetInfo::from_build_script_vars();
93 let mut obj_buf = Vec::new();
94 let mut object = Object::new(info.binfmt, info.arch, info.endian);
95 let (section, _) = object.add_subsection(
96 StandardSection::ReadOnlyData,
97 unique_name.as_bytes(),
98 &[],
99 1,
100 );
101 let symbol_name = unique_name.as_bytes().to_vec();
102 let sym = object.add_symbol(Symbol {
103 name: symbol_name.clone(),
104 value: 0,
105 size: content.len() as _,
106 kind: SymbolKind::Data,
107 scope: SymbolScope::Linkage,
108 weak: false,
109 section: SymbolSection::Section(section),
110 flags: SymbolFlags::None,
111 });
112 object.add_symbol_data(sym, section, &content, 1);
113 object.write_stream(&mut obj_buf)?;
114
115 let object_file_name = format!("{unique_name}.o").into_bytes();
116 write_archive(&info, &mut out_file, &object_file_name, &obj_buf)?;
117
118 println!("cargo:rustc-link-lib=static={unique_name}");
119 println!("cargo:rustc-link-search=native={out_dir}");
120 Ok(())
121}
122
123fn write_archive(
124 target_info: &TargetInfo,
125 out_file: &mut (impl Write + Seek),
126 object_file_name: &[u8],
127 object_file_contents: &[u8],
128) -> Result<()> {
129 let member = NewArchiveMember {
130 buf: Box::new(object_file_contents),
131 object_reader: &DEFAULT_OBJECT_READER,
132 member_name: String::from_utf8(object_file_name.to_vec()).unwrap(),
133 mtime: 0,
134 uid: 0,
135 gid: 0,
136 perms: 0o644,
137 };
138 write_archive_to_stream(out_file, &[member], target_info.archive_kind, false, false)?;
139
140 Ok(())
141}
142
143struct TargetInfo {
144 binfmt: BinaryFormat,
145 arch: Architecture,
146 endian: Endianness,
147 archive_kind: ArchiveKind,
148}
149
150impl TargetInfo {
151 fn from_build_script_vars() -> Self {
152 let (binfmt, archive_kind) = match &*env::var("CARGO_CFG_TARGET_OS").unwrap() {
153 "macos" | "ios" => (BinaryFormat::MachO, ArchiveKind::Darwin64),
154 "windows" => (BinaryFormat::Coff, ArchiveKind::Gnu),
155 "linux" | "android" => (BinaryFormat::Elf, ArchiveKind::Gnu),
156 unk => panic!("unhandled operating system '{unk}'"),
157 };
158 let arch = match &*env::var("CARGO_CFG_TARGET_ARCH").unwrap() {
159 "x86" => Architecture::I386,
163 "x86_64" => Architecture::X86_64,
164 "arm" => Architecture::Arm,
165 "aarch64" => Architecture::Aarch64,
166 "riscv32" => Architecture::Riscv32,
167 "riscv64" => Architecture::Riscv64,
168 "mips" => Architecture::Mips,
169 "mips64" => Architecture::Mips64,
170 "powerpc" => Architecture::PowerPc,
171 "powerpc64" => Architecture::PowerPc64,
172 unk => panic!("unhandled architecture '{unk}'"),
173 };
174 let endian = match &*env::var("CARGO_CFG_TARGET_ENDIAN").unwrap() {
175 "little" => Endianness::Little,
176 "big" => Endianness::Big,
177 unk => unreachable!("unhandled endianness '{unk}'"),
178 };
179
180 Self {
181 binfmt,
182 arch,
183 endian,
184 archive_kind,
185 }
186 }
187}
188
189fn lib_prefix_and_suffix() -> (&'static str, &'static str) {
190 if env::var_os("CARGO_CFG_UNIX").is_some() {
191 ("lib", ".a")
192 } else if env::var_os("CARGO_CFG_WINDOWS").is_some() {
193 ("", ".lib")
194 } else {
195 unimplemented!("target platform not supported");
196 }
197}