Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/+staticx-aarch64-wheel-tag.infrastructure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Retag the staticx wheel built on Linux aarch64 as `manylinux2014_aarch64`, because uv 0.9.16 and newer reject its `manylinux1_aarch64` tag, which blocked building the aarch64 standalone executables.
84 changes: 82 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
import re
import string
import subprocess
import sys
import tempfile
import urllib.request

import nox

if sys.version_info >= (3, 11):
import tomllib
else: # nox depends on tomli on these Python versions
import tomli as tomllib

UPSTREAM_REPO_URL = 'git@github.com:Backblaze/B2_Command_Line_Tool.git'

CI = os.environ.get('CI') is not None
Expand Down Expand Up @@ -64,6 +72,10 @@ def _detect_python_nox_id() -> str:
SYSTEM = platform.system().lower()
MACHINE = platform.machine().lower()

# staticx tags wheels built from its sdist as manylinux1, which does not exist for aarch64 (PEP 599),
# so uv refuses to install them. Keep in sync with the `wheel` marker in the `bundle` group in pyproject.toml.
RETAG_STATICX_WHEEL = SYSTEM == 'linux' and MACHINE == 'aarch64'

WINDOWS_TIMESTAMP_SERVER = 'http://timestamp.digicert.com'
WINDOWS_SIGNTOOL_PATH = 'C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe'

Expand All @@ -84,6 +96,7 @@ def uv_install(
*,
groups: tuple[str, ...] = (),
extras: tuple[str, ...] = (),
no_install_packages: tuple[str, ...] = (),
editable: bool = False,
) -> None:
args = ['sync', '--locked']
Expand All @@ -95,6 +108,8 @@ def uv_install(
for extra in extras:
if extra:
args.extend(['--extra', extra])
for package in no_install_packages:
args.extend(['--no-install-package', package])
uv_env = getattr(session.virtualenv, 'location', os.getenv('VIRTUAL_ENV'))
session.run_install('uv', *args, env={'UV_PROJECT_ENVIRONMENT': uv_env})
if INSTALL_SDK_FROM:
Expand All @@ -107,6 +122,33 @@ def uv_install(
)


def get_locked_sdist(
package: str,
lock_file: pathlib.Path = pathlib.Path('./uv.lock'),
) -> tuple[str, str]:
"""Return the URL and the `<algorithm>:<digest>` hash of the package's sdist in uv.lock."""
lock = tomllib.loads(lock_file.read_text())
entries = [entry for entry in lock['package'] if entry['name'] == package]
if len(entries) != 1:
raise RuntimeError(f'Expected one {package} entry in {lock_file}, found {len(entries)}.')
sdist = entries[0]['sdist']
return sdist['url'], sdist['hash']


def download_verified(url: str, expected_hash: str, directory: pathlib.Path) -> pathlib.Path:
"""Download a file into the directory and fail unless it matches the `<algorithm>:<digest>` hash."""
algorithm, expected_digest = expected_hash.split(':', maxsplit=1)
path = directory / url.rsplit('/', maxsplit=1)[-1]
with urllib.request.urlopen(url, timeout=60) as response:
path.write_bytes(response.read())
[actual_hash] = _calculate_hashes(path, [algorithm])
if actual_hash.hexdigest() != expected_digest:
raise RuntimeError(
f'{path.name} has {algorithm} digest {actual_hash.hexdigest()}, expected {expected_digest}.'
)
return path


def github_output(name, value, *, secret=False):
gh_output_path = os.environ.get('GITHUB_OUTPUT')
if secret:
Expand Down Expand Up @@ -323,6 +365,43 @@ def dump_license(session: nox.Session):
session.run('b2', 'license', '--dump', '--with-packages')


def install_retagged_staticx(session: nox.Session) -> None:
"""Install staticx from its locked sdist, retagging the built wheel so that uv accepts it."""
sdist_url, sdist_hash = get_locked_sdist('staticx')
with tempfile.TemporaryDirectory() as tmp_dir:
sdist = download_verified(sdist_url, sdist_hash, pathlib.Path(tmp_dir))
wheel_dir = pathlib.Path(tmp_dir, 'wheels')
# Build without isolation, as `no-build-isolation-package` in pyproject.toml does for `uv sync`.
session.run_install(
'uv',
'build',
'--wheel',
'--no-build-isolation',
'--python',
session.virtualenv.location,
'--out-dir',
str(wheel_dir),
str(sdist),
)
# Empty when `--no-install` skipped the build; `run_install` then skips retagging as well.
built_wheels = [str(path) for path in wheel_dir.glob('*.whl')]
session.run_install(
'wheel', 'tags', '--remove', '--platform-tag', f'manylinux2014_{MACHINE}', *built_wheels
)
session.run_install(
'uv',
'pip',
'install',
'--no-deps',
'--no-index',
'--find-links',
str(wheel_dir),
'--python',
session.virtualenv.location,
'staticx',
)


@nox.session(python=PYTHON_DEFAULT_VERSION)
def bundle(session: nox.Session):
"""Bundle the distribution."""
Expand All @@ -337,7 +416,10 @@ def bundle(session: nox.Session):
session,
groups=('bundle',),
extras=('full',),
no_install_packages=('staticx',) if RETAG_STATICX_WHEEL else (),
)
if RETAG_STATICX_WHEEL:
install_retagged_staticx(session)

template_spec = string.Template(pathlib.Path('b2.spec.template').read_text())
versions = get_versions()
Expand Down Expand Up @@ -730,8 +812,6 @@ def load_allowed_change_types(
"""
Load the list of allowed change types from the pyproject.toml file.
"""
import tomllib

configuration = tomllib.loads(project_toml.read_text())
return set(entry['directory'] for entry in configuration['tool']['towncrier']['type'])

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ bundle = [
"patchelf-wrapper==1.2.0; platform_system == \"Linux\"",
"staticx~=0.14.1; platform_system == \"Linux\"",
"setuptools>=60,<80; platform_system == \"Linux\"", # required by non-isolated Linux bundle builds
"wheel~=0.48.0; platform_system == \"Linux\" and platform_machine == \"aarch64\"", # retags the staticx wheel, see noxfile.py
]

[tool.uv]
Expand Down
14 changes: 14 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading