mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-18 17:50:18 +00:00
Support dotted function paths for script entrypoints (#1622)
Co-authored-by: markm <mark.mcmahon@autodesk.com> Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
4dfcf32e4c
commit
b76efc62a7
3 changed files with 32 additions and 10 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -43,9 +44,11 @@ impl Script {
|
||||||
// between the object reference and the left square bracket, between the extra names and the square brackets and colons delimiting them,
|
// between the object reference and the left square bracket, between the extra names and the square brackets and colons delimiting them,
|
||||||
// and after the right square bracket."
|
// and after the right square bracket."
|
||||||
// – https://packaging.python.org/en/latest/specifications/entry-points/#file-format
|
// – https://packaging.python.org/en/latest/specifications/entry-points/#file-format
|
||||||
let script_regex = Regex::new(r"^(?P<module>[\w\d_\-.]+)\s*:\s*(?P<function>[\w\d_\-.]+)(?:\s*\[\s*(?P<extras>(?:[^,]+,?\s*)+)\])?\s*$").unwrap();
|
static SCRIPT_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
|
Regex::new(r"^(?P<module>[\w\d_\-.]+)\s*:\s*(?P<function>[\w\d_\-.]+)(?:\s*\[\s*(?P<extras>(?:[^,]+,?\s*)+)\])?\s*$").unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
let captures = script_regex
|
let captures = SCRIPT_REGEX
|
||||||
.captures(value)
|
.captures(value)
|
||||||
.ok_or_else(|| Error::InvalidWheel(format!("invalid console script: '{value}'")))?;
|
.ok_or_else(|| Error::InvalidWheel(format!("invalid console script: '{value}'")))?;
|
||||||
if let Some(script_extras) = captures.name("extras") {
|
if let Some(script_extras) = captures.name("extras") {
|
||||||
|
@ -67,6 +70,12 @@ impl Script {
|
||||||
function: captures.name("function").unwrap().as_str().to_string(),
|
function: captures.name("function").unwrap().as_str().to_string(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn import_name(&self) -> &str {
|
||||||
|
self.function
|
||||||
|
.split_once('.')
|
||||||
|
.map_or(&self.function, |(import_name, _)| import_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -98,4 +107,15 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_of_import_name_from_function() {
|
||||||
|
let entrypoint = "foomod:mod_bar.sub_foo.func_baz";
|
||||||
|
|
||||||
|
let script = Script::from_value("script", entrypoint, None)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(script.function, "mod_bar.sub_foo.func_baz");
|
||||||
|
assert_eq!(script.import_name(), "mod_bar");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,13 @@ const LAUNCHER_AARCH64_CONSOLE: &[u8] =
|
||||||
/// Wrapper script template function
|
/// Wrapper script template function
|
||||||
///
|
///
|
||||||
/// <https://github.com/pypa/pip/blob/7f8a6844037fb7255cfd0d34ff8e8cf44f2598d4/src/pip/_vendor/distlib/scripts.py#L41-L48>
|
/// <https://github.com/pypa/pip/blob/7f8a6844037fb7255cfd0d34ff8e8cf44f2598d4/src/pip/_vendor/distlib/scripts.py#L41-L48>
|
||||||
fn get_script_launcher(module: &str, import_name: &str, shebang: &str) -> String {
|
fn get_script_launcher(entry_point: &Script, shebang: &str) -> String {
|
||||||
|
let Script {
|
||||||
|
module, function, ..
|
||||||
|
} = entry_point;
|
||||||
|
|
||||||
|
let import_name = entry_point.import_name();
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
r##"{shebang}
|
r##"{shebang}
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
@ -61,7 +67,7 @@ import sys
|
||||||
from {module} import {import_name}
|
from {module} import {import_name}
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
|
||||||
sys.exit({import_name}())
|
sys.exit({function}())
|
||||||
"##
|
"##
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -380,11 +386,7 @@ pub(crate) fn write_script_entrypoints(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the launcher script.
|
// Generate the launcher script.
|
||||||
let launcher_python_script = get_script_launcher(
|
let launcher_python_script = get_script_launcher(entrypoint, &get_shebang(location));
|
||||||
&entrypoint.module,
|
|
||||||
&entrypoint.function,
|
|
||||||
&get_shebang(location),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If necessary, wrap the launcher script in a Windows launcher binary.
|
// If necessary, wrap the launcher script in a Windows launcher binary.
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
|
|
|
@ -2682,7 +2682,7 @@ fn repeat_requirement() -> Result<()> {
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
Downloaded 1 package in [TIME]
|
Downloaded 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ anyio==4.2.0
|
+ anyio==4.3.0
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue