# Build the Pharo VM (CoInterpreter, sources generated by VMMaker on each # runner but Windows arm64, which builds those of the generate job) and # upload the CPack bin zip of each platform as an artifact: # # PharoVM--Linux-x86_64-bin.zip ubuntu-24.04 # PharoVM--Linux-aarch64-bin.zip ubuntu-24.04-arm # PharoVM--Darwin-x86_64-bin.zip macos-15-intel # PharoVM--Darwin-arm64-bin.zip macos-15 # PharoVM--Windows-x86_64-bin.zip windows-2025, MSYS2 UCRT64 # PharoVM--Windows-arm64-bin.zip windows-11-arm, MSYS2 CLANGARM64 # PharoVM--WebAssembly-bin.zip ubuntu-24.04, Emscripten # # where is the version of the VM without the count of commits # since its tag (v12.0.5-beta-4266c4ec4 for v12.0.5-beta+28.4266c4ec4). # On a push to pharo-12, when the seven jobs succeed, the zips are also # published as the GitHub release PharoVM-, tagged at the commit # built; other branches keep them as artifacts of their run. # # The WebAssembly zip is what `make wasm` stages (docs/WebAssembly.md): the # site web/ and the node VM node/, built with libgit2 and the images of the # site, Pharo 15 (the default), 14 and 13.1, each prepared for the world, # and the .sources of the Pharo 15 build that the site had before (for the # images saved in a browser from it), with serve.mjs and # packaging/emscripten/dist (README.md, which names the commit, and # node-try.sh). On a push to pharo-12, its web/ is also deployed to # GitHub Pages once every file of its manifest is checked. # # Every job compiles C with clang and C++ with clang++: CC and CXX on Linux # and macOS (AppleClang there), -DCMAKE_C_COMPILER and -DCMAKE_CXX_COMPILER # in MSYS2, and in the WebAssembly job emcc and em++ (clang) for the VMs and # clang for the native tree that generates their sources. After configuring, # scripts/check-clang.sh fails the job if CMake found another compiler. # # Linux and macOS bundle the prebuilt libraries of files.pharo.org # (PHARO_DEPENDENCIES_PREFER_DOWNLOAD_BINARIES), Windows the DLLs of the # MSYS2 packages (cmake/importMinGWDependencies.cmake). # # comes from `git describe` (cmake/versionExtraction.cmake), so # every job checks out the whole history and fetches the release tags of # pharo-project/pharo-vm, which a fork may not have. name: Build on: push: paths-ignore: ['docs/**', '**/*.md'] pull_request: paths-ignore: ['docs/**', '**/*.md'] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: linux: name: Linux ${{ matrix.arch }} runs-on: ${{ matrix.runner }} timeout-minutes: 120 strategy: fail-fast: false matrix: include: - { arch: x86_64, runner: ubuntu-24.04 } - { arch: aarch64, runner: ubuntu-24.04-arm } env: CC: clang CXX: clang++ steps: - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - name: Fetch the release tags run: | git fetch --no-tags --force https://github.com/pharo-project/pharo-vm.git '+refs/tags/v*:refs/tags/v*' git describe --tags --match 'v[0-9]*' - name: Install the dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends clang cmake make unzip uuid-dev libssl-dev - name: Configure run: | cmake -S . -B build \ -DFLAVOUR=CoInterpreter \ -DPHARO_DEPENDENCIES_PREFER_DOWNLOAD_BINARIES=TRUE \ -DICEBERG_DEFAULT_REMOTE=httpsUrl \ -DBUILD_WITH_GRAPHVIZ=OFF - name: Check that CMake uses clang and clang++ run: sh scripts/check-clang.sh build - name: Build run: cmake --build build --parallel "$(nproc)" - name: Package run: cmake --build build --target package - name: Drop the commit count from the name of the zip run: | cd build/build/packages for zip in PharoVM-*-bin.zip; do mv "$zip" "$(echo "$zip" | sed -E 's/\+[0-9]+\./-/')"; done ls PharoVM-*-bin.zip # --version stops before loading an image: also run the VMMaker image - name: Run the VM of the zip run: | unzip -q build/build/packages/PharoVM-*-Linux-${{ matrix.arch }}-bin.zip -d "$RUNNER_TEMP/vm" "$RUNNER_TEMP/vm/pharo" --version "$RUNNER_TEMP/vm/pharo" --headless build/build/vmmaker/image/VMMaker.image --no-default-preferences eval '3 + 4' | tee "$RUNNER_TEMP/eval.txt" grep -qx 7 "$RUNNER_TEMP/eval.txt" # The plugin is C++: it must load, and need no libstdc++ newer than the # GLIBCXX_3.4.21 of GCC 5 (cmake/simdjson.cmake) - name: Load the SimdJSONPlugin of the zip run: | "$RUNNER_TEMP/vm/pharo" --headless build/build/vmmaker/image/VMMaker.image --no-default-preferences st plugins/SimdJSONPlugin/tests/ci-load.st | tee "$RUNNER_TEMP/simdjson.txt" grep -q '^SimdJSONPlugin native ' "$RUNNER_TEMP/simdjson.txt" glibcxx=$(objdump -T "$RUNNER_TEMP/vm/lib/libSimdJSONPlugin.so" | grep -o 'GLIBCXX_[0-9.]*' | sort -uV | tail -n 1) echo "$glibcxx" test "$(printf '%s\n' "$glibcxx" GLIBCXX_3.4.21 | sort -V | tail -n 1)" = GLIBCXX_3.4.21 - uses: actions/upload-artifact@v7 with: path: build/build/packages/PharoVM-*-Linux-${{ matrix.arch }}-bin.zip archive: false if-no-files-found: error macos: name: macOS ${{ matrix.arch }} runs-on: ${{ matrix.runner }} timeout-minutes: 120 strategy: fail-fast: false matrix: include: - { arch: x86_64, runner: macos-15-intel } - { arch: arm64, runner: macos-15 } env: CC: clang CXX: clang++ steps: - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - name: Fetch the release tags run: | git fetch --no-tags --force https://github.com/pharo-project/pharo-vm.git '+refs/tags/v*:refs/tags/v*' git describe --tags --match 'v[0-9]*' # The platform name of the zip and the prebuilt libraries follow the # host, not CMAKE_OSX_ARCHITECTURES - name: Check the architecture of the runner run: test "$(uname -m)" = "${{ matrix.arch }}" # On arm64 the VM uses the libffi of the system, as upstream does # (FindFFI does not look into the SDK by itself) - name: Configure run: | extra=() if [ "${{ matrix.arch }}" = arm64 ]; then sdk="$(xcrun --show-sdk-path)" extra=(-DFFI_LIBRARY="$sdk/usr/lib/libffi.tbd" -DFFI_INCLUDE_DIR="$sdk/usr/include/ffi") fi cmake -S . -B build \ -DFLAVOUR=CoInterpreter \ -DPHARO_DEPENDENCIES_PREFER_DOWNLOAD_BINARIES=TRUE \ -DICEBERG_DEFAULT_REMOTE=httpsUrl \ -DBUILD_WITH_GRAPHVIZ=OFF \ -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ -DCMAKE_OSX_SYSROOT=macosx \ "${extra[@]}" - name: Check that CMake uses clang and clang++ run: sh scripts/check-clang.sh build - name: Build run: cmake --build build --parallel "$(sysctl -n hw.ncpu)" - name: Package run: cmake --build build --target package - name: Drop the commit count from the name of the zip run: | cd build/build/packages for zip in PharoVM-*-bin.zip; do mv "$zip" "$(echo "$zip" | sed -E 's/\+[0-9]+\./-/')"; done ls PharoVM-*-bin.zip # --version stops before loading an image: also run the VMMaker image - name: Run the VM of the zip run: | unzip -q build/build/packages/PharoVM-*-Darwin-${{ matrix.arch }}-bin.zip -d "$RUNNER_TEMP/vm" "$RUNNER_TEMP/vm/Pharo.app/Contents/MacOS/Pharo" --headless --version "$RUNNER_TEMP/vm/Pharo.app/Contents/MacOS/Pharo" --headless build/build/vmmaker/image/VMMaker.image --no-default-preferences eval '3 + 4' | tee "$RUNNER_TEMP/eval.txt" grep -qx 7 "$RUNNER_TEMP/eval.txt" - name: Load the SimdJSONPlugin of the zip run: | "$RUNNER_TEMP/vm/Pharo.app/Contents/MacOS/Pharo" --headless build/build/vmmaker/image/VMMaker.image --no-default-preferences st plugins/SimdJSONPlugin/tests/ci-load.st | tee "$RUNNER_TEMP/simdjson.txt" grep -q '^SimdJSONPlugin native ' "$RUNNER_TEMP/simdjson.txt" - uses: actions/upload-artifact@v7 with: path: build/build/packages/PharoVM-*-Darwin-${{ matrix.arch }}-bin.zip archive: false if-no-files-found: error # The C sources of VMMaker and the VMMaker image, for Windows arm64: there # the x86_64 VM that cmake/vmmaker.cmake downloads to run VMMaker dies # under the x64 emulation of Windows 11 (VirtualProtect fails, then exit # code 0xC00000FF), and files.pharo.org has no Windows arm64 VM. The # sources do not depend on the platform: cogit.c includes every JIT # backend and picks one with #if generate: name: Generate the C sources runs-on: ubuntu-24.04 timeout-minutes: 60 env: CC: clang CXX: clang++ steps: - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - name: Fetch the release tags run: | git fetch --no-tags --force https://github.com/pharo-project/pharo-vm.git '+refs/tags/v*:refs/tags/v*' git describe --tags --match 'v[0-9]*' - name: Install the dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends clang cmake make unzip uuid-dev libssl-dev - name: Configure run: | cmake -S . -B build \ -DFLAVOUR=CoInterpreter \ -DPHARO_DEPENDENCIES_PREFER_DOWNLOAD_BINARIES=TRUE \ -DICEBERG_DEFAULT_REMOTE=httpsUrl \ -DBUILD_WITH_GRAPHVIZ=OFF - name: Check that CMake uses clang and clang++ run: sh scripts/check-clang.sh build - name: Generate run: cmake --build build --target generate-sources # Under build/ (the common directory of the paths), as the Windows job # downloads them: build/generated, build/build/vmmaker/image - uses: actions/upload-artifact@v7 with: name: generated-sources path: | build/generated build/build/vmmaker/image/VMMaker.image build/build/vmmaker/image/VMMaker.changes build/build/vmmaker/image/*.sources if-no-files-found: error retention-days: 1 # x86_64 on windows-2025 (MSYS2 UCRT64), arm64 on windows-11-arm (MSYS2 # CLANGARM64): there bash, git and the other MSYS tools run under the x64 # emulation of Windows 11, but not VMMaker (generate: OFF), whose sources # and image come from the generate job windows: name: Windows ${{ matrix.arch }} (MSYS2 ${{ matrix.msystem }}) needs: generate runs-on: ${{ matrix.runner }} timeout-minutes: 150 strategy: fail-fast: false matrix: include: - { arch: x86_64, runner: windows-2025, msystem: UCRT64, machine: '8664', generate: 'ON' } - { arch: arm64, runner: windows-11-arm, msystem: CLANGARM64, machine: 'AA64', generate: 'OFF' } defaults: run: shell: msys2 {0} steps: # The Smalltalk sources VMMaker reads must keep their LF line endings # (.gitattributes has `* text=auto`) - name: Keep LF line endings shell: pwsh run: | git config --global core.autocrlf false git config --global core.eol lf - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false # clang, not gcc: the Windows flags of CMakeLists.txt have -fdeclspec. # libgit2-winhttp does HTTPS with the certificates of Windows. # :p is the package prefix of the msystem (mingw-w64-ucrt-x86_64-, # mingw-w64-clang-aarch64-); CLANGARM64 has the C++ runtime libc++ # (with libunwind) and the objdump of LLVM, not GNU binutils. - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} update: true install: git pacboy: >- clang:p cmake:p ninja:p libffi:p libgit2-winhttp:p freetype:p cairo:p SDL2:p sdl3:p - name: Fetch the release tags run: | git config --global --add safe.directory '*' git fetch --no-tags --force https://github.com/pharo-project/pharo-vm.git '+refs/tags/v*:refs/tags/v*' git describe --tags --match 'v[0-9]*' # GENERATED_SOURCE_DIR defaults to build, where these land - if: matrix.generate == 'OFF' uses: actions/download-artifact@v8 with: name: generated-sources path: build - name: Configure run: | cmake -S . -B build -G Ninja \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DFLAVOUR=CoInterpreter \ -DGENERATE_SOURCES=${{ matrix.generate }} \ -DICEBERG_DEFAULT_REMOTE=httpsUrl \ -DBUILD_WITH_GRAPHVIZ=OFF - name: Check that CMake uses clang and clang++ run: sh scripts/check-clang.sh build - name: Build run: cmake --build build - name: Package run: cmake --build build --target package # The file system ignores case, the globs below and the release do not # tell Windows-ARM64 from Windows-arm64: check the name exactly - name: Drop the commit count from the name of the zip run: | cd build/build/packages for zip in PharoVM-*-bin.zip; do mv "$zip" "$(echo "$zip" | sed -E 's/\+[0-9]+\./-/')"; done ls PharoVM-*-bin.zip ls PharoVM-*-bin.zip | grep -q -- '-Windows-${{ matrix.arch }}-bin\.zip$' # Outside of MSYS2, with only the zip and the system directories in the # PATH, so that a DLL missing from the zip makes this step fail: load # every DLL of the zip (the image loads cairo, SDL2, libgit2... through # FFI), then run the VMMaker image (--version stops before loading one) - name: Run the VM of the zip shell: pwsh run: | $vm = "$env:RUNNER_TEMP\vm" $zip = Get-ChildItem build/build/packages/PharoVM-*-Windows-${{ matrix.arch }}-bin.zip Expand-Archive -Path $zip.FullName -DestinationPath $vm $env:PATH = "$vm;$env:SystemRoot\System32;$env:SystemRoot" Get-ChildItem "$vm\*.dll" | ForEach-Object { [System.Runtime.InteropServices.NativeLibrary]::Load($_.FullName) | Out-Null } & "$vm\PharoConsole.exe" --version if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $out = & "$vm\PharoConsole.exe" --headless build/build/vmmaker/image/VMMaker.image --no-default-preferences eval '3 + 4' if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $out if (-not ($out -contains '7')) { exit 1 } # An x64 VM would also run on windows-11-arm, emulated: every exe and # DLL of the zip must be of the machine of the runner (PE header) - name: Check the machine of the binaries of the zip shell: pwsh run: | $want = [Convert]::ToUInt16('${{ matrix.machine }}', 16) $bad = 0 Get-ChildItem "$env:RUNNER_TEMP\vm\*.exe", "$env:RUNNER_TEMP\vm\*.dll" | ForEach-Object { $b = [System.IO.File]::ReadAllBytes($_.FullName) $m = [BitConverter]::ToUInt16($b, [BitConverter]::ToInt32($b, 0x3C) + 4) if ($m -ne $want) { "{0}: machine 0x{1:X4}" -f $_.Name, $m; $bad++ } } exit $bad # With the same PATH: the C++ runtime DLLs of the plugin must be in the zip - name: Load the SimdJSONPlugin of the zip shell: pwsh run: | $vm = "$env:RUNNER_TEMP\vm" $env:PATH = "$vm;$env:SystemRoot\System32;$env:SystemRoot" $out = & "$vm\PharoConsole.exe" --headless build/build/vmmaker/image/VMMaker.image --no-default-preferences st plugins/SimdJSONPlugin/tests/ci-load.st if ($LASTEXITCODE -ne 0) { $out; exit $LASTEXITCODE } $out if (-not ($out -match '^SimdJSONPlugin native ')) { exit 1 } - uses: actions/upload-artifact@v7 with: path: build/build/packages/PharoVM-*-Windows-${{ matrix.arch }}-bin.zip archive: false if-no-files-found: error wasm: name: WebAssembly runs-on: ubuntu-24.04 timeout-minutes: 120 env: # The version the port needs (docs/WebAssembly.md) EMSDK_VERSION: 6.0.10 # The images of the site (docs/WebAssembly.md, Pharo versions): 15 is the default PHARO15_URL: https://files.pharo.org/image/150/Pharo15.0-SNAPSHOT.build.50.sha.b85960fcc6.arch.64bit.zip PHARO15_SHA256: 27164cb9c16685c108df4d2b11106fd6d26a62f37b3e39bb7bfabd176336f036 PHARO14_URL: https://files.pharo.org/image/140/Pharo14.0-SNAPSHOT.build.785.sha.74f3a1b398.arch.64bit.zip PHARO14_SHA256: 5396878ea912f87ce99fdbb7137e01a8122745b690a2ee6949338c0aa438b0ea PHARO13_URL: https://files.pharo.org/image/130/Pharo13.0-SNAPSHOT.build.749.sha.d7c6f761d5.arch.64bit.zip PHARO13_SHA256: 31991e50d6ddbb19170d35fabc6d1fb0a8e95ea311b69b079109426e3fec76ae # Pharo 15 build 41, the image of the site before: only its .sources is staged, for the images saved on it PHARO15B41_URL: https://files.pharo.org/image/150/Pharo15.0-SNAPSHOT.build.41.sha.4e572fed79.arch.64bit.zip PHARO15B41_SHA256: 8ce97d8d8a1b466a4a591d52a8f9f5822e2cff03669eacf91713b8f29db46727 # The native tree of build-wasm/host, which generates the sources; # the toolchain file of emcmake makes the wasm tree use emcc and em++ CC: clang CXX: clang++ steps: # (with the submodule plugins/SimdJSONPlugin/simdjson.st, the # Smalltalk code that lane 68 of wasm-check loads) - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false submodules: true - name: Fetch the release tags run: | git fetch --no-tags --force https://github.com/pharo-project/pharo-vm.git '+refs/tags/v*:refs/tags/v*' git describe --tags --match 'v[0-9]*' - name: Install clang run: | sudo apt-get update sudo apt-get install -y --no-install-recommends clang - name: Install Emscripten run: | git clone --depth 1 https://github.com/emscripten-core/emsdk.git "$RUNNER_TEMP/emsdk" "$RUNNER_TEMP/emsdk/emsdk" install "$EMSDK_VERSION" "$RUNNER_TEMP/emsdk/emsdk" activate "$EMSDK_VERSION" # (under their own names, from which stage.mjs reads the build of each) - name: Download the Pharo images run: | mkdir -p "$RUNNER_TEMP/images" "$RUNNER_TEMP/legacy" for v in 15 14 13 15B41; do eval "url=\$PHARO${v}_URL sha=\$PHARO${v}_SHA256" f="$RUNNER_TEMP/images/$(basename "$url")" curl -fsSL -o "$f" "$url" echo "$sha $f" | sha256sum -c - echo "PHARO${v}_ZIP=$f" >> "$GITHUB_ENV" done unzip -j -o "$RUNNER_TEMP/images/$(basename "$PHARO15B41_URL")" '*.sources' -d "$RUNNER_TEMP/legacy" echo "PHARO_LEGACY_SOURCES=$(ls "$RUNNER_TEMP"/legacy/*.sources)" >> "$GITHUB_ENV" # make wasm records its settings but does not remember them: give # wasm-check the same ones, or it builds the defaults again. The node # VM (wasm64, a 64-bit table) needs node 24.15 or later: the node of # emsdk (EMSDK_NODE), which emsdk_env.sh leaves off the PATH, and not # the node 22 of the runner - name: Build run: | source "$RUNNER_TEMP/emsdk/emsdk_env.sh" export PATH="$(dirname "$EMSDK_NODE"):$PATH" node --version make wasm WASM_IMAGE_ZIP="$PHARO15_ZIP" WASM_IMAGE_ID=p15 \ WASM_EXTRA_IMAGES="p13=$PHARO13_ZIP p14=$PHARO14_ZIP" \ WASM_EXTRA_SOURCES="$PHARO_LEGACY_SOURCES" WASM_LIBGIT2=ON - name: Check that CMake uses clang and clang++ run: sh scripts/check-clang.sh build-wasm/host build-wasm/cmake - name: Run the node test lanes run: | source "$RUNNER_TEMP/emsdk/emsdk_env.sh" export PATH="$(dirname "$EMSDK_NODE"):$PATH" make wasm-check WASM_IMAGE_ZIP="$PHARO15_ZIP" WASM_IMAGE_ID=p15 \ WASM_EXTRA_IMAGES="p13=$PHARO13_ZIP p14=$PHARO14_ZIP" \ WASM_EXTRA_SOURCES="$PHARO_LEGACY_SOURCES" WASM_LIBGIT2=ON # The logs of the lanes (and the stderr of the VMs of the failed cases, # kept in their directories), which the job log shows only in part - name: Keep the logs of the lanes if: failure() uses: actions/upload-artifact@v7 with: name: wasm-check-logs path: | build-wasm/tests-run/**/*.log build-wasm/tests-run/**/*.err build-wasm/tests-run/**/*.out build-wasm/tests-run/**/*.txt if-no-files-found: ignore retention-days: 14 - name: Package run: | version=$(cut -d'*' -f1 build-wasm/cmake/version.info | sed -E 's/\+[0-9]+\./-/') sha=$(cut -d'*' -f8 build-wasm/cmake/version.info) name=PharoVM-$version-WebAssembly mkdir -p "dist/$name" cp -R build-wasm/web build-wasm/node packaging/emscripten/tools/serve.mjs \ packaging/emscripten/dist/node-try.sh "dist/$name/" sed "s/@PHAROVM_COMMIT@/$sha/" packaging/emscripten/dist/README.md > "dist/$name/README.md" du -sh build-wasm/web/image/* cd dist && zip -qr "$name-bin.zip" "$name" ls -l "$name-bin.zip" - uses: actions/upload-artifact@v7 with: path: dist/PharoVM-*-WebAssembly-bin.zip archive: false if-no-files-found: error # The site of the WebAssembly zip, deployed to GitHub Pages directly from # the workflow (Settings > Pages > Build and deployment > Source must be # set to GitHub Actions). pages: name: GitHub Pages if: github.event_name == 'push' && github.ref == 'refs/heads/pharo-12' needs: wasm runs-on: ubuntu-24.04 permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/download-artifact@v8 with: pattern: PharoVM-*-WebAssembly-bin.zip path: artifacts skip-decompress: true - name: Check the site of the zip run: | # one artifact is saved in artifacts itself, several each in a # directory of its own: take the one file wherever it is zip=$(find artifacts -type f) test -n "$zip" test "$(echo "$zip" | wc -l)" -eq 1 unzip -q "$zip" -d unpacked mkdir site cp -R unpacked/PharoVM-*-WebAssembly/web/. site/ # every file of the manifest is there and as large as it says node -e 'const fs=require("fs"),m=JSON.parse(fs.readFileSync("site/manifest.json")); const all=[...m.files,...(m.images||[]).flatMap(i=>i.files),...(m.extraSources||[])]; for(const f of all){const s=fs.statSync("site/"+f.url).size; if(s!==f.gzSize) throw new Error("bad "+f.url);} if(!m.images||m.images.length!==3) throw new Error("expected 3 images");' du -sh site - uses: actions/upload-pages-artifact@v5 with: path: site - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 release: name: Release if: github.event_name == 'push' && github.ref == 'refs/heads/pharo-12' needs: [linux, macos, windows, wasm] runs-on: ubuntu-24.04 permissions: contents: write steps: # Each artifact is a zip uploaded as is: keep it, in a directory named # after it (with skip-decompress the file itself may be named artifact) - uses: actions/download-artifact@v8 with: pattern: PharoVM-* path: artifacts skip-decompress: true - name: Publish the zips as a release env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | mkdir zips for dir in artifacts/*/; do mv "$dir"* "zips/$(basename "$dir")" done ls -l zips test "$(ls zips | wc -l)" -eq 7 tags=$(ls zips | sed -E 's/-(Linux-[^-]+|Darwin-[^-]+|Windows-[^-]+|WebAssembly)-bin\.zip$//' | sort -u) test "$(echo "$tags" | wc -l)" -eq 1 tag=$tags if gh release view "$tag" > /dev/null 2>&1; then gh release upload "$tag" zips/* --clobber else gh release create "$tag" zips/* \ --target "$GITHUB_SHA" \ --title "$tag" \ --notes "The Pharo VM built from $GITHUB_SHA ($GITHUB_REF_NAME) by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" fi