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(StandardSection::ReadOnlyData, unique_name.as_bytes());
96 let symbol_name = unique_name.as_bytes().to_vec();
97 let sym = object.add_symbol(Symbol {
98 name: symbol_name.clone(),
99 value: 0,
100 size: content.len() as _,
101 kind: SymbolKind::Data,
102 scope: SymbolScope::Linkage,
103 weak: false,
104 section: SymbolSection::Section(section),
105 flags: SymbolFlags::None,
106 });
107 object.add_symbol_data(sym, section, &content, 1);
108 object.write_stream(&mut obj_buf)?;
109
110 let object_file_name = format!("{unique_name}.o").into_bytes();
111 write_archive(&info, &mut out_file, &object_file_name, &obj_buf)?;
112
113 println!("cargo:rustc-link-lib=static={unique_name}");
114 println!("cargo:rustc-link-search=native={out_dir}");
115 Ok(())
116}
117
118fn write_archive(
119 target_info: &TargetInfo,
120 out_file: &mut (impl Write + Seek),
121 object_file_name: &[u8],
122 object_file_contents: &[u8],
123) -> Result<()> {
124 let member = NewArchiveMember {
125 buf: Box::new(object_file_contents),
126 object_reader: &DEFAULT_OBJECT_READER,
127 member_name: String::from_utf8(object_file_name.to_vec()).unwrap(),
128 mtime: 0,
129 uid: 0,
130 gid: 0,
131 perms: 0o644,
132 };
133 write_archive_to_stream(out_file, &[member], target_info.archive_kind, false, false)?;
134
135 Ok(())
136}
137
138struct TargetInfo {
139 binfmt: BinaryFormat,
140 arch: Architecture,
141 endian: Endianness,
142 archive_kind: ArchiveKind,
143}
144
145impl TargetInfo {
146 fn from_build_script_vars() -> Self {
147 let (binfmt, archive_kind) = match &*env::var("CARGO_CFG_TARGET_OS").unwrap() {
148 "macos" | "ios" => (BinaryFormat::MachO, ArchiveKind::Darwin64),
149 "windows" => (BinaryFormat::Coff, ArchiveKind::Gnu),
150 "linux" | "android" => (BinaryFormat::Elf, ArchiveKind::Gnu),
151 unk => panic!("unhandled operating system '{unk}'"),
152 };
153 let arch = match &*env::var("CARGO_CFG_TARGET_ARCH").unwrap() {
154 "x86" => Architecture::I386,
158 "x86_64" => Architecture::X86_64,
159 "arm" => Architecture::Arm,
160 "aarch64" => Architecture::Aarch64,
161 "riscv32" => Architecture::Riscv32,
162 "riscv64" => Architecture::Riscv64,
163 "mips" => Architecture::Mips,
164 "mips64" => Architecture::Mips64,
165 "powerpc" => Architecture::PowerPc,
166 "powerpc64" => Architecture::PowerPc64,
167 unk => panic!("unhandled architecture '{unk}'"),
168 };
169 let endian = match &*env::var("CARGO_CFG_TARGET_ENDIAN").unwrap() {
170 "little" => Endianness::Little,
171 "big" => Endianness::Big,
172 unk => unreachable!("unhandled endianness '{unk}'"),
173 };
174
175 Self {
176 binfmt,
177 arch,
178 endian,
179 archive_kind,
180 }
181 }
182}
183
184fn lib_prefix_and_suffix() -> (&'static str, &'static str) {
185 if env::var_os("CARGO_CFG_UNIX").is_some() {
186 ("lib", ".a")
187 } else if env::var_os("CARGO_CFG_WINDOWS").is_some() {
188 ("", ".lib")
189 } else {
190 unimplemented!("target platform not supported");
191 }
192}