mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
handle an existing shebang in uv init --script
(#14141)
Some checks are pending
CI / lint (push) Waiting to run
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / check system | pyston (push) Blocked by required conditions
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
Some checks are pending
CI / lint (push) Waiting to run
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / check system | pyston (push) Blocked by required conditions
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
Closes https://github.com/astral-sh/uv/issues/14085.
This commit is contained in:
parent
c3e4b63806
commit
cc8d5a9215
6 changed files with 88 additions and 8 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -5742,6 +5742,7 @@ dependencies = [
|
|||
"fs-err 3.1.1",
|
||||
"indoc",
|
||||
"memchr",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
|
@ -5751,6 +5752,7 @@ dependencies = [
|
|||
"uv-pypi-types",
|
||||
"uv-redacted",
|
||||
"uv-settings",
|
||||
"uv-warnings",
|
||||
"uv-workspace",
|
||||
]
|
||||
|
||||
|
|
|
@ -16,11 +16,13 @@ uv-pep508 = { workspace = true }
|
|||
uv-pypi-types = { workspace = true }
|
||||
uv-redacted = { workspace = true }
|
||||
uv-settings = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
uv-workspace = { workspace = true }
|
||||
|
||||
fs-err = { workspace = true, features = ["tokio"] }
|
||||
indoc = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
|
|
@ -14,6 +14,7 @@ use uv_pep508::PackageName;
|
|||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
use uv_redacted::DisplaySafeUrl;
|
||||
use uv_settings::{GlobalOptions, ResolverInstallerOptions};
|
||||
use uv_warnings::warn_user;
|
||||
use uv_workspace::pyproject::Sources;
|
||||
|
||||
static FINDER: LazyLock<Finder> = LazyLock::new(|| Finder::new(b"# /// script"));
|
||||
|
@ -238,11 +239,25 @@ impl Pep723Script {
|
|||
let metadata = serialize_metadata(&default_metadata);
|
||||
|
||||
let script = if let Some(existing_contents) = existing_contents {
|
||||
let (mut shebang, contents) = extract_shebang(&existing_contents)?;
|
||||
if !shebang.is_empty() {
|
||||
shebang.push_str("\n#\n");
|
||||
// If the shebang doesn't contain `uv`, it's probably something like
|
||||
// `#! /usr/bin/env python`, which isn't going to respect the inline metadata.
|
||||
// Issue a warning for users who might not know that.
|
||||
// TODO: There are a lot of mistakes we could consider detecting here, like
|
||||
// `uv run` without `--script` when the file doesn't end in `.py`.
|
||||
if !regex::Regex::new(r"\buv\b").unwrap().is_match(&shebang) {
|
||||
warn_user!(
|
||||
"If you execute {} directly, it might ignore its inline metadata.\nConsider replacing its shebang with: {}",
|
||||
file.to_string_lossy().cyan(),
|
||||
"#!/usr/bin/env -S uv run --script".cyan(),
|
||||
);
|
||||
}
|
||||
}
|
||||
indoc::formatdoc! {r"
|
||||
{metadata}
|
||||
{content}
|
||||
",
|
||||
content = String::from_utf8(existing_contents).map_err(|err| Pep723Error::Utf8(err.utf8_error()))?}
|
||||
{shebang}{metadata}
|
||||
{contents}" }
|
||||
} else {
|
||||
indoc::formatdoc! {r#"
|
||||
{metadata}
|
||||
|
|
|
@ -24,7 +24,7 @@ pub fn disable() {
|
|||
/// Warn a user, if warnings are enabled.
|
||||
#[macro_export]
|
||||
macro_rules! warn_user {
|
||||
($($arg:tt)*) => {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::anstream::eprintln;
|
||||
use $crate::owo_colors::OwoColorize;
|
||||
|
||||
|
@ -33,7 +33,7 @@ macro_rules! warn_user {
|
|||
let formatted = message.bold();
|
||||
eprintln!("{}{} {formatted}", "warning".yellow().bold(), ":".bold());
|
||||
}
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
pub static WARNINGS: LazyLock<Mutex<FxHashSet<String>>> = LazyLock::new(Mutex::default);
|
||||
|
@ -42,7 +42,7 @@ pub static WARNINGS: LazyLock<Mutex<FxHashSet<String>>> = LazyLock::new(Mutex::d
|
|||
/// message.
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once {
|
||||
($($arg:tt)*) => {
|
||||
($($arg:tt)*) => {{
|
||||
use $crate::anstream::eprintln;
|
||||
use $crate::owo_colors::OwoColorize;
|
||||
|
||||
|
@ -54,5 +54,5 @@ macro_rules! warn_user_once {
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
|
|
@ -929,6 +929,65 @@ fn init_script_file_conflicts() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Init script should not trash an existing shebang.
|
||||
#[test]
|
||||
fn init_script_shebang() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let script_path = context.temp_dir.child("script.py");
|
||||
|
||||
let contents = "#! /usr/bin/env python3\nprint(\"Hello, world!\")";
|
||||
fs_err::write(&script_path, contents)?;
|
||||
uv_snapshot!(context.filters(), context.init().arg("--script").arg("script.py"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: If you execute script.py directly, it might ignore its inline metadata.
|
||||
Consider replacing its shebang with: #!/usr/bin/env -S uv run --script
|
||||
Initialized script at `script.py`
|
||||
");
|
||||
let resulting_script = fs_err::read_to_string(&script_path)?;
|
||||
assert_snapshot!(resulting_script, @r#"
|
||||
#! /usr/bin/env python3
|
||||
#
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
# dependencies = []
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#
|
||||
);
|
||||
|
||||
// If the shebang already contains `uv`, the result is the same, but we suppress the warning.
|
||||
let contents = "#!/usr/bin/env -S uv run --script\nprint(\"Hello, world!\")";
|
||||
fs_err::write(&script_path, contents)?;
|
||||
uv_snapshot!(context.filters(), context.init().arg("--script").arg("script.py"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Initialized script at `script.py`
|
||||
");
|
||||
let resulting_script = fs_err::read_to_string(&script_path)?;
|
||||
assert_snapshot!(resulting_script, @r#"
|
||||
#!/usr/bin/env -S uv run --script
|
||||
#
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
# dependencies = []
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init --lib` with an existing py.typed file
|
||||
#[test]
|
||||
fn init_py_typed_exists() -> Result<()> {
|
||||
|
|
|
@ -241,10 +241,12 @@ Declaration of dependencies is also supported in this context, for example:
|
|||
|
||||
```python title="example"
|
||||
#!/usr/bin/env -S uv run --script
|
||||
#
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
# dependencies = ["httpx"]
|
||||
# ///
|
||||
|
||||
import httpx
|
||||
|
||||
print(httpx.get("https://example.com"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue