pyoxidizerlib/py_packaging/
distutils.rs1use {
10 anyhow::{Context, Result},
11 log::warn,
12 once_cell::sync::Lazy,
13 python_packaging::resource::{LibraryDependency, PythonExtensionModule},
14 serde::Deserialize,
15 simple_file_manifest::FileData,
16 std::{
17 collections::{BTreeMap, HashMap},
18 fs::{create_dir_all, read_dir, read_to_string},
19 path::{Path, PathBuf},
20 },
21};
22
23static MODIFIED_DISTUTILS_FILES: Lazy<BTreeMap<&'static str, &'static [u8]>> = Lazy::new(|| {
24 let mut res: BTreeMap<&'static str, &'static [u8]> = BTreeMap::new();
25
26 res.insert(
27 "command/build_ext.py",
28 include_bytes!("../distutils/command/build_ext.py"),
29 );
30 res.insert(
31 "_msvccompiler.py",
32 include_bytes!("../distutils/_msvccompiler.py"),
33 );
34 res.insert(
35 "unixccompiler.py",
36 include_bytes!("../distutils/unixccompiler.py"),
37 );
38
39 res
40});
41
42pub fn prepare_hacked_distutils(
56 orig_distutils_path: &Path,
57 dest_dir: &Path,
58 extra_python_paths: &[&Path],
59) -> Result<HashMap<String, String>> {
60 let extra_sys_path = dest_dir.join("packages");
61
62 warn!(
63 "installing modified distutils to {}",
64 extra_sys_path.display()
65 );
66
67 let dest_distutils_path = extra_sys_path.join("distutils");
68
69 for entry in walkdir::WalkDir::new(orig_distutils_path) {
70 let entry = entry?;
71
72 if entry.path().is_dir() {
73 continue;
74 }
75
76 let source_path = entry.path();
77 let rel_path = source_path
78 .strip_prefix(orig_distutils_path)
79 .with_context(|| format!("stripping prefix from {}", source_path.display()))?;
80 let dest_path = dest_distutils_path.join(rel_path);
81
82 let dest_dir = dest_path.parent().unwrap();
83 std::fs::create_dir_all(dest_dir)?;
84 std::fs::copy(source_path, &dest_path)?;
85 }
86
87 for (path, data) in MODIFIED_DISTUTILS_FILES.iter() {
88 let dest_path = dest_distutils_path.join(path);
89
90 warn!("modifying distutils/{} for oxidation", path);
91 std::fs::write(&dest_path, data)
92 .with_context(|| format!("writing {}", dest_path.display()))?;
93 }
94
95 let state_dir = dest_dir.join("pyoxidizer-build-state");
96 create_dir_all(&state_dir)?;
97
98 let mut python_paths = vec![extra_sys_path.display().to_string()];
99 python_paths.extend(extra_python_paths.iter().map(|p| p.display().to_string()));
100
101 let path_separator = if cfg!(windows) { ";" } else { ":" };
102
103 let python_path = python_paths.join(path_separator);
104
105 let mut res = HashMap::new();
106 res.insert("PYTHONPATH".to_string(), python_path);
107 res.insert(
108 "PYOXIDIZER_DISTUTILS_STATE_DIR".to_string(),
109 state_dir.display().to_string(),
110 );
111 res.insert("PYOXIDIZER".to_string(), "1".to_string());
112
113 Ok(res)
114}
115
116#[derive(Debug, Deserialize)]
117struct DistutilsExtensionState {
118 name: String,
119 objects: Vec<String>,
120 output_filename: String,
121 libraries: Vec<String>,
122 #[allow(dead_code)]
123 library_dirs: Vec<String>,
124 #[allow(dead_code)]
125 runtime_library_dirs: Vec<String>,
126}
127
128pub fn read_built_extensions(state_dir: &Path) -> Result<Vec<PythonExtensionModule>> {
129 let mut res = Vec::new();
130
131 let entries = read_dir(state_dir).context(format!(
132 "reading built extensions from {}",
133 state_dir.display()
134 ))?;
135
136 for entry in entries {
137 let entry = entry?;
138 let path = entry.path();
139 let file_name = path.file_name().unwrap().to_str().unwrap();
140
141 if !file_name.starts_with("extension.") || !file_name.ends_with(".json") {
142 continue;
143 }
144
145 let data = read_to_string(&path).context(format!("reading {}", path.display()))?;
146
147 let info: DistutilsExtensionState = serde_json::from_str(&data).context("parsing JSON")?;
148
149 let module_components: Vec<&str> = info.name.split('.').collect();
150 let final_name = module_components[module_components.len() - 1];
151 let init_fn = "PyInit_".to_string() + final_name;
152
153 let extension_path = PathBuf::from(&info.output_filename);
154
155 let extension_file_name = extension_path
157 .file_name()
158 .unwrap()
159 .to_string_lossy()
160 .to_string();
161
162 let extension_file_suffix = if let Some(idx) = extension_file_name.find('.') {
163 extension_file_name[idx..extension_file_name.len()].to_string()
164 } else {
165 extension_file_name
166 };
167
168 let extension_data = if let Ok(data) = std::fs::read(&extension_path) {
170 Some(FileData::Memory(data))
171 } else {
172 None
173 };
174
175 let mut object_file_data = Vec::new();
176
177 for object_path in &info.objects {
178 let path = PathBuf::from(object_path);
179 let data = std::fs::read(&path).context(format!("reading {}", path.display()))?;
180
181 object_file_data.push(FileData::Memory(data));
182 }
183
184 let link_libraries = info
185 .libraries
186 .iter()
187 .map(|l| LibraryDependency {
188 name: l.clone(),
189 static_library: None,
190 static_filename: None,
191 dynamic_library: None,
192 dynamic_filename: None,
193 framework: false,
194 system: false,
195 })
196 .collect();
197
198 res.push(PythonExtensionModule {
203 name: info.name.clone(),
204 init_fn: Some(init_fn),
205 extension_file_suffix,
206 shared_library: extension_data,
207 object_file_data,
208 is_package: final_name == "__init__",
209 link_libraries,
210 is_stdlib: false,
211 builtin_default: false,
212 required: false,
213 variant: None,
214 license: None,
215 });
216 }
217
218 Ok(res)
219}