Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ targets:
checksums:
- algorithm: sha256
format: hex
- name: pypi
- name: sentry-pypi
internalPypiRepo: getsentry/pypi
- name: docker
id: Docker Hub (release)
source: ghcr.io/getsentry/sentry-cli
Expand All @@ -115,3 +118,5 @@ requireNames:
- /^sentry-cli-Windows-i686.exe$/
- /^sentry-cli-Windows-x86_64.exe$/
- /^sentry-cli-Windows-aarch64.exe$/
- /^sentry_cli-.*.tar.gz$/
- /^sentry_cli-.*.whl$/
45 changes: 44 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,49 @@ jobs:
path: '*.tgz'
if-no-files-found: 'error'

python-base:
name: python (base)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0
- name: Add Rustup Target
run: rustup target add x86_64-unknown-linux-musl
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
with:
python-version: '3.11'
- run: python3 -m pip install build && python3 -m build
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2
with:
name: python-base
path: dist/*
if-no-files-found: 'error'

python:
name: python
runs-on: ubuntu-24.04
needs: [linux, sign-macos-binaries, windows, python-base]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
with:
python-version: '3.11'
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # 5.0.0
with:
pattern: artifact-bin-*
merge-multiple: true
path: binaries
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # 5.0.0
with:
name: python-base
merge-multiple: true
path: python-base
- run: scripts/wheels --binaries binaries --base python-base --dest dist
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2
with:
name: artifact-pkg-python
path: dist/*
if-no-files-found: 'error'

npm-distributions:
name: 'Build NPM distributions'
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -350,7 +393,7 @@ jobs:
merge:
name: Create Release Artifact
runs-on: ubuntu-24.04
needs: [linux, sign-macos-binaries, windows, npm-distributions, node]
needs: [linux, sign-macos-binaries, windows, npm-distributions, node, python]
steps:
- uses: actions/upload-artifact/merge@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2
with:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build-system]
requires = ["setuptools", "wheel", "setuptools-rust"]
179 changes: 179 additions & 0 deletions scripts/wheels
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
import argparse
import base64
import hashlib
import os.path
import shutil
import tempfile
import zipfile
from typing import NamedTuple


class Wheel(NamedTuple):
src: str
plat: str
exe: str = 'sentry-cli'


WHEELS = (
Wheel(
src='sentry-cli-Darwin-arm64',
plat='macosx_11_0_arm64',
),
Wheel(
src='sentry-cli-Darwin-universal',
plat='macosx_11_0_universal2',
),
Wheel(
src='sentry-cli-Darwin-x86_64',
plat='macosx_10_15_x86_64',
),
Wheel(
src='sentry-cli-Linux-aarch64',
plat='manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_2_aarch64',
),
Wheel(
src='sentry-cli-Linux-armv7',
plat='manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_2_armv7l',
),
Wheel(
src='sentry-cli-Linux-i686',
plat='manylinux_2_17_i686.manylinux2014_i686.musllinux_1_2_i686',
),
Wheel(
src='sentry-cli-Linux-x86_64',
plat='manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_2_x86_64',
),
Wheel(
src='sentry-cli-Windows-i686.exe',
plat='win32',
exe='sentry-cli.exe',
),
Wheel(
src='sentry-cli-Windows-x86_64.exe',
plat='win_amd64',
exe='sentry-cli.exe',
),
Wheel(
src='sentry-cli-Windows-aarch64.exe',
plat='win_arm64',
exe='sentry-cli.exe',
),
)


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--binaries', required=True)
parser.add_argument('--base', required=True)
parser.add_argument('--dest', required=True)
args = parser.parse_args()

expected = {wheel.src for wheel in WHEELS}
received = set(os.listdir(args.binaries))
if expected < received:
raise SystemExit(
f'Unexpected binaries:\n\n'
f'- extra: {", ".join(sorted(received - expected))}\n'
f'- missing: {", ".join(sorted(expected - received))}'
)

sdist_path = wheel_path = None
for fname in os.listdir(args.base):
if fname.endswith('.tar.gz'):
sdist_path = os.path.join(args.base, fname)
elif fname.endswith('.whl'):
wheel_path = os.path.join(args.base, fname)
else:
raise SystemExit(f'unexpected file in `--base`: {fname}')

if sdist_path is None or wheel_path is None:
raise SystemExit('expected wheel and sdist in `--base`')

os.makedirs(args.dest, exist_ok=True)
shutil.copy(sdist_path, args.dest)

for wheel in WHEELS:
binary_src = os.path.join(args.binaries, wheel.src)
binary_size = os.stat(binary_src).st_size
with open(binary_src, 'rb') as bf:
digest = hashlib.sha256(bf.read()).digest()
digest_b64 = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()

basename = os.path.basename(wheel_path)
wheelname, _ = os.path.splitext(basename)
name, version, py, abi, plat = wheelname.split('-')

with tempfile.TemporaryDirectory() as tmp:
with zipfile.ZipFile(wheel_path) as zipf:
zipf.extractall(tmp)

distinfo = os.path.join(tmp, f'{name}-{version}.dist-info')
scripts = os.path.join(tmp, f'{name}-{version}.data', 'scripts')

# replace the script binary with our copy
os.remove(os.path.join(scripts, 'sentry-cli'))
shutil.copy(binary_src, os.path.join(scripts, wheel.exe))

# rewrite RECORD to include the new file
record_fname = os.path.join(distinfo, 'RECORD')
with open(record_fname) as f:
record_lines = list(f)

record = f'{name}-{version}.data/scripts/sentry-cli,'
for i, line in enumerate(record_lines):
if line.startswith(record):
record_lines[i] = (
f'{name}-{version}.data/scripts/{wheel.exe},'
f'sha256={digest_b64},'
f'{binary_size}\n'
)
break
else:
raise SystemExit(f'could not find {record!r} in RECORD')

with open(record_fname, 'w') as f:
f.writelines(record_lines)

# rewrite WHEEL to have the new tags
wheel_fname = os.path.join(distinfo, 'WHEEL')
with open(wheel_fname) as f:
wheel_lines = list(f)

for i, line in enumerate(wheel_lines):
if line.startswith('Tag: '):
wheel_lines[i:i + 1] = [
f'Tag: {py}-{abi}-{plat}\n'
for plat in wheel.plat.split('.')
]
break
else:
raise SystemExit("could not find 'Tag: ' in WHEEL")

with open(wheel_fname, 'w') as f:
f.writelines(wheel_lines)

# write out the final zip
new_basename = f'{name}-{version}-{py}-{abi}-{wheel.plat}.whl'
tmp_new_wheel = os.path.join(tmp, new_basename)
fnames = sorted(
os.path.join(root, fname)
for root, _, fnames in os.walk(tmp)
for fname in fnames
)
with zipfile.ZipFile(tmp_new_wheel, 'w') as zipf:
for fname in fnames:
zinfo = zipfile.ZipInfo(os.path.relpath(fname, tmp))
if '/scripts/' in zinfo.filename:
zinfo.external_attr = 0o100755 << 16
with open(fname, 'rb') as fb:
zipf.writestr(zinfo, fb.read())

# move into dest
shutil.move(tmp_new_wheel, args.dest)

return 0


if __name__ == '__main__':
raise SystemExit(main())
21 changes: 21 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[metadata]
name = sentry_cli
description = A command line utility to work with Sentry.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/getsentry/sentry-cli
author = Sentry
author_email = oss@sentry.io
license = BSD-3-Clause
license_file = LICENSE
classifiers =
License :: OSI Approved :: BSD License
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy

[options]
packages =
py_modules =
python_requires = >=3.7
26 changes: 26 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from setuptools import setup
from setuptools_rust import RustBin
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel

with open('Cargo.toml') as f:
for line in f:
if line.startswith('version = "'):
_, VERSION, _ = line.split('"')
break


class bdist_wheel(_bdist_wheel):
def finalize_options(self):
super().finalize_options()
self.root_is_pure = False

def get_tag(self):
_, _, plat = super().get_tag()
return 'py3', 'none', plat


setup(
version=VERSION,
rust_extensions=[RustBin("sentry-cli")],
cmdclass={'bdist_wheel': bdist_wheel},
)