use std::collections::HashMap; use std::io::{BufRead, BufReader, BufWriter, Cursor, Read, Seek, Write}; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::str::FromStr; use std::{env, io, iter}; use configparser::ini::Ini; use data_encoding::BASE64URL_NOPAD; use fs_err as fs; use fs_err::{DirEntry, File}; use mailparse::MailHeaderMap; use rustc_hash::{FxHashMap, FxHashSet}; use sha2::{Digest, Sha256}; use tempfile::tempdir; use tracing::{debug, error, instrument, warn}; use walkdir::WalkDir; use zip::result::ZipError; use zip::write::FileOptions; use zip::{ZipArchive, ZipWriter}; use distribution_filename::WheelFilename; use pep440_rs::Version; use pypi_types::DirectUrl; use uv_fs::Normalized; use uv_normalize::PackageName; use crate::install_location::{InstallLocation, LockedDir}; use crate::record::RecordEntry; use crate::script::Script; use crate::{find_dist_info, Error}; /// `#!/usr/bin/env python` pub const SHEBANG_PYTHON: &str = "#!/usr/bin/env python"; #[cfg(all(windows, target_arch = "x86_64"))] const LAUNCHER_X86_64_GUI: &[u8] = include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe"); #[cfg(all(windows, target_arch = "x86_64"))] const LAUNCHER_X86_64_CONSOLE: &[u8] = include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-x86_64-console.exe"); #[cfg(all(windows, target_arch = "aarch64"))] const LAUNCHER_AARCH64_GUI: &[u8] = include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-aarch64-gui.exe"); #[cfg(all(windows, target_arch = "aarch64"))] const LAUNCHER_AARCH64_CONSOLE: &[u8] = include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-aarch64-console.exe"); /// Wrapper script template function /// /// fn get_script_launcher(entry_point: &Script, shebang: &str) -> String { let Script { module, function, .. } = entry_point; let import_name = entry_point.import_name(); format!( r##"{shebang} # -*- coding: utf-8 -*- import re import sys from {module} import {import_name} if __name__ == "__main__": sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) sys.exit({function}()) "## ) } /// Part of entrypoints parsing pub(crate) fn read_scripts_from_section( scripts_section: &HashMap>, section_name: &str, extras: Option<&[String]>, ) -> Result, Error> { let mut scripts = Vec::new(); for (script_name, python_location) in scripts_section { match python_location { Some(value) => { if let Some(script) = Script::from_value(script_name, value, extras)? { scripts.push(script); } } None => { return Err(Error::InvalidWheel(format!( "[{section_name}] key {script_name} must have a value" ))); } } } Ok(scripts) } /// Parses the `entry_points.txt` entry in the wheel for console scripts /// /// Returns (`script_name`, module, function) /// /// Extras are supposed to be ignored, which happens if you pass None for extras fn parse_scripts( archive: &mut ZipArchive, dist_info_dir: &str, extras: Option<&[String]>, ) -> Result<(Vec