diff --git a/backend/src/hatchling/builders/wheel.py b/backend/src/hatchling/builders/wheel.py index 9bfd8408b..4b304d3ed 100644 --- a/backend/src/hatchling/builders/wheel.py +++ b/backend/src/hatchling/builders/wheel.py @@ -37,6 +37,10 @@ TIME_TUPLE = tuple[int, int, int, int, int, int] +# The Linux kernel only reads the first BINPRM_BUF_SIZE (256) bytes of a shebang +# line, so anything longer cannot be a functional shebang and is left untouched. +MAX_SHEBANG_LENGTH = 256 + class FileSelectionOptions(NamedTuple): include: list[str] @@ -162,6 +166,10 @@ def add_shared_file(self, shared_file: IncludedFile) -> tuple[str, str, str]: shared_file.distribution_path = f"{self.shared_data_directory}/data/{shared_file.distribution_path}" return self.add_file(shared_file) + def add_shared_script(self, shared_script: IncludedFile) -> tuple[str, str, str]: + shared_script.distribution_path = f"{self.shared_data_directory}/scripts/{shared_script.distribution_path}" + return self.add_file(shared_script) + def add_extra_metadata_file(self, extra_metadata_file: IncludedFile) -> tuple[str, str, str]: extra_metadata_file.distribution_path = ( f"{self.metadata_directory}/extra_metadata/{extra_metadata_file.distribution_path}" @@ -681,24 +689,28 @@ def add_shared_scripts(self, archive: WheelArchive, records: RecordFile, build_d for shared_script in self.recurse_explicit_files(shared_scripts): with open(shared_script.path, "rb") as f: - content = BytesIO() - for line in f: - # Ignore leading blank lines - if not line.strip(): - continue + prefix = f.read(2) - match = shebang.match(line) - if match is None: - content.write(line) + # A shebang is only honored on the first line starting at the very + # first byte, so anything else is kept as is. + if prefix != b"#!": + record = archive.add_shared_script(shared_script) + else: + line = prefix + f.readline(MAX_SHEBANG_LENGTH - len(prefix)) + if len(line) == MAX_SHEBANG_LENGTH and not line.endswith(b"\n"): + # The line is too long to be a functional shebang. + record = archive.add_shared_script(shared_script) + elif (match := shebang.match(line)) is None: + record = archive.add_shared_script(shared_script) else: + content = BytesIO() content.write(b"#!python") if remaining := match.group(1): content.write(remaining) + content.write(f.read()) - content.write(f.read()) - break + record = archive.write_shared_script(shared_script, content.getvalue()) - record = archive.write_shared_script(shared_script, content.getvalue()) records.write(record) def add_sboms(self, archive: WheelArchive, records: RecordFile, build_data: dict[str, Any]) -> None: diff --git a/tests/backend/builders/test_wheel.py b/tests/backend/builders/test_wheel.py index ecc59a08b..f9c737fbb 100644 --- a/tests/backend/builders/test_wheel.py +++ b/tests/backend/builders/test_wheel.py @@ -2088,7 +2088,6 @@ def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config (shared_data_path / "other_script.sh").write_text( helpers.dedent( """ - #!/bin/sh arg1 arg2 echo "Hello, World!" """ @@ -2097,7 +2096,6 @@ def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config (shared_data_path / "python_script.sh").write_text( helpers.dedent( """ - #!/usr/bin/env python3.11 arg1 arg2 print("Hello, World!") """ @@ -2106,7 +2104,6 @@ def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config (shared_data_path / "pythonw_script.sh").write_text( helpers.dedent( """ - #!/usr/bin/pythonw3.11 arg1 arg2 print("Hello, World!") """ @@ -2115,7 +2112,6 @@ def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config (shared_data_path / "pypy_script.sh").write_text( helpers.dedent( """ - #!/usr/bin/env pypy print("Hello, World!") """ @@ -2124,7 +2120,6 @@ def test_default_shared_scripts(self, hatch, platform, helpers, temp_dir, config (shared_data_path / "pypyw_script.sh").write_text( helpers.dedent( """ - #!pypyw3.11 arg1 arg2 print("Hello, World!") """ @@ -2199,7 +2194,6 @@ def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, (shared_data_path / "other_script.sh").write_text( helpers.dedent( """ - #!/bin/sh arg1 arg2 echo "Hello, World!" """ @@ -2208,7 +2202,6 @@ def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, (shared_data_path / "python_script.sh").write_text( helpers.dedent( """ - #!/usr/bin/env python3.11 arg1 arg2 print("Hello, World!") """ @@ -2217,7 +2210,6 @@ def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, (shared_data_path / "pythonw_script.sh").write_text( helpers.dedent( """ - #!/usr/bin/pythonw3.11 arg1 arg2 print("Hello, World!") """ @@ -2226,7 +2218,6 @@ def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, (shared_data_path / "pypy_script.sh").write_text( helpers.dedent( """ - #!/usr/bin/env pypy print("Hello, World!") """ @@ -2235,7 +2226,6 @@ def test_default_shared_scripts_from_build_data(self, hatch, platform, helpers, (shared_data_path / "pypyw_script.sh").write_text( helpers.dedent( """ - #!pypyw3.11 arg1 arg2 print("Hello, World!") """