Skip to content

Commit

Permalink
Fix entry points in the pre-built distribution that binaries use (#1505)
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek authored May 22, 2024
1 parent ae8c3fb commit 32c667e
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 2 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/build-distributions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ jobs:
/home/python/bin/python -m pip install
${{ inputs.version && format('hatch=={0}', inputs.version) || '/home/hatch' }}
- name: Make scripts portable
run: >-
docker exec builder
/home/python/bin/python /home/hatch/release/unix/make_scripts_portable.py
- name: Strip debug symbols
run: >-
docker exec builder
Expand Down Expand Up @@ -155,6 +160,11 @@ jobs:
-m pip install
${{ inputs.version && format('hatch=={0}', inputs.version) || '.' }}
- name: Make scripts portable
run: >-
${{ startsWith(matrix.job.os, 'windows-') && '.\\python\\python.exe' || './python/bin/python' }}
release/${{ startsWith(matrix.job.os, 'windows-') && 'windows' || 'unix' }}/make_scripts_portable.py
- name: Strip debug symbols
if: startsWith(matrix.job.os, 'macos-')
run: find python -name '*.so' | xargs strip -S
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-hatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ jobs:
skip-existing: true

binaries:
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
name: Binary ${{ matrix.job.target }} (${{ matrix.job.os }})
needs:
- python-artifacts
runs-on: ${{ matrix.job.os }}
Expand Down
4 changes: 4 additions & 0 deletions docs/history/hatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

***Fixed:***

- Fix entry points in the pre-built distribution that binaries use

## [1.11.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.11.0) - 2024-05-14 ## {: #hatch-v1.11.0 }

***Added:***
Expand Down
49 changes: 49 additions & 0 deletions release/unix/make_scripts_portable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations

import sys
import sysconfig
from io import BytesIO
from pathlib import Path


def main():
interpreter = Path(sys.executable).resolve()

# https://github.com/indygreg/python-build-standalone/blob/20240415/cpython-unix/build-cpython.sh#L812-L813
portable_shebang = b'#!/bin/sh\n"exec" "$(dirname $0)/%s" "$0" "$@"\n' % interpreter.name.encode()

scripts_dir = Path(sysconfig.get_path('scripts'))
for script in scripts_dir.iterdir():
if not script.is_file():
continue

with script.open('rb') as f:
data = BytesIO()
for line in f:
# Ignore leading blank lines
if not line.strip():
continue

# Ignore binaries
if not line.startswith(b'#'):
break

if line.startswith(b'#!%s' % interpreter.parent):
executable = Path(line[2:].rstrip().decode()).resolve()
data.write(portable_shebang if executable == interpreter else line)
else:
data.write(line)

data.write(f.read())
break

contents = data.getvalue()
if not contents:
continue

with script.open('wb') as f:
f.write(contents)


if __name__ == '__main__':
main()
92 changes: 92 additions & 0 deletions release/windows/make_scripts_portable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import annotations

import sys
import sysconfig
from contextlib import closing
from importlib.metadata import entry_points
from io import BytesIO
from os.path import relpath
from pathlib import Path
from tempfile import TemporaryDirectory
from urllib.request import urlopen
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo

LAUNCHERS_URL = 'https://raw.githubusercontent.com/astral-sh/uv/main/crates/uv-trampoline/trampolines'
SCRIPT_TEMPLATE = """\
#!{executable}
# -*- 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}())
"""


def select_entry_points(ep, group):
return ep.select(group=group) if sys.version_info[:2] >= (3, 10) else ep.get(group, [])


def fetch_launcher(launcher_name):
with urlopen(f'{LAUNCHERS_URL}/{launcher_name}') as f: # noqa: S310
return f.read()


def main():
interpreters_dir = Path(sys.executable).parent
scripts_dir = Path(sysconfig.get_path('scripts'))

ep = entry_points()
for group, interpreter_name, launcher_name in (
('console_scripts', 'python.exe', 'uv-trampoline-x86_64-console.exe'),
('gui_scripts', 'pythonw.exe', 'uv-trampoline-x86_64-gui.exe'),
):
interpreter = interpreters_dir / interpreter_name
relative_interpreter_path = relpath(interpreter, scripts_dir)
launcher_data = fetch_launcher(launcher_name)

for script in select_entry_points(ep, group):
# https://github.com/astral-sh/uv/tree/main/crates/uv-trampoline#how-do-you-use-it
with closing(BytesIO()) as buf:
# Launcher
buf.write(launcher_data)

# Zipped script
with TemporaryDirectory() as td:
zip_path = Path(td) / 'script.zip'
with ZipFile(zip_path, 'w') as zf:
# Ensure reproducibility
zip_info = ZipInfo('__main__.py', (2020, 2, 2, 0, 0, 0))
zip_info.external_attr = (0o644 & 0xFFFF) << 16

module, _, attrs = script.value.partition(':')
contents = SCRIPT_TEMPLATE.format(
executable=relative_interpreter_path,
module=module,
import_name=attrs.split('.')[0],
function=attrs,
)
zf.writestr(zip_info, contents, compress_type=ZIP_DEFLATED)

buf.write(zip_path.read_bytes())

# Interpreter path
interpreter_path = relative_interpreter_path.encode('utf-8')
buf.write(interpreter_path)

# Interpreter path length
interpreter_path_length = len(interpreter_path).to_bytes(4, 'little')
buf.write(interpreter_path_length)

# Magic number
buf.write(b'UVUV')

script_data = buf.getvalue()

script_path = scripts_dir / f'{script.name}.exe'
script_path.write_bytes(script_data)


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ignore = [
"backend/src/hatchling/bridge/app.py" = ["T201"]
"backend/tests/downstream/integrate.py" = ["INP001", "T201"]
"docs/.hooks/*" = ["INP001", "T201"]
"release/macos/build_pkg.py" = ["INP001"]
"release/**/*" = ["INP001"]

[lint.isort]
known-first-party = ["hatch", "hatchling"]

0 comments on commit 32c667e

Please sign in to comment.