From bc7ffe8955aa26c351d58841d4070a26420e48cd Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Fri, 15 May 2026 05:26:53 +0300 Subject: [PATCH 1/3] fix(ci): Make install-test HTTP server startup deterministic The fixed 2s sleep before running install.ps1 was not reliable on the windows-latest runner: when the Python http.server had not started yet, the install script failed with "connection actively refused". The failure was masked until #143 surfaced install.ps1 exit codes. - Replace the fixed sleep with a 30s readiness poll that fails loudly and dumps the server log when the server never becomes reachable. - Add actions/setup-python so the runner has a known-good Python on PATH regardless of windows-latest image migrations. - Extract the duplicated server-startup and binary-verification logic into two composite actions (cli/serve-archive, cli/verify-installed-binary), used by all three install-test jobs. - Standardize on http://127.0.0.1:8080 across platforms. - Allow .github in .gitignore so the new composite actions are tracked despite the broad .* hidden-file rule. --- .github/actions/cli/serve-archive/action.yml | 74 +++++++++++++++++++ .../cli/verify-installed-binary/action.yml | 30 ++++++++ .github/workflows/ci-cli.yaml | 57 ++++++++------ .gitignore | 1 + 4 files changed, 138 insertions(+), 24 deletions(-) create mode 100644 .github/actions/cli/serve-archive/action.yml create mode 100644 .github/actions/cli/verify-installed-binary/action.yml diff --git a/.github/actions/cli/serve-archive/action.yml b/.github/actions/cli/serve-archive/action.yml new file mode 100644 index 000000000..397febceb --- /dev/null +++ b/.github/actions/cli/serve-archive/action.yml @@ -0,0 +1,74 @@ +name: 'Serve test archive over HTTP' +description: 'Starts a Python http.server in a directory and waits until it is reachable' + +inputs: + archive-dir: + description: 'Directory whose contents to serve' + required: true + port: + description: 'Port to listen on' + required: false + default: '8080' + timeout-seconds: + description: 'How long to wait for the server to become ready' + required: false + default: '30' + +runs: + using: 'composite' + steps: + - name: Start server (unix) + if: runner.os != 'Windows' + shell: bash + working-directory: ${{ inputs.archive-dir }} + env: + SERVE_PORT: ${{ inputs.port }} + SERVE_TIMEOUT: ${{ inputs.timeout-seconds }} + run: | + LOG="${RUNNER_TEMP}/http-server.log" + python3 -m http.server "$SERVE_PORT" > "$LOG" 2>&1 & + for i in $(seq 1 "$SERVE_TIMEOUT"); do + if curl -fsS "http://127.0.0.1:${SERVE_PORT}/" -o /dev/null 2>/dev/null; then + echo "Server ready after ${i}s" + exit 0 + fi + sleep 1 + done + echo "Server failed to start within ${SERVE_TIMEOUT}s. Log:" + cat "$LOG" || true + exit 1 + + - name: Start server (windows) + if: runner.os == 'Windows' + shell: pwsh + env: + SERVE_DIR: ${{ inputs.archive-dir }} + SERVE_PORT: ${{ inputs.port }} + SERVE_TIMEOUT: ${{ inputs.timeout-seconds }} + run: | + $port = [int]$env:SERVE_PORT + $timeout = [int]$env:SERVE_TIMEOUT + $logOut = Join-Path $env:RUNNER_TEMP 'http-server.out.log' + $logErr = Join-Path $env:RUNNER_TEMP 'http-server.err.log' + $server = Start-Process -NoNewWindow -PassThru ` + -FilePath python ` + -ArgumentList '-m', 'http.server', "$port" ` + -WorkingDirectory $env:SERVE_DIR ` + -RedirectStandardOutput $logOut ` + -RedirectStandardError $logErr + for ($i = 1; $i -le $timeout; $i++) { + if ($server.HasExited) { break } + try { + Invoke-WebRequest -Uri "http://127.0.0.1:$port/" -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop | Out-Null + Write-Host "Server ready after ${i}s" + exit 0 + } catch { + Start-Sleep -Seconds 1 + } + } + Write-Host "Server failed to start within ${timeout}s." + Write-Host '--- stdout ---' + Get-Content $logOut -ErrorAction SilentlyContinue + Write-Host '--- stderr ---' + Get-Content $logErr -ErrorAction SilentlyContinue + throw 'Local HTTP server did not become ready' diff --git a/.github/actions/cli/verify-installed-binary/action.yml b/.github/actions/cli/verify-installed-binary/action.yml new file mode 100644 index 000000000..8f31c1e99 --- /dev/null +++ b/.github/actions/cli/verify-installed-binary/action.yml @@ -0,0 +1,30 @@ +name: 'Verify installed opentaint binary' +description: 'Runs ` --version`, falling back to the standard per-OS install location when no binary path is provided' + +inputs: + binary-path: + description: 'Path to the opentaint binary (typically from an install step output). Falls back to the standard per-OS install location when empty.' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Verify (unix) + if: runner.os != 'Windows' + shell: bash + env: + BINARY_PATH: ${{ inputs.binary-path }} + run: | + if [ -z "$BINARY_PATH" ]; then BINARY_PATH="$HOME/.opentaint/install/opentaint"; fi + "$BINARY_PATH" --version + + - name: Verify (windows) + if: runner.os == 'Windows' + shell: pwsh + env: + BINARY_PATH: ${{ inputs.binary-path }} + run: | + $binaryPath = $env:BINARY_PATH + if ([string]::IsNullOrEmpty($binaryPath)) { $binaryPath = "$env:LOCALAPPDATA\opentaint\install\opentaint.exe" } + & $binaryPath --version diff --git a/.github/workflows/ci-cli.yaml b/.github/workflows/ci-cli.yaml index c14735a35..4e23f0067 100644 --- a/.github/workflows/ci-cli.yaml +++ b/.github/workflows/ci-cli.yaml @@ -323,26 +323,29 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: cli/go.sum + - uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Build test archive working-directory: cli run: | go build -o opentaint . tar -czf opentaint-full_linux_amd64.tar.gz opentaint sha256sum opentaint-full_linux_amd64.tar.gz > checksums.txt - - name: Start file server - working-directory: cli - run: python3 -m http.server 8080 & + - name: Serve test archive + uses: ./.github/actions/cli/serve-archive + with: + archive-dir: cli - name: Run install.sh id: install-linux env: - OPENTAINT_DOWNLOAD_BASE_URL: http://localhost:8080 + OPENTAINT_DOWNLOAD_BASE_URL: http://127.0.0.1:8080 run: | echo "OPENTAINT_BINARY_PATH=$(bash scripts/install/install.sh | grep ^OPENTAINT_BINARY_PATH= | cut -d= -f2)" >> $GITHUB_OUTPUT - name: Verify installation - run: | - BINARY_PATH="${{ steps.install-linux.outputs.OPENTAINT_BINARY_PATH }}" - if [ -z "$BINARY_PATH" ]; then BINARY_PATH="$HOME/.opentaint/install/opentaint"; fi - "$BINARY_PATH" --version + uses: ./.github/actions/cli/verify-installed-binary + with: + binary-path: ${{ steps.install-linux.outputs.OPENTAINT_BINARY_PATH }} test-install-sh-macos: runs-on: macos-latest @@ -352,26 +355,29 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: cli/go.sum + - uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Build test archive working-directory: cli run: | go build -o opentaint . tar -czf opentaint-full_darwin_arm64.tar.gz opentaint shasum -a 256 opentaint-full_darwin_arm64.tar.gz > checksums.txt - - name: Start file server - working-directory: cli - run: python3 -m http.server 8080 & + - name: Serve test archive + uses: ./.github/actions/cli/serve-archive + with: + archive-dir: cli - name: Run install.sh id: install-macos env: - OPENTAINT_DOWNLOAD_BASE_URL: http://localhost:8080 + OPENTAINT_DOWNLOAD_BASE_URL: http://127.0.0.1:8080 run: | echo "OPENTAINT_BINARY_PATH=$(bash scripts/install/install.sh | grep ^OPENTAINT_BINARY_PATH= | cut -d= -f2)" >> $GITHUB_OUTPUT - name: Verify installation - run: | - BINARY_PATH="${{ steps.install-macos.outputs.OPENTAINT_BINARY_PATH }}" - if [ -z "$BINARY_PATH" ]; then BINARY_PATH="$HOME/.opentaint/install/opentaint"; fi - "$BINARY_PATH" --version + uses: ./.github/actions/cli/verify-installed-binary + with: + binary-path: ${{ steps.install-macos.outputs.OPENTAINT_BINARY_PATH }} test-install-ps1: runs-on: windows-latest @@ -381,6 +387,9 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: cli/go.sum + - uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Build test archive shell: pwsh working-directory: cli @@ -389,27 +398,27 @@ jobs: Compress-Archive -Path opentaint.exe -DestinationPath opentaint-full_windows_amd64.zip $hash = (Get-FileHash -Path opentaint-full_windows_amd64.zip -Algorithm SHA256).Hash.ToLower() "$hash opentaint-full_windows_amd64.zip" | Out-File -FilePath checksums.txt -Encoding utf8NoBOM + - name: Serve test archive + uses: ./.github/actions/cli/serve-archive + with: + archive-dir: cli - name: Run install.ps1 id: install-windows shell: pwsh env: OPENTAINT_DOWNLOAD_BASE_URL: http://127.0.0.1:8080 run: | - Start-Process -NoNewWindow python -ArgumentList "-m", "http.server", "8080" -WorkingDirectory cli - Start-Sleep -Seconds 2 $output = pwsh -File scripts/install/install.ps1 2>&1 | Out-String Write-Host $output if ($LASTEXITCODE -ne 0) { throw "install.ps1 exited with code $LASTEXITCODE" } if ($output -notmatch 'OPENTAINT_BINARY_PATH=(.+)') { - throw "install.ps1 did not emit OPENTAINT_BINARY_PATH" + throw 'install.ps1 did not emit OPENTAINT_BINARY_PATH' } $binaryPath = $Matches[1].Trim() "OPENTAINT_BINARY_PATH=$binaryPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 - name: Verify installation - shell: pwsh - run: | - $binaryPath = "${{ steps.install-windows.outputs.OPENTAINT_BINARY_PATH }}" - if ([string]::IsNullOrEmpty($binaryPath)) { $binaryPath = "$env:LOCALAPPDATA\opentaint\install\opentaint.exe" } - & $binaryPath --version + uses: ./.github/actions/cli/verify-installed-binary + with: + binary-path: ${{ steps.install-windows.outputs.OPENTAINT_BINARY_PATH }} diff --git a/.gitignore b/.gitignore index 30765f8d9..27bf36d6d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ config.local.* # Optional: Allow the .gitignore file itself to be tracked !.gitignore +!.github From 387418dec26a404385ab0798fb8e13c63d7cbc05 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Fri, 15 May 2026 05:46:02 +0300 Subject: [PATCH 2/3] fix(ci): Keep Windows install-test in single step and share polling logic The previous fix split the windows-latest install-test into a "serve archive" step plus a "run install.ps1" step. The python http.server started in the first step was killed by the runner's Job Object at step end, so install.ps1 in the next step hit "connection actively refused". - Inline the python server start, readiness poll and install.ps1 invocation into a single step in test-install-ps1. Add an inline comment recording why the step cannot be split. - Extract the readiness-poll algorithm into scripts/ci/wait-for-http-server.{sh,ps1} so each shell has one canonical implementation. cli/serve-archive now delegates to the bash helper; the Windows step calls the pwsh helper. - Reject windows-latest from cli/serve-archive with a clear error pointing callers at the inline pattern. - Parameterize the Windows step's SERVE_PORT and SERVE_TIMEOUT through env so the constants live in one place per call site. --- .github/actions/cli/serve-archive/action.yml | 65 +++++--------------- .github/workflows/ci-cli.yaml | 51 ++++++++++----- scripts/ci/wait-for-http-server.ps1 | 28 +++++++++ scripts/ci/wait-for-http-server.sh | 23 +++++++ 4 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 scripts/ci/wait-for-http-server.ps1 create mode 100644 scripts/ci/wait-for-http-server.sh diff --git a/.github/actions/cli/serve-archive/action.yml b/.github/actions/cli/serve-archive/action.yml index 397febceb..f31124211 100644 --- a/.github/actions/cli/serve-archive/action.yml +++ b/.github/actions/cli/serve-archive/action.yml @@ -1,5 +1,10 @@ -name: 'Serve test archive over HTTP' -description: 'Starts a Python http.server in a directory and waits until it is reachable' +name: 'Serve test archive over HTTP (unix only)' +description: | + Starts a Python http.server in a directory and waits until it is reachable. + Linux/macOS only: on Windows the GitHub Actions Job Object kills background + processes at step end, so the server would not survive into the next step. + Windows callers must inline the server start, readiness poll, and consumer + command in a single step. inputs: archive-dir: @@ -17,7 +22,14 @@ inputs: runs: using: 'composite' steps: - - name: Start server (unix) + - name: Reject Windows runner + if: runner.os == 'Windows' + shell: pwsh + run: | + Write-Error 'cli/serve-archive does not support Windows runners. Inline the server start, readiness poll, and the consumer command in a single step.' + exit 1 + + - name: Start server if: runner.os != 'Windows' shell: bash working-directory: ${{ inputs.archive-dir }} @@ -27,48 +39,5 @@ runs: run: | LOG="${RUNNER_TEMP}/http-server.log" python3 -m http.server "$SERVE_PORT" > "$LOG" 2>&1 & - for i in $(seq 1 "$SERVE_TIMEOUT"); do - if curl -fsS "http://127.0.0.1:${SERVE_PORT}/" -o /dev/null 2>/dev/null; then - echo "Server ready after ${i}s" - exit 0 - fi - sleep 1 - done - echo "Server failed to start within ${SERVE_TIMEOUT}s. Log:" - cat "$LOG" || true - exit 1 - - - name: Start server (windows) - if: runner.os == 'Windows' - shell: pwsh - env: - SERVE_DIR: ${{ inputs.archive-dir }} - SERVE_PORT: ${{ inputs.port }} - SERVE_TIMEOUT: ${{ inputs.timeout-seconds }} - run: | - $port = [int]$env:SERVE_PORT - $timeout = [int]$env:SERVE_TIMEOUT - $logOut = Join-Path $env:RUNNER_TEMP 'http-server.out.log' - $logErr = Join-Path $env:RUNNER_TEMP 'http-server.err.log' - $server = Start-Process -NoNewWindow -PassThru ` - -FilePath python ` - -ArgumentList '-m', 'http.server', "$port" ` - -WorkingDirectory $env:SERVE_DIR ` - -RedirectStandardOutput $logOut ` - -RedirectStandardError $logErr - for ($i = 1; $i -le $timeout; $i++) { - if ($server.HasExited) { break } - try { - Invoke-WebRequest -Uri "http://127.0.0.1:$port/" -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop | Out-Null - Write-Host "Server ready after ${i}s" - exit 0 - } catch { - Start-Sleep -Seconds 1 - } - } - Write-Host "Server failed to start within ${timeout}s." - Write-Host '--- stdout ---' - Get-Content $logOut -ErrorAction SilentlyContinue - Write-Host '--- stderr ---' - Get-Content $logErr -ErrorAction SilentlyContinue - throw 'Local HTTP server did not become ready' + bash "${GITHUB_WORKSPACE}/scripts/ci/wait-for-http-server.sh" \ + "http://127.0.0.1:${SERVE_PORT}/" "$SERVE_TIMEOUT" "$LOG" diff --git a/.github/workflows/ci-cli.yaml b/.github/workflows/ci-cli.yaml index 4e23f0067..b08abe623 100644 --- a/.github/workflows/ci-cli.yaml +++ b/.github/workflows/ci-cli.yaml @@ -398,26 +398,49 @@ jobs: Compress-Archive -Path opentaint.exe -DestinationPath opentaint-full_windows_amd64.zip $hash = (Get-FileHash -Path opentaint-full_windows_amd64.zip -Algorithm SHA256).Hash.ToLower() "$hash opentaint-full_windows_amd64.zip" | Out-File -FilePath checksums.txt -Encoding utf8NoBOM - - name: Serve test archive - uses: ./.github/actions/cli/serve-archive - with: - archive-dir: cli - - name: Run install.ps1 + # Server start, readiness poll and install must share a single step: + # on windows-latest the runner's Job Object kills background processes + # at step end, so the python server cannot live across steps. + - name: Serve archive and run install.ps1 id: install-windows shell: pwsh env: OPENTAINT_DOWNLOAD_BASE_URL: http://127.0.0.1:8080 + SERVE_PORT: '8080' + SERVE_TIMEOUT: '30' run: | - $output = pwsh -File scripts/install/install.ps1 2>&1 | Out-String - Write-Host $output - if ($LASTEXITCODE -ne 0) { - throw "install.ps1 exited with code $LASTEXITCODE" - } - if ($output -notmatch 'OPENTAINT_BINARY_PATH=(.+)') { - throw 'install.ps1 did not emit OPENTAINT_BINARY_PATH' + $port = [int]$env:SERVE_PORT + $logOut = Join-Path $env:RUNNER_TEMP 'http-server.out.log' + $logErr = Join-Path $env:RUNNER_TEMP 'http-server.err.log' + $server = Start-Process -NoNewWindow -PassThru ` + -FilePath python ` + -ArgumentList '-m', 'http.server', "$port" ` + -WorkingDirectory cli ` + -RedirectStandardOutput $logOut ` + -RedirectStandardError $logErr + try { + & "$env:GITHUB_WORKSPACE/scripts/ci/wait-for-http-server.ps1" ` + -Url "http://127.0.0.1:$port/" ` + -TimeoutSec ([int]$env:SERVE_TIMEOUT) ` + -Process $server ` + -StdoutLog $logOut ` + -StderrLog $logErr + + $output = pwsh -File scripts/install/install.ps1 2>&1 | Out-String + Write-Host $output + if ($LASTEXITCODE -ne 0) { + throw "install.ps1 exited with code $LASTEXITCODE" + } + if ($output -notmatch 'OPENTAINT_BINARY_PATH=(.+)') { + throw 'install.ps1 did not emit OPENTAINT_BINARY_PATH' + } + $binaryPath = $Matches[1].Trim() + "OPENTAINT_BINARY_PATH=$binaryPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + } finally { + if ($server -and -not $server.HasExited) { + Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue + } } - $binaryPath = $Matches[1].Trim() - "OPENTAINT_BINARY_PATH=$binaryPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 - name: Verify installation uses: ./.github/actions/cli/verify-installed-binary with: diff --git a/scripts/ci/wait-for-http-server.ps1 b/scripts/ci/wait-for-http-server.ps1 new file mode 100644 index 000000000..62acb9ccc --- /dev/null +++ b/scripts/ci/wait-for-http-server.ps1 @@ -0,0 +1,28 @@ +# Poll an HTTP URL until it responds; fail and dump the server log on timeout. +# Returns to the caller on success; throws on timeout or early server exit. +[CmdletBinding()] +param( + [Parameter(Mandatory)] [string]$Url, + [int]$TimeoutSec = 30, + [System.Diagnostics.Process]$Process, + [string]$StdoutLog, + [string]$StderrLog +) + +$ErrorActionPreference = 'Stop' + +for ($i = 1; $i -le $TimeoutSec; $i++) { + if ($Process -and $Process.HasExited) { break } + try { + Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop | Out-Null + Write-Host "Server ready after ${i}s" + return + } catch { + Start-Sleep -Seconds 1 + } +} + +Write-Host "Server did not become ready at $Url within ${TimeoutSec}s." +if ($StdoutLog) { Write-Host '--- stdout ---'; Get-Content $StdoutLog -ErrorAction SilentlyContinue } +if ($StderrLog) { Write-Host '--- stderr ---'; Get-Content $StderrLog -ErrorAction SilentlyContinue } +throw "Local HTTP server did not become ready at $Url" diff --git a/scripts/ci/wait-for-http-server.sh b/scripts/ci/wait-for-http-server.sh new file mode 100644 index 000000000..88a9a43f2 --- /dev/null +++ b/scripts/ci/wait-for-http-server.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Poll an HTTP URL until it responds; fail and dump the server log on timeout. +# Usage: wait-for-http-server.sh URL [TIMEOUT_SECONDS] [LOG_PATH] +set -euo pipefail + +URL="${1:?usage: $0 URL [TIMEOUT_SECONDS] [LOG_PATH]}" +TIMEOUT="${2:-30}" +LOG="${3:-}" + +for i in $(seq 1 "$TIMEOUT"); do + if curl -fsS "$URL" -o /dev/null 2>/dev/null; then + echo "Server ready after ${i}s" + exit 0 + fi + sleep 1 +done + +echo "Server did not become ready at ${URL} within ${TIMEOUT}s." +if [ -n "$LOG" ] && [ -f "$LOG" ]; then + echo '--- log ---' + cat "$LOG" +fi +exit 1 From 152fd925fa1c9bb74d5be4341e08e8df40eec945 Mon Sep 17 00:00:00 2001 From: Aleksandr Misonizhnik Date: Fri, 15 May 2026 10:56:36 +0300 Subject: [PATCH 3/3] refactor(ci): Extract build-test-archive action and installer wrappers The install-test jobs still inlined per-OS build commands and the parse logic that translates an install script's OPENTAINT_BINARY_PATH line into a step output. Pull those out so each job reads as a declarative step list. - Add cli/build-test-archive composite action that handles the three per-OS build + checksum recipes behind one uses: call. - Add scripts/ci/run-installer-sh.sh and run-installer-pwsh.ps1 that run an install script, echo its output, and propagate OPENTAINT_BINARY_PATH to GITHUB_OUTPUT. The bash and pwsh install-test jobs each have one canonical implementation of this parse rule now. - Rewrite the three install-test jobs around these helpers; the Windows step still inlines server orchestration due to the Job Object constraint, but everything else collapses to uses: / script calls. --- .../actions/cli/build-test-archive/action.yml | 39 +++++++++++++++++++ .github/workflows/ci-cli.yaml | 39 ++++--------------- scripts/ci/run-installer-pwsh.ps1 | 22 +++++++++++ scripts/ci/run-installer-sh.sh | 18 +++++++++ 4 files changed, 86 insertions(+), 32 deletions(-) create mode 100644 .github/actions/cli/build-test-archive/action.yml create mode 100644 scripts/ci/run-installer-pwsh.ps1 create mode 100644 scripts/ci/run-installer-sh.sh diff --git a/.github/actions/cli/build-test-archive/action.yml b/.github/actions/cli/build-test-archive/action.yml new file mode 100644 index 000000000..df4fdf403 --- /dev/null +++ b/.github/actions/cli/build-test-archive/action.yml @@ -0,0 +1,39 @@ +name: 'Build opentaint test archive' +description: 'Builds the opentaint CLI and packages it into the per-OS archive plus checksums.txt that the installers expect at the download URL' + +inputs: + working-directory: + description: 'Directory containing the Go module to build' + required: false + default: 'cli' + +runs: + using: 'composite' + steps: + - name: Build archive (linux) + if: runner.os == 'Linux' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + go build -o opentaint . + tar -czf opentaint-full_linux_amd64.tar.gz opentaint + sha256sum opentaint-full_linux_amd64.tar.gz > checksums.txt + + - name: Build archive (macos) + if: runner.os == 'macOS' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + go build -o opentaint . + tar -czf opentaint-full_darwin_arm64.tar.gz opentaint + shasum -a 256 opentaint-full_darwin_arm64.tar.gz > checksums.txt + + - name: Build archive (windows) + if: runner.os == 'Windows' + shell: pwsh + working-directory: ${{ inputs.working-directory }} + run: | + go build -o opentaint.exe . + Compress-Archive -Path opentaint.exe -DestinationPath opentaint-full_windows_amd64.zip + $hash = (Get-FileHash -Path opentaint-full_windows_amd64.zip -Algorithm SHA256).Hash.ToLower() + "$hash opentaint-full_windows_amd64.zip" | Out-File -FilePath checksums.txt -Encoding utf8NoBOM diff --git a/.github/workflows/ci-cli.yaml b/.github/workflows/ci-cli.yaml index b08abe623..a4e382d9f 100644 --- a/.github/workflows/ci-cli.yaml +++ b/.github/workflows/ci-cli.yaml @@ -327,11 +327,7 @@ jobs: with: python-version: '3.x' - name: Build test archive - working-directory: cli - run: | - go build -o opentaint . - tar -czf opentaint-full_linux_amd64.tar.gz opentaint - sha256sum opentaint-full_linux_amd64.tar.gz > checksums.txt + uses: ./.github/actions/cli/build-test-archive - name: Serve test archive uses: ./.github/actions/cli/serve-archive with: @@ -340,8 +336,7 @@ jobs: id: install-linux env: OPENTAINT_DOWNLOAD_BASE_URL: http://127.0.0.1:8080 - run: | - echo "OPENTAINT_BINARY_PATH=$(bash scripts/install/install.sh | grep ^OPENTAINT_BINARY_PATH= | cut -d= -f2)" >> $GITHUB_OUTPUT + run: bash scripts/ci/run-installer-sh.sh scripts/install/install.sh - name: Verify installation uses: ./.github/actions/cli/verify-installed-binary with: @@ -359,11 +354,7 @@ jobs: with: python-version: '3.x' - name: Build test archive - working-directory: cli - run: | - go build -o opentaint . - tar -czf opentaint-full_darwin_arm64.tar.gz opentaint - shasum -a 256 opentaint-full_darwin_arm64.tar.gz > checksums.txt + uses: ./.github/actions/cli/build-test-archive - name: Serve test archive uses: ./.github/actions/cli/serve-archive with: @@ -372,8 +363,7 @@ jobs: id: install-macos env: OPENTAINT_DOWNLOAD_BASE_URL: http://127.0.0.1:8080 - run: | - echo "OPENTAINT_BINARY_PATH=$(bash scripts/install/install.sh | grep ^OPENTAINT_BINARY_PATH= | cut -d= -f2)" >> $GITHUB_OUTPUT + run: bash scripts/ci/run-installer-sh.sh scripts/install/install.sh - name: Verify installation uses: ./.github/actions/cli/verify-installed-binary with: @@ -391,13 +381,7 @@ jobs: with: python-version: '3.x' - name: Build test archive - shell: pwsh - working-directory: cli - run: | - go build -o opentaint.exe . - Compress-Archive -Path opentaint.exe -DestinationPath opentaint-full_windows_amd64.zip - $hash = (Get-FileHash -Path opentaint-full_windows_amd64.zip -Algorithm SHA256).Hash.ToLower() - "$hash opentaint-full_windows_amd64.zip" | Out-File -FilePath checksums.txt -Encoding utf8NoBOM + uses: ./.github/actions/cli/build-test-archive # Server start, readiness poll and install must share a single step: # on windows-latest the runner's Job Object kills background processes # at step end, so the python server cannot live across steps. @@ -425,17 +409,8 @@ jobs: -Process $server ` -StdoutLog $logOut ` -StderrLog $logErr - - $output = pwsh -File scripts/install/install.ps1 2>&1 | Out-String - Write-Host $output - if ($LASTEXITCODE -ne 0) { - throw "install.ps1 exited with code $LASTEXITCODE" - } - if ($output -notmatch 'OPENTAINT_BINARY_PATH=(.+)') { - throw 'install.ps1 did not emit OPENTAINT_BINARY_PATH' - } - $binaryPath = $Matches[1].Trim() - "OPENTAINT_BINARY_PATH=$binaryPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + & "$env:GITHUB_WORKSPACE/scripts/ci/run-installer-pwsh.ps1" ` + -Script scripts/install/install.ps1 } finally { if ($server -and -not $server.HasExited) { Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue diff --git a/scripts/ci/run-installer-pwsh.ps1 b/scripts/ci/run-installer-pwsh.ps1 new file mode 100644 index 000000000..e0be11d3e --- /dev/null +++ b/scripts/ci/run-installer-pwsh.ps1 @@ -0,0 +1,22 @@ +# Run an opentaint PowerShell install script and propagate its +# OPENTAINT_BINARY_PATH line to $env:GITHUB_OUTPUT so a verify step can +# consume it. +[CmdletBinding()] +param( + [Parameter(Mandatory)] [string]$Script +) + +$ErrorActionPreference = 'Stop' + +$output = pwsh -File $Script 2>&1 | Out-String +Write-Host $output + +if ($LASTEXITCODE -ne 0) { + throw "$Script exited with code $LASTEXITCODE" +} +if ($output -notmatch 'OPENTAINT_BINARY_PATH=(.+)') { + throw "$Script did not emit OPENTAINT_BINARY_PATH" +} + +$binaryPath = $Matches[1].Trim() +"OPENTAINT_BINARY_PATH=$binaryPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 diff --git a/scripts/ci/run-installer-sh.sh b/scripts/ci/run-installer-sh.sh new file mode 100644 index 000000000..8104e460c --- /dev/null +++ b/scripts/ci/run-installer-sh.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Run an opentaint POSIX install script and propagate its OPENTAINT_BINARY_PATH +# line to $GITHUB_OUTPUT so a verify step can consume it. +# Usage: run-installer-sh.sh PATH_TO_INSTALL_SCRIPT +set -euo pipefail + +SCRIPT="${1:?usage: $0 PATH_TO_INSTALL_SCRIPT}" + +OUTPUT=$(bash "$SCRIPT") +echo "$OUTPUT" + +BINARY_PATH=$(echo "$OUTPUT" | grep '^OPENTAINT_BINARY_PATH=' | cut -d= -f2-) +if [ -z "$BINARY_PATH" ]; then + echo "$SCRIPT did not emit OPENTAINT_BINARY_PATH" >&2 + exit 1 +fi + +echo "OPENTAINT_BINARY_PATH=$BINARY_PATH" >> "$GITHUB_OUTPUT"