diff options
Diffstat (limited to '.github/workflows')
| -rw-r--r-- | .github/workflows/.gitattributes | 1 | ||||
| -rw-r--r-- | .github/workflows/build.yaml | 172 | ||||
| -rw-r--r-- | .github/workflows/check-pr.yaml | 21 | ||||
| -rw-r--r-- | .github/workflows/cla.yaml | 4 | ||||
| -rw-r--r-- | .github/workflows/pre-commit.yaml | 4 | ||||
| -rw-r--r-- | .github/workflows/qatest.yaml | 610 | ||||
| -rw-r--r-- | .github/workflows/tag-release.yaml | 20 |
7 files changed, 716 insertions, 116 deletions
diff --git a/.github/workflows/.gitattributes b/.github/workflows/.gitattributes new file mode 100644 index 0000000000..6dc1cca42a --- /dev/null +++ b/.github/workflows/.gitattributes @@ -0,0 +1 @@ +qatest.yaml -text eol=crlf diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 964e4e3e0b..4c948e5586 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,68 +1,58 @@ name: Build on: + workflow_dispatch: pull_request: push: branches: ["main", "release/*", "project/*"] tags: ["Second_Life*"] jobs: - # The whole point of the setvar job is that we want to set a variable once - # that will be consumed by multiple subsequent jobs. We tried setting it in - # the global env, but a job.env can't directly reference the global env - # context. - setvar: + # The whole point of the setup job is that we want to set variables once + # that will be consumed by multiple subsequent jobs. + setup: runs-on: ubuntu-latest outputs: release_run: ${{ steps.setvar.outputs.release_run }} - branch: ${{ steps.which-branch.outputs.branch }} - relnotes: ${{ steps.which-branch.outputs.relnotes }} + configurations: ${{ steps.setvar.outputs.configurations }} + bugsplat_db: ${{ steps.setvar.outputs.bugsplat_db }} env: # Build with a tag like "Second_Life#abcdef0" to generate a release page # (used for builds we are planning to deploy). - # Even though inputs.release_run is specified with type boolean, which - # correctly presents a checkbox, its *value* is a GH workflow string - # 'true' or 'false'. If you simply test github.event.inputs.release_run, - # it always evaluates as true because it's a non-empty string either way. # When you want to use a string variable as a workflow YAML boolean, it's # important to ensure it's the empty string when false. If you omit || '', - # its value when false is "false", which (again) is interpreted as true. - RELEASE_RUN: ${{ (github.event.inputs.release_run != 'false' || (github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life'))) && 'Y' || '' }} + # its value when false is "false", which is interpreted as true. + RELEASE_RUN: ${{ (github.event.inputs.release_run || github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life')) && 'Y' || '' }} + FROM_FORK: ${{ github.event.pull_request.head.repo.fork }} steps: - - name: Set Variable + - name: Set Variables id: setvar shell: bash run: | echo "release_run=$RELEASE_RUN" >> "$GITHUB_OUTPUT" - - name: Determine source branch - id: which-branch - uses: secondlife/viewer-build-util/which-branch@v2 - with: - token: ${{ github.token }} - + if [[ "$FROM_FORK" == "true" ]]; then + # PR from fork; don't build with Bugsplat, proprietary libs + echo 'configurations=["ReleaseOS"]' >> $GITHUB_OUTPUT + echo "bugsplat_db=" >> $GITHUB_OUTPUT + else + echo 'configurations=["Release"]' >> $GITHUB_OUTPUT + echo "bugsplat_db=SecondLife_Viewer_2018" >> $GITHUB_OUTPUT + fi build: - needs: setvar + needs: setup strategy: matrix: - runner: [windows-large, macos-12-xl, linux-large] - configuration: [Release, ReleaseOS] - Linden: [true] - include: - - runner: macos-12-xl - developer_dir: "/Applications/Xcode_14.0.1.app/Contents/Developer" - exclude: - - runner: windows-large - configuration: ReleaseOS - - runner: macos-12-xl - configuration: ReleaseOS - - runner: linux-large - configuration: Release + runner: ${{ fromJson((github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/Second_Life')) && '["windows-large","macos-15-xlarge"]' || '["windows-2022","macos-15-xlarge"]') }} + configuration: ${{ fromJson(needs.setup.outputs.configurations) }} runs-on: ${{ matrix.runner }} outputs: viewer_channel: ${{ steps.build.outputs.viewer_channel }} viewer_version: ${{ steps.build.outputs.viewer_version }} - imagename: ${{ steps.build.outputs.imagename }} + viewer_branch: ${{ steps.which-branch.outputs.branch }} + relnotes: ${{ steps.which-branch.outputs.relnotes }} + imagename: ${{ steps.build.outputs.imagename }} + configuration: ${{ matrix.configuration }} env: AUTOBUILD_ADDRSIZE: 64 AUTOBUILD_BUILD_ID: ${{ github.run_id }} @@ -75,12 +65,9 @@ jobs: # autobuild-package.xml. AUTOBUILD_VCS_INFO: "true" AUTOBUILD_VSVER: "170" - DEVELOPER_DIR: ${{ matrix.developer_dir }} + DEVELOPER_DIR: "/Applications/Xcode_16.4.app/Contents/Developer" # Ensure that Linden viewer builds engage Bugsplat. - BUGSPLAT_DB: ${{ matrix.Linden && 'SecondLife_Viewer_2018' || '' }} - # Run BUILD steps for Release configuration. - # Run BUILD steps for ReleaseOS configuration only for release runs. - BUILD: ${{ (matrix.Linden || needs.setvar.outputs.release_run) && 'Y' || '' }} + BUGSPLAT_DB: ${{ needs.setup.outputs.bugsplat_db }} build_coverity: false build_log_dir: ${{ github.workspace }}/.logs build_viewer: true @@ -99,39 +86,32 @@ jobs: variants: ${{ matrix.configuration }} steps: - name: Checkout code - if: env.BUILD - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Setup python - if: env.BUILD uses: actions/setup-python@v5 with: python-version: "3.11" - - name: Checkout build variables - if: env.BUILD - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: secondlife/build-variables ref: master path: .build-variables - name: Checkout master-message-template - if: env.BUILD - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: secondlife/master-message-template path: .master-message-template - name: Install autobuild and python dependencies - if: env.BUILD run: pip3 install autobuild llsd - name: Cache autobuild packages id: cache-installables - if: env.BUILD uses: actions/cache@v4 with: path: .autobuild-installables @@ -140,27 +120,17 @@ jobs: ${{ runner.os }}-64-${{ matrix.configuration }}- ${{ runner.os }}-64- - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install -y \ - libpulse-dev libunwind-dev \ - libgl1-mesa-dev libglu1-mesa-dev libxinerama-dev \ - libxcursor-dev libxfixes-dev libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev ninja-build libxft-dev \ - llvm mold libpipewire-0.3-dev - - - name: Install windows dependencies - if: env.BUILD && runner.os == 'Windows' - run: choco install nsis-unicode + - name: Determine source branch + id: which-branch + uses: secondlife/viewer-build-util/which-branch@v2 + with: + token: ${{ github.token }} - name: Build id: build - if: env.BUILD shell: bash env: - AUTOBUILD_VCS_BRANCH: ${{ needs.setvar.outputs.branch }} + AUTOBUILD_VCS_BRANCH: ${{ steps.which-branch.outputs.branch }} RUNNER_OS: ${{ runner.os }} run: | # set up things the viewer's build.sh script expects @@ -232,23 +202,34 @@ jobs: [[ "$arch" == "MINGW6" ]] && arch=CYGWIN export AUTOBUILD="$(which autobuild)" - # determine the viewer channel from the branch name + # determine the viewer channel from the branch or tag name + # trigger an EDU build by including "edu" in the tag + edu=${{ github.ref_type == 'tag' && contains(github.ref_name, 'edu') }} + echo "ref_type=${{ github.ref_type }}, ref_name=${{ github.ref_name }}, edu='$edu'" branch=$AUTOBUILD_VCS_BRANCH - IFS='/' read -ra ba <<< "$branch" - prefix=${ba[0]} - if [ "$prefix" == "project" ]; then - IFS='_' read -ra prj <<< "${ba[1]}" - # uppercase first letter of each word - export viewer_channel="Second Life Project ${prj[*]^}" - elif [[ "$prefix" == "release" || "$prefix" == "main" ]]; + if [[ "$edu" == "true" ]] then - export viewer_channel="Second Life Release" + export viewer_channel="Second Life Release edu" elif [[ "$branch" == "develop" ]]; then export viewer_channel="Second Life Develop" else - export viewer_channel="Second Life Test" + IFS='/' read -ra ba <<< "$branch" + prefix=${ba[0]} + if [ "$prefix" == "project" ]; then + IFS='_' read -ra prj <<< "${ba[1]}" + prj_str="${prj[*]}" + # uppercase first letter of each word + capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}') + export viewer_channel="Second Life Project $capitalized" + elif [[ "$prefix" == "release" || "$prefix" == "main" ]]; + then + export viewer_channel="Second Life Release" + else + export viewer_channel="Second Life Test" + fi fi + echo "viewer_channel=$viewer_channel" echo "viewer_channel=$viewer_channel" >> "$GITHUB_OUTPUT" # On windows we need to point the build to the correct python # as neither CMake's FindPython nor our custom Python.cmake module @@ -262,13 +243,6 @@ jobs: fi export PYTHON_COMMAND_NATIVE="$(native_path "$PYTHON_COMMAND")" - # Compile with clang, link with mold on linux. - if [[ "$RUNNER_OS" == "Linux" ]]; then - export CC=clang - export CXX=clang++ - export CMAKE_OPTIONS='-DLINK_WITH_MOLD=ON' - fi - ./build.sh # Each artifact is downloaded as a distinct .zip file. Multiple jobs @@ -290,26 +264,23 @@ jobs: echo "artifact=$RUNNER_OS$cfg_suffix" >> $GITHUB_OUTPUT - name: Upload executable - if: (matrix.Linden && steps.build.outputs.viewer_app) || runner.os == 'Linux' + if: steps.build.outputs.viewer_app uses: actions/upload-artifact@v4 with: name: "${{ steps.build.outputs.artifact }}-app" path: | ${{ steps.build.outputs.viewer_app }} - # The other upload of nontrivial size is the symbol file. Use a distinct # artifact for that too. - name: Upload symbol file - if: matrix.Linden + if: steps.build.outputs.symbolfile uses: actions/upload-artifact@v4 with: name: "${{ steps.build.outputs.artifact }}-symbols" - path: | - ${{ steps.build.outputs.symbolfile }} + path: ${{ steps.build.outputs.symbolfile }} - name: Upload metadata - if: matrix.Linden uses: actions/upload-artifact@v4 with: name: "${{ steps.build.outputs.artifact }}-metadata" @@ -320,7 +291,7 @@ jobs: - name: Upload physics package uses: actions/upload-artifact@v4 # should only be set for viewer-private - if: matrix.Linden && steps.build.outputs.physicstpv + if: matrix.configuration == 'Release' && steps.build.outputs.physicstpv with: name: "${{ steps.build.outputs.artifact }}-physics" # emitted by build.sh, zero or one lines @@ -335,11 +306,11 @@ jobs: AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} needs: build - runs-on: windows-large + runs-on: windows-2022 steps: - name: Sign and package Windows viewer if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID - uses: secondlife/viewer-build-util/sign-pkg-windows@v2 + uses: secondlife/viewer-build-util/sign-pkg-windows@v2.0.4 with: vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}" cert_name: "${{ env.AZURE_CERT_NAME }}" @@ -394,6 +365,7 @@ jobs: BUGSPLAT_USER: ${{ secrets.BUGSPLAT_USER }} BUGSPLAT_PASS: ${{ secrets.BUGSPLAT_PASS }} needs: build + if: needs.build.outputs.configuration == 'Release' runs-on: ubuntu-latest steps: - name: Download viewer exe @@ -428,6 +400,7 @@ jobs: BUGSPLAT_USER: ${{ secrets.BUGSPLAT_USER }} BUGSPLAT_PASS: ${{ secrets.BUGSPLAT_PASS }} needs: build + if: needs.build.outputs.configuration == 'Release' runs-on: ubuntu-latest steps: - name: Download Mac Symbols @@ -448,13 +421,9 @@ jobs: files: "**/*.xcarchive.zip" release: - needs: [setvar, build, sign-and-package-windows, sign-and-package-mac] + needs: [setup, build, sign-and-package-windows, sign-and-package-mac] runs-on: ubuntu-latest - # action-gh-release requires a tag (presumably for automatic generation of - # release notes). Possible TODO: if we arrive here but do not have a - # suitable tag for github.sha, create one? If we do that, of course remove - # this == 'tag' condition. - if: needs.setvar.outputs.release_run && github.ref_type == 'tag' + if: needs.setup.outputs.release_run steps: - uses: actions/download-artifact@v4 with: @@ -464,10 +433,6 @@ jobs: with: pattern: "*-metadata" - - uses: actions/download-artifact@v4 - with: - pattern: "LinuxOS-app" - - name: Rename metadata run: | cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml @@ -488,11 +453,10 @@ jobs: Build ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ${{ needs.build.outputs.viewer_channel }} ${{ needs.build.outputs.viewer_version }} - ${{ needs.setvar.outputs.relnotes }} + ${{ needs.build.outputs.relnotes }} prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} - previous_tag: release append_body: true fail_on_unmatched_files: true files: | diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml new file mode 100644 index 0000000000..a5cee9157c --- /dev/null +++ b/.github/workflows/check-pr.yaml @@ -0,0 +1,21 @@ +name: Check PR + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +permissions: + contents: read + +jobs: + check-description: + runs-on: ubuntu-latest + steps: + - name: Check PR description + uses: actions/github-script@v7 + with: + script: | + const description = context.payload.pull_request.body || ''; + if (description.trim().length < 20) { + core.setFailed("❌ PR description is too short. Please provide at least 20 characters."); + } diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 3f4bf21864..627ba512c4 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: CLA Assistant if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' - uses: secondlife-3p/contributor-assistant@v2 + uses: secondlife-3p/contributor-assistant@v2.6.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PERSONAL_ACCESS_TOKEN: ${{ secrets.SHARED_CLA_TOKEN }} @@ -23,4 +23,4 @@ jobs: path-to-signatures: signatures.json remote-organization-name: secondlife remote-repository-name: cla-signatures - allowlist: callum@mbp.localdomain + allowlist: callum@mbp.localdomain,rye@lindenlab.com,rye diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index d626eef38d..726e1cd889 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -11,8 +11,8 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v4 with: python-version: 3.x - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/qatest.yaml b/.github/workflows/qatest.yaml new file mode 100644 index 0000000000..b6883d88d4 --- /dev/null +++ b/.github/workflows/qatest.yaml @@ -0,0 +1,610 @@ +name: Run QA Test # Runs automated tests on self-hosted QA machines + +permissions: + contents: read + +on: + workflow_run: + workflows: ["Build"] + types: + - completed + workflow_dispatch: + inputs: + build_id: + description: 'Build workflow run ID (e.g. For github.com/secondlife/viewer/actions/runs/1234567890 the ID is 1234567890)' + required: true + default: '14806728332' + +jobs: + debug-workflow: + runs-on: ubuntu-latest + steps: + - name: Debug Workflow Variables + run: | + echo "Workflow Conclusion: ${{ github.event.workflow_run.conclusion }}" + echo "Workflow Head Branch: ${{ github.event.workflow_run.head_branch }}" + echo "Workflow Run ID: ${{ github.event.workflow_run.id }}" + echo "Head Commit Message: ${{ github.event.workflow_run.head_commit.message }}" + echo "GitHub Ref: ${{ github.ref }}" + echo "GitHub Ref Name: ${{ github.ref_name }}" + echo "GitHub Event Name: ${{ github.event_name }}" + echo "GitHub Workflow Name: ${{ github.workflow }}" + + install-viewer-and-run-tests: + concurrency: + group: ${{ github.workflow }}-${{ matrix.runner }} + cancel-in-progress: false # Prevents cancellation of in-progress jobs + + strategy: + matrix: + include: + - os: windows + runner: qa-windows-atlas + artifact: Windows-installer + install-path: 'C:\viewer-automation-main' + - os: windows + runner: qa-windows-asus-dan + artifact: Windows-installer + install-path: 'C:\viewer-automation-main' + - os: windows + runner: qa-windows-kurt + artifact: Windows-installer + install-path: 'C:\viewer-automation-main' + - os: mac + runner: qa-mac-dan + artifact: macOS-installer + install-path: '$HOME/Documents/viewer-automation' + - os: mac + runner: qa-mac-atlas + artifact: macOS-installer + install-path: '$HOME/Documents/viewer-automation' + - os: mac + runner: qa-mac-caleb + artifact: macOS-installer + install-path: '$HOME/Documents/viewer-automation' + fail-fast: false + + runs-on: [self-hosted, "${{ matrix.runner }}"] + # Run test only on successful builds of Second_Life_X branches or on manual dispatch + if: > + (github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + startsWith(github.event.workflow_run.head_branch, 'Second_Life')) || + github.event_name == 'workflow_dispatch' + + steps: + # Windows-specific steps + - name: Set Build ID + if: matrix.os == 'windows' + shell: pwsh + run: | + if ("${{ github.event_name }}" -eq "workflow_dispatch") { + echo "BUILD_ID=${{ github.event.inputs.build_id }}" | Out-File -FilePath $env:GITHUB_ENV -Append + echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.inputs.build_id }}/artifacts" | Out-File -FilePath $env:GITHUB_ENV -Append + } else { + echo "BUILD_ID=${{ github.event.workflow_run.id }}" | Out-File -FilePath $env:GITHUB_ENV -Append + echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.workflow_run.id }}/artifacts" | Out-File -FilePath $env:GITHUB_ENV -Append + } + + - name: Temporarily Allow PowerShell Scripts (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + Set-ExecutionPolicy RemoteSigned -Scope Process -Force + + - name: Verify viewer-automation-main Exists (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + if (-Not (Test-Path -Path '${{ matrix.install-path }}')) { + Write-Host '❌ Error: viewer-automation folder not found on runner!' + exit 1 + } + Write-Host '✅ viewer-automation folder is provided.' + + - name: Verify viewer-automation-main is Up-To-Date (Windows) + if: matrix.os == 'windows' + shell: pwsh + continue-on-error: true + run: | + cd ${{ matrix.install-path }} + Write-Host "Checking for repository updates..." + + # Check if .git directory exists + if (Test-Path -Path ".git") { + try { + # Save local changes instead of discarding them + git stash push -m "Automated stash before update $(Get-Date)" + Write-Host "Local changes saved (if any)" + + # Update the repository + git pull + Write-Host "✅ Repository updated successfully" + + # Try to restore local changes if any were stashed + $stashList = git stash list + if ($stashList -match "Automated stash before update") { + try { + git stash pop + Write-Host "✅ Local changes restored successfully" + } catch { + Write-Host "⚠️ Conflict when restoring local changes" + # Save the conflicted state in a new branch for later review + $branchName = "conflict-recovery-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + git checkout -b $branchName + Write-Host "✅ Created branch '$branchName' with conflicted state" + + # For test execution, revert to a clean state + git reset --hard HEAD + Write-Host "✅ Reset to clean state for test execution" + } + } + } catch { + Write-Host "⚠️ Could not update repository: $_" + Write-Host "Continuing with existing files..." + } + } else { + Write-Host "⚠️ Not a Git repository, using existing files" + } + + - name: Verify Python Installation (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + try { + $pythonVersion = (python --version) + Write-Host "✅ Python found: $pythonVersion" + } catch { + Write-Host "❌ Error: Python not found in PATH. Please install Python on this runner." + exit 1 + } + + - name: Setup Python Virtual Environment (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force + cd ${{ matrix.install-path }} + + if (-Not (Test-Path -Path ".venv")) { + Write-Host "Creating virtual environment..." + python -m venv .venv + } else { + Write-Host "Using existing virtual environment" + } + + # Direct environment activation to avoid script execution issues + $env:VIRTUAL_ENV = "$PWD\.venv" + $env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH" + + # Install dependencies + if (Test-Path -Path "requirements.txt") { + Write-Host "Installing dependencies from requirements.txt..." + pip install -r requirements.txt + + # Install Playwright browsers - add this line + Write-Host "Installing Playwright browsers..." + python -m playwright install + } else { + pip install outleap requests behave playwright + # Install Playwright browsers - add this line + Write-Host "Installing Playwright browsers..." + python -m playwright install + } + + - name: Fetch & Download Installer Artifact (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + $BUILD_ID = "${{ env.BUILD_ID }}" + $ARTIFACTS_URL = "${{ env.ARTIFACTS_URL }}" + + # Fetch the correct artifact URL + $response = Invoke-RestMethod -Headers @{Authorization="token ${{ secrets.GITHUB_TOKEN }}" } -Uri $ARTIFACTS_URL + $ARTIFACT_NAME = ($response.artifacts | Where-Object { $_.name -eq "${{ matrix.artifact }}" }).archive_download_url + + if (-Not $ARTIFACT_NAME) { + Write-Host "❌ Error: ${{ matrix.artifact }} artifact not found!" + exit 1 + } + + Write-Host "✅ Artifact found: $ARTIFACT_NAME" + + # Secure download path + $DownloadPath = "$env:TEMP\secondlife-build-$BUILD_ID" + New-Item -ItemType Directory -Path $DownloadPath -Force | Out-Null + $InstallerPath = "$DownloadPath\installer.zip" + + # Download the ZIP + Invoke-WebRequest -Uri $ARTIFACT_NAME -Headers @{Authorization="token ${{ secrets.GITHUB_TOKEN }}"} -OutFile $InstallerPath + + # Ensure download succeeded + if (-Not (Test-Path $InstallerPath)) { + Write-Host "❌ Error: Failed to download ${{ matrix.artifact }}.zip" + exit 1 + } + + # Set the path for other steps + echo "DOWNLOAD_PATH=$DownloadPath" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Extract Installer & Locate Executable (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + $BUILD_ID = "${{ env.BUILD_ID }}" + $ExtractPath = "${{ env.DOWNLOAD_PATH }}" + $InstallerZip = "$ExtractPath\installer.zip" + + # Print paths for debugging + Write-Host "Extract Path: $ExtractPath" + Write-Host "Installer ZIP Path: $InstallerZip" + + # Verify ZIP exists before extracting + if (-Not (Test-Path $InstallerZip)) { + Write-Host "❌ Error: ZIP file not found at $InstallerZip!" + exit 1 + } + + Write-Host "✅ ZIP file exists and is valid. Extracting..." + + Expand-Archive -Path $InstallerZip -DestinationPath $ExtractPath -Force + + # Find installer executable + $INSTALLER_PATH = (Get-ChildItem -Path $ExtractPath -Filter '*.exe' -Recurse | Select-Object -First 1).FullName + + if (-Not $INSTALLER_PATH -or $INSTALLER_PATH -eq "") { + Write-Host "❌ Error: No installer executable found in the extracted files!" + Write-Host "📂 Extracted Files:" + Get-ChildItem -Path $ExtractPath -Recurse | Format-Table -AutoSize + exit 1 + } + + Write-Host "✅ Installer found: $INSTALLER_PATH" + echo "INSTALLER_PATH=$INSTALLER_PATH" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Install Second Life (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + # Windows - Use Task Scheduler to bypass UAC + $action = New-ScheduledTaskAction -Execute "${{ env.INSTALLER_PATH }}" -Argument "/S" + $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest + $task = New-ScheduledTask -Action $action -Principal $principal + Register-ScheduledTask -TaskName "SilentSLInstaller" -InputObject $task -Force + Start-ScheduledTask -TaskName "SilentSLInstaller" + + - name: Wait for Installation to Complete (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + Write-Host "Waiting for the Second Life installer to finish..." + do { + Start-Sleep -Seconds 5 + $installerProcess = Get-Process | Where-Object { $_.Path -eq "${{ env.INSTALLER_PATH }}" } + } while ($installerProcess) + + Write-Host "✅ Installation completed!" + + - name: Cleanup After Installation (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + # Cleanup Task Scheduler Entry + Unregister-ScheduledTask -TaskName "SilentSLInstaller" -Confirm:$false + Write-Host "✅ Task Scheduler entry removed." + + # Delete Installer ZIP + $DeletePath = "${{ env.DOWNLOAD_PATH }}\installer.zip" + + Write-Host "Checking if installer ZIP exists: $DeletePath" + + # Ensure the ZIP file exists before trying to delete it + if (Test-Path $DeletePath) { + Remove-Item -Path $DeletePath -Force + Write-Host "✅ Successfully deleted: $DeletePath" + } else { + Write-Host "⚠️ Warning: ZIP file does not exist, skipping deletion." + } + + - name: Run QA Test Script (Windows) + if: matrix.os == 'windows' + shell: pwsh + run: | + Write-Host "Running QA Test script on Windows runner: ${{ matrix.runner }}..." + cd ${{ matrix.install-path }} + + # Activate virtual environment + Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force + $env:VIRTUAL_ENV = "$PWD\.venv" + $env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH" + + # Set runner name as environment variable + $env:RUNNER_NAME = "${{ matrix.runner }}" + + # Run the test script + python runTests.py + + # Mac-specific steps + - name: Set Build ID (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "BUILD_ID=${{ github.event.inputs.build_id }}" >> $GITHUB_ENV + echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.inputs.build_id }}/artifacts" >> $GITHUB_ENV + else + echo "BUILD_ID=${{ github.event.workflow_run.id }}" >> $GITHUB_ENV + echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.workflow_run.id }}/artifacts" >> $GITHUB_ENV + fi + + - name: Verify viewer-automation-main Exists (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + if [ ! -d "${{ matrix.install-path }}" ]; then + echo "❌ Error: viewer-automation folder not found on runner!" + exit 1 + fi + echo "✅ viewer-automation is provided." + + - name: Verify viewer-automation-main is Up-To-Date (Mac) + if: matrix.os == 'mac' + shell: bash + continue-on-error: true + run: | + cd ${{ matrix.install-path }} + echo "Checking for repository updates..." + + # Check if .git directory exists + if [ -d ".git" ]; then + # Save local changes instead of discarding them + git stash push -m "Automated stash before update $(date)" + echo "Local changes saved (if any)" + + # Update the repository + git pull || echo "⚠️ Could not update repository" + echo "✅ Repository updated (or attempted update)" + + # Try to restore local changes if any were stashed + if git stash list | grep -q "Automated stash before update"; then + # Try to pop the stash, but be prepared for conflicts + if ! git stash pop; then + echo "⚠️ Conflict when restoring local changes" + # Save the conflicted state in a new branch for later review + branch_name="conflict-recovery-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$branch_name" + echo "✅ Created branch '$branch_name' with conflicted state" + + # For test execution, revert to a clean state + git reset --hard HEAD + echo "✅ Reset to clean state for test execution" + else + echo "✅ Local changes restored successfully" + fi + fi + else + echo "⚠️ Not a Git repository, using existing files" + fi + + - name: Verify Python Installation (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + if command -v python3 &> /dev/null; then + PYTHON_VERSION=$(python3 --version) + echo "✅ Python found: $PYTHON_VERSION" + else + echo "❌ Error: Python3 not found in PATH. Please install Python on this runner." + exit 1 + fi + + - name: Setup Python Virtual Environment (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + cd ${{ matrix.install-path }} + + # Create virtual environment if it doesn't exist + if [ ! -d ".venv" ]; then + echo "Creating virtual environment..." + python3 -m venv .venv + else + echo "Using existing virtual environment" + fi + + # Activate virtual environment + source .venv/bin/activate + + # Install dependencies + if [ -f "requirements.txt" ]; then + pip install -r requirements.txt + echo "✅ Installed dependencies from requirements.txt" + + # Install Playwright browsers - add this line + echo "Installing Playwright browsers..." + python -m playwright install + else + pip install outleap requests behave playwright + echo "⚠️ requirements.txt not found, installed basic dependencies" + + # Install Playwright browsers - add this line + echo "Installing Playwright browsers..." + python -m playwright install + fi + + - name: Fetch & Download Installer Artifact (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + # Mac-specific Bash commands + response=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s ${{ env.ARTIFACTS_URL }}) + ARTIFACT_NAME=$(echo $response | jq -r '.artifacts[] | select(.name=="${{ matrix.artifact }}") | .archive_download_url') + + if [ -z "$ARTIFACT_NAME" ]; then + echo "❌ Error: ${{ matrix.artifact }} artifact not found!" + exit 1 + fi + + echo "✅ Artifact found: $ARTIFACT_NAME" + + # Secure download path + DOWNLOAD_PATH="/tmp/secondlife-build-${{ env.BUILD_ID }}" + mkdir -p $DOWNLOAD_PATH + INSTALLER_PATH="$DOWNLOAD_PATH/installer.zip" + + # Download the ZIP + curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -L $ARTIFACT_NAME -o $INSTALLER_PATH + + # Ensure download succeeded + if [ ! -f "$INSTALLER_PATH" ]; then + echo "❌ Error: Failed to download ${{ matrix.artifact }}.zip" + exit 1 + fi + + # Set the path for other steps + echo "DOWNLOAD_PATH=$DOWNLOAD_PATH" >> $GITHUB_ENV + + - name: Extract Installer & Locate Executable (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + EXTRACT_PATH="${{ env.DOWNLOAD_PATH }}" + INSTALLER_ZIP="$EXTRACT_PATH/installer.zip" + + # Debug output + echo "Extract Path: $EXTRACT_PATH" + echo "Installer ZIP Path: $INSTALLER_ZIP" + + # Verify ZIP exists + if [ ! -f "$INSTALLER_ZIP" ]; then + echo "❌ Error: ZIP file not found at $INSTALLER_ZIP!" + exit 1 + fi + + echo "✅ ZIP file exists and is valid. Extracting..." + + # Extract the ZIP + unzip -o "$INSTALLER_ZIP" -d "$EXTRACT_PATH" + + # Find DMG file + INSTALLER_PATH=$(find "$EXTRACT_PATH" -name "*.dmg" -type f | head -1) + + if [ -z "$INSTALLER_PATH" ]; then + echo "❌ Error: No installer DMG found in the extracted files!" + echo "📂 Extracted Files:" + ls -la "$EXTRACT_PATH" + exit 1 + fi + + echo "✅ Installer found: $INSTALLER_PATH" + echo "INSTALLER_PATH=$INSTALLER_PATH" >> $GITHUB_ENV + + - name: Install Second Life (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + # Mac installation + echo "Mounting DMG installer..." + MOUNT_POINT="/tmp/secondlife-dmg" + mkdir -p "$MOUNT_POINT" + + # Mount the DMG + hdiutil attach "$INSTALLER_PATH" -mountpoint "$MOUNT_POINT" -nobrowse + + echo "✅ DMG mounted at $MOUNT_POINT" + + echo "Installing application to default location from DMG..." + + # Find the .app bundle in the DMG + APP_PATH=$(find "$MOUNT_POINT" -name "*.app" -type d | head -1) + + if [ -z "$APP_PATH" ]; then + echo "❌ Error: No .app bundle found in the mounted DMG!" + exit 1 + fi + + APP_NAME=$(basename "$APP_PATH") + DEST_PATH="/Applications/$APP_NAME" + + # Handle existing installation + if [ -d "$DEST_PATH" ]; then + echo "Found existing installation at: $DEST_PATH" + echo "Moving existing installation to trash..." + + # Move to trash instead of force removing + TRASH_PATH="$HOME/.Trash/$(date +%Y%m%d_%H%M%S)_$APP_NAME" + mv "$DEST_PATH" "$TRASH_PATH" || { + echo "⚠️ Could not move to trash, trying direct removal..." + rm -rf "$DEST_PATH" || { + echo "❌ Could not remove existing installation" + echo "Please manually remove: $DEST_PATH" + exit 1 + } + } + + echo "✅ Existing installation handled successfully" + fi + + # Copy the .app to /Applications + echo "Copying app from: $APP_PATH" + echo "To destination: /Applications/" + cp -R "$APP_PATH" /Applications/ + + # Verify the app was copied successfully + if [ ! -d "$DEST_PATH" ]; then + echo "❌ Error: Failed to install application to /Applications!" + exit 1 + fi + + echo "✅ Application installed successfully to /Applications" + + # Save mount point for cleanup + echo "MOUNT_POINT=$MOUNT_POINT" >> $GITHUB_ENV + + - name: Wait for Installation to Complete (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + echo "Waiting for installation to complete..." + # Sleep to allow installation to finish (adjust as needed) + sleep 30 + echo "✅ Installation completed" + + - name: Cleanup After Installation (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + # Mac cleanup + # Unmount the DMG + echo "Unmounting DMG..." + hdiutil detach "${{ env.MOUNT_POINT }}" -force + + # Clean up temporary files + echo "Cleaning up temporary files..." + rm -rf "${{ env.DOWNLOAD_PATH }}" + rm -rf "${{ env.MOUNT_POINT }}" + + echo "✅ Cleanup completed" + + - name: Run QA Test Script (Mac) + if: matrix.os == 'mac' + shell: bash + run: | + echo "Running QA Test script on Mac runner: ${{ matrix.runner }}..." + cd ${{ matrix.install-path }} + + # Activate virtual environment + source .venv/bin/activate + + # Set runner name as environment variable + export RUNNER_NAME="${{ matrix.runner }}" + + # Run the test script + python runTests.py + + # - name: Upload Test Results + # if: always() + # uses: actions/upload-artifact@v4 + # with: + # name: test-results-${{ matrix.runner }} + # path: ${{ matrix.install-path }}/regressionTest/test_results.html diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml index b73ec502f1..24ee2de794 100644 --- a/.github/workflows/tag-release.yaml +++ b/.github/workflows/tag-release.yaml @@ -26,23 +26,27 @@ on: jobs: tag-release: runs-on: ubuntu-latest - env: - GITHUB_TAG_TOKEN: ${{ secrets.GITHUB_TAG_TOKEN }} steps: - name: Setup Env Vars run: | CHANNEL="${{ inputs.channel }}" echo VIEWER_CHANNEL="Second_Life_${CHANNEL:-Develop}" >> ${GITHUB_ENV} - echo NIGHTLY_DATE=$(date --rfc-3339=date) >> ${GITHUB_ENV} + NIGHTLY_DATE=$(date --rfc-3339=date) + echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV} + echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV} - name: Update Tag uses: actions/github-script@v7.0.1 - if: env.GITHUB_TAG_TOKEN with: - github-token: ${{ env.GITHUB_TAG_TOKEN }} + # use a real access token instead of GITHUB_TOKEN default. + # required so that the results of this tag creation can trigger the build workflow + # https://stackoverflow.com/a/71372524 + # https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow + # this token will need to be renewed anually in January + github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }} script: | - github.rest.git.createRef( + github.rest.git.createRef({ owner: context.repo.owner, repo: context.repo.repo, - ref: "refs/tags/${{ env.VIEWER_CHANNEL }}#${{ env.NIGHTLY_DATE }}", + ref: "refs/tags/${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}", sha: context.sha - ) + }) |
