diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4842474..c259c3c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,24 +1,24 @@ -# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -name: Build CI - -on: [pull_request, push] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Set up Python 3.x (Latest) - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - name: Checkout current repo - uses: actions/checkout@v3 - with: - submodules: true - - name: Install pre-commit - run: pip install pre-commit - - name: Run pre-commit - run: pre-commit run --all-files +# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: Build CI + +on: [pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Set up Python 3.x (Latest) + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Checkout current repo + uses: actions/checkout@v4 + with: + submodules: true + - name: Install pre-commit + run: pip install pre-commit + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a40ea92..04dc6ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,19 @@ -# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -repos: - - repo: https://github.com/python/black - rev: 23.3.0 - hooks: - - id: black - - repo: https://github.com/fsfe/reuse-tool - rev: v1.1.2 - hooks: - - id: reuse - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace +# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +repos: + - repo: https://github.com/python/black + rev: 25.1.0 + hooks: + - id: black + - repo: https://github.com/fsfe/reuse-tool + rev: v3.0.2 + hooks: + - id: reuse + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace diff --git a/README.rst b/README.rst index fd83e8d..7d8c9cb 100644 --- a/README.rst +++ b/README.rst @@ -10,8 +10,8 @@ build-mpy ========= GitHub Action for building packages of ``.mpy`` files for CircuitPython projects and attaching them to releases -as ZIP files. Files other than ``.mpy`` and ``.py`` files will be added to the ZIP file as well. Note that -any files named or ``code.py`` are automatically not compiled for convenience. +as ZIP files. Files other than ``.mpy`` and ``.py`` files will be added to the ZIP file as well. Note that +``code.py`` and any files excluded via the `mpy-manifest-file` are not compiled. Inputs ====== @@ -19,23 +19,23 @@ Inputs ======================= ===================================================================== ==================== ===================================================================== Argument Name Description Default Notes ======================= ===================================================================== ==================== ===================================================================== -github-token Your GitHub token N/A N/A -circuitpy-tag The version of CircuitPython to compile for N/A You can use any valid tag (or branch) from ``adafruit/circuitpython`` +github-token Your GitHub token N/A (required) N/A +circuitpy-version The version of CircuitPython to download and use N/A (required) You can specify any version from ``https://adafruit-circuit-python.s3.amazonaws.com/index.html?prefix=bin/mpy-cross`` zip-filename The name of the ZIP file that will be attached "mpy-release.zip" N/A -circuitpython-repo-name The name of the clone CircuitPython repo "circuitpython-repo" Change if it conflicts with another file mpy-directory The directory to search for files to compile "." (top folder) Becomes the basis for filepaths in ``mpy-manifest-file`` mpy-manifest-file A file with a list of files to compile or exclude "" If none is given, all files in ``mpy-directory`` are used mpy-manifest-type Whether the files in the manifest file should be included or excluded "include" N/A zip-directory The directory to add to the ZIP file "." (top folder) Becomes the basis for filepaths in ``zip-manifest-file`` zip-manifest-file A file with a list of files to add to the ZIP file or exclude from it "" If none is given, all files in ``zip-directory`` are used zip-manifest-type Whether the files in the manifest file should be included or excluded "include" N/A +compiler-directory The directory where the CircuitPython compiler will be downloaded "circuitpy-compiler" N/A ======================= ===================================================================== ==================== ===================================================================== Examples ======== If you have just a repository with files intended for a CircuitPython board, your release -file could be very simple! This release CI creates .mpy files for CircuitPython 8.2.0: +file could be very simple! .. code-block:: yaml @@ -48,19 +48,20 @@ file could be very simple! This release CI creates .mpy files for CircuitPython runs-on: ubuntu-latest steps: - name: Checkout the current repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - name: Run MPY Action - uses: adafruit/build-mpy@v1 + uses: adafruit/build-mpy@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - circuitpy-tag: "8.2.0" + circuitpy-version: "9.2.8" You also have granular control of which directories to compile and zip and the ability to specify which -files should or should not be compiled and/or zipped. For example, if you wanted to compile and zip -files in a folder named ``microcontroller`` and you wanted to use a manifest file named ``mpy_manifest.txt`` -to specify certain files NOT to compile, you could modify the script above to be: +files should or should not be compiled and/or zipped as well as the ability to specify a different CircuitPython version. +For example, if you wanted to compile and zip files in a folder named ``microcontroller`` and you wanted to +use a manifest file named ``mpy_manifest.txt`` to specify certain files NOT to compile, using CircuitPython +version ``9.2.8``, you could modify the script above to be: .. code-block:: yaml @@ -73,14 +74,14 @@ to specify certain files NOT to compile, you could modify the script above to be runs-on: ubuntu-latest steps: - name: Checkout the current repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - name: Run MPY Action - uses: adafruit/build-mpy@v1 + uses: adafruit/build-mpy@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - circuitpy-tag: "8.2.0" + circuitpy-version: "9.2.8" mpy-directory: "microcontroller" mpy-manifest-file: "mpy_manifest.txt" mpy-manifest-type: "exclude" diff --git a/action.yml b/action.yml index d4a0521..8b6d1f8 100644 --- a/action.yml +++ b/action.yml @@ -1,211 +1,222 @@ -# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries -# -# SPDX-License-Identifier: MIT - -name: 'Build package of .mpy files' -description: 'Build mpy-cross for usage in GitHub Actions' -inputs: - github-token: - description: 'Your GitHub token, needed to upload the ZIP file' - required: true - circuitpy-tag: - description: 'The CircuitPython version of mpy-cross to build' - required: true - zip-filename: - description: 'The name of the ZIP file to attach' - required: true - default: "mpy-release.zip" - circuitpython-repo-name: - description: > - The name for the CircuitPython repo. The default is 'circuitpython-repo', - and there typically isn't any reason to change this unless it conflicts - with an existing folder name in your repository. - required: true - default: 'circuitpython-repo' - mpy-directory: - description: > - The directory to look for files to compile with mpy-cross. If none - is provided, the root directory will be used. This also becomess the - basis for filepaths in the mpy manifest file, if one is provided. - required: true - default: "." - mpy-manifest-file: - description: > - Path to a manifest file containing filepaths to convert. If no - manifest is provided, all applicable files will be converted. - required: true - default: "" - mpy-manifest-type: - description: > - The type of manifest to use for compiling if one is provided. The - default is "include", but if "exclude" is provided, then all Python - files EXCEPT the ones listed will be compuled with mpy-cross. - required: true - default: "include" - zip-directory: - description: > - The directory of files to bundle. If none is provided, the root - directory will be used. This also becomess the basis for filepaths - in the zip manifest file, if one is provided. - required: true - default: "." - zip-manifest-file: - description: > - Path to a manifest file containing filepaths to include in the zip - file. If no manifest is provided, all files will be included. Either - this or exclude-zip-manifest can be defined. - required: true - default: "" - zip-manifest-type: - description: > - The type of manifest to use for zipping, if one is provided. The - default is "include", but if "exclude" is provided, then all files - EXCEPT the ones listed will be zipped. - required: true - default: "include" -runs: - using: "composite" - steps: - - name: Install zip - shell: bash - run: | - sudo apt install zip - - name: Install build tools - shell: bash - run: | - sudo apt install build-essential - sudo add-apt-repository ppa:pybricks/ppa - sudo apt install git gettext uncrustify - - name: Clone adafruit/circuitpython - uses: actions/checkout@v3 - with: - repository: adafruit/circuitpython - path: ${{ inputs.circuitpython-repo-name }} - - name: Enter CircuitPython repo, checkout tag, and update - shell: bash - run: | - function version() { - echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; - } - - cd ${{ inputs.circuitpython-repo-name }} - git fetch - git checkout ${{ inputs.circuitpy-tag }} - - if [ $(version "${{ inputs.circuitpy-tag }}") -ge $(version "8.2.0") ]; then - make fetch-all-submodules - else - make fetch-submodules - fi - - name: Build mpy-cross - shell: bash - run: | - cd ${{ inputs.circuitpython-repo-name }}/mpy-cross - make clean - make - - name: Compile MPY files - shell: bash - run: | - # Store repo name - reponame="${{ inputs.circuitpython-repo-name }}" - - # Read MPY manifest file contents, if needed - if [[ "${{ inputs.mpy-manifest-file }}" != "" ]] - then - readarray mpymanifestfiles < "${{ inputs.mpy-manifest-file }}" - for file in ${mpymanifestfiles[@]} - do - if [[ $file = *[[:space:]]* ]] || [[ $file == "#*" ]] - then - mpymanifestfiles=( $"{mpymanifestfiles[@]/$file}" ) - fi - done - fi - - # Compile MPY files - mpyresults=() - prempyfiles=() - pyfiles=$(find ${{ inputs.mpy-directory }} -name "*.py" ! -path "./${{ inputs.circuitpython-repo-name }}/*" ! -name "code.py" -printf '%P\n') - for file in ${pyfiles[@]} - do - if [[ "${{ inputs.mpy-manifest-file }}" == "" ]] || \ - [[ ( "${{ inputs.mpy-manifest-type }}" == "include" && "${mpymanifestfiles[*]}" =~ "${file}" ) || \ - ( "${{ inputs.mpy-manifest-type }}" == "exclude" && ! "${mpymanifestfiles[*]}" =~ "${file}" ) ]] - then - echo "Compiling $file" - outputmpy="${{ inputs.mpy-directory }}/${file%.*}.mpy" - mpyresults+=("$outputmpy") - prempyfile="${{ inputs.mpy-directory }}/$file" - prempyfiles+=("$prempyfile") - ${reponame}/mpy-cross/mpy-cross $prempyfile -o $outputmpy - fi - done - - # Delete the CircuitPython repo - echo "Deleting CircuitPython repository folder" - rm -r ${{ inputs.circuitpython-repo-name }} - - # Read ZIP manifest file contents, if needed - if [[ "${{ inputs.zip-manifest-file }}" != "" ]] - then - readarray zipmanifestfiles < "${{ inputs.zip-manifest-file }}" - for file in ${zipmanifestfiles[@]} - do - if [[ $file = *[[:space:]]* ]] || [[ $file == "#*" ]] - then - zipmanifestfiles=( $"{zipmanifestfiles[@]/$file}" ) - fi - done - fi - - # Zip files - zipresults=() - allfiles=$(find ${{ inputs.zip-directory }} -type f ! -path "./.git/*" -printf '%P\n') - for file in ${allfiles[@]} - do - - if [[ "${{ inputs.zip-manifest-file }}" == "" ]] || \ - [[ ( "${{ inputs.zip-manifest-type }}" == "include" && "${zipmanifestfiles[*]}" =~ "${file}" ) || \ - ( "${{ inputs.zip-manifest-type }}" == "exclude" && ! "${zipmanifestfiles[*]}" =~ "${file}" ) ]] - then - potentialmpy="${file%.*}.mpy" - potentialfile="$file" - if [[ "${{ inputs.zip-directory }}" != "." ]] - then - potentialmpy="${{ inputs.zip-directory }}/$potentialmpy" - potentialfile="${{ inputs.zip-directory }}/$file" - fi - - if [[ "${prempyfiles[*]}" =~ "${potentialfile}" ]] - then - continue - fi - - if [[ "${mpyresults[*]}" =~ "${potentialmpy}" ]] && [[ ! -a "$file" ]] - then - zipresults+=("$potentialmpy") - else - zipresults+=("$potentialfile") - fi - fi - done - - # Create the ZIP file - zip ${{ inputs.zip-filename }} "${zipresults[@]}" - - # Delete MPY files - for file in ${mpyresults[@]} - do - echo "Deleting $file" - rm $file - done - - name: Upload ZIP file to release - uses: shogo82148/actions-upload-release-asset@v1 - with: - asset_path: ${{ inputs.zip-filename }} - github_token: ${{ inputs.github-token }} - upload_url: ${{ github.event.release.upload_url }} - - name: Delete ZIP file - shell: bash - run: | - rm ${{ inputs.zip-filename }} +# SPDX-FileCopyrightText: 2022 Alec Delaney, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +name: 'Build package of .mpy files' +description: 'Build mpy-cross for usage in GitHub Actions' +inputs: + github-token: + description: 'Your GitHub token, needed to upload the ZIP file' + required: true + circuitpy-version: + description: 'The version of CircuitPython to use' + required: true + zip-filename: + description: 'The name of the ZIP file to attach' + required: true + default: "mpy-release.zip" + mpy-directory: + description: > + The directory to look for files to compile with mpy-cross. If none + is provided, the root directory will be used. This also becomes the + basis for filepaths in the mpy manifest file, if one is provided. + required: true + default: "." + mpy-manifest-file: + description: > + Path to a manifest file containing filepaths to convert. If no + manifest is provided, all applicable files will be converted. + required: true + default: "" + mpy-manifest-type: + description: > + The type of manifest to use for compiling if one is provided. The + default is "include", but if "exclude" is provided, then all Python + files EXCEPT the ones listed will be compiled with CircuitPython. + required: true + default: "include" + zip-directory: + description: > + The directory of files to bundle. If none is provided, the root + directory will be used. This also becomes the basis for filepaths + in the zip manifest file, if one is provided. + required: true + default: "." + zip-manifest-file: + description: > + Path to a manifest file containing filepaths to include in the zip + file. If no manifest is provided, all files will be included. Either + this or exclude-zip-manifest can be defined. + required: true + default: "" + zip-manifest-type: + description: > + The type of manifest to use for zipping, if one is provided. The + default is "include", but if "exclude" is provided, then all files + EXCEPT the ones listed will be zipped. + required: true + default: "include" + compiler-directory: + description: > + The directory where the mpy-cross compiler will be downloaded. + required: true + default: "circuitpy-compiler" +runs: + using: "composite" + steps: + - name: Install zip + shell: bash + run: | + sudo apt install zip + - name: Install build tools + shell: bash + run: | + sudo apt install build-essential + sudo add-apt-repository ppa:pybricks/ppa + sudo apt install git gettext uncrustify + - name: Download mpy-cross + shell: bash + run: | + mkdir ${{ inputs.compiler-directory }} + cd ${{ inputs.compiler-directory }} + BASE_NEW="https://adafruit-circuit-python.s3.amazonaws.com/bin/mpy-cross/linux-amd64" + BASE_OLD="https://adafruit-circuit-python.s3.amazonaws.com/bin/mpy-cross" + VER="${{ inputs.circuitpy-version }}" + OUT="mpy-cross" + + set -euo pipefail + + # Try modern name in modern folder + URL="$BASE_NEW/mpy-cross-linux-amd64-$VER.static" + if curl -fsI "$URL" >/dev/null; then + curl -fL "$URL" -o "$OUT" + else + # Try legacy name in legacy folder + URL="$BASE_OLD/mpy-cross.static-amd64-linux-$VER" + if curl -fsI "$URL" >/dev/null; then + curl -fL "$URL" -o "$OUT" + else + # Last resort: list and match either pattern in linux-amd64, then legacy + esc_ver="$(printf '%s' "$VER" | sed -e 's/[.[\\*^$()+?{}|]/\\&/g')" + + for IDX in \ + "$BASE_NEW/?delimiter=/ $BASE_NEW" \ + "$BASE_OLD/?delimiter=/ $BASE_OLD" + do + # If direct URLs fail, list the files in the new place; if no match, list the old place; grab the first filename that starts with your version (new or old pattern); otherwise error out. + BASE="${IDX##* }" + IDX="${IDX%% *}" + html="$(curl -fsSL "${IDX%% *}")" + cand="$(printf '%s' "$html" | grep -o "mpy-cross-linux-amd64-${esc_ver}[^\"']*\.static" | head -n1)" + [ -z "$cand" ] && cand="$(printf '%s' "$html" | grep -o "mpy-cross\.static-amd64-linux-${esc_ver}" | head -n1)" + if [ -n "$cand" ]; then + curl -fL "${IDX##* }/$cand" -o "$OUT" + break + fi + done + + [ -f "$OUT" ] || { echo "No mpy-cross found for $VER"; exit 1; } + fi + fi + + chmod +x "$OUT" + - name: Compile MPY files + shell: bash + run: | + echo "Compiling using mpy-cross version ${{ inputs.circuitpy-version }}" + # Read MPY manifest file contents, if needed + if [[ "${{ inputs.mpy-manifest-file }}" != "" ]] + then + readarray mpymanifestfiles < "${{ inputs.mpy-manifest-file }}" + for file in ${mpymanifestfiles[@]} + do + if [[ $file = *[[:space:]]* ]] || [[ $file == "#*" ]] + then + mpymanifestfiles=( $"{mpymanifestfiles[@]/$file}" ) + fi + done + fi + + # Compile MPY files + mpyresults=() + prempyfiles=() + pyfiles=$(find ${{ inputs.mpy-directory }} -name "*.py" ! -name "code.py" -printf '%P\n') + for file in ${pyfiles[@]} + do + if [[ "${{ inputs.mpy-manifest-file }}" == "" ]] || \ + [[ ( "${{ inputs.mpy-manifest-type }}" == "include" && "${mpymanifestfiles[*]}" =~ "${file}" ) || \ + ( "${{ inputs.mpy-manifest-type }}" == "exclude" && ! "${mpymanifestfiles[*]}" =~ "${file}" ) ]] + then + echo "Compiling $file" + outputmpy="${{ inputs.mpy-directory }}/${file%.*}.mpy" + mpyresults+=("$outputmpy") + prempyfile="${{ inputs.mpy-directory }}/$file" + prempyfiles+=("$prempyfile") + ${{ inputs.compiler-directory }}/mpy-cross $prempyfile -o $outputmpy + fi + done + + # Read ZIP manifest file contents, if needed + if [[ "${{ inputs.zip-manifest-file }}" != "" ]] + then + readarray zipmanifestfiles < "${{ inputs.zip-manifest-file }}" + for file in ${zipmanifestfiles[@]} + do + if [[ $file = *[[:space:]]* ]] || [[ $file == "#*" ]] + then + zipmanifestfiles=( $"{zipmanifestfiles[@]/$file}" ) + fi + done + fi + + # Zip files + zipresults=() + allfiles=$(find ${{ inputs.zip-directory }} -type f ! -path "./.git/*" -printf '%P\n') + for file in ${allfiles[@]} + do + + if [[ "${{ inputs.zip-manifest-file }}" == "" ]] || \ + [[ ( "${{ inputs.zip-manifest-type }}" == "include" && "${zipmanifestfiles[*]}" =~ "${file}" ) || \ + ( "${{ inputs.zip-manifest-type }}" == "exclude" && ! "${zipmanifestfiles[*]}" =~ "${file}" ) ]] + then + potentialmpy="${file%.*}.mpy" + potentialfile="$file" + if [[ "${{ inputs.zip-directory }}" != "." ]] + then + potentialmpy="${{ inputs.zip-directory }}/$potentialmpy" + potentialfile="${{ inputs.zip-directory }}/$file" + fi + + if [[ "${prempyfiles[*]}" =~ "${potentialfile}" ]] + then + continue + fi + + if [[ "${mpyresults[*]}" =~ "${potentialmpy}" ]] && [[ ! -a "$file" ]] + then + zipresults+=("$potentialmpy") + else + zipresults+=("$potentialfile") + fi + fi + done + + # Create the ZIP file + zip ${{ inputs.zip-filename }} "${zipresults[@]}" + + # Delete MPY files + for file in ${mpyresults[@]} + do + echo "Deleting $file" + rm $file + done + - name: Upload ZIP file to release + uses: shogo82148/actions-upload-release-asset@v1 + with: + asset_path: ${{ inputs.zip-filename }} + github_token: ${{ inputs.github-token }} + upload_url: ${{ github.event.release.upload_url }} + - name: Delete ZIP file + shell: bash + run: | + rm ${{ inputs.zip-filename }}