From c763800143bf70b0fe62ad3d796c11ea885efa7b Mon Sep 17 00:00:00 2001 From: Shohei KAMON Date: Sun, 27 Apr 2025 16:45:46 +0800 Subject: [PATCH 1/5] add workflows/publish-homebrew.yml Signed-off-by: Shohei KAMON --- .github/workflows/publish-homebrew.yml | 79 +++++++++++++++++++ .../workflows/{pypi.yml => publish-pypi.yml} | 24 +++++- docs/devops/release_workflow.md | 37 +++++++++ pyproject.toml | 2 +- 4 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/publish-homebrew.yml rename .github/workflows/{pypi.yml => publish-pypi.yml} (68%) create mode 100644 docs/devops/release_workflow.md diff --git a/.github/workflows/publish-homebrew.yml b/.github/workflows/publish-homebrew.yml new file mode 100644 index 0000000..59ef441 --- /dev/null +++ b/.github/workflows/publish-homebrew.yml @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2025 Ethersecurity Inc. +# +# SPDX-License-Identifier: MPL-2.0 +# Author: Shohei KAMON + +name: Publish to Homebrew + +on: + push: + tags: + - 'v*' # Trigger on any tag push + workflow_dispatch: + +jobs: + build-and-publish: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: macos-13 + arch: x86_64 + - os: macos-14 + arch: arm64 + - os: ubuntu-latest + arch: amd64 # WSL Linux build + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install pyinstaller + run: | + python -m pip install pyinstaller + + - name: Build Intel binary (x86_64) with PyInstaller + if: matrix.arch == 'x86_64' + run: | + pyinstaller --onefile --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py + mkdir -p release/fireblocks-cli-${{ github.ref_name }}-x86_64 + mv dist/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-x86_64/fireblocks-cli + tar -czvf fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-x86_64 + if command -v sha256sum; then sha256sum fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz.sha256; else shasum -a 256 fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz.sha256; fi + + - name: Build ARM binary (arm64) with PyInstaller + if: matrix.arch == 'arm64' + run: | + pyinstaller --onefile --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py + mkdir -p release/fireblocks-cli-${{ github.ref_name }}-arm64 + mv dist/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-arm64/fireblocks-cli + tar -czvf fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-arm64 + if command -v sha256sum; then sha256sum fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz.sha256; else shasum -a 256 fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz.sha256; fi + + - name: Build Linux binary (amd64) with PyInstaller (for WSL) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libx11-dev libxext-dev libxrender-dev libxrandr-dev libpng-dev + pyinstaller --onefile --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py + mkdir -p release/fireblocks-cli-${{ github.ref_name }}-amd64 + mv dist/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-amd64/fireblocks-cli + tar -czvf fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-amd64 + sha256sum fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz > fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz.sha256 + + - name: Upload to GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: | + fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz + fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz + fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz + fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz.sha256 + fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz.sha256 + fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz.sha256 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pypi.yml b/.github/workflows/publish-pypi.yml similarity index 68% rename from .github/workflows/pypi.yml rename to .github/workflows/publish-pypi.yml index cd66123..5e11f71 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -8,7 +8,7 @@ name: Publish to PyPI on: push: tags: - - 'v*' # v1.0.0 のようなタグで実行 + - 'v*' # Trigger on any tag push workflow_dispatch: jobs: @@ -33,14 +33,30 @@ jobs: python -m pip install --upgrade pip pip install --upgrade setuptools wheel build twine - - name: Build and publish + - name: Check version format + id: check_version + run: | + if [[ "${GITHUB_REF##*/}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Valid version tag detected: ${GITHUB_REF##*/}" + echo "VERSION_VALID=true" >> $GITHUB_ENV + else + echo "Invalid version tag detected: ${GITHUB_REF##*/}" + echo "VERSION_VALID=false" >> $GITHUB_ENV + fi + + - name: Build for PyPI + run: | + python -m build + + - name: publish to PyPI + if: env.VERSION_VALID == 'true' env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | - python -m build twine upload --repository pypi dist/* + - name: Generate SHA256 run: | sha256sum dist/*.tar.gz > dist/fireblocks-cli-${GITHUB_REF##*/}.tar.gz.sha256 @@ -52,6 +68,6 @@ jobs: dist/*.tar.gz dist/*.tar.gz.sha256 generate_release_notes: true - create_release: true + create_release: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/devops/release_workflow.md b/docs/devops/release_workflow.md new file mode 100644 index 0000000..54bc955 --- /dev/null +++ b/docs/devops/release_workflow.md @@ -0,0 +1,37 @@ + + + +# Release Workflow for fireblocks-cli + +This document outlines the release process for the **fireblocks-cli** project, covering both **PyPI** and **Homebrew** releases, and how they are handled through GitHub Actions. + +## Overview + +When a tag (e.g., `v0.1.8`) is pushed, the following actions are triggered: + +1. **PyPI release**: The Python package is built and uploaded to **PyPI** (source tar.gz and wheel). +2. **Homebrew release**: Binary files for **Intel** and **ARM** Macs are built using **PyInstaller** and uploaded to **GitHub Releases**. + +## Workflow Steps + +1. **Trigger**: A tag is pushed (e.g., `v0.1.8`) to GitHub. +2. **GitHub Actions**: + - **publish-pypi.yml**: This workflow builds the Python package and uploads it to PyPI. + - **publish-homebrew.yml**: This workflow builds binaries (Intel/ARM) using `pyinstaller` and uploads them to GitHub Releases. + + +# Additional Notes + +- The process can be automated completely, minimizing manual steps for each release. +- This workflow is designed for seamless integration with both **Homebrew** and **PyPI**, making it easy for developers and users alike to install the tool. + +--- + +# Future Improvements + +- Add version management for Homebrew (automatic update of version on Homebrew tap). +- Enhance testing and validation steps for both PyPI and Homebrew releases. diff --git a/pyproject.toml b/pyproject.toml index a9fdce9..ed321db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ [project] name = "fireblocks-cli" -version = "0.1.8" +version = "0.1.9" description = "An unofficial CLI for managing Fireblocks services." authors = [{ name = "Kamon Shohei", email = "cameong@stir.network" }] readme = "README.md" From 2f571de73ba94070540f8e17a64d4e3679b5ca85 Mon Sep 17 00:00:00 2001 From: Shohei KAMON Date: Mon, 28 Apr 2025 00:34:31 +0800 Subject: [PATCH 2/5] Sync __init__.py with pyproject Signed-off-by: Shohei KAMON --- .pre-commit-config.yaml | 7 ++++ fireblocks_cli/__init__.py | 4 +- scripts/sync_init_with_pyproject.py | 60 +++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 scripts/sync_init_with_pyproject.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27159e2..0847841 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,6 +20,13 @@ repos: pass_filenames: false always_run: true stages: [commit] + - repo: local + hooks: + - id: update-__init__.py + name: Sync __init__.py with pyproject.toml + entry: python ./scripts/sync_init_with_pyproject.py + language: python + files: pyproject.toml - repo: local hooks: - id: add-spdx diff --git a/fireblocks_cli/__init__.py b/fireblocks_cli/__init__.py index d4a2139..c4046af 100644 --- a/fireblocks_cli/__init__.py +++ b/fireblocks_cli/__init__.py @@ -4,7 +4,5 @@ # Author: Shohei KAMON -from importlib.metadata import version as get_version - -__version__ = get_version(__name__) +__version__ = "0.1.9" diff --git a/scripts/sync_init_with_pyproject.py b/scripts/sync_init_with_pyproject.py new file mode 100644 index 0000000..28db3b5 --- /dev/null +++ b/scripts/sync_init_with_pyproject.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2025 Ethersecurity Inc. +# +# SPDX-License-Identifier: MPL-2.0 +# Author: Shohei KAMON + +import toml +import re + + +def update_version( + init_path: str = "fireblocks_cli/__init__.py", + pyproject_path: str = "pyproject.toml", +) -> None: + """pyproject.tomlのversionと__init__.pyのversionを同期する""" + + # 1. pyproject.toml を読み込む + with open(pyproject_path, "r") as f: + pyproject = toml.load(f) + pyproject_version = pyproject["project"]["version"] + + # 2. __init__.py を読み込む + with open(init_path, "r") as f: + lines = f.readlines() + + # 3. __init__.pyの中の__version__を探す + current_version = None + for line in lines: + match = re.match(r'^__version__\s*=\s*[\'"]([^\'"]+)[\'"]', line.strip()) + if match: + current_version = match.group(1) + break + + # 4. バージョンが同じなら何もしない + if current_version == pyproject_version: + print( + f"No update needed: {init_path} version {current_version} matches pyproject.toml version {pyproject_version}" + ) + return + + # 5. SPDXヘッダーのみ残して、後続を書き直す + header = [] + for line in lines: + if line.strip().startswith("#") or not line.strip(): + header.append(line) + else: + break + + # 6. ファイルを書き直す + with open(init_path, "w") as f: + f.writelines(header) + f.write("\n") + f.write(f'__version__ = "{pyproject_version}"\n') + + print(f"Updated {init_path}: {current_version} → {pyproject_version}") + + +if __name__ == "__main__": + update_version() From 63eb4d235701206faeb44292a6fe66e757dac886 Mon Sep 17 00:00:00 2001 From: Shohei KAMON Date: Mon, 28 Apr 2025 01:09:30 +0800 Subject: [PATCH 3/5] update workflow of publish-homebrew Signed-off-by: Shohei KAMON --- .github/workflows/publish-homebrew.yml | 32 +++++++++++--------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/.github/workflows/publish-homebrew.yml b/.github/workflows/publish-homebrew.yml index 59ef441..aa8af1e 100644 --- a/.github/workflows/publish-homebrew.yml +++ b/.github/workflows/publish-homebrew.yml @@ -34,34 +34,30 @@ jobs: - name: Install pyinstaller run: | + python -m pip install . python -m pip install pyinstaller - - name: Build Intel binary (x86_64) with PyInstaller - if: matrix.arch == 'x86_64' + - name: Build binary for MacOS with PyInstaller for both arch + if: matrix.os == 'macos-13' || matrix.os == 'macos-14' run: | - pyinstaller --onefile --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py - mkdir -p release/fireblocks-cli-${{ github.ref_name }}-x86_64 - mv dist/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-x86_64/fireblocks-cli - tar -czvf fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-x86_64 - if command -v sha256sum; then sha256sum fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz.sha256; else shasum -a 256 fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz.sha256; fi - - - name: Build ARM binary (arm64) with PyInstaller - if: matrix.arch == 'arm64' - run: | - pyinstaller --onefile --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py - mkdir -p release/fireblocks-cli-${{ github.ref_name }}-arm64 - mv dist/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-arm64/fireblocks-cli - tar -czvf fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-arm64 - if command -v sha256sum; then sha256sum fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz.sha256; else shasum -a 256 fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz.sha256; fi + pyinstaller --onedir --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py + mkdir -p release/fireblocks-cli-${{ github.ref_name }}-${{ matrix.arch }} + mv dist/fireblocks-cli/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-${{ matrix.arch }}/fireblocks-cli + tar -czvf fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-${{ matrix.arch }} + if command -v sha256sum; then + sha256sum fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz.sha256 + else + shasum -a 256 fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz.sha256 + fi - name: Build Linux binary (amd64) with PyInstaller (for WSL) if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y libx11-dev libxext-dev libxrender-dev libxrandr-dev libpng-dev - pyinstaller --onefile --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py + pyinstaller --onedir --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py mkdir -p release/fireblocks-cli-${{ github.ref_name }}-amd64 - mv dist/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-amd64/fireblocks-cli + mv dist/fireblocks-cli/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-amd64/fireblocks-cli tar -czvf fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-amd64 sha256sum fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz > fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz.sha256 From 167a7499b76dedf474269d328d48d6aa705d7134 Mon Sep 17 00:00:00 2001 From: Shohei KAMON Date: Mon, 28 Apr 2025 01:26:16 +0800 Subject: [PATCH 4/5] delete github action: publish-homebrew Signed-off-by: Shohei KAMON --- .github/workflows/publish-homebrew.yml | 75 -------------------------- .github/workflows/publish-pypi.yml | 2 +- docs/devops/release_workflow.md | 2 - 3 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 .github/workflows/publish-homebrew.yml diff --git a/.github/workflows/publish-homebrew.yml b/.github/workflows/publish-homebrew.yml deleted file mode 100644 index aa8af1e..0000000 --- a/.github/workflows/publish-homebrew.yml +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Ethersecurity Inc. -# -# SPDX-License-Identifier: MPL-2.0 -# Author: Shohei KAMON - -name: Publish to Homebrew - -on: - push: - tags: - - 'v*' # Trigger on any tag push - workflow_dispatch: - -jobs: - build-and-publish: - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: macos-13 - arch: x86_64 - - os: macos-14 - arch: arm64 - - os: ubuntu-latest - arch: amd64 # WSL Linux build - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install pyinstaller - run: | - python -m pip install . - python -m pip install pyinstaller - - - name: Build binary for MacOS with PyInstaller for both arch - if: matrix.os == 'macos-13' || matrix.os == 'macos-14' - run: | - pyinstaller --onedir --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py - mkdir -p release/fireblocks-cli-${{ github.ref_name }}-${{ matrix.arch }} - mv dist/fireblocks-cli/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-${{ matrix.arch }}/fireblocks-cli - tar -czvf fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-${{ matrix.arch }} - if command -v sha256sum; then - sha256sum fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz.sha256 - else - shasum -a 256 fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz > fireblocks_cli-${{ github.ref_name }}-macos-${{ matrix.arch }}.tar.gz.sha256 - fi - - - name: Build Linux binary (amd64) with PyInstaller (for WSL) - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libx11-dev libxext-dev libxrender-dev libxrandr-dev libpng-dev - pyinstaller --onedir --name fireblocks-cli --hidden-import=importlib.metadata fireblocks_cli/main.py - mkdir -p release/fireblocks-cli-${{ github.ref_name }}-amd64 - mv dist/fireblocks-cli/fireblocks-cli release/fireblocks-cli-${{ github.ref_name }}-amd64/fireblocks-cli - tar -czvf fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz -C release fireblocks-cli-${{ github.ref_name }}-amd64 - sha256sum fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz > fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz.sha256 - - - name: Upload to GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: | - fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz - fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz - fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz - fireblocks_cli-${{ github.ref_name }}-macos-x86_64.tar.gz.sha256 - fireblocks_cli-${{ github.ref_name }}-macos-arm64.tar.gz.sha256 - fireblocks_cli-${{ github.ref_name }}-linux-amd64.tar.gz.sha256 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 5e11f71..117645e 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -68,6 +68,6 @@ jobs: dist/*.tar.gz dist/*.tar.gz.sha256 generate_release_notes: true - create_release: false + create_release: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/devops/release_workflow.md b/docs/devops/release_workflow.md index 54bc955..423cc2e 100644 --- a/docs/devops/release_workflow.md +++ b/docs/devops/release_workflow.md @@ -14,14 +14,12 @@ This document outlines the release process for the **fireblocks-cli** project, c When a tag (e.g., `v0.1.8`) is pushed, the following actions are triggered: 1. **PyPI release**: The Python package is built and uploaded to **PyPI** (source tar.gz and wheel). -2. **Homebrew release**: Binary files for **Intel** and **ARM** Macs are built using **PyInstaller** and uploaded to **GitHub Releases**. ## Workflow Steps 1. **Trigger**: A tag is pushed (e.g., `v0.1.8`) to GitHub. 2. **GitHub Actions**: - **publish-pypi.yml**: This workflow builds the Python package and uploads it to PyPI. - - **publish-homebrew.yml**: This workflow builds binaries (Intel/ARM) using `pyinstaller` and uploads them to GitHub Releases. # Additional Notes From 68a67756810bdaae7b84b7713810a07457574d72 Mon Sep 17 00:00:00 2001 From: Shohei KAMON Date: Mon, 28 Apr 2025 03:13:21 +0800 Subject: [PATCH 5/5] update for release and homebrew Signed-off-by: Shohei KAMON --- .github/workflows/publish-pypi.yml | 1 + scripts/update_homebrew_formula.py | 170 +++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 scripts/update_homebrew_formula.py diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 117645e..70307bc 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -64,6 +64,7 @@ jobs: - name: Upload Release Assets to GitHub uses: softprops/action-gh-release@v2 with: + tag_name: ${{ github.ref_name }} files: | dist/*.tar.gz dist/*.tar.gz.sha256 diff --git a/scripts/update_homebrew_formula.py b/scripts/update_homebrew_formula.py new file mode 100644 index 0000000..5244719 --- /dev/null +++ b/scripts/update_homebrew_formula.py @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2025 Ethersecurity Inc. +# +# SPDX-License-Identifier: MPL-2.0 +# Author: Shohei KAMON + +import tomllib +import requests +import hashlib +import os +import re + +FORMULA_TEMPLATE = """class {formula_class_name} < Formula + include Language::Python::Virtualenv + + desc "{desc}" + homepage "{homepage}" + url "{sdist_url}" + sha256 "{sdist_sha256}" + license "{license}" + + depends_on "python@3.11" + +{resources} + def install + virtualenv_install_with_resources + end + + test do + system "#{{bin}}/{command_name}", "--version" + end +end +""" + +# 手動で追加したいパッケージ一覧 +extra_packages = [ + "requests", + "urllib3", + "fireblocks_sdk", + "idna", + "certifi", + "PyJWT", + "chardet", +] + + +def get_pypi_metadata(package_name): + response = requests.get(f"https://pypi.org/pypi/{package_name}/json") + response.raise_for_status() + return response.json() + + +def get_sdist_info(pypi_data, package_name="(unknown)"): + for file in pypi_data["urls"]: + if file["packagetype"] == "sdist": + return file["url"], file["digests"]["sha256"] + print(f"Warning: No sdist found for {package_name}, skipping.") + return None, None + + +def get_sha256_from_url(url): + response = requests.get(url) + response.raise_for_status() + return hashlib.sha256(response.content).hexdigest() + + +def generate_resource_block(package_name): + data = get_pypi_metadata(package_name) + sdist_info = get_sdist_info(data, package_name) + if sdist_info == (None, None): + return "" # resourceを生成しない + sdist_url, sdist_sha256 = sdist_info + return f""" resource "{package_name}" do\n url "{sdist_url}"\n sha256 "{sdist_sha256}"\n end\n""" + + +def sanitize_formula_class_name(name): + parts = re.split(r"[-_]", name) + return "".join(part.capitalize() for part in parts) + + +def normalize_package_name(dep_string): + return re.split(r"[<>=\[]", dep_string)[0] + + +def extract_extras(dep_string): + match = re.search(r"\[([^\]]+)\]", dep_string) + if match: + extras = match.group(1) + return [e.strip() for e in extras.split(",")] + return [] + + +def extract_dependencies_with_extras(project_name, extras=[]): + data = get_pypi_metadata(project_name) + requires_dist = data["info"].get("requires_dist", []) + result = [] + + for dep in requires_dist: + if "; extra ==" in dep: + extra_match = re.search(r"extra == [\'\"]([^\'\"]+)[\'\"]", dep) + if extra_match and extra_match.group(1) in extras: + result.append(dep.split(";")[0].strip()) + else: + result.append(dep.split(";")[0].strip()) + + return result + + +def main(): + with open("pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + + project = pyproject["project"] + project_name = project["name"] + version = project["version"] + homepage = project.get("urls", {}).get( + "Homepage", "https://github.com/stirnetwork/fireblocks-cli" + ) + license_name = project.get("license", {}).get("text", "MPL-2.0") + desc = project.get("description", f"{project_name} CLI tool") + + pypi_data = get_pypi_metadata(project_name) + sdist_url, sdist_sha256 = get_sdist_info(pypi_data) + + dependencies = [] + for dep in project.get("dependencies", []): + dep_name = normalize_package_name(dep) + extras = extract_extras(dep) + dependencies.append((dep_name, extras)) + + for extra in extra_packages: + dependencies.append((extra, [])) + + resources = "" + seen = set() + for dep_name, extras in dependencies: + if dep_name in seen: + continue + seen.add(dep_name) + resources += generate_resource_block(dep_name) + + if extras: + for sub_dep in extract_dependencies_with_extras(dep_name, extras): + sub_dep_name = normalize_package_name(sub_dep) + if sub_dep_name not in seen: + seen.add(sub_dep_name) + resources += generate_resource_block(sub_dep_name) + + if not resources: + raise ValueError("No resources generated. Check dependency parsing.") + + formula_content = FORMULA_TEMPLATE.format( + formula_class_name=sanitize_formula_class_name(project_name), + desc=desc, + homepage=homepage, + sdist_url=sdist_url, + sdist_sha256=sdist_sha256, + license=license_name, + resources=resources, + command_name=project_name, + ) + + formula_filename = f"{project_name}.rb" + with open(formula_filename, "w") as f: + f.write(formula_content) + + print(f"Formula generated: {formula_filename}") + + +if __name__ == "__main__": + main()