diff --git a/.github/actions/setup-unity/action.yaml b/.github/actions/setup-unity/action.yaml new file mode 100644 index 0000000..c2e058c --- /dev/null +++ b/.github/actions/setup-unity/action.yaml @@ -0,0 +1,108 @@ +name: Set up Unity +description: Install Unity CLI and the Editor, then activate a license on this runner. + +inputs: + editor-version: + description: Exact Unity Editor version to install. + required: true + email: + description: Unity account email address. + required: true + password: + description: Unity account password. + required: true + +runs: + using: composite + steps: + - name: Check Unity credentials + shell: bash + env: + UNITY_EMAIL: ${{ inputs.email }} + UNITY_PASSWORD: ${{ inputs.password }} + run: | + if [[ -z "$UNITY_EMAIL" || -z "$UNITY_PASSWORD" ]]; then + echo "::error::UNITY_EMAIL and UNITY_PASSWORD are required. GitHub does not provide repository secrets to pull requests from forks." + exit 1 + fi + + - name: Install Linux dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install --yes --no-install-recommends \ + libasound2 libgbm1 libgconf-2-4 libglu1-mesa libgtk-3-0 \ + libncurses5 libnotify4 libnss3 libxss1 libxtst6 + + # Unity 2021's .NET tooling requires OpenSSL 1.1, absent from Ubuntu 22.04. + - name: Install OpenSSL 1.1 for Unity 2021 + if: startsWith(inputs.editor-version, '2021.') + shell: bash + run: | + curl --fail --silent --show-error --location \ + https://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb \ + --output "$RUNNER_TEMP/libssl1.1.deb" + echo "7cf39d70a639017d1dd7c8d36daa2258063608688e449fddf40ffdd46f992a78 $RUNNER_TEMP/libssl1.1.deb" | sha256sum --check + sudo apt-get install --yes --no-install-recommends "$RUNNER_TEMP/libssl1.1.deb" + + - name: Install Unity CLI + shell: bash + env: + UNITY_CLI_CHANNEL: beta + UNITY_CLI_VERSION: 1.0.0-beta.12 + UNITY_CLI_HOME: ${{ runner.temp }}/unity-cli + run: | + curl --fail --silent --show-error --location \ + https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.sh \ + --output "$RUNNER_TEMP/install-unity-cli.sh" + bash "$RUNNER_TEMP/install-unity-cli.sh" + echo "$UNITY_CLI_HOME/bin" >> "$GITHUB_PATH" + "$UNITY_CLI_HOME/bin/unity" --version + + - name: Install Unity Editor + shell: bash + env: + UNITY_EDITOR_VERSION: ${{ inputs.editor-version }} + run: unity --non-interactive install "$UNITY_EDITOR_VERSION" --yes --accept-eula + + # Unity CLI cannot sign in with email/password. The bundled client can + # activate a license for this runner instead of importing a desktop's ULF. + - name: Activate Unity license + shell: bash + env: + UNITY_EDITOR_VERSION: ${{ inputs.editor-version }} + UNITY_EMAIL: ${{ inputs.email }} + UNITY_PASSWORD: ${{ inputs.password }} + run: | + editor_root="$(unity --non-interactive editors path "$UNITY_EDITOR_VERSION")" + licensing_client="$editor_root/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client" + test -x "$licensing_client" + + umask 077 + activation_log="$(mktemp "$RUNNER_TEMP/unity-activation-XXXXXX")" + trap 'rm -f "$activation_log"' EXIT + # Arm cleanup before activation, including a partially failed request. + echo "UNITY_LICENSE_ACTIVATION_ATTEMPTED=true" >> "$GITHUB_ENV" + result=0 + timeout 120 "$licensing_client" \ + --activate-all --include-personal \ + --username "$UNITY_EMAIL" --password "$UNITY_PASSWORD" \ + > "$activation_log" 2>&1 || result=$? + + if [[ "$result" != 0 ]]; then + if grep -qiE 'no (available )?seats|seat is not available|LICENSE_LIMIT|maximum number of' "$activation_log"; then + echo "::error::Unity reports no available license seats for this account." + elif grep -qiE 'two.?factor|2fa|verification code|unauthorized|invalid (username|password|credentials)' "$activation_log"; then + echo "::error::Unity rejected the credentials or requires interactive account verification." + else + echo "::error::Unity account activation failed (exit $result)." + fi + exit 1 + fi + + # The client can exit zero without granting a seat. Check license state. + if ! unity --non-interactive license status > "$activation_log" 2>&1; then + echo "::error::Unity account activation did not produce an active license." + exit 1 + fi + echo "Unity license is active on this runner." diff --git a/.github/actions/setup-unity/return-license.sh b/.github/actions/setup-unity/return-license.sh new file mode 100644 index 0000000..9290e87 --- /dev/null +++ b/.github/actions/setup-unity/return-license.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +editor_root="$(unity --non-interactive editors path "$UNITY_EDITOR_VERSION")" +licensing_client="$editor_root/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client" + +umask 077 +return_directory="$(mktemp -d "$RUNNER_TEMP/unity-license-return-XXXXXX")" +return_log="$return_directory/return.log" +trap 'rm -rf "$return_directory"' EXIT + +result=0 +timeout 120 "$licensing_client" --return-ulf \ + --username "$UNITY_EMAIL" --password "$UNITY_PASSWORD" \ + > "$return_log" 2>&1 || result=$? + +# Entitlement licenses have no ULF file; the Editor returns those with the +# account credentials. A scratch project keeps cleanup out of the real project. +if [[ "$result" != 0 ]] && grep -qi 'Ulf license file not found' "$return_log"; then + result=0 + timeout 120 "$editor_root/Editor/Unity" \ + -batchmode -nographics -quit -returnlicense \ + -username "$UNITY_EMAIL" -password "$UNITY_PASSWORD" \ + -projectPath "$return_directory/project" -logFile /dev/stdout \ + > "$return_log" 2>&1 || result=$? +fi + +# The Editor may return the entitlement and then exit nonzero while also +# attempting a ULF return. Its explicit success message confirms the return. +if grep -qE 'Successfully returned ULF license|Successfully returned the entitlement license|License has been returned' "$return_log"; then + result=0 +fi + +if [[ "$result" != 0 ]]; then + echo "::error::Unity license return failed (exit $result)." + exit 1 +fi +echo "Unity license returned." diff --git a/.github/scripts/release.sh b/.github/scripts/release.sh new file mode 100644 index 0000000..d32b122 --- /dev/null +++ b/.github/scripts/release.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +operation="${1:?Expected prepare or create}" +case "$operation" in + prepare|create) ;; + *) echo "::error::Expected prepare or create."; exit 1 ;; +esac + +# Package versions follow SemVer, including prerelease and build identifiers. +number='(0|[1-9][0-9]*)' +prerelease='(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)' +if [[ ! "$VERSION" =~ ^${number}\.${number}\.${number}(-${prerelease}(\.${prerelease})*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "::error::Release version must follow SemVer (for example 1.7.1 or 1.8.0-preview.1)." + exit 1 +fi +git check-ref-format "refs/tags/$VERSION" + +if [[ "$operation" == prepare ]]; then + if [[ "$RELEASE_REF" != refs/heads/* ]]; then + echo "::error::Dispatch Release from a branch." + exit 1 + fi + + source_sha="$(git rev-parse HEAD)" + current_version="$(jq -er '.version | select(type == "string" and length > 0)' "$TARGET_FILE")" + if [[ "$current_version" != "$VERSION" ]]; then + manifest="$(mktemp "$RUNNER_TEMP/release-package-XXXXXX")" + trap 'rm -f "$manifest"' EXIT + jq --arg version "$VERSION" '.version = $version' "$TARGET_FILE" > "$manifest" + cat "$manifest" > "$TARGET_FILE" + git add -- "$TARGET_FILE" + git commit --only -m "chore(release): update package version to $VERSION" -- "$TARGET_FILE" + fi + RELEASE_SHA="$(git rev-parse HEAD)" +fi + +remote_tags="$(git ls-remote --tags origin "refs/tags/$VERSION" "refs/tags/$VERSION^{}")" +tag_commit="" +while read -r object ref; do + case "$ref" in + "refs/tags/$VERSION") tag_commit="$object" ;; + # Annotated tags identify a tag object; compare the peeled commit instead. + "refs/tags/$VERSION^{}") tag_commit="$object"; break ;; + esac +done <<< "$remote_tags" + +if [[ -n "$tag_commit" && "$tag_commit" != "$RELEASE_SHA" ]]; then + echo "::error::Tag $VERSION belongs to a different commit. Choose a new release version." + exit 1 +fi + +if [[ "$operation" == prepare ]]; then + if [[ "$current_version" != "$VERSION" ]]; then + # Require a fast-forward from the checked-out commit and an unchanged remote + # branch. The lease also rejects deletion or rollback during preparation. + git merge-base --is-ancestor "$source_sha" "$RELEASE_SHA" + git push --no-follow-tags --recurse-submodules=no \ + --force-with-lease="$RELEASE_REF:$source_sha" origin "$RELEASE_SHA:$RELEASE_REF" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" +elif [[ -z "$tag_commit" ]]; then + git tag -- "$VERSION" "$RELEASE_SHA" + # A non-forced tag push rejects a concurrent release for a different commit. + git push --no-follow-tags --recurse-submodules=no origin "refs/tags/$VERSION" +fi diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 3f31c6f..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: Build - -on: - pull_request: {} - push: { branches: [main] } - -jobs: - build: - name: ${{ matrix.targetPlatform }} - if: "((github.event_name == 'push' && github.repository_owner == 'mackysoft') || startsWith(github.event.pull_request.head.label, 'mackysoft:'))" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - targetPlatform: - - StandaloneOSX # Build a macOS standalone (Intel 64-bit). - - StandaloneWindows # Build a Windows standalone. - - StandaloneWindows64 # Build a Windows 64-bit standalone. - - StandaloneLinux64 # Build a Linux 64-bit standalone. - - iOS # Build an iOS player. - - Android # Build an Android .apk standalone app. - - WebGL # WebGL. - - steps: - # Checkout - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - lfs: true - - # Cache - - name: Cache - uses: actions/cache@v4 - with: - path: Library - key: Library-${{ matrix.targetPlatform }} - restore-keys: Library- - - # Build - - name: Build - uses: game-ci/unity-builder@v4 - env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} - UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} - with: - targetPlatform: ${{ matrix.targetPlatform }} - unityVersion: 2021.3.45f2 diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml deleted file mode 100644 index f83c6c3..0000000 --- a/.github/workflows/package.yaml +++ /dev/null @@ -1,45 +0,0 @@ -name: Export Package - -on: - pull_request: {} - push: { branches: [main] } - -env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - -jobs: - build: - name: Build UnityPackage - runs-on: ubuntu-latest - steps: - # Checkout - - name: Checkout repository - uses: actions/checkout@v4 - with: - lfs: true - - # Cache - - name: Cache - uses: actions/cache@v4 - with: - path: Library - key: Library - restore-keys: Library- - - # Build - - name: Build .unitypackage - uses: game-ci/unity-builder@v4 - env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} - UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} - with: - unityVersion: 2021.3.45f2 - buildMethod: MackySoft.PackageTools.Editor.UnityPackageExporter.Export - - # Upload - - name: Upload .unitypackage - uses: actions/upload-artifact@v4 - with: - name: Unity Package - path: Build diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 57149e8..4d257c6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,91 +4,90 @@ on: workflow_dispatch: inputs: tag: - description: "tag: git tag you want create. (sample 1.0.0)" + description: Package version and Git tag (for example 1.7.1). required: true + type: string + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false -env: - GIT_TAG: ${{ github.event.inputs.tag }} permissions: - contents: write + contents: read jobs: - update-packagejson: + prepare: runs-on: ubuntu-latest env: TARGET_FILE: ./Assets/MackySoft/MackySoft.SerializeReferenceExtensions/package.json outputs: - sha: ${{ steps.commit.outputs.sha }} + sha: ${{ steps.metadata.outputs.sha }} + version: ${{ steps.metadata.outputs.version }} steps: - - uses: actions/checkout@v4 - - name: Output package.json (Before) - run: cat ${{ env.TARGET_FILE}} - - - name: Update package.json to version ${{ env.GIT_TAG }} - run: sed -i -e "s/\(\"version\":\) \"\(.*\)\",/\1 \"${{ env.GIT_TAG }}\",/" ${{ env.TARGET_FILE }} + - name: Create release app token + id: release_app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RELEASE_BOT_APP_ID }} + private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} + permission-contents: write - - name: Check update - id: check_update - run: | - cat ${{ env.TARGET_FILE}} - if ! git diff --exit-code; then - echo "changed=1" >> "$GITHUB_OUTPUT" - fi + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + token: ${{ steps.release_app.outputs.token }} - - name: Commit files - id: commit - if: steps.check_update.outputs.changed == '1' + - name: Configure release app identity + env: + APP_SLUG: ${{ steps.release_app.outputs.app-slug }} + GH_TOKEN: ${{ steps.release_app.outputs.token }} run: | - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git commit -m "feat: Update package.json to ${{ env.GIT_TAG }}" -a - echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + bot_login="${APP_SLUG}[bot]" + bot_id="$(gh api "/users/${APP_SLUG}%5Bbot%5D" --jq '.id')" + git config user.name "$bot_login" + git config user.email "${bot_id}+${bot_login}@users.noreply.github.com" - - name: Check sha - run: echo "SHA ${SHA}" + - name: Prepare release version + id: metadata env: - SHA: ${{ steps.commit.outputs.sha }} - - - name: Create Tag - if: steps.check_update.outputs.changed == '1' - run: git tag ${{ env.GIT_TAG }} - - - name: Push changes - if: steps.check_update.outputs.changed == '1' - uses: ad-m/github-push-action@v0.6.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.ref }} - tags: true + VERSION: ${{ inputs.tag }} + RELEASE_REF: ${{ github.ref }} + run: bash .github/scripts/release.sh prepare build: - needs: [update-packagejson] - strategy: - matrix: - unity: ["2021.3.45f2"] - include: - - unityVersion: 2021.3.45f2 - license: UNITY_LICENSE - runs-on: ubuntu-latest - timeout-minutes: 15 + needs: prepare + runs-on: ubuntu-22.04 + timeout-minutes: 45 steps: - - run: echo ${{ needs.update-packagejson.outputs.sha }} - uses: actions/checkout@v4 with: - ref: ${{ needs.update-packagejson.outputs.sha }} + ref: ${{ needs.prepare.outputs.sha }} + lfs: true - - name: Export unitypackage - uses: game-ci/unity-builder@v4 - env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} - UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} - UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + - name: Set up Unity + uses: ./.github/actions/setup-unity with: - projectPath: . - unityVersion: ${{ matrix.unityVersion }} - targetPlatform: StandaloneLinux64 - buildMethod: MackySoft.PackageTools.Editor.UnityPackageExporter.Export - versioning: None + editor-version: 2021.3.45f2 + email: ${{ secrets.UNITY_EMAIL }} + password: ${{ secrets.UNITY_PASSWORD }} + + - name: Build example player + run: | + unity --non-interactive build . \ + --editor-version 2021.3.45f2 \ + --target StandaloneLinux64 \ + --output-path Build/StandaloneLinux64/SerializeReferenceExtensions \ + --log-file Logs/build.log \ + --timeout 900 + tar -czf Build/StandaloneLinux64.tar.gz -C Build StandaloneLinux64 + + - name: Export unitypackage + run: | + unity --non-interactive run . \ + --editor-version 2021.3.45f2 \ + --log-file Logs/package.log \ + --timeout 900 \ + -- -nographics -executeMethod MackySoft.PackageTools.Editor.UnityPackageExporter.Export - name: check all .meta is commited run: | @@ -100,27 +99,75 @@ jobs: fi working-directory: . + - name: Return Unity license + if: always() && env.UNITY_LICENSE_ACTIVATION_ATTEMPTED == 'true' + timeout-minutes: 5 + env: + UNITY_EDITOR_VERSION: 2021.3.45f2 + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + run: bash .github/actions/setup-unity/return-license.sh + # Store artifacts. + - name: Upload example player + uses: actions/upload-artifact@v4 + with: + name: StandaloneLinux64 + path: Build/StandaloneLinux64.tar.gz + if-no-files-found: error + - uses: actions/upload-artifact@v4 with: name: SerializeReference-Extensions.unitypackage path: ./Build/SerializeReference-Extensions.unitypackage + if-no-files-found: error + + - name: Upload Unity logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: Release-Logs + path: Logs/ create-release: - needs: [update-packagejson, build] + needs: [prepare, build] runs-on: ubuntu-latest steps: - # Download(All) Artifacts to current directory + # Each job owns a short-lived token; prepare's token is revoked before build. + - name: Create release app token + id: release_app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RELEASE_BOT_APP_ID }} + private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} + permission-contents: write + + - uses: actions/checkout@v4 + with: + ref: ${{ needs.prepare.outputs.sha }} + token: ${{ steps.release_app.outputs.token }} + + # Publish the exact commit that produced the package. Recheck the remote + # after the build, since another release may have created the tag meanwhile. + - name: Create release tag + env: + VERSION: ${{ needs.prepare.outputs.version }} + RELEASE_SHA: ${{ needs.prepare.outputs.sha }} + run: bash .github/scripts/release.sh create + + # Download the package artifact for the release. - uses: actions/download-artifact@v4 + with: + name: SerializeReference-Extensions.unitypackage + path: SerializeReference-Extensions.unitypackage # Create release and upload unitypackage. - - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: softprops/action-gh-release@v3 with: - tag_name: ${{ env.GIT_TAG }} - name: ${{ env.GIT_TAG }} - target_commitish: ${{ needs.update-packagejson.outputs.sha }} + token: ${{ steps.release_app.outputs.token }} + tag_name: ${{ needs.prepare.outputs.version }} + name: ${{ needs.prepare.outputs.version }} + target_commitish: ${{ needs.prepare.outputs.sha }} draft: true prerelease: false files: SerializeReference-Extensions.unitypackage/SerializeReference-Extensions.unitypackage diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1a4225f..073b7c0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -6,23 +6,51 @@ on: - documentation - gh-pages pull_request: {} + workflow_dispatch: {} + +permissions: + contents: read jobs: - test: - name: ${{ matrix.testMode }} on Unity ${{ matrix.unityVersion }} + changes: + name: Determine required checks + runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: read + outputs: + unity: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.filter.outputs.unity }} + steps: + - name: Checkout + if: github.event_name == 'push' + uses: actions/checkout@v4 - runs-on: ubuntu-latest + - name: Detect Unity changes + id: filter + if: github.event_name != 'workflow_dispatch' + uses: dorny/paths-filter@v4 + with: + filters: | + unity: + - 'Assets/**' + - 'Packages/**' + - 'ProjectSettings/**' + - '.github/actions/setup-unity/**' + - '.github/workflows/tests.yaml' + - '.gitattributes' + test: + name: EditMode on Unity ${{ matrix.unityVersion }} + needs: changes + if: needs.changes.outputs.unity == 'true' + runs-on: ubuntu-22.04 + timeout-minutes: 45 strategy: fail-fast: false matrix: - projectPath: - - . unityVersion: - 2021.3.45f2 - 2023.2.22f1 - testMode: - - editmode steps: - name: Checkout @@ -30,26 +58,70 @@ jobs: with: lfs: true - - name: Cache + - name: Set up Unity + uses: ./.github/actions/setup-unity + with: + editor-version: ${{ matrix.unityVersion }} + email: ${{ secrets.UNITY_EMAIL }} + password: ${{ secrets.UNITY_PASSWORD }} + + - name: Cache Library uses: actions/cache@v4 with: - path: ${{ matrix.projectPath }}/Library - key: Library-${{ runner.os }}-${{ matrix.projectPath }}-${{ matrix.unityVersion }}-${{ matrix.testMode }}-${{ hashFiles('**/Packages/manifest.json', '**/Packages/packages-lock.json', '**/ProjectSettings/ProjectVersion.txt') }} - restore-keys: | - Library-${{ runner.os }}-${{ matrix.projectPath }}-${{ matrix.unityVersion }}-${{ matrix.testMode }}- - Library-${{ runner.os }}-${{ matrix.projectPath }}-${{ matrix.unityVersion }}- - Library-${{ runner.os }}-${{ matrix.projectPath }}- - - - name: Tests - uses: game-ci/unity-test-runner@v4 - id: tests + path: Library + key: Library-${{ runner.os }}-${{ matrix.unityVersion }}-EditMode-${{ hashFiles('Packages/manifest.json', 'Packages/packages-lock.json', 'ProjectSettings/ProjectVersion.txt') }} + restore-keys: Library-${{ runner.os }}-${{ matrix.unityVersion }}-EditMode- + + - name: Run EditMode tests + run: | + unity --non-interactive test . \ + --editor-version "${{ matrix.unityVersion }}" \ + --mode EditMode \ + --output Logs/TestResults/editmode.xml \ + --timeout 900 \ + -- -nographics -logFile Logs/editmode.log + + - name: Return Unity license + if: always() && env.UNITY_LICENSE_ACTIVATION_ATTEMPTED == 'true' + timeout-minutes: 5 env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + UNITY_EDITOR_VERSION: ${{ matrix.unityVersion }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + run: bash .github/actions/setup-unity/return-license.sh + + - name: Upload test results and logs + if: always() + uses: actions/upload-artifact@v4 with: - projectPath: ${{ matrix.projectPath }} - unityVersion: ${{ matrix.unityVersion }} - testMode: ${{ matrix.testMode }} - artifactsPath: test-results - artifactsName: TestResults-${{ matrix.unityVersion }}-${{ matrix.testMode }} + name: TestResults-${{ matrix.unityVersion }}-EditMode + path: Logs/ + + verify: + name: Verification + needs: [changes, test] + if: always() + runs-on: ubuntu-22.04 + steps: + - name: Require all applicable checks to pass + env: + CHANGES_RESULT: ${{ needs.changes.result }} + UNITY_REQUIRED: ${{ needs.changes.outputs.unity }} + TEST_RESULT: ${{ needs.test.result }} + run: | + if [[ "$CHANGES_RESULT" != success ]]; then + echo "::error::Could not determine required checks ($CHANGES_RESULT)." + exit 1 + fi + case "$UNITY_REQUIRED:$TEST_RESULT" in + true:success) + echo "All required EditMode tests passed." + ;; + false:skipped) + echo "No Unity changes require EditMode tests." + ;; + *) + echo "::error::EditMode validation did not pass (required=$UNITY_REQUIRED, result=$TEST_RESULT)." + exit 1 + ;; + esac diff --git a/AGENTS.md b/AGENTS.md index 8a4dc9d..c5e59e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,26 +17,25 @@ Primary risk area: type discovery & filtering (what types appear in the selector - Unity 2023.2+ has enhanced generic type support (variance, etc.). Changes must not break 2021.3 behavior and guarded by `UNITY_2023_2_OR_NEWER`. ## CI (GitHub Actions) -I use GameCI `unity-test-runner`. -- Always run EditMode tests. +Use Unity CLI through `.github/actions/setup-unity`. +- Run EditMode tests when Unity source, assets, project settings, or the test setup changes. Documentation-only changes can skip Unity tests. +- Run tests on Linux. +- Always report the final `Verification` check on pull requests. It passes only when change detection succeeds and all required EditMode matrix jobs succeed, or Unity tests are not required and are skipped. +- Build the example player for Linux 64-bit and export the package only in the Release workflow. +- Release accepts a version tag, updates `package.json` through the MackySoft Release GitHub App, and builds that exact commit. Configure `RELEASE_BOT_APP_ID`, `RELEASE_BOT_PRIVATE_KEY`, and the App's bypass for the pull request rule before releasing. - Run a Unity matrix that includes: - 2021.3.x (minimum baseline) - 2023.2+ (generic/variance feature gate) ## How to run tests locally ### EditMode -Run Unity in batchmode: +Run tests through Unity CLI (which manages batch mode): ``` -PROJECT_ROOT="$(pwd)" -RESULT_XML="$PROJECT_ROOT/TestResults/editmode.xml" - -mkdir -p "$(dirname "$RESULT_XML")" - -"" -batchmode -nographics -quit \ - -projectPath "$PROJECT_ROOT" \ - -runTests -testPlatform editmode \ - -testResults "$RESULT_XML" +unity --non-interactive test . --editor-version 2021.3.45f2 \ + --mode EditMode --output Logs/TestResults/editmode.xml --timeout 900 \ + -- -nographics -logFile Logs/editmode.log ``` +Repeat with `--editor-version 2023.2.22f1` for the generic/variance feature gate. ## Architecture guardrails - Runtime surface area should remain minimal (mainly attributes / data structures). @@ -48,4 +47,4 @@ mkdir -p "$(dirname "$RESULT_XML")" - Keep allocations low in IMGUI paths (e.g., `OnGUI`). - Keep public API stable; if changing type filtering behavior, add/adjust tests. - As a general rule, you should follow the restrictions on SerializeReference in Unity's official documentation. - - https://docs.unity3d.com/ScriptReference/SerializeReference.html \ No newline at end of file + - https://docs.unity3d.com/ScriptReference/SerializeReference.html diff --git a/README.md b/README.md index 56e4638..3aa5077 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Unity SerializeReferenceExtensions -[![Tests](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/actions/workflows/tests.yaml/badge.svg)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/actions/workflows/tests.yaml) [![Build](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/actions/workflows/build.yaml/badge.svg)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/actions/workflows/build.yaml) [![Release](https://img.shields.io/github/v/release/mackysoft/Unity-SerializeReferenceExtensions)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/releases) [![openupm](https://img.shields.io/npm/v/com.mackysoft.serializereference-extensions?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.mackysoft.serializereference-extensions/) +[![Tests](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/actions/workflows/tests.yaml/badge.svg)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/actions/workflows/tests.yaml) [![Release](https://img.shields.io/github/v/release/mackysoft/Unity-SerializeReferenceExtensions)](https://github.com/mackysoft/Unity-SerializeReferenceExtensions/releases) [![openupm](https://img.shields.io/npm/v/com.mackysoft.serializereference-extensions?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.mackysoft.serializereference-extensions/) This library provides an Inspector dropdown (`SubclassSelector`) for fields serialized by Unity's `[SerializeReference]`.