1use crate::install::PythonInfo;
2use digest::Output;
3use rattler_conda_types::{
4 package::EntryPoint,
5 prefix_record::{PathType, PathsEntry},
6 Platform,
7};
8use rattler_digest::HashingWriter;
9use rattler_digest::Sha256;
10use std::{fs::File, io, io::Write, path::Path};
11
12use super::Prefix;
13
14pub fn get_windows_launcher(platform: &Platform) -> &'static [u8] {
16 match platform {
17 Platform::Win32 => unimplemented!("32 bit windows is not supported for entry points"),
18 Platform::Win64 => include_bytes!("../../resources/launcher64.exe"),
19 Platform::WinArm64 => unimplemented!("arm64 windows is not supported for entry points"),
20 _ => panic!("unsupported platform"),
21 }
22}
23
24pub fn create_windows_python_entry_point(
39 target_dir: &Prefix,
40 target_prefix: &str,
41 entry_point: &EntryPoint,
42 python_info: &PythonInfo,
43 target_platform: &Platform,
44) -> Result<[PathsEntry; 2], std::io::Error> {
45 let relative_path_script_py = python_info
47 .bin_dir
48 .join(format!("{}-script.py", &entry_point.command));
49
50 let script_path = target_dir.path().join(&relative_path_script_py);
52 std::fs::create_dir_all(
53 script_path
54 .parent()
55 .expect("since we joined with target_dir there must be a parent"),
56 )?;
57 let script_contents =
58 python_entry_point_template(target_prefix, true, entry_point, python_info);
59 let (hash, size) = write_and_hash(&script_path, script_contents)?;
60
61 let relative_path_script_exe = python_info
63 .bin_dir
64 .join(format!("{}.exe", &entry_point.command));
65
66 let launcher_bytes = get_windows_launcher(target_platform);
68 std::fs::write(
69 target_dir.path().join(&relative_path_script_exe),
70 launcher_bytes,
71 )?;
72
73 let fixed_launcher_digest = rattler_digest::parse_digest_from_hex::<rattler_digest::Sha256>(
74 "28b001bb9a72ae7a24242bfab248d767a1ac5dec981c672a3944f7a072375e9a",
75 )
76 .unwrap();
77
78 Ok([
79 PathsEntry {
80 relative_path: relative_path_script_py,
81 original_path: None,
83 path_type: PathType::WindowsPythonEntryPointScript,
84 no_link: false,
85 sha256: Some(hash),
86 sha256_in_prefix: None,
87 size_in_bytes: Some(size as _),
88 prefix_placeholder: None,
89 file_mode: None,
90 },
91 PathsEntry {
92 relative_path: relative_path_script_exe,
93 original_path: None,
94 path_type: PathType::WindowsPythonEntryPointExe,
95 no_link: false,
96 sha256: Some(fixed_launcher_digest),
97 sha256_in_prefix: None,
98 size_in_bytes: Some(launcher_bytes.len() as u64),
99 prefix_placeholder: None,
100 file_mode: None,
101 },
102 ])
103}
104
105pub fn create_unix_python_entry_point(
113 target_dir: &Prefix,
114 target_prefix: &str,
115 entry_point: &EntryPoint,
116 python_info: &PythonInfo,
117) -> Result<PathsEntry, std::io::Error> {
118 let relative_path = python_info.bin_dir.join(&entry_point.command);
120
121 let script_path = target_dir.path().join(&relative_path);
123 std::fs::create_dir_all(
124 script_path
125 .parent()
126 .expect("since we joined with target_dir there must be a parent"),
127 )?;
128 let script_contents =
129 python_entry_point_template(target_prefix, false, entry_point, python_info);
130 let (hash, size) = write_and_hash(&script_path, script_contents)?;
131
132 #[cfg(unix)]
134 std::fs::set_permissions(
135 script_path,
136 std::os::unix::fs::PermissionsExt::from_mode(0o775),
137 )?;
138
139 Ok(PathsEntry {
140 relative_path,
141 original_path: None,
143 path_type: PathType::UnixPythonEntryPoint,
144 no_link: false,
145 sha256: Some(hash),
146 sha256_in_prefix: None,
147 size_in_bytes: Some(size as _),
148 prefix_placeholder: None,
149 file_mode: None,
150 })
151}
152
153pub fn python_entry_point_template(
156 target_prefix: &str,
157 for_windows: bool,
158 entry_point: &EntryPoint,
159 python_info: &PythonInfo,
160) -> String {
161 let shebang = if for_windows {
163 String::new()
166 } else {
167 python_info.shebang(target_prefix)
168 };
169
170 let (import_name, _) = entry_point
172 .function
173 .split_once('.')
174 .unwrap_or((&entry_point.function, ""));
175
176 let module = &entry_point.module;
177 let func = &entry_point.function;
178 format!(
179 "{shebang}\n\
180 # -*- coding: utf-8 -*-\n\
181 import re\n\
182 import sys\n\n\
183 from {module} import {import_name}\n\n\
184 if __name__ == '__main__':\n\
185 \tsys.argv[0] = re.sub(r'(-script\\.pyw?|\\.exe)?$', '', sys.argv[0])\n\
186 \tsys.exit({func}())\n\
187 "
188 )
189}
190
191fn write_and_hash(path: &Path, contents: impl AsRef<[u8]>) -> io::Result<(Output<Sha256>, usize)> {
193 let bytes = contents.as_ref();
194 let mut writer = HashingWriter::<_, Sha256>::new(File::create(path)?);
195 writer.write_all(bytes)?;
196 let (_, hash) = writer.finalize();
197 Ok((hash, bytes.len()))
198}
199
200#[cfg(test)]
201mod test {
202 use crate::install::PythonInfo;
203 use rattler_conda_types::package::EntryPoint;
204 use rattler_conda_types::{Platform, Version};
205 use std::str::FromStr;
206
207 #[test]
208 fn test_entry_point_script() {
209 let script = super::python_entry_point_template(
210 "/prefix",
211 false,
212 &EntryPoint::from_str("jupyter-lab = jupyterlab.labapp:main").unwrap(),
213 &PythonInfo::from_version(
214 &Version::from_str("3.11.0").unwrap(),
215 None,
216 Platform::Linux64,
217 )
218 .unwrap(),
219 );
220 insta::assert_snapshot!(script);
221
222 let script = super::python_entry_point_template(
223 "/prefix",
224 true,
225 &EntryPoint::from_str("jupyter-lab = jupyterlab.labapp:main").unwrap(),
226 &PythonInfo::from_version(
227 &Version::from_str("3.11.0").unwrap(),
228 None,
229 Platform::Linux64,
230 )
231 .unwrap(),
232 );
233 insta::assert_snapshot!("windows", script);
234 }
235}