diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..3a9c076d1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,75 @@ +name: main + +on: + pull_request: + paths: + - .github/workflows/build.yml + - .github/workflows/build_orchestrator.yml + - .github/workflows/llvmdev_build.yml + - .github/workflows/llvmlite_build.yml + - buildscripts/github/** + - conda-recipes/** + - llvmlite/** + workflow_dispatch: + inputs: + platform: + description: Conda Platform + default: win-64 + required: true + type: choice + options: + - win-64 + label: + types: [created] + +concurrency: + # Concurrency group that uses the workflow name and PR number if available + # or commit SHA as a fallback. If a new build is triggered under that + # concurrency group while a previous build is running it will be canceled. + # Repeated pushes to a PR will cancel all previous builds, while multiple + # merges to master will not cancel. + group: >- + ${{ github.workflow }}- + ${{ github.event.pull_request.number + || toJson(github.event.inputs) + || github.event.label.name + || github.sha }} + cancel-in-progress: true + +jobs: + matrix: + name: Generate matrix + runs-on: ubuntu-24.04 + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v45 + - name: Generate + id: generate + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_LABEL_NAME: ${{ github.event.label.name }} + GITHUB_WORKFLOW_INPUT: ${{ toJson(github.event.inputs) }} + run: | + ./buildscripts/github/gen_matrix.py + + orchestrator: + name: Orchestrate build + needs: matrix + strategy: + matrix: + include: ${{fromJson(needs.matrix.outputs.matrix)}} + fail-fast: false + uses: ./.github/workflows/build_orchestrator.yml + with: + rebuild_llvmdev: ${{ matrix.rebuild_llvmdev }} + platform: ${{ matrix.platform }} + recipe: ${{ matrix.recipe }} + type: ${{ matrix.type }} diff --git a/.github/workflows/build_orchestrator.yml b/.github/workflows/build_orchestrator.yml new file mode 100644 index 000000000..56b282dbe --- /dev/null +++ b/.github/workflows/build_orchestrator.yml @@ -0,0 +1,38 @@ +on: + workflow_call: + inputs: + rebuild_llvmdev: + description: Rebuild llvmdev + required: true + type: boolean + platform: + description: Conda Platform + required: true + type: string + type: + description: Package type to build+test + required: true + type: string + recipe: + description: Recipe to build + required: true + type: string + +name: orchestrator + +jobs: + llvmdev: + if: ${{ inputs.rebuild_llvmdev }} + uses: ./.github/workflows/llvmdev_build.yml + with: + platform: ${{ inputs.platform }} + recipe: ${{ inputs.recipe }} + + llvmlite: + needs: llvmdev + if: always() + uses: ./.github/workflows/llvmlite_build.yml + with: + llvmdev_run_id: ${{ inputs.rebuild_llvmdev && needs.llvmdev.outputs.run_id || '' }} + platform: ${{ inputs.platform }} + type: ${{ inputs.type }} diff --git a/.github/workflows/llvmdev_build.yml b/.github/workflows/llvmdev_build.yml index 8c55e0765..de2d5f0b6 100644 --- a/.github/workflows/llvmdev_build.yml +++ b/.github/workflows/llvmdev_build.yml @@ -1,24 +1,14 @@ name: llvmdev on: - pull_request: - paths: - - .github/workflows/llvmdev_build.yml - - buildscripts/github/llvmdev_evaluate.py - label: - types: [created] workflow_dispatch: inputs: platform: description: Conda Platform - default: linux-64 + default: win-64 required: true type: choice options: - - linux-64 - - linux-aarch64 - - osx-64 - - osx-arm64 - win-64 recipe: description: Recipe to build @@ -28,52 +18,31 @@ on: options: - llvmdev - llvmdev_for_wheels + workflow_call: + inputs: + platform: + description: Conda Platform + required: true + type: string + recipe: + description: Recipe to build + required: true + type: string + outputs: + run_id: + description: The workflow run ID + value: ${{ jobs.build.outputs.run_id }} -concurrency: - # Concurrency group that uses the workflow name and PR number if available - # or commit SHA as a fallback. If a new build is triggered under that - # concurrency group while a previous build is running it will be canceled. - # Repeated pushes to a PR will cancel all previous builds, while multiple - # merges to master will not cancel. - group: >- - ${{ github.workflow }}- - ${{ github.event.pull_request.number - || toJson(github.event.inputs) - || github.event.label.name - || github.sha }} - cancel-in-progress: true +defaults: + run: + shell: bash -el {0} jobs: - - check: - runs-on: ubuntu-24.04 - outputs: - matrix: ${{ steps.evaluate.outputs.matrix }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.13' - - name: Evaluate - id: evaluate - env: - GITHUB_EVENT_NAME: ${{ github.event_name }} - GITHUB_LABEL_NAME: ${{ github.event.label.name }} - GITHUB_WORKFLOW_INPUT: ${{ toJson(github.event.inputs) }} - run: | - ./buildscripts/github/llvmdev_evaluate.py - build: - needs: check - name: ${{ matrix.recipe }}-${{ matrix.platform }} - runs-on: ${{ matrix.runner }} - defaults: - run: - shell: bash -el {0} - strategy: - matrix: ${{fromJson(needs.check.outputs.matrix)}} - fail-fast: false - + name: Build ${{ inputs.recipe }} + runs-on: windows-2019 + outputs: + run_id: ${{ steps.get_run_id.outputs.run_id }} steps: - name: Clone repository uses: actions/checkout@v4 @@ -96,19 +65,20 @@ jobs: run: | set -x mkdir "${CONDA_CHANNEL_DIR}" - conda build "./conda-recipes/${{ matrix.recipe }}" "--output-folder=${CONDA_CHANNEL_DIR}" + conda build "./conda-recipes/${{ inputs.recipe }}" "--output-folder=${CONDA_CHANNEL_DIR}" ls -lah "${CONDA_CHANNEL_DIR}" - name: Upload conda package uses: actions/upload-artifact@v4 with: - name: ${{ matrix.recipe }}_${{ matrix.platform }} + name: ${{ inputs.recipe }}_${{ inputs.platform }} path: conda_channel_dir compression-level: 0 retention-days: 7 if-no-files-found: error - name: Get Workflow Run ID + id: get_run_id run: | - echo "Current workflow run ID: ${{ github.run_id }}" echo "Use this ID when triggering llvmlite workflow" + echo "run_id=${{ github.run_id }}" | tee -a "$GITHUB_OUTPUT" diff --git a/.github/workflows/llvmlite_build.yml b/.github/workflows/llvmlite_build.yml new file mode 100644 index 000000000..cab3fe1d7 --- /dev/null +++ b/.github/workflows/llvmlite_build.yml @@ -0,0 +1,215 @@ +name: llvmlite + +on: + workflow_dispatch: + inputs: + llvmdev_run_id: + description: llvmdev workflow run ID (optional) + required: false + type: string + platform: + description: Conda Platform + default: win-64 + required: true + type: choice + options: + - win-64 + type: + description: Package type to build+test + default: conda + required: true + type: choice + options: + - conda + - wheel + workflow_call: + inputs: + llvmdev_run_id: + description: llvmdev workflow run ID + required: true + type: string + platform: + description: Conda Platform + required: true + type: string + type: + description: Package type to build+test + required: true + type: string + +env: + LOCAL_LLVMDEV_ARTIFACT_PATH: D:/a/llvmlite/llvmlite/llvmdev_conda_packages + FALLBACK_LLVMDEV_VERSION: '15' + CONDA_CHANNEL_NUMBA: numba + CONDA_CHANNEL_NUMBA_WHEELS: numba/label/win64_wheel + VALIDATION_PYTHON_VERSION: '3.12' + ARTIFACT_RETENTION_DAYS: 7 + +defaults: + run: + shell: bash -el {0} + +jobs: + build: + name: Build llvmlite ${{ inputs.type }} ${{ matrix.python-version }} + runs-on: windows-2019 + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + fail-fast: false + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Miniconda + if: ${{ inputs.type == 'conda' }} + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + auto-activate-base: true + activate-environment: '' + run-post: false + + - name: Setup Miniconda + if: ${{ inputs.type == 'wheel' }} + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python-version }} + conda-remove-defaults: true + auto-update-conda: true + auto-activate-base: true + run-post: false + + - name: Download llvmdev Artifact + if: ${{ inputs.llvmdev_run_id != '' }} + uses: actions/download-artifact@v4 + with: + name: llvmdev_${{ inputs.platform }} + path: llvmdev_conda_packages + run-id: ${{ inputs.llvmdev_run_id }} + repository: ${{ github.repository }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Prepare build + run: | + set -x + if [ "${{ inputs.llvmdev_run_id }}" != "" ]; then + CHAN="file:///${{ env.LOCAL_LLVMDEV_ARTIFACT_PATH }}" + elif [ "${{ inputs.type }}" == "conda" ]; then + CHAN="${{ env.CONDA_CHANNEL_NUMBA }}" + else + CHAN="${{ env.CONDA_CHANNEL_NUMBA_WHEELS }}" + fi + echo "CHAN=$CHAN" | tee -a "$GITHUB_ENV" + mkdir artifacts + + - name: Install dependencies + run: | + if [ "${{ inputs.type }}" == "conda" ]; then + conda install conda-build + else + conda install -c "$CHAN" "llvmdev=$FALLBACK_LLVMDEV_VERSION" cmake libxml2 python-build + fi + + - name: Build ${{ inputs.type }} + run: | + if [ "${{ inputs.type }}" == "conda" ]; then + conda build --debug -c "$CHAN" -c defaults --python=${{ matrix.python-version }} conda-recipes/llvmlite --output-folder=artifacts --no-test + else + python -m build && mv dist/*.whl artifacts/ + fi + + - name: Upload llvmlite ${{ inputs.type }} package + uses: actions/upload-artifact@v4 + with: + name: llvmlite-${{ inputs.platform }}-${{ inputs.type}}-${{ matrix.python-version }} + path: artifacts + compression-level: 0 + retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} + if-no-files-found: error + + validate: + name: Validate llvmlite ${{ inputs.type }} ${{ matrix.python-version }} + needs: build + if: ${{ inputs.type == 'wheel' }} + runs-on: windows-2019 + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + fail-fast: false + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ env.VALIDATION_PYTHON_VERSION }} + conda-remove-defaults: true + auto-update-conda: true + auto-activate-base: true + run-post: false + + - name: Install validation dependencies + run: | + conda install -c defaults py-lief wheel twine keyring rfc3986 + + - name: Download llvmlite wheels + uses: actions/download-artifact@v4 + with: + name: llvmlite-${{ inputs.platform }}-${{ inputs.type}}-${{ matrix.python-version }} + path: dist + + - name: Validate wheels + run: | + cd dist + for WHL_FILE in *.whl; do + wheel unpack "$WHL_FILE" + ../buildscripts/github/validate_win64_wheel.py llvmlite/binding/llvmlite.dll + twine check "$WHL_FILE" + done + + test: + name: Test llvmlite ${{ inputs.type }} ${{ matrix.python-version }} + needs: build + runs-on: windows-2019 + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13'] + fail-fast: false + steps: + - name: Setup miniconda + if: ${{ inputs.type == 'conda' }} + uses: conda-incubator/setup-miniconda@v3 + with: + run-post: false + + - name: Setup Python + if: ${{ inputs.type == 'wheel' }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Download llvmlite artifact + uses: actions/download-artifact@v4 + with: + name: llvmlite-${{ inputs.platform }}-${{ inputs.type}}-${{ matrix.python-version }} + path: artifacts + + - name: Install + run: | + if [ "${{ inputs.type }}" == "conda" ]; then + conda install conda-build + else + pip install dist/*.whl + fi + + - name: Run tests + run: | + if [ "${{ inputs.type }}" == "conda" ]; then + conda build --test artifacts/${{ inputs.platform }}/llvmlite*.conda + else + python -m llvmlite.tests + fi diff --git a/.github/workflows/llvmlite_win-64_conda_builder.yml b/.github/workflows/llvmlite_win-64_conda_builder.yml deleted file mode 100644 index 38e54a899..000000000 --- a/.github/workflows/llvmlite_win-64_conda_builder.yml +++ /dev/null @@ -1,99 +0,0 @@ - -name: llvmlite_win-64_conda_builder - -on: - pull_request: - paths: - - .github/workflows/llvmlite_win-64_conda_builder.yml - workflow_dispatch: - inputs: - llvmdev_run_id: - description: 'llvmdev workflow run ID (optional)' - required: false - type: string - -jobs: - win-64-build: - name: win-64-build - runs-on: windows-2019 - defaults: - run: - shell: bash -elx {0} - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - fail-fast: false - - steps: - - name: Clone repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - auto-activate-base: true - activate-environment: "" - - - name: Install conda-build - run: conda install conda-build - - - name: Download llvmdev Artifact - if: ${{ inputs.llvmdev_run_id != '' }} - uses: actions/download-artifact@v4 - with: - name: llvmdev_win-64 - path: llvmdev_conda_packages - run-id: ${{ inputs.llvmdev_run_id }} - repository: ${{ github.repository }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Build llvmlite conda package - run: | - if [ "${{ inputs.llvmdev_run_id }}" != "" ]; then - LLVMDEV_CHANNEL="file:///D:/a/llvmlite/llvmlite/llvmdev_conda_packages" - else - LLVMDEV_CHANNEL="numba" - fi - CONDA_CHANNEL_DIR="conda_channel_dir" - mkdir $CONDA_CHANNEL_DIR - conda build --debug -c $LLVMDEV_CHANNEL -c defaults --python=${{ matrix.python-version }} conda-recipes/llvmlite --output-folder=$CONDA_CHANNEL_DIR --no-test - - - name: Upload llvmlite conda package - uses: actions/upload-artifact@v4 - with: - name: llvmlite-win-64-py${{ matrix.python-version }} - path: conda_channel_dir - compression-level: 0 - retention-days: 7 - if-no-files-found: error - - win-64-test: - name: win-64-test - needs: win-64-build - runs-on: windows-2019 - defaults: - run: - shell: bash -elx {0} - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - fail-fast: false - - steps: - - name: Setup miniconda - uses: conda-incubator/setup-miniconda@v3 - - - name: Download llvmlite artifact - uses: actions/download-artifact@v4 - with: - name: llvmlite-win-64-py${{ matrix.python-version }} - - - name: Install conda-build and llvmlite - run: | - conda install conda-build - - - name: Run tests - run: conda build --test win-64/llvmlite*.conda diff --git a/.github/workflows/llvmlite_win-64_wheel_builder.yml b/.github/workflows/llvmlite_win-64_wheel_builder.yml deleted file mode 100644 index 554bbe34b..000000000 --- a/.github/workflows/llvmlite_win-64_wheel_builder.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: llvmlite_win-64_wheel_builder - -on: - pull_request: - paths: - - .github/workflows/llvmlite_win-64_wheel_builder.yml - workflow_dispatch: - inputs: - llvmdev_run_id: - description: 'llvmdev workflow run ID (optional)' - required: false - type: string - -# Add concurrency control -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -env: - LOCAL_LLVMDEV_ARTIFACT_PATH: D:/a/llvmlite/llvmlite/llvmdev_conda_packages - FALLBACK_LLVMDEV_VERSION: "15" - CONDA_CHANNEL_NUMBA: numba/label/win64_wheel - VALIDATION_PYTHON_VERSION: "3.12" - ARTIFACT_RETENTION_DAYS: 7 - -jobs: - win-64-build: - name: win-64-build - runs-on: windows-2019 - defaults: - run: - shell: bash -el {0} - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - fail-fast: false - - steps: - - name: Clone repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v3 - with: - python-version: ${{ matrix.python-version }} - conda-remove-defaults: true - auto-update-conda: true - auto-activate-base: true - - - name: Download llvmdev Artifact - if: ${{ inputs.llvmdev_run_id != '' }} - uses: actions/download-artifact@v4 - with: - name: llvmdev_win-64 - path: llvmdev_conda_packages - run-id: ${{ inputs.llvmdev_run_id }} - repository: ${{ github.repository }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Install build dependencies - run: | - set -x - if [ "${{ inputs.llvmdev_run_id }}" != "" ]; then - CHAN="file:///${{ env.LOCAL_LLVMDEV_ARTIFACT_PATH }}" - else - CHAN="${{ env.CONDA_CHANNEL_NUMBA }}" - fi - conda install -c "$CHAN" llvmdev=${{ env.FALLBACK_LLVMDEV_VERSION }} cmake libxml2 python-build - - - name: Build wheel - run: python -m build - - - name: Upload llvmlite wheel - uses: actions/upload-artifact@v4 - with: - name: llvmlite-win-64-py${{ matrix.python-version }} - path: dist/*.whl - compression-level: 0 - retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} - if-no-files-found: error - - win-64-validate: - name: win-64-validate - needs: win-64-build - runs-on: windows-2019 - defaults: - run: - shell: bash -el {0} - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - fail-fast: false - steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Setup Miniconda - uses: conda-incubator/setup-miniconda@v3 - with: - python-version: ${{ env.VALIDATION_PYTHON_VERSION }} - conda-remove-defaults: true - auto-update-conda: true - auto-activate-base: true - - - name: Install validation dependencies - run: conda install -c defaults py-lief wheel twine keyring rfc3986 - - - name: Download llvmlite wheels - uses: actions/download-artifact@v4 - with: - name: llvmlite-win-64-py${{ matrix.python-version }} - path: dist - - - name: Validate wheels - run: | - cd dist - for WHL_FILE in *.whl; do - wheel unpack "$WHL_FILE" - python "$GITHUB_WORKSPACE"/buildscripts/github/validate_win64_wheel.py llvmlite/binding/llvmlite.dll - twine check "$WHL_FILE" - done - - win-64-test: - name: win-64-test - needs: win-64-validate - runs-on: windows-2019 - defaults: - run: - shell: bash -el {0} - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - fail-fast: false - - steps: - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Download llvmlite wheel - uses: actions/download-artifact@v4 - with: - name: llvmlite-win-64-py${{ matrix.python-version }} - path: dist - - - name: Install and test - run: | - pip install dist/*.whl - python -m llvmlite.tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31946c91e..eebed5d2d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,11 @@ repos: hooks: - id: clang-format types_or: [c++, c, c#, cuda, metal] + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 25.1.0 + hooks: + - id: black + files: buildscripts/.*\.py$ - repo: https://github.com/PyCQA/flake8 rev: 7.1.1 hooks: @@ -23,7 +28,7 @@ repos: hooks: - id: yamlfmt args: [--mapping, '2', --offset, '2', --sequence, '4', --implicit_start] - files: .pre-commit-config.yaml$ + files: .pre-commit-config.yaml|.github/workflows/.*\.yml$ - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.31.1 hooks: diff --git a/buildscripts/github/llvmdev_evaluate.py b/buildscripts/github/gen_matrix.py similarity index 57% rename from buildscripts/github/llvmdev_evaluate.py rename to buildscripts/github/gen_matrix.py index 2c3c36def..f89b12d0a 100755 --- a/buildscripts/github/llvmdev_evaluate.py +++ b/buildscripts/github/gen_matrix.py @@ -8,6 +8,7 @@ event = os.environ.get("GITHUB_EVENT_NAME") label = os.environ.get("GITHUB_LABEL_NAME") inputs = os.environ.get("GITHUB_WORKFLOW_INPUT", "{}") +changed_files = os.environ.get("ALL_CHANGED_FILES", "").split() runner_mapping = { "linux-64": "ubuntu-24.04", @@ -16,28 +17,29 @@ "osx-arm64": "macos-14", "win-64": "windows-2019", } +print(f"runner_mapping: {runner_mapping}") default_include = [ - { - "runner": runner_mapping["linux-64"], - "platform": "linux-64", - "recipe": "llvmdev", - }, { "runner": runner_mapping["win-64"], + "rebuild_llvmdev": False, "platform": "win-64", "recipe": "llvmdev", + "type": "conda", }, { "runner": runner_mapping["win-64"], + "rebuild_llvmdev": True, "platform": "win-64", - "recipe": "llvmdev_for_wheel", + "recipe": "llvmdev", + "type": "wheel", }, ] print( - "Deciding what to do based on event: " - f"'{event}', label: '{label}', inputs: '{inputs}'" + f"Generationg matrix based on, event: '{event}', " + f"label: '{label}', inputs: '{inputs}' ", + f"changed_files: {changed_files}", ) if event == "pull_request": print("pull_request detected") @@ -51,14 +53,22 @@ include = [ { "runner": runner_mapping[params.get("platform", "linux-64")], - "platform": params.get("platform", "linux-64"), - "recipe": params.get("recipe", "llvmdev"), - } + "rebuild_llvmdev": True, + "platform": params.get("platform", "win-64"), + "recipe": "llvmdev", + "type": "conda", + }, + { + "runner": runner_mapping[params.get("platform", "linux-64")], + "rebuild_llvmdev": True, + "platform": params.get("platform", "win-64"), + "recipe": "llvmdev", + "type": "wheel", + }, ] else: include = {} -matrix = {"include": include} -print(f"Emitting matrix:\n {json.dumps(matrix, indent=4)}") +print(f"Emitting matrix:\n {json.dumps(include, indent=4)}") -Path(os.environ["GITHUB_OUTPUT"]).write_text(f"matrix={json.dumps(matrix)}") +Path(os.environ["GITHUB_OUTPUT"]).write_text(f"matrix={json.dumps(include)}") diff --git a/buildscripts/github/validate_win64_wheel.py b/buildscripts/github/validate_win64_wheel.py old mode 100644 new mode 100755 index 7161dac06..764df4fb6 --- a/buildscripts/github/validate_win64_wheel.py +++ b/buildscripts/github/validate_win64_wheel.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import lief import pathlib