mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Add a newline after metadata when initializing scripts with other metadata blocks (#12501)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (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 cache | macos aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (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 / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
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 / build binary | windows aarch64 (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 linux (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 | 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 | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (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 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 opensuse (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 | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (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.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 (push) Blocked by required conditions
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (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 cache | macos aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (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 / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
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 / build binary | windows aarch64 (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 linux (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 | 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 | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (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 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 opensuse (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 | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (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.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 (push) Blocked by required conditions
## Summary uv doesn't separate the metadata block from other blocks when adding the `script` block to a script, which results in the next block being considered part of the script block and causes errors when running. See #12499 for more details. Closes #12499 ## Test Plan I manually tested the most common scenario, but there's a few edge cases that would be good to have tests for. I would have written the tests also, but I was running into errors like this: ```bash $ cargo test --package uv-scripts Compiling uv-configuration v0.0.1 (/home/merlin/Projects/uv/crates/uv-configuration) error: cannot find attribute `value` in this scope --> crates/uv-configuration/src/project_build_backend.rs:8:38 | 8 | #[cfg_attr(feature = "schemars", value(hide = true))] | ^^^^^ error: could not compile `uv-configuration` (lib) due to 1 previous error ``` --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
9e10f83ce7
commit
5b2a8abef9
1 changed files with 275 additions and 9 deletions
|
@ -158,7 +158,22 @@ impl Pep723Script {
|
|||
requires_python: &VersionSpecifiers,
|
||||
) -> Result<Self, Pep723Error> {
|
||||
let contents = fs_err::tokio::read(&file).await?;
|
||||
let (prelude, metadata, postlude) = Self::init_metadata(&contents, requires_python)?;
|
||||
Ok(Self {
|
||||
path: std::path::absolute(file)?,
|
||||
metadata,
|
||||
prelude,
|
||||
postlude,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates a default PEP 723 metadata table from the provided script contents.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0723/>
|
||||
pub fn init_metadata(
|
||||
contents: &[u8],
|
||||
requires_python: &VersionSpecifiers,
|
||||
) -> Result<(String, Pep723Metadata, String), Pep723Error> {
|
||||
// Define the default metadata.
|
||||
let default_metadata = indoc::formatdoc! {r#"
|
||||
requires-python = "{requires_python}"
|
||||
|
@ -168,19 +183,30 @@ impl Pep723Script {
|
|||
};
|
||||
let metadata = Pep723Metadata::from_str(&default_metadata)?;
|
||||
|
||||
// Extract the shebang and script content.
|
||||
let (shebang, postlude) = extract_shebang(&contents)?;
|
||||
// Extract the shebang and script content.
|
||||
let (shebang, postlude) = extract_shebang(contents)?;
|
||||
|
||||
Ok(Self {
|
||||
path: std::path::absolute(file)?,
|
||||
prelude: if shebang.is_empty() {
|
||||
// Add a newline to the beginning if it starts with a valid metadata comment line.
|
||||
let postlude = if postlude.strip_prefix('#').is_some_and(|postlude| {
|
||||
postlude
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|c| matches!(c, ' ' | '\r' | '\n'))
|
||||
}) {
|
||||
format!("\n{postlude}")
|
||||
} else {
|
||||
postlude
|
||||
};
|
||||
|
||||
Ok((
|
||||
if shebang.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{shebang}\n")
|
||||
},
|
||||
metadata,
|
||||
postlude,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a PEP 723 script at the given path.
|
||||
|
@ -554,7 +580,7 @@ fn serialize_metadata(metadata: &str) -> String {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{serialize_metadata, Pep723Error, ScriptTag};
|
||||
use crate::{serialize_metadata, Pep723Error, Pep723Script, ScriptTag};
|
||||
|
||||
#[test]
|
||||
fn missing_space() {
|
||||
|
@ -688,6 +714,7 @@ mod tests {
|
|||
assert_eq!(actual.metadata, expected_metadata);
|
||||
assert_eq!(actual.postlude, expected_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_comment() {
|
||||
let contents = indoc::indoc! {r"
|
||||
|
@ -751,7 +778,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_metadata_formatting() {
|
||||
fn serialize_metadata_formatting() {
|
||||
let metadata = indoc::indoc! {r"
|
||||
requires-python = '>=3.11'
|
||||
dependencies = [
|
||||
|
@ -775,11 +802,250 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_metadata_empty() {
|
||||
fn serialize_metadata_empty() {
|
||||
let metadata = "";
|
||||
let expected_output = "# /// script\n# ///\n";
|
||||
|
||||
let result = serialize_metadata(metadata);
|
||||
assert_eq!(result, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_empty() {
|
||||
let contents = "".as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
assert_eq!(postlude, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_with_hashbang() {
|
||||
let contents = indoc::indoc! {r#"
|
||||
#!/usr/bin/env python3
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
.as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "#!/usr/bin/env python3\n");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
assert_eq!(
|
||||
postlude,
|
||||
indoc::indoc! {r#"
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_with_other_metadata() {
|
||||
let contents = indoc::indoc! {r#"
|
||||
# /// noscript
|
||||
# Hello,
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
.as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
// Note the extra line at the beginning
|
||||
assert_eq!(
|
||||
postlude,
|
||||
indoc::indoc! {r#"
|
||||
|
||||
# /// noscript
|
||||
# Hello,
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_with_hashbang_and_other_metadata() {
|
||||
let contents = indoc::indoc! {r#"
|
||||
#!/usr/bin/env python3
|
||||
# /// noscript
|
||||
# Hello,
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
.as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "#!/usr/bin/env python3\n");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
// Note the extra line at the beginning.
|
||||
assert_eq!(
|
||||
postlude,
|
||||
indoc::indoc! {r#"
|
||||
|
||||
# /// noscript
|
||||
# Hello,
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_with_valid_metadata_line() {
|
||||
let contents = indoc::indoc! {r#"
|
||||
# Hello,
|
||||
# /// noscript
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
.as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
// Note the extra line at the beginning
|
||||
assert_eq!(
|
||||
postlude,
|
||||
indoc::indoc! {r#"
|
||||
|
||||
# Hello,
|
||||
# /// noscript
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_with_valid_empty_metadata_line() {
|
||||
let contents = indoc::indoc! {r#"
|
||||
#
|
||||
# /// noscript
|
||||
# Hello,
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
.as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
// Note the extra line at the beginning
|
||||
assert_eq!(
|
||||
postlude,
|
||||
indoc::indoc! {r#"
|
||||
|
||||
#
|
||||
# /// noscript
|
||||
# Hello,
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_init_with_non_metadata_comment() {
|
||||
let contents = indoc::indoc! {r#"
|
||||
#Hello,
|
||||
# /// noscript
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
.as_bytes();
|
||||
let (prelude, metadata, postlude) =
|
||||
Pep723Script::init_metadata(contents, &uv_pep440::VersionSpecifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(prelude, "");
|
||||
assert_eq!(
|
||||
metadata.raw,
|
||||
indoc::indoc! {r#"
|
||||
requires-python = ""
|
||||
dependencies = []
|
||||
"#}
|
||||
);
|
||||
assert_eq!(
|
||||
postlude,
|
||||
indoc::indoc! {r#"
|
||||
#Hello,
|
||||
# /// noscript
|
||||
#
|
||||
# World!
|
||||
# ///
|
||||
|
||||
print("Hello, world!")
|
||||
"#}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue