pyoxidizerlib/py_packaging/
embedding.rs1use {
8 crate::py_packaging::config::PyembedPythonInterpreterConfig,
9 anyhow::{anyhow, Context, Result},
10 pyo3_build_config::{
11 BuildFlags, InterpreterConfig as PyO3InterpreterConfig, PythonImplementation, PythonVersion,
12 },
13 python_packaging::{
14 licensing::{LicensedComponent, LicensedComponents},
15 resource_collection::CompiledResourcesCollection,
16 },
17 simple_file_manifest::{FileEntry, FileManifest},
18 std::path::{Path, PathBuf},
19};
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum LinkingAnnotation {
24 LinkFramework(String),
26
27 LinkLibrary(String),
29
30 LinkLibraryStatic(String),
32
33 Search(PathBuf),
35
36 SearchNative(PathBuf),
38
39 Argument(String),
41}
42
43impl LinkingAnnotation {
44 pub fn to_cargo_annotation(&self) -> String {
46 match self {
47 Self::LinkFramework(framework) => {
48 format!("cargo:rustc-link-lib=framework={}", framework)
49 }
50 Self::LinkLibrary(lib) => format!("cargo:rustc-link-lib={}", lib),
51 Self::LinkLibraryStatic(lib) => format!("cargo:rustc-link-lib=static={}", lib),
52 Self::Search(path) => format!("cargo:rustc-link-search={}", path.display()),
53 Self::SearchNative(path) => {
54 format!("cargo:rustc-link-search=native={}", path.display())
55 }
56 Self::Argument(arg) => {
57 format!("cargo:rustc-link-arg={}", arg)
58 }
59 }
60 }
61}
62
63pub fn linking_annotations_for_target(target_triple: &str) -> Vec<LinkingAnnotation> {
65 if target_triple.contains("-linux-") {
79 vec![LinkingAnnotation::Argument("-Wl,-export-dynamic".into())]
80 } else if target_triple.contains("-apple-darwin") {
81 vec![LinkingAnnotation::Argument("-rdynamic".into())]
82 } else {
83 vec![]
84 }
85}
86
87pub trait LinkablePython {
89 fn write_files(&self, dest_dir: &Path, target_triple: &str) -> Result<()>;
93
94 fn linking_annotations(
101 &self,
102 dest_dir: &Path,
103 alias: bool,
104 target_triple: &str,
105 ) -> Result<Vec<LinkingAnnotation>>;
106}
107
108#[derive(Clone, Debug)]
110pub struct LinkSharedLibraryPath {
111 pub library_path: PathBuf,
113
114 pub linking_annotations: Vec<LinkingAnnotation>,
116}
117
118impl LinkSharedLibraryPath {
119 fn library_name(&self) -> Result<String> {
121 let filename = self
122 .library_path
123 .file_name()
124 .ok_or_else(|| anyhow!("unable to resolve shared library file name"))?
125 .to_string_lossy();
126
127 if filename.ends_with(".dll") {
128 Ok(filename.trim_end_matches(".dll").to_string())
129 } else if filename.ends_with(".dylib") {
130 Ok(filename
131 .trim_end_matches(".dylib")
132 .trim_start_matches("lib")
133 .to_string())
134 } else if filename.ends_with(".so") {
135 Ok(filename
136 .trim_end_matches(".so")
137 .trim_start_matches("lib")
138 .to_string())
139 } else {
140 Err(anyhow!(
141 "unhandled libpython shared library filename: {}",
142 filename
143 ))
144 }
145 }
146}
147
148impl LinkablePython for LinkSharedLibraryPath {
149 fn write_files(&self, _dest_dir: &Path, _target_triple: &str) -> Result<()> {
150 Ok(())
151 }
152
153 fn linking_annotations(
154 &self,
155 _dest_dir: &Path,
156 alias: bool,
157 target_triple: &str,
158 ) -> Result<Vec<LinkingAnnotation>> {
159 let lib_dir = self
160 .library_path
161 .parent()
162 .ok_or_else(|| anyhow!("could not derive parent directory of library path"))?;
163
164 let mut annotations = vec![
165 LinkingAnnotation::LinkLibrary(if alias {
166 format!("pythonXY:{}", self.library_name()?)
167 } else {
168 self.library_name()?
169 }),
170 LinkingAnnotation::SearchNative(lib_dir.to_path_buf()),
171 ];
172
173 annotations.extend(self.linking_annotations.iter().cloned());
174 annotations.extend(linking_annotations_for_target(target_triple));
175
176 Ok(annotations)
177 }
178}
179
180#[derive(Clone, Debug)]
182pub struct LinkStaticLibraryData {
183 pub library_data: Vec<u8>,
185
186 pub linking_annotations: Vec<LinkingAnnotation>,
188}
189
190impl LinkStaticLibraryData {
191 fn library_name(&self) -> &'static str {
192 "python3"
193 }
194
195 fn library_path(&self, dest_dir: impl AsRef<Path>, target_triple: &str) -> PathBuf {
196 dest_dir
197 .as_ref()
198 .join(if target_triple.contains("-windows-") {
199 format!("{}.lib", self.library_name())
200 } else {
201 format!("lib{}.a", self.library_name())
202 })
203 }
204}
205
206impl LinkablePython for LinkStaticLibraryData {
207 fn write_files(&self, dest_dir: &Path, target_triple: &str) -> Result<()> {
208 let lib_path = self.library_path(dest_dir, target_triple);
209
210 std::fs::write(&lib_path, &self.library_data)
211 .with_context(|| format!("writing {}", lib_path.display()))?;
212
213 Ok(())
214 }
215
216 fn linking_annotations(
217 &self,
218 dest_dir: &Path,
219 alias: bool,
220 target_triple: &str,
221 ) -> Result<Vec<LinkingAnnotation>> {
222 let mut annotations = vec![
223 LinkingAnnotation::LinkLibraryStatic(if alias {
224 format!("pythonXY:{}", self.library_name())
225 } else {
226 self.library_name().to_string()
227 }),
228 LinkingAnnotation::SearchNative(dest_dir.to_path_buf()),
229 ];
230
231 annotations.extend(self.linking_annotations.iter().cloned());
232 annotations.extend(linking_annotations_for_target(target_triple));
233
234 Ok(annotations)
235 }
236}
237
238pub enum LibpythonLinkSettings {
240 ExistingDynamic(LinkSharedLibraryPath),
242 StaticData(LinkStaticLibraryData),
244}
245
246impl LinkablePython for LibpythonLinkSettings {
247 fn write_files(&self, dest_dir: &Path, target_triple: &str) -> Result<()> {
248 match self {
249 Self::ExistingDynamic(l) => l.write_files(dest_dir, target_triple),
250 Self::StaticData(l) => l.write_files(dest_dir, target_triple),
251 }
252 }
253
254 fn linking_annotations(
255 &self,
256 dest_dir: &Path,
257 alias: bool,
258 target_triple: &str,
259 ) -> Result<Vec<LinkingAnnotation>> {
260 match self {
261 Self::ExistingDynamic(l) => l.linking_annotations(dest_dir, alias, target_triple),
262 Self::StaticData(l) => l.linking_annotations(dest_dir, alias, target_triple),
263 }
264 }
265}
266
267impl From<LinkSharedLibraryPath> for LibpythonLinkSettings {
268 fn from(l: LinkSharedLibraryPath) -> Self {
269 Self::ExistingDynamic(l)
270 }
271}
272
273impl From<LinkStaticLibraryData> for LibpythonLinkSettings {
274 fn from(l: LinkStaticLibraryData) -> Self {
275 Self::StaticData(l)
276 }
277}
278
279pub const DEFAULT_PYTHON_CONFIG_FILENAME: &str = "default_python_config.rs";
281
282pub struct EmbeddedPythonContext<'a> {
284 pub config: PyembedPythonInterpreterConfig,
286
287 pub link_settings: LibpythonLinkSettings,
289
290 pub pending_resources: Vec<(CompiledResourcesCollection<'a>, PathBuf)>,
292
293 pub extra_files: FileManifest,
295
296 pub host_triple: String,
298
299 pub target_triple: String,
301
302 pub python_implementation: PythonImplementation,
304
305 pub python_version: PythonVersion,
307
308 pub python_exe_host: PathBuf,
310
311 pub python_build_flags: BuildFlags,
315
316 pub licensing_filename: Option<String>,
318
319 pub licensing: LicensedComponents,
321}
322
323impl<'a> EmbeddedPythonContext<'a> {
324 pub fn interpreter_config_rs_path(&self, dest_dir: impl AsRef<Path>) -> PathBuf {
326 dest_dir.as_ref().join(DEFAULT_PYTHON_CONFIG_FILENAME)
327 }
328
329 pub fn pyo3_config_path(&self, dest_dir: impl AsRef<Path>) -> PathBuf {
331 dest_dir.as_ref().join("pyo3-build-config-file.txt")
332 }
333
334 pub fn pyo3_interpreter_config(
336 &self,
337 dest_dir: impl AsRef<Path>,
338 ) -> Result<PyO3InterpreterConfig> {
339 Ok(PyO3InterpreterConfig {
340 implementation: self.python_implementation,
341 version: self.python_version,
342 shared: matches!(
344 &self.link_settings,
345 LibpythonLinkSettings::ExistingDynamic(_)
346 ),
347 abi3: false,
349 lib_name: None,
351 lib_dir: None,
352 executable: Some(self.python_exe_host.to_string_lossy().to_string()),
353 pointer_width: Some(if self.target_triple.starts_with("i686-") {
355 32
356 } else {
357 64
358 }),
359 build_flags: BuildFlags(self.python_build_flags.0.clone()),
360 suppress_build_script_link_lines: true,
361 extra_build_script_lines: self
362 .link_settings
363 .linking_annotations(
364 dest_dir.as_ref(),
365 self.target_triple.contains("-windows-"),
366 &self.target_triple,
367 )?
368 .iter()
369 .map(|la| la.to_cargo_annotation())
370 .collect::<Vec<_>>(),
371 })
372 }
373
374 pub fn write_packed_resources(&self, dest_dir: impl AsRef<Path>) -> Result<()> {
376 for (collection, path) in &self.pending_resources {
377 let dest_path = dest_dir.as_ref().join(path);
378
379 let mut writer = std::io::BufWriter::new(
380 std::fs::File::create(&dest_path)
381 .with_context(|| format!("opening {} for writing", dest_path.display()))?,
382 );
383 collection
384 .write_packed_resources(&mut writer)
385 .context("writing packed resources")?;
386 }
387
388 Ok(())
389 }
390
391 pub fn write_libpython(&self, dest_dir: impl AsRef<Path>) -> Result<()> {
393 self.link_settings
394 .write_files(dest_dir.as_ref(), &self.target_triple)
395 }
396
397 pub fn write_interpreter_config_rs(&self, dest_dir: impl AsRef<Path>) -> Result<()> {
399 self.config
400 .write_default_python_config_rs(self.interpreter_config_rs_path(&dest_dir))?;
401
402 Ok(())
403 }
404
405 pub fn write_pyo3_config(&self, dest_dir: impl AsRef<Path>) -> Result<()> {
407 let dest_dir = dest_dir.as_ref();
408
409 let mut fh = std::fs::File::create(self.pyo3_config_path(dest_dir))?;
410 self.pyo3_interpreter_config(dest_dir)?
411 .to_writer(&mut fh)
412 .map_err(|e| anyhow!("error writing PyO3 config file: {}", e))?;
413
414 Ok(())
415 }
416
417 pub fn write_licensing(&self, dest_dir: impl AsRef<Path>) -> Result<()> {
419 if let Some(filename) = &self.licensing_filename {
420 let text = self.licensing.aggregate_license_document(false)?;
421
422 std::fs::write(dest_dir.as_ref().join(filename), text.as_bytes())?;
423 }
424
425 Ok(())
426 }
427
428 pub fn write_files(&self, dest_dir: &Path) -> Result<()> {
430 self.write_packed_resources(dest_dir)
431 .context("write_packed_resources()")?;
432 self.write_libpython(dest_dir)
433 .context("write_libpython()")?;
434 self.write_interpreter_config_rs(dest_dir)
435 .context("write_interpreter_config_rs()")?;
436 self.write_pyo3_config(dest_dir)
437 .context("write_pyo3_config()")?;
438 self.write_licensing(dest_dir)
439 .context("write_licensing()")?;
440
441 Ok(())
442 }
443
444 pub fn licensing(&self) -> &LicensedComponents {
446 &self.licensing
447 }
448
449 pub fn add_licensed_component(&mut self, component: LicensedComponent) -> Result<()> {
451 self.licensing.add_component(component);
452
453 self.synchronize_licensing()?;
454
455 Ok(())
456 }
457
458 pub fn synchronize_licensing(&mut self) -> Result<()> {
460 if let Some(filename) = &self.licensing_filename {
462 self.extra_files.add_file_entry(
463 filename,
464 FileEntry::new_from_data(
465 self.licensing.aggregate_license_document(false)?.as_bytes(),
466 false,
467 ),
468 )?;
469 }
470
471 Ok(())
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_dynamic_library_name() -> Result<()> {
481 assert_eq!(
482 LinkSharedLibraryPath {
483 library_path: "libpython3.9.so".into(),
484 linking_annotations: vec![],
485 }
486 .library_name()?,
487 "python3.9"
488 );
489
490 assert_eq!(
491 LinkSharedLibraryPath {
492 library_path: "libpython3.9.dylib".into(),
493 linking_annotations: vec![],
494 }
495 .library_name()?,
496 "python3.9"
497 );
498
499 assert_eq!(
500 LinkSharedLibraryPath {
501 library_path: "python3.dll".into(),
502 linking_annotations: vec![],
503 }
504 .library_name()?,
505 "python3"
506 );
507
508 Ok(())
509 }
510}