Compare commits

..

No commits in common. "main" and "v3.1.0" have entirely different histories.
main ... v3.1.0

136 changed files with 2624 additions and 6112 deletions

View File

@ -1,11 +1,22 @@
{ {
"plugins": ["jest", "@typescript-eslint", "prettier", "unicorn"], "plugins": [
"extends": ["plugin:unicorn/recommended", "plugin:github/recommended", "plugin:prettier/recommended"], "jest",
"@typescript-eslint",
"prettier",
"unicorn"
],
"extends": [
"plugin:unicorn/recommended",
"plugin:github/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 2020, "ecmaVersion": 2020,
"sourceType": "module", "sourceType": "module",
"extraFileExtensions": [".mjs"], "extraFileExtensions": [
".mjs"
],
"ecmaFeatures": { "ecmaFeatures": {
"impliedStrict": true "impliedStrict": true
}, },
@ -22,7 +33,10 @@
// Namespaces or sometimes needed // Namespaces or sometimes needed
"import/no-namespace": "off", "import/no-namespace": "off",
// Properly format comments // Properly format comments
"spaced-comment": ["error", "always"], "spaced-comment": [
"error",
"always"
],
"lines-around-comment": [ "lines-around-comment": [
"error", "error",
{ {
@ -57,7 +71,12 @@
// Enforce camelCase // Enforce camelCase
"camelcase": "error", "camelcase": "error",
// Allow forOfStatements // Allow forOfStatements
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"], "no-restricted-syntax": [
"error",
"ForInStatement",
"LabeledStatement",
"WithStatement"
],
// Continue is viable in forOf loops in generators // Continue is viable in forOf loops in generators
"no-continue": "off", "no-continue": "off",
// From experience, named exports are almost always desired. I got tired of this rule // From experience, named exports are almost always desired. I got tired of this rule

View File

@ -2,28 +2,13 @@
- ... - ...
#### Related Issues
- ...
#### Related PRs
- ...
#### Successful Workflow Run Link
PRs don't have access to secrets so you will need to provide a link to a successful run of the workflows from your own
repo.
- ...
#### Checklist #### Checklist
<!-- please check all items and add your own --> <!-- please check all items and add your own -->
- [x] Read the contribution [guide](https://github.com/game-ci/unity-builder/blob/main/CONTRIBUTING.md) and accept the - [x] Read the contribution [guide](https://github.com/game-ci/unity-builder/blob/main/CONTRIBUTING.md) and accept the
[code](https://github.com/game-ci/unity-builder/blob/main/CODE_OF_CONDUCT.md) of conduct [code](https://github.com/game-ci/unity-builder/blob/main/CODE_OF_CONDUCT.md) of conduct
- [ ] Docs (If new inputs or outputs have been added or changes to behavior that should be documented. Please make a PR - [ ] Docs (If new inputs or outputs have been added or changes to behavior that should be documented. Please make
in the [documentation repo](https://github.com/game-ci/documentation)) a PR in the [documentation repo](https://github.com/game-ci/documentation))
- [ ] Readme (updated or not needed) - [ ] Readme (updated or not needed)
- [ ] Tests (added, updated or not needed) - [ ] Tests (added, updated or not needed)

View File

@ -13,7 +13,7 @@ jobs:
id: requestActivationFile id: requestActivationFile
uses: game-ci/unity-request-activation-file@v2.0-alpha-1 uses: game-ci/unity-request-activation-file@v2.0-alpha-1
- name: Upload activation file - name: Upload activation file
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: ${{ steps.requestActivationFile.outputs.filePath }} name: ${{ steps.requestActivationFile.outputs.filePath }}
path: ${{ steps.requestActivationFile.outputs.filePath }} path: ${{ steps.requestActivationFile.outputs.filePath }}

View File

@ -3,13 +3,15 @@ name: Builds - MacOS
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches:
- main
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
buildForAllPlatformsMacOS: buildForAllPlatformsWindows:
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }} name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
runs-on: macos-latest runs-on: macos-latest
strategy: strategy:
@ -18,25 +20,26 @@ jobs:
projectPath: projectPath:
- test-project - test-project
unityVersion: unityVersion:
- 2021.3.45f1 - 2021.3.29f1
- 2022.3.13f1 - 2022.1.24f1
- 2023.2.2f1 - 2022.2.21f1
- 2022.3.7f1
- 2023.1.8f1
targetPlatform: targetPlatform:
- StandaloneOSX # Build a MacOS executable - StandaloneOSX # Build a MacOS executable
- iOS # Build an iOS executable
steps: steps:
########################### ###########################
# Checkout # # Checkout #
########################### ###########################
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
lfs: true lfs: true
########################### ###########################
# Cache # # Cache #
########################### ###########################
- uses: actions/cache@v4 - uses: actions/cache@v3
with: with:
path: ${{ matrix.projectPath }}/Library path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-macos-${{ matrix.targetPlatform }} key: Library-${{ matrix.projectPath }}-macos-${{ matrix.targetPlatform }}
@ -59,9 +62,7 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
@ -72,8 +73,8 @@ jobs:
########################### ###########################
# Upload # # Upload #
########################### ###########################
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: Build ${{ matrix.targetPlatform }} on MacOS (${{ matrix.unityVersion }}) name: Build MacOS (${{ matrix.unityVersion }})
path: build path: build
retention-days: 14 retention-days: 14

View File

@ -3,6 +3,11 @@ name: Builds - Ubuntu
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches:
- main
pull_request:
paths-ignore:
- '.github/**'
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@ -36,8 +41,7 @@ env:
jobs: jobs:
buildForAllPlatformsUbuntu: buildForAllPlatformsUbuntu:
name: name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
"${{ matrix.targetPlatform }} on ${{ matrix.unityVersion}}${{startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}"
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@ -48,73 +52,36 @@ jobs:
projectPath: projectPath:
- test-project - test-project
unityVersion: unityVersion:
- 2021.3.32f1 - 2021.3.29f1
- 2022.3.13f1 - 2022.1.24f1
- 2023.2.2f1 - 2022.2.21f1
- 2022.3.7f1
- 2023.1.8f1
targetPlatform: targetPlatform:
- StandaloneOSX # Build a macOS standalone (Intel 64-bit) with mono backend. - StandaloneOSX # Build a macOS standalone (Intel 64-bit) with mono backend.
- StandaloneWindows64 # Build a Windows 64-bit standalone with mono backend. - StandaloneWindows64 # Build a Windows 64-bit standalone with mono backend.
- StandaloneLinux64 # Build a Linux 64-bit standalone with mono/il2cpp backend. - StandaloneLinux64 # Build a Linux 64-bit standalone with mono backend.
- iOS # Build an iOS project. - iOS # Build an iOS player.
- Android # Build an Android .apk. - Android # Build an Android .apk.
- WebGL # WebGL. - WebGL # WebGL.
buildWithIl2cpp: # - StandaloneWindows # Build a Windows standalone.
- false # - WSAPlayer # Build an Windows Store Apps player.
- true # - PS4 # Build a PS4 Standalone.
additionalParameters: # - XboxOne # Build a Xbox One Standalone.
- -param value # - tvOS # Build to Apple's tvOS platform.
- -standaloneBuildSubtarget Server # - Switch # Build a Nintendo Switch player
# Skipping configurations that are not supported
exclude:
# No il2cpp support on Linux Host
- targetPlatform: StandaloneOSX
buildWithIl2cpp: true
- targetPlatform: StandaloneWindows64
buildWithIl2cpp: true
# Only builds with Il2cpp
- targetPlatform: iOS
buildWithIl2cpp: false
- targetPlatform: Android
buildWithIl2cpp: false
- targetPlatform: WebGL
buildWithIl2cpp: false
# No dedicated server support
- targetPlatform: WebGL
additionalParameters: -standaloneBuildSubtarget Server
- targetPlatform: Android
additionalParameters: -standaloneBuildSubtarget Server
- targetPlatform: iOS
additionalParameters: -standaloneBuildSubtarget Server
# No dedicated server support on Linux Host
- targetPlatform: StandaloneOSX
additionalParameters: -standaloneBuildSubtarget Server
# No il2cpp dedicated server support on Linux Host
- targetPlatform: StandaloneWindows64
additionalParameters: -standaloneBuildSubtarget Server
buildWithIl2cpp: true
include:
- unityVersion: 6000.0.36f1
targetPlatform: WebGL
- unityVersion: 6000.0.36f1
targetPlatform: WebGL
buildProfile: 'Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset'
steps: steps:
- name: Clear Space for Android Build
if: matrix.targetPlatform == 'Android'
uses: jlumbroso/free-disk-space@v1.3.1
########################### ###########################
# Checkout # # Checkout #
########################### ###########################
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
lfs: true lfs: true
########################### ###########################
# Cache # # Cache #
########################### ###########################
- uses: actions/cache@v4 - uses: actions/cache@v3
with: with:
path: ${{ matrix.projectPath }}/Library path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-ubuntu-${{ matrix.targetPlatform }} key: Library-${{ matrix.projectPath }}-ubuntu-${{ matrix.targetPlatform }}
@ -122,85 +89,22 @@ jobs:
Library-${{ matrix.projectPath }}-ubuntu- Library-${{ matrix.projectPath }}-ubuntu-
Library- Library-
###########################
# Set Scripting Backend #
###########################
- name: Set Scripting Backend To il2cpp
if: matrix.buildWithIl2cpp == true
run: |
mv -f "./test-project/ProjectSettings/ProjectSettingsIl2cpp.asset" "./test-project/ProjectSettings/ProjectSettings.asset"
########################### ###########################
# Build # # Build #
########################### ###########################
- name: Build - uses: ./
uses: ./
id: build-1
continue-on-error: true
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with: with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
buildProfile: ${{ matrix.buildProfile }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
providerStrategy: ${{ matrix.providerStrategy }} providerStrategy: ${{ matrix.providerStrategy }}
allowDirtyBuild: true
- name: Sleep for Retry
if: ${{ steps.build-1.outcome == 'failure' }}
run: |
sleep 60
- name: Build (Retry 1)
uses: ./
id: build-2
if: ${{ steps.build-1.outcome == 'failure' }}
continue-on-error: true
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }}
buildProfile: ${{ matrix.buildProfile }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
providerStrategy: ${{ matrix.providerStrategy }}
allowDirtyBuild: true
- name: Sleep for Retry
if: ${{ steps.build-2.outcome == 'failure' }}
run: |
sleep 240
- name: Build (Retry 2)
uses: ./
id: build-3
if: ${{ steps.build-2.outcome == 'failure' }}
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }}
buildProfile: ${{ matrix.buildProfile }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
providerStrategy: ${{ matrix.providerStrategy }}
allowDirtyBuild: true
########################### ###########################
# Upload # # Upload #
########################### ###########################
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: name: Build Ubuntu (${{ matrix.unityVersion }})
"Build ${{ matrix.targetPlatform }}${{ startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }} on Ubuntu (${{ matrix.unityVersion }}_il2cpp_${{ matrix.buildWithIl2cpp }}_params_${{ matrix.additionalParameters }})"
path: build path: build
retention-days: 14 retention-days: 14

View File

@ -3,6 +3,8 @@ name: Builds - Windows
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches:
- main
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@ -11,42 +13,36 @@ concurrency:
jobs: jobs:
buildForAllPlatformsWindows: buildForAllPlatformsWindows:
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }} name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
runs-on: windows-2022 runs-on: windows-2019
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
projectPath: projectPath:
- test-project - test-project
unityVersion: unityVersion:
- 2021.3.32f1 - 2021.3.29f1
- 2022.3.13f1 - 2022.1.24f1
- 2023.2.2f1 - 2022.2.21f1
- 2022.3.7f1
- 2023.1.8f1
targetPlatform: targetPlatform:
- Android # Build an Android apk.
- StandaloneWindows64 # Build a Windows 64-bit standalone. - StandaloneWindows64 # Build a Windows 64-bit standalone.
- StandaloneWindows # Build a Windows 32-bit standalone.
- WSAPlayer # Build a UWP App - WSAPlayer # Build a UWP App
- tvOS # Build an Apple TV XCode project - tvOS # Build an Apple TV XCode project
enableGpu:
- false
include:
# Additionally test enableGpu build for a standalone windows target
- projectPath: test-project
unityVersion: 2023.2.2f1
targetPlatform: StandaloneWindows64
enableGpu: true
steps: steps:
########################### ###########################
# Checkout # # Checkout #
########################### ###########################
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
lfs: true lfs: true
########################### ###########################
# Cache # # Cache #
########################### ###########################
- uses: actions/cache@v4 - uses: actions/cache@v3
with: with:
path: ${{ matrix.projectPath }}/Library path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-windows-${{ matrix.targetPlatform }} key: Library-${{ matrix.projectPath }}-windows-${{ matrix.targetPlatform }}
@ -73,13 +69,10 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
enableGpu: ${{ matrix.enableGpu }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above # We use dirty build because we are replacing the default project settings file above
@ -99,13 +92,10 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
enableGpu: ${{ matrix.enableGpu }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above # We use dirty build because we are replacing the default project settings file above
@ -124,13 +114,10 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
enableGpu: ${{ matrix.enableGpu }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above # We use dirty build because we are replacing the default project settings file above
@ -138,8 +125,8 @@ jobs:
########################### ###########################
# Upload # # Upload #
########################### ###########################
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: Build ${{ matrix.targetPlatform }} on Windows (${{ matrix.unityVersion }})${{ matrix.enableGpu && ' With GPU' || '' }} name: Build Windows (${{ matrix.unityVersion }})
path: build path: build
retention-days: 14 retention-days: 14

View File

@ -15,11 +15,11 @@ jobs:
cleanupCloudRunner: cleanupCloudRunner:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
if: github.event.event_type != 'pull_request_target' if: github.event.event_type != 'pull_request_target'
with: with:
lfs: true lfs: true
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: '18' node-version: '18'
- run: yarn - run: yarn

View File

@ -18,40 +18,45 @@ env:
GCP_PROJECT: unitykubernetesbuilder GCP_PROJECT: unitykubernetesbuilder
GCP_LOG_FILE: ${{ github.workspace }}/cloud-runner-logs.txt GCP_LOG_FILE: ${{ github.workspace }}/cloud-runner-logs.txt
AWS_REGION: eu-west-2 AWS_REGION: eu-west-2
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: eu-west-2 AWS_DEFAULT_REGION: eu-west-2
AWS_STACK_NAME: game-ci-team-pipelines AWS_STACK_NAME: game-ci-team-pipelines
CLOUD_RUNNER_BRANCH: ${{ github.ref }} CLOUD_RUNNER_BRANCH: ${{ github.ref }}
DEBUG: true DEBUG: true
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
PROJECT_PATH: test-project PROJECT_PATH: test-project
UNITY_VERSION: 2019.3.15f1 UNITY_VERSION: 2019.3.15f1
USE_IL2CPP: false USE_IL2CPP: false
USE_GKE_GCLOUD_AUTH_PLUGIN: true USE_GKE_GCLOUD_AUTH_PLUGIN: true
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs: jobs:
tests: smokeTests:
name: Tests name: Smoke Tests
if: github.event.event_type != 'pull_request_target' if: github.event.event_type != 'pull_request_target'
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
test: test:
- 'cloud-runner-end2end-locking' #- 'cloud-runner-async-workflow'
- 'cloud-runner-end2end-caching'
- 'cloud-runner-end2end-retaining'
- 'cloud-runner-caching' - 'cloud-runner-caching'
# - 'cloud-runner-end2end-caching'
# - 'cloud-runner-end2end-retaining'
- 'cloud-runner-environment' - 'cloud-runner-environment'
- 'cloud-runner-image'
- 'cloud-runner-hooks' - 'cloud-runner-hooks'
- 'cloud-runner-local-persistence' - 'cloud-runner-local-persistence'
- 'cloud-runner-locking-core' - 'cloud-runner-locking-core'
- 'cloud-runner-locking-get-locked' - 'cloud-runner-locking-get-locked'
providerStrategy:
#- aws
- local-docker
#- k8s
steps: steps:
- name: Checkout (default) - name: Checkout (default)
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
lfs: false lfs: false
- name: Configure AWS Credentials - name: Configure AWS Credentials
@ -60,77 +65,55 @@ jobs:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-2 aws-region: eu-west-2
- uses: google-github-actions/auth@v1
if: matrix.providerStrategy == 'k8s'
with:
credentials_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
- name: 'Set up Cloud SDK'
if: matrix.providerStrategy == 'k8s'
uses: 'google-github-actions/setup-gcloud@v1.1.0'
- name: Get GKE cluster credentials
if: matrix.providerStrategy == 'k8s'
run: |
export USE_GKE_GCLOUD_AUTH_PLUGIN=True
gcloud components install gke-gcloud-auth-plugin
gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $GKE_PROJECT
- run: yarn - run: yarn
- run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand - run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand
timeout-minutes: 60 timeout-minutes: 35
env: env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
PROJECT_PATH: test-project PROJECT_PATH: test-project
TARGET_PLATFORM: StandaloneWindows64 TARGET_PLATFORM: StandaloneWindows64
cloudRunnerTests: true cloudRunnerTests: true
versioning: None versioning: None
KUBE_STORAGE_CLASS: local-path CLOUD_RUNNER_CLUSTER: ${{ matrix.providerStrategy }}
PROVIDER_STRATEGY: local-docker tests:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} # needs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # - smokeTests
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} # - buildTargetTests
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} name: Integration Tests
k8sTests:
name: K8s Tests
if: github.event.event_type != 'pull_request_target'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
test:
# - 'cloud-runner-async-workflow'
- 'cloud-runner-end2end-locking'
- 'cloud-runner-end2end-caching'
- 'cloud-runner-end2end-retaining'
- 'cloud-runner-kubernetes'
- 'cloud-runner-environment'
- 'cloud-runner-github-checks'
steps:
- name: Checkout (default)
uses: actions/checkout@v2
with:
lfs: false
- run: yarn
- name: actions-k3s
uses: debianmaster/actions-k3s@v1.0.5
with:
version: 'latest'
- run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand
timeout-minutes: 60
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
PROJECT_PATH: test-project
TARGET_PLATFORM: StandaloneWindows64
cloudRunnerTests: true
versioning: None
KUBE_STORAGE_CLASS: local-path
PROVIDER_STRATEGY: k8s
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
awsTests:
name: AWS Tests
if: github.event.event_type != 'pull_request_target' if: github.event.event_type != 'pull_request_target'
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
providerStrategy:
- aws
- local-docker
- k8s
test: test:
- 'cloud-runner-async-workflow'
#- 'cloud-runner-caching'
- 'cloud-runner-end2end-locking' - 'cloud-runner-end2end-locking'
- 'cloud-runner-end2end-caching' - 'cloud-runner-end2end-caching'
- 'cloud-runner-end2end-retaining' - 'cloud-runner-end2end-retaining'
- 'cloud-runner-environment' - 'cloud-runner-environment'
#- 'cloud-runner-hooks'
- 'cloud-runner-s3-steps' - 'cloud-runner-s3-steps'
#- 'cloud-runner-local-persistence'
#- 'cloud-runner-locking-core'
#- 'cloud-runner-locking-get-locked'
steps: steps:
- name: Checkout (default) - name: Checkout (default)
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -142,24 +125,29 @@ jobs:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-2 aws-region: eu-west-2
- uses: google-github-actions/auth@v1
if: matrix.providerStrategy == 'k8s'
with:
credentials_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
- name: 'Set up Cloud SDK'
if: matrix.providerStrategy == 'k8s'
uses: 'google-github-actions/setup-gcloud@v1.1.0'
- name: Get GKE cluster credentials
if: matrix.providerStrategy == 'k8s'
run: |
export USE_GKE_GCLOUD_AUTH_PLUGIN=True
gcloud components install gke-gcloud-auth-plugin
gcloud container clusters get-credentials $GKE_CLUSTER --zone $GKE_ZONE --project $GKE_PROJECT
- run: yarn - run: yarn
- run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand - run: yarn run test "${{ matrix.test }}" --detectOpenHandles --forceExit --runInBand
timeout-minutes: 60 timeout-minutes: 60
env: env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
PROJECT_PATH: test-project PROJECT_PATH: test-project
TARGET_PLATFORM: StandaloneWindows64 TARGET_PLATFORM: StandaloneWindows64
cloudRunnerTests: true cloudRunnerTests: true
versioning: None versioning: None
KUBE_STORAGE_CLASS: local-path PROVIDER_STRATEGY: ${{ matrix.providerStrategy }}
PROVIDER_STRATEGY: aws
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
buildTargetTests: buildTargetTests:
name: Local Build Target Tests name: Local Build Target Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -176,10 +164,10 @@ jobs:
- StandaloneLinux64 # Build a Linux 64-bit standalone. - StandaloneLinux64 # Build a Linux 64-bit standalone.
- WebGL # WebGL. - WebGL # WebGL.
- iOS # Build an iOS player. - iOS # Build an iOS player.
# - Android # Build an Android .apk. - Android # Build an Android .apk.
steps: steps:
- name: Checkout (default) - name: Checkout (default)
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
lfs: false lfs: false
- run: yarn - run: yarn
@ -187,14 +175,7 @@ jobs:
id: unity-build id: unity-build
timeout-minutes: 30 timeout-minutes: 30
env: env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
cloudRunnerTests: true cloudRunnerTests: true
versioning: None versioning: None
@ -202,7 +183,7 @@ jobs:
providerStrategy: ${{ matrix.providerStrategy }} providerStrategy: ${{ matrix.providerStrategy }}
- run: | - run: |
cp ./cloud-runner-cache/cache/${{ steps.unity-build.outputs.CACHE_KEY }}/build/${{ steps.unity-build.outputs.BUILD_ARTIFACT }} ${{ steps.unity-build.outputs.BUILD_ARTIFACT }} cp ./cloud-runner-cache/cache/${{ steps.unity-build.outputs.CACHE_KEY }}/build/${{ steps.unity-build.outputs.BUILD_ARTIFACT }} ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.providerStrategy }} Build (${{ matrix.targetPlatform }}) name: ${{ matrix.providerStrategy }} Build (${{ matrix.targetPlatform }})
path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }} path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}

View File

@ -16,8 +16,8 @@ jobs:
name: Tests name: Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-node@v4 - uses: actions/setup-node@v3
with: with:
node-version: '18' node-version: '18'
- run: yarn - run: yarn

7
.vscode/launch.json vendored
View File

@ -1,12 +1,5 @@
{ {
"configurations": [ "configurations": [
{
"name": "PowerShell Launch Current File",
"type": "PowerShell",
"request": "launch",
"script": "${file}",
"cwd": "${cwd}"
},
{ {
"type": "node", "type": "node",
"request": "launch", "request": "launch",

View File

@ -25,7 +25,7 @@ Steps to be performed to submit a pull request:
#### Pull Request Prerequisites #### Pull Request Prerequisites
You have [Node](https://nodejs.org/) installed at v18+ and [Yarn](https://yarnpkg.com/) at v1.22.0+. You have [Node](https://nodejs.org/) installed at v12.2.0+ and [Yarn](https://yarnpkg.com/) at v1.18.0+.
Please note that commit hooks will run automatically to perform some tasks; Please note that commit hooks will run automatically to perform some tasks;
@ -36,8 +36,7 @@ Please note that commit hooks will run automatically to perform some tasks;
#### Windows users #### Windows users
Make sure your editor and terminal that run the tests are set to `Powershell 7` or above with Make sure your editor and terminal that run the tests are set to `Powershell 7` or above with
`Git's Unix tools for Windows` installed. This is because some tests require you to be able to run `sh` and other `Git's Unix tools for Windows` installed. Some tests require you to be able to run `sh` and other unix commands.
unix commands.
#### License #### License

View File

@ -18,11 +18,7 @@ inputs:
projectPath: projectPath:
required: false required: false
default: '' default: ''
description: 'Path to the project to be built, relative to the repository root.' description: 'Relative path to the project to be built.'
buildProfile:
required: false
default: ''
description: 'Path to the build profile to activate, relative to the project root.'
buildName: buildName:
required: false required: false
default: '' default: ''
@ -35,14 +31,6 @@ inputs:
required: false required: false
default: '' default: ''
description: 'Path to a Namespace.Class.StaticMethod to run to perform the build.' description: 'Path to a Namespace.Class.StaticMethod to run to perform the build.'
manualExit:
required: false
default: ''
description: 'Suppresses `-quit`. Exit your build method using `EditorApplication.Exit(0)` instead.'
enableGpu:
required: false
default: ''
description: 'Launches unity without specifying `-nographics`.'
customParameters: customParameters:
required: false required: false
default: '' default: ''
@ -109,43 +97,11 @@ inputs:
required: false required: false
default: '' default: ''
description: '[CloudRunner] GitHub owner name or organization/team name' description: '[CloudRunner] GitHub owner name or organization/team name'
runAsHostUser:
required: false
default: 'false'
description:
'Whether to run as a user that matches the host system or the default root container user. Only applicable to
Linux hosts and containers. This is useful for fixing permission errors on Self-Hosted runners.'
chownFilesTo: chownFilesTo:
required: false required: false
default: '' default: ''
description: description:
'User and optionally group (user or user:group or uid:gid) to give ownership of the resulting build artifacts' 'User and optionally group (user or user:group or uid:gid) to give ownership of the resulting build artifacts'
dockerCpuLimit:
required: false
default: ''
description: 'Number of CPU cores to assign the docker container. Defaults to all available cores on all platforms.'
dockerMemoryLimit:
required: false
default: ''
description:
'Amount of memory to assign the docker container. Defaults to 95% of total system memory rounded down to the
nearest megabyte on Linux and 80% on Windows. On unrecognized platforms, defaults to 75% of total system memory.
To manually specify a value, use the format <number><unit>, where unit is either m or g. ie: 512m = 512 megabytes'
dockerIsolationMode:
required: false
default: 'default'
description:
'Isolation mode to use for the docker container. Can be one of process, hyperv, or default. Default will pick the
default mode as described by Microsoft where server versions use process and desktop versions use hyperv. Only
applicable on Windows'
containerRegistryRepository:
required: false
default: 'unityci/editor'
description: 'Container registry and repository to pull image from. Only applicable if customImage is not set.'
containerRegistryImageVersion:
required: false
default: '3'
description: 'Container registry image version. Only applicable if customImage is not set.'
allowDirtyBuild: allowDirtyBuild:
required: false required: false
default: '' default: ''
@ -194,11 +150,11 @@ inputs:
description: description:
'[CloudRunner] Either local, k8s or aws can be used to run builds on a remote cluster. Additional parameters must '[CloudRunner] Either local, k8s or aws can be used to run builds on a remote cluster. Additional parameters must
be configured.' be configured.'
containerCpu: cloudRunnerCpu:
default: '' default: ''
required: false required: false
description: '[CloudRunner] Amount of CPU time to assign the remote build container' description: '[CloudRunner] Amount of CPU time to assign the remote build container'
containerMemory: cloudRunnerMemory:
default: '' default: ''
required: false required: false
description: '[CloudRunner] Amount of memory to assign the remote build container' description: '[CloudRunner] Amount of memory to assign the remote build container'
@ -261,10 +217,6 @@ inputs:
description: description:
'The path to mount the workspace inside the docker container. For windows, leave out the drive letter. For example 'The path to mount the workspace inside the docker container. For windows, leave out the drive letter. For example
c:/github/workspace should be defined as /github/workspace' c:/github/workspace should be defined as /github/workspace'
skipActivation:
default: 'false'
required: false
description: 'Skip the activation/deactivation of Unity. This assumes Unity is already activated.'
outputs: outputs:
volume: volume:
@ -273,14 +225,9 @@ outputs:
description: 'The generated version used for the Unity build' description: 'The generated version used for the Unity build'
androidVersionCode: androidVersionCode:
description: 'The generated versionCode used for the Android Unity build' description: 'The generated versionCode used for the Android Unity build'
engineExitCode:
description:
'Returns the exit code from the build scripts. This code is 0 if the build was successful. If there was an error
during activation, the code is from the activation step. If activation is successful, the code is from the project
build step.'
branding: branding:
icon: 'box' icon: 'box'
color: 'gray-dark' color: 'gray-dark'
runs: runs:
using: 'node20' using: 'node16'
main: 'dist/index.js' main: 'dist/index.js'

BIN
dist/BlankProject/Packages/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
{
"m_SettingKeys": [
"VR Device Disabled",
"VR Device User Alert"
],
"m_SettingValues": [
"False",
"False"
]
}

View File

@ -0,0 +1,709 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<LangVersion>latest</LangVersion>
<_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package</_TargetFrameworkDirectories>
<_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package</_FullFrameworkReferenceAssemblyPaths>
<DisableHandlePackageFileConflicts>true</DisableHandlePackageFileConflicts>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.20506</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<RootNamespace></RootNamespace>
<ProjectGuid>{B7F8614B-1EC2-9D3A-DA1C-4D279A867D74}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<AssemblyName>Assembly-CSharp-Editor</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<BaseDirectory>.</BaseDirectory>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>Temp\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;UNITY_2019_2_11;UNITY_2019_2;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;NET_4_6;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoWarn>0169</NoWarn>
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>Temp\bin\Release\</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoWarn>0169</NoWarn>
<AllowUnsafeBlocks>False</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<NoConfig>true</NoConfig>
<NoStdLib>true</NoStdLib>
<AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
<ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>
<ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>
</PropertyGroup>
<ItemGroup>
<Reference Include="UnityEngine">
<HintPath>C:\Program Files\Unity\2019.2.11f1\Editor\Data\Managed/UnityEngine/UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEditor">
<HintPath>C:\Program Files\Unity\2019.2.11f1\Editor\Data\Managed/UnityEditor.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Assets\Editor\Builder.cs" />
<Compile Include="Assets\Editor\Input\ArgumentsParser.cs" />
<Compile Include="Assets\Editor\Reporting\StdOutReporter.cs" />
<Compile Include="Assets\Editor\System\ProcessExtensions.cs" />
<Compile Include="Assets\Editor\Versioning\VersionApplicator.cs" />
<Compile Include="Assets\Editor\Versioning\Git.cs" />
<Compile Include="Assets\Editor\Versioning\VersionGenerator.cs" />
<Compile Include="Assets\Editor\Versioning\GitException.cs" />
<Reference Include="UnityEditor.TestRunner">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEditor.TestRunner.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TestRunner">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEngine.TestRunner.dll</HintPath>
</Reference>
<Reference Include="Unity.Timeline.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.Timeline.Editor.dll</HintPath>
</Reference>
<Reference Include="com.unity.multiplayer-hlapi.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/com.unity.multiplayer-hlapi.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.VSCode.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.VSCode.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.TextMeshPro.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.TextMeshPro.Editor.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="Unity.Timeline">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.Timeline.dll</HintPath>
</Reference>
<Reference Include="Unity.CollabProxy.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.CollabProxy.Editor.dll</HintPath>
</Reference>
<Reference Include="com.unity.multiplayer-weaver.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/com.unity.multiplayer-weaver.Editor.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.XR.LegacyInputHelpers">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEngine.XR.LegacyInputHelpers.dll</HintPath>
</Reference>
<Reference Include="Unity.Rider.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.Rider.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.2D.Sprite.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.2D.Sprite.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.2D.Tilemap.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.2D.Tilemap.Editor.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.SpatialTracking">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEditor.SpatialTracking.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SpatialTracking">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEngine.SpatialTracking.dll</HintPath>
</Reference>
<Reference Include="Unity.TextMeshPro">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.TextMeshPro.dll</HintPath>
</Reference>
<Reference Include="Unity.Analytics.DataPrivacy">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/Unity.Analytics.DataPrivacy.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.XR.LegacyInputHelpers">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEditor.XR.LegacyInputHelpers.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.UI">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/UnityEditor.UI.dll</HintPath>
</Reference>
<Reference Include="com.unity.multiplayer-hlapi.Runtime">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/ScriptAssemblies/com.unity.multiplayer-hlapi.Runtime.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AIModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.AIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ARModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ARModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AccessibilityModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.AccessibilityModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AndroidJNIModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.AndroidJNIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AnimationModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.AnimationModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.AssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AudioModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.AudioModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClothModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ClothModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClusterInputModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ClusterInputModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ClusterRendererModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ClusterRendererModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CrashReportingModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.CrashReportingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.DSPGraphModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.DSPGraphModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.DirectorModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.DirectorModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.FileSystemHttpModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.FileSystemHttpModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.GameCenterModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.GameCenterModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.GridModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.GridModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.HotReloadModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.HotReloadModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.IMGUIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ImageConversionModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ImageConversionModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.InputModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.InputModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.InputLegacyModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.JSONSerializeModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.JSONSerializeModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.LocalizationModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.LocalizationModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ParticleSystemModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ParticleSystemModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PerformanceReportingModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.PerformanceReportingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.Physics2DModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.Physics2DModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ProfilerModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ProfilerModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.ScreenCaptureModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.ScreenCaptureModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SharedInternalsModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.SharedInternalsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SpriteMaskModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.SpriteMaskModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SpriteShapeModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.SpriteShapeModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.StreamingModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.StreamingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.SubstanceModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.SubstanceModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TLSModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.TLSModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TerrainModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.TerrainModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TerrainPhysicsModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.TerrainPhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextCoreModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.TextCoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.TextRenderingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TilemapModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.TilemapModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UIModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UIElementsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UNETModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UNETModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UmbraModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UmbraModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityAnalyticsModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityAnalyticsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityConnectModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityConnectModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityTestProtocolModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityTestProtocolModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestAssetBundleModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestAssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestAudioModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestAudioModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestTextureModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestTextureModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestWWWModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.UnityWebRequestWWWModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VFXModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.VFXModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VRModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.VRModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VehiclesModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.VehiclesModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.VideoModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.VideoModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.WindModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.WindModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.XRModule">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEngine/UnityEngine.XRModule.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.VR">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/UnityExtensions/Unity/UnityVR/Editor/UnityEditor.VR.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.Graphs">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/Managed/UnityEditor.Graphs.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.WindowsStandalone.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/PlaybackEngines/WindowsStandaloneSupport/UnityEditor.WindowsStandalone.Extensions.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.WebGL.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/PlaybackEngines/WebGLSupport/UnityEditor.WebGL.Extensions.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.Android.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/PlaybackEngines/AndroidPlayer/UnityEditor.Android.Extensions.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.UWP.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/PlaybackEngines/MetroSupport/UnityEditor.UWP.Extensions.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.Advertisements">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/PackageCache/com.unity.ads@2.0.8/Editor/UnityEditor.Advertisements.dll</HintPath>
</Reference>
<Reference Include="Unity.Analytics.Editor">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/PackageCache/com.unity.analytics@3.3.2/Unity.Analytics.Editor.dll</HintPath>
</Reference>
<Reference Include="Unity.Analytics.StandardEvents">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/PackageCache/com.unity.analytics@3.3.2/Unity.Analytics.StandardEvents.dll</HintPath>
</Reference>
<Reference Include="Unity.Analytics.Tracker">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/PackageCache/com.unity.analytics@3.3.2/Unity.Analytics.Tracker.dll</HintPath>
</Reference>
<Reference Include="UnityEditor.Purchasing">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/PackageCache/com.unity.purchasing@2.0.6/Editor/UnityEditor.Purchasing.dll</HintPath>
</Reference>
<Reference Include="nunit.framework">
<HintPath>C:/Repositories/unity-builder/builder/default-build-script/Library/PackageCache/com.unity.ext.nunit@1.0.0/net35/unity-custom/nunit.framework.dll</HintPath>
</Reference>
<Reference Include="mscorlib">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/mscorlib.dll</HintPath>
</Reference>
<Reference Include="System">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.dll</HintPath>
</Reference>
<Reference Include="System.Core">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Core.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Runtime.Serialization.dll</HintPath>
</Reference>
<Reference Include="System.Xml">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Xml.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Numerics">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Microsoft.CSharp.dll</HintPath>
</Reference>
<Reference Include="System.Data">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/System.Data.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Win32.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/Microsoft.Win32.Primitives.dll</HintPath>
</Reference>
<Reference Include="netstandard">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/netstandard.dll</HintPath>
</Reference>
<Reference Include="System.AppContext">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.AppContext.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Concurrent">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Concurrent.dll</HintPath>
</Reference>
<Reference Include="System.Collections">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.dll</HintPath>
</Reference>
<Reference Include="System.Collections.NonGeneric">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.NonGeneric.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Specialized">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Collections.Specialized.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Annotations">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Annotations.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.EventBasedAsync">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.EventBasedAsync.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.TypeConverter">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ComponentModel.TypeConverter.dll</HintPath>
</Reference>
<Reference Include="System.Console">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Console.dll</HintPath>
</Reference>
<Reference Include="System.Data.Common">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Data.Common.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Contracts">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Contracts.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Debug">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Debug.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.FileVersionInfo">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.FileVersionInfo.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Process">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Process.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.StackTrace">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.StackTrace.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.TextWriterTraceListener">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TextWriterTraceListener.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.Tools">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.Tools.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.TraceSource">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Diagnostics.TraceSource.dll</HintPath>
</Reference>
<Reference Include="System.Drawing.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Drawing.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Dynamic.Runtime">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Dynamic.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Globalization.Calendars">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Calendars.dll</HintPath>
</Reference>
<Reference Include="System.Globalization">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.dll</HintPath>
</Reference>
<Reference Include="System.Globalization.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Globalization.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.ZipFile">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Compression.ZipFile.dll</HintPath>
</Reference>
<Reference Include="System.IO">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.DriveInfo">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.DriveInfo.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Watcher">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.FileSystem.Watcher.dll</HintPath>
</Reference>
<Reference Include="System.IO.IsolatedStorage">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.IsolatedStorage.dll</HintPath>
</Reference>
<Reference Include="System.IO.MemoryMappedFiles">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.MemoryMappedFiles.dll</HintPath>
</Reference>
<Reference Include="System.IO.Pipes">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.Pipes.dll</HintPath>
</Reference>
<Reference Include="System.IO.UnmanagedMemoryStream">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.IO.UnmanagedMemoryStream.dll</HintPath>
</Reference>
<Reference Include="System.Linq">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Expressions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Expressions.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Parallel">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Parallel.dll</HintPath>
</Reference>
<Reference Include="System.Linq.Queryable">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Linq.Queryable.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Rtc">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Http.Rtc.dll</HintPath>
</Reference>
<Reference Include="System.Net.NameResolution">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NameResolution.dll</HintPath>
</Reference>
<Reference Include="System.Net.NetworkInformation">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.NetworkInformation.dll</HintPath>
</Reference>
<Reference Include="System.Net.Ping">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Ping.dll</HintPath>
</Reference>
<Reference Include="System.Net.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Net.Requests">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Requests.dll</HintPath>
</Reference>
<Reference Include="System.Net.Security">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Security.dll</HintPath>
</Reference>
<Reference Include="System.Net.Sockets">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.Sockets.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebHeaderCollection">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebHeaderCollection.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebSockets.Client">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.Client.dll</HintPath>
</Reference>
<Reference Include="System.Net.WebSockets">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Net.WebSockets.dll</HintPath>
</Reference>
<Reference Include="System.ObjectModel">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="System.Reflection">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit.ILGeneration">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.ILGeneration.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Emit.Lightweight">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Emit.Lightweight.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Reflection.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Resources.Reader">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Reader.dll</HintPath>
</Reference>
<Reference Include="System.Resources.ResourceManager">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.ResourceManager.dll</HintPath>
</Reference>
<Reference Include="System.Resources.Writer">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Resources.Writer.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.VisualC">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.CompilerServices.VisualC.dll</HintPath>
</Reference>
<Reference Include="System.Runtime">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Handles">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Handles.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.RuntimeInformation">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices.WindowsRuntime">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.InteropServices.WindowsRuntime.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Numerics">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Numerics.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Formatters">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Formatters.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Json">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Json.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization.Xml">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Runtime.Serialization.Xml.dll</HintPath>
</Reference>
<Reference Include="System.Security.Claims">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Claims.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Algorithms.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Csp">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Csp.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Cryptography.X509Certificates.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.Principal.dll</HintPath>
</Reference>
<Reference Include="System.Security.SecureString">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Security.SecureString.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Duplex">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Duplex.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Http">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Http.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.NetTcp">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.NetTcp.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Primitives">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel.Security">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ServiceModel.Security.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.Extensions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.Encoding.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Text.RegularExpressions">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Text.RegularExpressions.dll</HintPath>
</Reference>
<Reference Include="System.Threading">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Overlapped">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Overlapped.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Parallel">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Tasks.Parallel.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Thread">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Thread.dll</HintPath>
</Reference>
<Reference Include="System.Threading.ThreadPool">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.ThreadPool.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Timer">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Threading.Timer.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.ReaderWriter">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.ReaderWriter.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XDocument">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlDocument">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlSerializer">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XmlSerializer.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath.XDocument">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/4.7.1-api/Facades/System.Xml.XPath.XDocument.dll</HintPath>
</Reference>
<Reference Include="UnityScript">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.dll</HintPath>
</Reference>
<Reference Include="UnityScript.Lang">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/UnityScript.Lang.dll</HintPath>
</Reference>
<Reference Include="Boo.Lang">
<HintPath>C:/Program Files/Unity/2019.2.11f1/Editor/Data/MonoBleedingEdge/lib/mono/unityscript/Boo.Lang.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include=".editorconfig" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -6,9 +6,6 @@ using UnityBuilderAction.Reporting;
using UnityBuilderAction.Versioning; using UnityBuilderAction.Versioning;
using UnityEditor; using UnityEditor;
using UnityEditor.Build.Reporting; using UnityEditor.Build.Reporting;
#if UNITY_6000_0_OR_NEWER
using UnityEditor.Build.Profile;
#endif
using UnityEngine; using UnityEngine;
namespace UnityBuilderAction namespace UnityBuilderAction
@ -20,9 +17,47 @@ namespace UnityBuilderAction
// Gather values from args // Gather values from args
var options = ArgumentsParser.GetValidatedOptions(); var options = ArgumentsParser.GetValidatedOptions();
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
// Get all buildOptions from options
BuildOptions buildOptions = BuildOptions.None;
foreach (string buildOptionString in Enum.GetNames(typeof(BuildOptions))) {
if (options.ContainsKey(buildOptionString)) {
BuildOptions buildOptionEnum = (BuildOptions) Enum.Parse(typeof(BuildOptions), buildOptionString);
buildOptions |= buildOptionEnum;
}
}
#if UNITY_2021_2_OR_NEWER
// Determine subtarget
StandaloneBuildSubtarget buildSubtarget;
if (!options.TryGetValue("standaloneBuildSubtarget", out var subtargetValue) || !Enum.TryParse(subtargetValue, out buildSubtarget)) {
buildSubtarget = default;
}
#endif
// Define BuildPlayer Options
var buildPlayerOptions = new BuildPlayerOptions {
scenes = scenes,
locationPathName = options["customBuildPath"],
target = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]),
options = buildOptions,
#if UNITY_2021_2_OR_NEWER
subtarget = (int) buildSubtarget
#endif
};
// Set version for this build // Set version for this build
VersionApplicator.SetVersion(options["buildVersion"]); VersionApplicator.SetVersion(options["buildVersion"]);
// Apply Android settings
if (buildPlayerOptions.target == BuildTarget.Android)
{
VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]);
AndroidSettings.Apply(options);
}
// Execute default AddressableAsset content build, if the package is installed. // Execute default AddressableAsset content build, if the package is installed.
// Version defines would be the best solution here, but Unity 2018 doesn't support that, // Version defines would be the best solution here, but Unity 2018 doesn't support that,
// so we fall back to using reflection instead. // so we fall back to using reflection instead.
@ -39,76 +74,10 @@ namespace UnityBuilderAction
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogError("Failed to run default addressables build:\n" + e); Debug.LogError($"Failed to run default addressables build:\n{e}");
} }
} }
// Get all buildOptions from options
BuildOptions buildOptions = BuildOptions.None;
foreach (string buildOptionString in Enum.GetNames(typeof(BuildOptions))) {
if (options.ContainsKey(buildOptionString)) {
BuildOptions buildOptionEnum = (BuildOptions) Enum.Parse(typeof(BuildOptions), buildOptionString);
buildOptions |= buildOptionEnum;
}
}
// Depending on whether the build is using a build profile, `buildPlayerOptions` will an instance
// of either `UnityEditor.BuildPlayerOptions` or `UnityEditor.BuildPlayerWithProfileOptions`
dynamic buildPlayerOptions;
if (options["customBuildProfile"] != "") {
#if UNITY_6000_0_OR_NEWER
// Load build profile from Assets folder
BuildProfile buildProfile = AssetDatabase.LoadAssetAtPath<BuildProfile>(options["customBuildProfile"]);
// Set it as active
BuildProfile.SetActiveBuildProfile(buildProfile);
// Define BuildPlayerWithProfileOptions
buildPlayerOptions = new BuildPlayerWithProfileOptions {
buildProfile = buildProfile,
locationPathName = options["customBuildPath"],
options = buildOptions,
};
#else
throw new Exception("Build profiles are not supported by this version of Unity (" + Application.unityVersion +")");
#endif
} else {
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
#if UNITY_2021_2_OR_NEWER
// Determine subtarget
StandaloneBuildSubtarget buildSubtarget;
if (!options.TryGetValue("standaloneBuildSubtarget", out var subtargetValue) || !Enum.TryParse(subtargetValue, out buildSubtarget)) {
buildSubtarget = default;
}
#endif
BuildTarget buildTarget = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]);
// Define BuildPlayerOptions
buildPlayerOptions = new BuildPlayerOptions {
scenes = scenes,
locationPathName = options["customBuildPath"],
target = buildTarget,
options = buildOptions,
#if UNITY_2021_2_OR_NEWER
subtarget = (int) buildSubtarget
#endif
};
// Apply Android settings
if (buildTarget == BuildTarget.Android) {
VersionApplicator.SetAndroidVersionCode(options["androidVersionCode"]);
AndroidSettings.Apply(options);
}
}
// Perform build // Perform build
BuildReport buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions); BuildReport buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions);

View File

@ -56,17 +56,17 @@ namespace UnityBuilderAction.Input
case "androidStudioProject": case "androidStudioProject":
EditorUserBuildSettings.exportAsGoogleAndroidProject = true; EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
if (buildAppBundle != null) if (buildAppBundle != null)
buildAppBundle.SetValue(null, false, null); buildAppBundle.SetValue(null, false);
break; break;
case "androidAppBundle": case "androidAppBundle":
EditorUserBuildSettings.exportAsGoogleAndroidProject = false; EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
if (buildAppBundle != null) if (buildAppBundle != null)
buildAppBundle.SetValue(null, true, null); buildAppBundle.SetValue(null, true);
break; break;
case "androidPackage": case "androidPackage":
EditorUserBuildSettings.exportAsGoogleAndroidProject = false; EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
if (buildAppBundle != null) if (buildAppBundle != null)
buildAppBundle.SetValue(null, false, null); buildAppBundle.SetValue(null, false);
break; break;
} }
} }
@ -74,20 +74,7 @@ namespace UnityBuilderAction.Input
string symbolType; string symbolType;
if (options.TryGetValue("androidSymbolType", out symbolType) && !string.IsNullOrEmpty(symbolType)) if (options.TryGetValue("androidSymbolType", out symbolType) && !string.IsNullOrEmpty(symbolType))
{ {
#if UNITY_6000_0_OR_NEWER #if UNITY_2021_1_OR_NEWER
switch (symbolType)
{
case "public":
SetDebugSymbols("SymbolTable");
break;
case "debugging":
SetDebugSymbols("Full");
break;
case "none":
SetDebugSymbols("None");
break;
}
#elif UNITY_2021_1_OR_NEWER
switch (symbolType) switch (symbolType)
{ {
case "public": case "public":
@ -114,35 +101,5 @@ namespace UnityBuilderAction.Input
#endif #endif
} }
} }
private static void SetDebugSymbols(string enumValueName)
{
// UnityEditor.Android.UserBuildSettings and Unity.Android.Types.DebugSymbolLevel are part of the Unity Android module.
// Reflection is used here to ensure the code works even if the module is not installed.
var debugSymbolsType = Type.GetType("UnityEditor.Android.UserBuildSettings+DebugSymbols, UnityEditor.Android.Extensions");
if (debugSymbolsType == null)
{
return;
}
var levelProp = debugSymbolsType.GetProperty("level", BindingFlags.Static | BindingFlags.Public);
if (levelProp == null)
{
return;
}
var enumType = Type.GetType("Unity.Android.Types.DebugSymbolLevel, Unity.Android.Types");
if (enumType == null)
{
return;
}
if (!Enum.TryParse(enumType, enumValueName, false , out var enumValue))
{
return;
}
levelProp.SetValue(null, enumValue);
}
} }
} }

View File

@ -28,7 +28,7 @@ namespace UnityBuilderAction.Input
} }
if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) { if (!Enum.IsDefined(typeof(BuildTarget), buildTarget)) {
Console.WriteLine(buildTarget + " is not a defined " + typeof(BuildTarget).Name); Console.WriteLine($"{buildTarget} is not a defined {nameof(BuildTarget)}");
EditorApplication.Exit(121); EditorApplication.Exit(121);
} }
@ -41,10 +41,10 @@ namespace UnityBuilderAction.Input
const string defaultCustomBuildName = "TestBuild"; const string defaultCustomBuildName = "TestBuild";
string customBuildName; string customBuildName;
if (!validatedOptions.TryGetValue("customBuildName", out customBuildName)) { if (!validatedOptions.TryGetValue("customBuildName", out customBuildName)) {
Console.WriteLine("Missing argument -customBuildName, defaulting to" + defaultCustomBuildName); Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName); validatedOptions.Add("customBuildName", defaultCustomBuildName);
} else if (customBuildName == "") { } else if (customBuildName == "") {
Console.WriteLine("Invalid argument -customBuildName, defaulting to" + defaultCustomBuildName); Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName); validatedOptions.Add("customBuildName", defaultCustomBuildName);
} }
@ -57,11 +57,11 @@ namespace UnityBuilderAction.Input
string[] args = Environment.GetCommandLineArgs(); string[] args = Environment.GetCommandLineArgs();
Console.WriteLine( Console.WriteLine(
EOL + $"{EOL}" +
"###########################" + EOL + $"###########################{EOL}" +
"# Parsing settings #" + EOL + $"# Parsing settings #{EOL}" +
"###########################" + EOL + $"###########################{EOL}" +
EOL $"{EOL}"
); );
// Extract flags with optional values // Extract flags with optional values
@ -78,7 +78,7 @@ namespace UnityBuilderAction.Input
string displayValue = secret ? "*HIDDEN*" : "\"" + value + "\""; string displayValue = secret ? "*HIDDEN*" : "\"" + value + "\"";
// Assign // Assign
Console.WriteLine("Found flag \"" + flag + "\" with value " + displayValue); Console.WriteLine($"Found flag \"{flag}\" with value {displayValue}.");
providedArguments.Add(flag, value); providedArguments.Add(flag, value);
} }
} }

View File

@ -1,36 +0,0 @@
using System;
using UnityEngine;
using UnityEditor;
namespace UnityBuilderAction.Reporting
{
[InitializeOnLoad]
static class CompileListener
{
static CompileListener()
{
if (Application.isBatchMode)
{
Application.logMessageReceived += Application_logMessageReceived;
}
}
private static void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
string prefix = "";
switch (type)
{
case LogType.Error:
prefix = "error";
break;
case LogType.Warning:
prefix = "warning";
break;
case LogType.Exception:
prefix = "error";
break;
}
Console.WriteLine(Environment.NewLine + "::" + prefix + "::" + condition + Environment.NewLine + stackTrace);
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: fad44373fb7b61a4bb584e2675795aca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -11,16 +11,16 @@ namespace UnityBuilderAction.Reporting
public static void ReportSummary(BuildSummary summary) public static void ReportSummary(BuildSummary summary)
{ {
Console.WriteLine( Console.WriteLine(
EOL + $"{EOL}" +
"###########################" + EOL + $"###########################{EOL}" +
"# Build results #" + EOL + $"# Build results #{EOL}" +
"###########################" + EOL + $"###########################{EOL}" +
EOL + $"{EOL}" +
"Duration: " + summary.totalTime.ToString() + EOL + $"Duration: {summary.totalTime.ToString()}{EOL}" +
"Warnings: " + summary.totalWarnings.ToString() + EOL + $"Warnings: {summary.totalWarnings.ToString()}{EOL}" +
"Errors: " + summary.totalErrors.ToString() + EOL + $"Errors: {summary.totalErrors.ToString()}{EOL}" +
"Size: " + summary.totalSize.ToString() + " bytes" + EOL + $"Size: {summary.totalSize.ToString()} bytes{EOL}" +
EOL $"{EOL}"
); );
} }

View File

@ -21,11 +21,11 @@ namespace UnityBuilderAction.Versioning
version = GetSemanticCommitVersion(); version = GetSemanticCommitVersion();
Console.WriteLine("Repository has a valid version tag."); Console.WriteLine("Repository has a valid version tag.");
} else { } else {
version = "0.0." + GetTotalNumberOfCommits(); version = $"0.0.{GetTotalNumberOfCommits()}";
Console.WriteLine("Repository does not have tags to base the version on."); Console.WriteLine("Repository does not have tags to base the version on.");
} }
Console.WriteLine("Version is " + version); Console.WriteLine($"Version is {version}");
return version; return version;
} }

View File

@ -0,0 +1,10 @@
{
"m_SettingKeys": [
"VR Device Disabled",
"VR Device User Alert"
],
"m_SettingValues": [
"False",
"False"
]
}

View File

@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{B7F8614B-1EC2-9D3A-DA1C-4D279A867D74}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B7F8614B-1EC2-9D3A-DA1C-4D279A867D74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7F8614B-1EC2-9D3A-DA1C-4D279A867D74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7F8614B-1EC2-9D3A-DA1C-4D279A867D74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7F8614B-1EC2-9D3A-DA1C-4D279A867D74}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Untracked/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Versioning/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

BIN
dist/index.js generated vendored

Binary file not shown.

BIN
dist/index.js.map generated vendored

Binary file not shown.

BIN
dist/licenses.txt generated vendored

Binary file not shown.

View File

@ -1,40 +1,28 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Perform Activation # Create directories for license activation
# #
if [ "$SKIP_ACTIVATION" != "true" ]; then sudo mkdir /Library/Application\ Support/Unity
UNITY_LICENSE_PATH="/Library/Application Support/Unity" sudo chmod -R 777 /Library/Application\ Support/Unity
if [ ! -d "$UNITY_LICENSE_PATH" ]; then ACTIVATE_LICENSE_PATH="$ACTION_FOLDER/BlankProject"
echo "Creating Unity License Directory" mkdir -p "$ACTIVATE_LICENSE_PATH"
sudo mkdir -p "$UNITY_LICENSE_PATH"
sudo chmod -R 777 "$UNITY_LICENSE_PATH"
fi;
ACTIVATE_LICENSE_PATH="$ACTION_FOLDER/BlankProject"
mkdir -p "$ACTIVATE_LICENSE_PATH"
source $ACTION_FOLDER/platforms/mac/steps/activate.sh
else
echo "Skipping activation"
fi
# #
# Run Build # Run steps
# #
source $ACTION_FOLDER/platforms/mac/steps/activate.sh
source $ACTION_FOLDER/platforms/mac/steps/build.sh source $ACTION_FOLDER/platforms/mac/steps/build.sh
source $ACTION_FOLDER/platforms/mac/steps/return_license.sh
# #
# License Cleanup # Remove license activation directory
# #
if [ "$SKIP_ACTIVATION" != "true" ]; then sudo rm -r /Library/Application\ Support/Unity
source $ACTION_FOLDER/platforms/mac/steps/return_license.sh rm -r "$ACTIVATE_LICENSE_PATH"
rm -r "$ACTIVATE_LICENSE_PATH"
fi
# #
# Instructions for debugging # Instructions for debugging
@ -49,7 +37,7 @@ echo ""
echo "Please note that the exit code is not very descriptive." echo "Please note that the exit code is not very descriptive."
echo "Most likely it will not help you solve the issue." echo "Most likely it will not help you solve the issue."
echo "" echo ""
echo "To find the reason for failure: please search for errors in the log above and check for annotations in the summary view." echo "To find the reason for failure: please search for errors in the log above."
echo "" echo ""
fi; fi;

View File

@ -30,7 +30,6 @@ else
# Activation failed so exit with the code from the license verification step # Activation failed so exit with the code from the license verification step
echo "Unclassified error occured while trying to activate license." echo "Unclassified error occured while trying to activate license."
echo "Exit code was: $UNITY_EXIT_CODE" echo "Exit code was: $UNITY_EXIT_CODE"
echo "::error ::There was an error while trying to activate the Unity license."
exit $UNITY_EXIT_CODE exit $UNITY_EXIT_CODE
fi fi

View File

@ -19,23 +19,6 @@ echo "Using build name \"$BUILD_NAME\"."
echo "Using build target \"$BUILD_TARGET\"." echo "Using build target \"$BUILD_TARGET\"."
#
# Display the build profile
#
if [ -z "$BUILD_PROFILE" ]; then
# User has not provided a build profile
#
echo "Doing a default \"$BUILD_TARGET\" platform build."
#
else
# User has provided a path to a build profile `.asset` file
#
echo "Using build profile \"$BUILD_PROFILE\" relative to \"$UNITY_PROJECT_PATH\"."
#
fi
# #
# Display build path and file # Display build path and file
# #
@ -146,9 +129,9 @@ echo ""
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \ /Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
-logFile - \ -logFile - \
$( [ "${MANUAL_EXIT}" == "true" ] || echo "-quit" ) \ -quit \
-batchmode \ -batchmode \
$( [ "${ENABLE_GPU}" == "true" ] || echo "-nographics" ) \ -nographics \
-username "$UNITY_EMAIL" \ -username "$UNITY_EMAIL" \
-password "$UNITY_PASSWORD" \ -password "$UNITY_PASSWORD" \
-customBuildName "$BUILD_NAME" \ -customBuildName "$BUILD_NAME" \
@ -156,7 +139,6 @@ echo ""
-buildTarget "$BUILD_TARGET" \ -buildTarget "$BUILD_TARGET" \
-customBuildTarget "$BUILD_TARGET" \ -customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \ -customBuildPath "$CUSTOM_BUILD_PATH" \
-customBuildProfile "$BUILD_PROFILE" \
-executeMethod "$BUILD_METHOD" \ -executeMethod "$BUILD_METHOD" \
-buildVersion "$VERSION" \ -buildVersion "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \ -androidVersionCode "$ANDROID_VERSION_CODE" \

View File

@ -1,83 +1,46 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Ensure machine ID is randomized for personal license activation
if [[ "$UNITY_SERIAL" = F* ]]; then
echo "Randomizing machine ID for personal license activation"
dbus-uuidgen > /etc/machine-id && mkdir -p /var/lib/dbus/ && ln -sf /etc/machine-id /var/lib/dbus/machine-id
fi
# #
# Prepare Android SDK, if needed # Create directory for license activation
# We do this here to ensure it has root permissions
# #
fullProjectPath="$GITHUB_WORKSPACE/$PROJECT_PATH" ACTIVATE_LICENSE_PATH="$GITHUB_WORKSPACE/_activate-license~"
mkdir -p "$ACTIVATE_LICENSE_PATH"
if [[ "$BUILD_TARGET" == "Android" ]]; then #
export JAVA_HOME="$(awk -F'=' '/JAVA_HOME=/{print $2}' /usr/bin/unity-editor.d/*)" # Run steps
ANDROID_HOME_DIRECTORY="$(awk -F'=' '/ANDROID_HOME=/{print $2}' /usr/bin/unity-editor.d/*)" #
SDKMANAGER=$(find $ANDROID_HOME_DIRECTORY/cmdline-tools -name sdkmanager) source /steps/set_extra_git_configs.sh
if [ -z "${SDKMANAGER}" ] source /steps/set_gitcredential.sh
then source /steps/activate.sh
SDKMANAGER=$(find $ANDROID_HOME_DIRECTORY/tools/bin -name sdkmanager) source /steps/build.sh
if [ -z "${SDKMANAGER}" ] source /steps/return_license.sh
then
echo "No sdkmanager found"
exit 1
fi
fi
if [[ -n "$ANDROID_SDK_MANAGER_PARAMETERS" ]]; then #
echo "Updating Android SDK with parameters: $ANDROID_SDK_MANAGER_PARAMETERS" # Remove license activation directory
$SDKMANAGER "$ANDROID_SDK_MANAGER_PARAMETERS" #
else
echo "Updating Android SDK with auto detected target API version"
# Read the line containing AndroidTargetSdkVersion from the file
targetAPILine=$(grep 'AndroidTargetSdkVersion' "$fullProjectPath/ProjectSettings/ProjectSettings.asset")
# Extract the number after the semicolon rm -r "$ACTIVATE_LICENSE_PATH"
targetAPI=$(echo "$targetAPILine" | cut -d':' -f2 | tr -d '[:space:]')
$SDKMANAGER "platforms;android-$targetAPI" #
fi # Instructions for debugging
#
echo "Updated Android SDK." if [[ $BUILD_EXIT_CODE -gt 0 ]]; then
else echo ""
echo "Not updating Android SDK." echo "###########################"
fi echo "# Failure #"
echo "###########################"
echo ""
echo "Please note that the exit code is not very descriptive."
echo "Most likely it will not help you solve the issue."
echo ""
echo "To find the reason for failure: please search for errors in the log above."
echo ""
fi;
if [[ "$RUN_AS_HOST_USER" == "true" ]]; then #
echo "Running as host user" # Exit with code from the build step.
#
# Stop on error if we can't set up the user exit $BUILD_EXIT_CODE
set -e
# Get host user/group info so we create files with the correct ownership
USERNAME=$(stat -c '%U' "$fullProjectPath")
USERID=$(stat -c '%u' "$fullProjectPath")
GROUPNAME=$(stat -c '%G' "$fullProjectPath")
GROUPID=$(stat -c '%g' "$fullProjectPath")
groupadd -g $GROUPID $GROUPNAME
useradd -u $USERID -g $GROUPID $USERNAME
usermod -aG $GROUPNAME $USERNAME
mkdir -p "/home/$USERNAME"
chown $USERNAME:$GROUPNAME "/home/$USERNAME"
# Normally need root permissions to access when using su
chmod 777 /dev/stdout
chmod 777 /dev/stderr
# Don't stop on error when running our scripts as error handling is baked in
set +e
# Switch to the host user so we can create files with the correct ownership
su $USERNAME -c "$SHELL -c 'source /steps/runsteps.sh'"
else
echo "Running as root"
# Run as root
source /steps/runsteps.sh
fi
exit $?

View File

@ -1,65 +1,78 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# if blankproject folder doesn't exist create it # Run in ACTIVATE_LICENSE_PATH directory
if [ ! -d "/BlankProject" ]; then echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
mkdir /BlankProject pushd "$ACTIVATE_LICENSE_PATH"
fi
# if blankproject folder doesn't exist create it
if [ ! -d "/BlankProject/Assets" ]; then
mkdir /BlankProject/Assets
fi
if [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then if [[ -n "$UNITY_LICENSE" ]] || [[ -n "$UNITY_LICENSE_FILE" ]]; then
# #
# SERIAL LICENSE MODE # PERSONAL LICENSE MODE
# #
# This will activate unity, using the serial activation process. # This will activate Unity, using a license file
# #
echo "Requesting activation" # Note that this is the ONLY WAY for PERSONAL LICENSES in 2020.
# * See for more details: https://gitlab.com/gableroux/unity3d-gitlab-ci-example/issues/5#note_72815478
#
# The license file can be acquired using `webbertakken/request-manual-activation-file` action.
echo "Requesting activation (personal license)"
# Loop the unity-editor call until the license is activated with exponential backoff and a maximum of 5 retries # Set the license file path
retry_count=0 FILE_PATH=UnityLicenseFile.ulf
# Initialize delay to 15 seconds if [[ -n "$UNITY_LICENSE" ]]; then
delay=15 # Copy license file from Github variables
echo "$UNITY_LICENSE" | tr -d '\r' > $FILE_PATH
elif [[ -n "$UNITY_LICENSE_FILE" ]]; then
# Copy license file from file system
cat "$UNITY_LICENSE_FILE" | tr -d '\r' > $FILE_PATH
fi
# Loop until UNITY_EXIT_CODE is 0 or retry count reaches 5 # Activate license
while [[ $retry_count -lt 5 ]] ACTIVATION_OUTPUT=$(unity-editor \
do
# Activate license
unity-editor \
-logFile /dev/stdout \ -logFile /dev/stdout \
-quit \ -quit \
-serial "$UNITY_SERIAL" \ -manualLicenseFile $FILE_PATH)
-username "$UNITY_EMAIL" \
-password "$UNITY_PASSWORD" \
-projectPath "/BlankProject"
# Store the exit code from the verify command # Store the exit code from the verify command
UNITY_EXIT_CODE=$? UNITY_EXIT_CODE=$?
# Check if UNITY_EXIT_CODE is 0 # The exit code for personal activation is always 1;
if [[ $UNITY_EXIT_CODE -eq 0 ]] # Determine whether activation was successful.
then #
echo "Activation successful" # Successful output should include the following:
break #
else # "LICENSE SYSTEM [2020120 18:51:20] Next license update check is after 2019-11-25T18:23:38"
# Increment retry count #
((retry_count++)) ACTIVATION_SUCCESSFUL=$(echo $ACTIVATION_OUTPUT | grep 'Next license update check is after' | wc -l)
echo "::warning ::Activation failed, attempting retry #$retry_count" # Set exit code to 0 if activation was successful
echo "Activation failed, retrying in $delay seconds..." if [[ $ACTIVATION_SUCCESSFUL -eq 1 ]]; then
sleep $delay UNITY_EXIT_CODE=0
fi;
# Double the delay for the next iteration # Remove license file
delay=$((delay * 2)) rm -f $FILE_PATH
fi
done
if [[ $retry_count -eq 5 ]] elif [[ -n "$UNITY_SERIAL" && -n "$UNITY_EMAIL" && -n "$UNITY_PASSWORD" ]]; then
then #
echo "Activation failed after 5 retries" # PROFESSIONAL (SERIAL) LICENSE MODE
fi #
# This will activate unity, using the activating process.
#
# Note: This is the preferred way for PROFESSIONAL LICENSES.
#
echo "Requesting activation (professional license)"
# Activate license
unity-editor \
-logFile /dev/stdout \
-quit \
-serial "$UNITY_SERIAL" \
-username "$UNITY_EMAIL" \
-password "$UNITY_PASSWORD"
# Store the exit code from the verify command
UNITY_EXIT_CODE=$?
elif [[ -n "$UNITY_LICENSING_SERVER" ]]; then elif [[ -n "$UNITY_LICENSING_SERVER" ]]; then
# #
@ -84,13 +97,10 @@ else
# #
echo "License activation strategy could not be determined." echo "License activation strategy could not be determined."
echo "" echo ""
echo "Visit https://game.ci/docs/github/activation for more" echo "Visit https://game.ci/docs/github/getting-started for more"
echo "details on how to set up one of the possible activation strategies." echo "details on how to set up one of the possible activation strategies."
echo "::error ::No valid license activation strategy could be determined. Make sure to provide UNITY_EMAIL, UNITY_PASSWORD, and either a UNITY_SERIAL \ # Immediately exit as no UNITY_EXIT_CODE can be derrived.
or UNITY_LICENSE. Otherwise please use UNITY_LICENSING_SERVER. See more info at https://game.ci/docs/github/activation"
# Immediately exit as no UNITY_EXIT_CODE can be derived.
exit 1; exit 1;
fi fi
@ -105,6 +115,8 @@ else
# Activation failed so exit with the code from the license verification step # Activation failed so exit with the code from the license verification step
echo "Unclassified error occured while trying to activate license." echo "Unclassified error occured while trying to activate license."
echo "Exit code was: $UNITY_EXIT_CODE" echo "Exit code was: $UNITY_EXIT_CODE"
echo "::error ::There was an error while trying to activate the Unity license."
exit $UNITY_EXIT_CODE exit $UNITY_EXIT_CODE
fi fi
# Return to previous working directory
popd

View File

@ -19,22 +19,6 @@ echo "Using build name \"$BUILD_NAME\"."
echo "Using build target \"$BUILD_TARGET\"." echo "Using build target \"$BUILD_TARGET\"."
#
# Display the build profile
#
if [ -z "$BUILD_PROFILE" ]; then
# User has not provided a build profile
#
echo "Doing a default \"$BUILD_TARGET\" platform build."
#
else
# User has provided a path to a build profile `.asset` file
#
echo "Using build profile \"$BUILD_PROFILE\" relative to \"$UNITY_PROJECT_PATH\"."
#
fi
# #
# Display build path and file # Display build path and file
# #
@ -78,6 +62,19 @@ else
# #
fi fi
#
# Prepare Android SDK, if needed
#
if [[ "$BUILD_TARGET" == "Android" && -n "$ANDROID_SDK_MANAGER_PARAMETERS" ]]; then
echo "Updating Android SDK with parameters: $ANDROID_SDK_MANAGER_PARAMETERS"
export JAVA_HOME="$(awk -F'=' '/JAVA_HOME=/{print $2}' /usr/bin/unity-editor.d/*)"
"$(awk -F'=' '/ANDROID_HOME=/{print $2}' /usr/bin/unity-editor.d/*)/tools/bin/sdkmanager" "$ANDROID_SDK_MANAGER_PARAMETERS"
echo "Updated Android SDK."
else
echo "Not updating Android SDK."
fi
# #
# Pre-build debug information # Pre-build debug information
# #
@ -122,13 +119,12 @@ echo ""
unity-editor \ unity-editor \
-logfile /dev/stdout \ -logfile /dev/stdout \
$( [ "${MANUAL_EXIT}" == "true" ] || echo "-quit" ) \ -quit \
-customBuildName "$BUILD_NAME" \ -customBuildName "$BUILD_NAME" \
-projectPath "$UNITY_PROJECT_PATH" \ -projectPath "$UNITY_PROJECT_PATH" \
-buildTarget "$BUILD_TARGET" \ -buildTarget "$BUILD_TARGET" \
-customBuildTarget "$BUILD_TARGET" \ -customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \ -customBuildPath "$CUSTOM_BUILD_PATH" \
-customBuildProfile "$BUILD_PROFILE" \
-executeMethod "$BUILD_METHOD" \ -executeMethod "$BUILD_METHOD" \
-buildVersion "$VERSION" \ -buildVersion "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \ -androidVersionCode "$ANDROID_VERSION_CODE" \

View File

@ -1,6 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
if [[ -n "$UNITY_LICENSING_SERVER" ]]; then # Run in ACTIVATE_LICENSE_PATH directory
echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
pushd "$ACTIVATE_LICENSE_PATH"
if [[ -n "$UNITY_LICENSING_SERVER" ]]; then #
# #
# Return any floating license used. # Return any floating license used.
# #
@ -8,15 +13,15 @@ if [[ -n "$UNITY_LICENSING_SERVER" ]]; then
/opt/unity/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client --return-floating "$FLOATING_LICENSE" /opt/unity/Editor/Data/Resources/Licensing/Client/Unity.Licensing.Client --return-floating "$FLOATING_LICENSE"
elif [[ -n "$UNITY_SERIAL" ]]; then elif [[ -n "$UNITY_SERIAL" ]]; then
# #
# SERIAL LICENSE MODE # PROFESSIONAL (SERIAL) LICENSE MODE
# #
# This will return the license that is currently in use. # This will return the license that is currently in use.
# #
unity-editor \ unity-editor \
-logFile /dev/stdout \ -logFile /dev/stdout \
-quit \ -quit \
-returnlicense \ -returnlicense
-username "$UNITY_EMAIL" \
-password "$UNITY_PASSWORD" \
-projectPath "/BlankProject"
fi fi
# Return to previous working directory
popd

View File

@ -1,48 +0,0 @@
#!/usr/bin/env bash
#
# Run steps
#
source /steps/set_extra_git_configs.sh
source /steps/set_gitcredential.sh
if [ "$SKIP_ACTIVATION" != "true" ]; then
source /steps/activate.sh
# If we didn't activate successfully, exit with the exit code from the activation step.
if [[ $UNITY_EXIT_CODE -ne 0 ]]; then
exit $UNITY_EXIT_CODE
fi
else
echo "Skipping activation"
fi
source /steps/build.sh
if [ "$SKIP_ACTIVATION" != "true" ]; then
source /steps/return_license.sh
fi
#
# Instructions for debugging
#
if [[ $BUILD_EXIT_CODE -gt 0 ]]; then
echo ""
echo "###########################"
echo "# Failure #"
echo "###########################"
echo ""
echo "Please note that the exit code is not very descriptive."
echo "Most likely it will not help you solve the issue."
echo ""
echo "To find the reason for failure: please search for errors in the log above and check for annotations in the summary view."
echo ""
fi;
#
# Exit with code from the build step.
#
# Exiting su
exit $BUILD_EXIT_CODE

View File

@ -1,93 +1,7 @@
# Activates Unity # Activates Unity
& "C:\Program Files\Unity\Hub\Editor\$Env:UNITY_VERSION\Editor\Unity.exe" -batchmode -quit -nographics `
Write-Output "" -username $Env:UNITY_EMAIL `
Write-Output "###########################" -password $Env:UNITY_PASSWORD `
Write-Output "# Activating #" -serial $Env:UNITY_SERIAL `
Write-Output "###########################" -projectPath "c:/BlankProject" `
Write-Output "" -logfile | Out-Host
if ( ($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($null -ne ${env:UNITY_PASSWORD}) )
{
#
# SERIAL LICENSE MODE
#
# This will activate unity, using the serial activation process.
#
Write-Output "Requesting activation"
$ACTIVATION_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH/Editor/Unity.exe" `
-NoNewWindow `
-PassThru `
-ArgumentList "-batchmode `
-quit `
-nographics `
-username $Env:UNITY_EMAIL `
-password $Env:UNITY_PASSWORD `
-serial $Env:UNITY_SERIAL `
-projectPath c:/BlankProject `
-logfile -"
# Cache the handle so exit code works properly
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
$unityHandle = $ACTIVATION_OUTPUT.Handle
while ($true) {
if ($ACTIVATION_OUTPUT.HasExited) {
$ACTIVATION_EXIT_CODE = $ACTIVATION_OUTPUT.ExitCode
# Display results
if ($ACTIVATION_EXIT_CODE -eq 0)
{
Write-Output "Activation Succeeded"
} else
{
Write-Output "Activation failed, with exit code $ACTIVATION_EXIT_CODE"
}
break
}
Start-Sleep -Seconds 3
}
}
elseif( ($null -ne ${env:UNITY_LICENSING_SERVER}))
{
#
# Custom Unity License Server
#
Write-Output "Adding licensing server config"
$ACTIVATION_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH\Editor\Data\Resources\Licensing\Client\Unity.Licensing.Client.exe" `
-ArgumentList "--acquire-floating" `
-NoNewWindow `
-PassThru `
-Wait `
-RedirectStandardOutput "license.txt"
$PARSEDFILE = (Get-Content "license.txt" | Select-String -AllMatches -Pattern '\".*?\"' | ForEach-Object { $_.Matches.Value }) -replace '"'
$env:FLOATING_LICENSE = $PARSEDFILE[1]
$FLOATING_LICENSE_TIMEOUT = $PARSEDFILE[3]
Write-Output "Acquired floating license: ""$env:FLOATING_LICENSE"" with timeout $FLOATING_LICENSE_TIMEOUT"
# Store the exit code from the verify command
$ACTIVATION_EXIT_CODE = $ACTIVATION_OUTPUT.ExitCode
}
else
{
#
# NO LICENSE ACTIVATION STRATEGY MATCHED
#
# This will exit since no activation strategies could be matched.
#
Write-Output "License activation strategy could not be determined."
Write-Output ""
Write-Output "Visit https://game.ci/docs/github/activation for more"
Write-Output "details on how to set up one of the possible activation strategies."
Write-Output "::error ::No valid license activation strategy could be determined. Make sure to provide UNITY_EMAIL, UNITY_PASSWORD, and either a UNITY_SERIAL \
or UNITY_LICENSE. See more info at https://game.ci/docs/github/activation"
$ACTIVATION_EXIT_CODE = 1;
}

View File

@ -16,25 +16,6 @@ Write-Output "$('Using build name "')$($Env:BUILD_NAME)$('".')"
Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')" Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')"
#
# Display the build profile
#
if ($Env:BUILD_PROFILE)
{
# User has provided a path to a build profile `.asset` file
#
Write-Output "$('Using build profile "')$($Env:BUILD_PROFILE)$('" relative to "')$($Env:UNITY_PROJECT_PATH)$('".')"
#
}
else
{
# User has not provided a build profile
#
Write-Output "$('Doing a default "')$($Env:BUILD_TARGET)$('" platform build.')"
#
}
# #
# Display build path and file # Display build path and file
# #
@ -85,26 +66,6 @@ else
Get-ChildItem -Path $Env:UNITY_PROJECT_PATH\Assets\Editor -Recurse Get-ChildItem -Path $Env:UNITY_PROJECT_PATH\Assets\Editor -Recurse
} }
if ( "$Env:BUILD_TARGET" -eq "Android" -and -not ([string]::IsNullOrEmpty("$Env:ANDROID_KEYSTORE_BASE64")) )
{
Write-Output "Creating Android keystore."
# Write to consistent location as Windows Unity seems to have issues with pwd and can't find the keystore
$keystorePath = "C:/android.keystore"
[System.IO.File]::WriteAllBytes($keystorePath, [System.Convert]::FromBase64String($Env:ANDROID_KEYSTORE_BASE64))
# Ensure the project settings are pointed at the correct path
$unitySettingsPath = "$Env:UNITY_PROJECT_PATH\ProjectSettings\ProjectSettings.asset"
$fileContent = Get-Content -Path "$unitySettingsPath"
$fileContent = $fileContent -replace "AndroidKeystoreName:\s+.*", "AndroidKeystoreName: $keystorePath"
$fileContent | Set-Content -Path "$unitySettingsPath"
Write-Output "Created Android keystore."
}
else {
Write-Output "Not creating Android keystore."
}
# #
# Pre-build debug information # Pre-build debug information
# #
@ -148,78 +109,51 @@ Write-Output "# Building project #"
Write-Output "###########################" Write-Output "###########################"
Write-Output "" Write-Output ""
$unityGraphics = "-nographics"
if ($LLVMPIPE_INSTALLED -eq "true")
{
$unityGraphics = "-force-opengl"
}
# If $Env:CUSTOM_PARAMETERS contains spaces and is passed directly on the command line to Unity, powershell will wrap it # If $Env:CUSTOM_PARAMETERS contains spaces and is passed directly on the command line to Unity, powershell will wrap it
# in double quotes. To avoid this, parse $Env:CUSTOM_PARAMETERS into an array, while respecting any quotations within the string. # in double quotes. To avoid this, parse $Env:CUSTOM_PARAMETERS into an array, while respecting any quotations within the string.
$_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUSTOM_PARAMETERS) $_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUSTOM_PARAMETERS)
$unityArgs = @(
"-quit",
"-batchmode",
$unityGraphics,
"-silent-crashes",
"-customBuildName", "`"$Env:BUILD_NAME`"",
"-projectPath", "`"$Env:UNITY_PROJECT_PATH`"",
"-executeMethod", "`"$Env:BUILD_METHOD`"",
"-buildTarget", "`"$Env:BUILD_TARGET`"",
"-customBuildTarget", "`"$Env:BUILD_TARGET`"",
"-customBuildPath", "`"$Env:CUSTOM_BUILD_PATH`"",
"-customBuildProfile", "`"$Env:BUILD_PROFILE`"",
"-buildVersion", "`"$Env:VERSION`"",
"-androidVersionCode", "`"$Env:ANDROID_VERSION_CODE`"",
"-androidKeystorePass", "`"$Env:ANDROID_KEYSTORE_PASS`"",
"-androidKeyaliasName", "`"$Env:ANDROID_KEYALIAS_NAME`"",
"-androidKeyaliasPass", "`"$Env:ANDROID_KEYALIAS_PASS`"",
"-androidTargetSdkVersion", "`"$Env:ANDROID_TARGET_SDK_VERSION`"",
"-androidExportType", "`"$Env:ANDROID_EXPORT_TYPE`"",
"-androidSymbolType", "`"$Env:ANDROID_SYMBOL_TYPE`"",
"-logfile", "-"
) + $customParametersArray
# Remove null items as that will fail the Start-Process call & "C:\Program Files\Unity\Hub\Editor\$Env:UNITY_VERSION\Editor\Unity.exe" -quit -batchmode -nographics `
$unityArgs = $unityArgs | Where-Object { $_ -ne $null } -projectPath $Env:UNITY_PROJECT_PATH `
-executeMethod $Env:BUILD_METHOD `
-buildTarget $Env:BUILD_TARGET `
-customBuildTarget $Env:BUILD_TARGET `
-customBuildPath $Env:CUSTOM_BUILD_PATH `
-buildVersion $Env:VERSION `
-androidVersionCode $Env:ANDROID_VERSION_CODE `
-androidKeystoreName $Env:ANDROID_KEYSTORE_NAME `
-androidKeystorePass $Env:ANDROID_KEYSTORE_PASS `
-androidKeyaliasName $Env:ANDROID_KEYALIAS_NAME `
-androidKeyaliasPass $Env:ANDROID_KEYALIAS_PASS `
-androidTargetSdkVersion $Env:ANDROID_TARGET_SDK_VERSION `
-androidExportType $Env:ANDROID_EXPORT_TYPE `
-androidSymbolType $Env:ANDROID_SYMBOL_TYPE `
$customParametersArray `
-logfile | Out-Host
$unityProcess = Start-Process -FilePath "$Env:UNITY_PATH/Editor/Unity.exe" ` # Catch exit code
-ArgumentList $unityArgs ` $Env:BUILD_EXIT_CODE=$LastExitCode
-PassThru `
-NoNewWindow
# Cache the handle so exit code works properly # Display results
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait if ($Env:BUILD_EXIT_CODE -eq 0)
$unityHandle = $unityProcess.Handle {
Write-Output "Build Succeeded!"
while ($true) { } else
if ($unityProcess.HasExited) { {
Start-Sleep -Seconds 3 Write-Output "$('Build failed, with exit code ')$($Env:BUILD_EXIT_CODE)$('"')"
Get-Process
$BUILD_EXIT_CODE = $unityProcess.ExitCode
# Display results
if ($BUILD_EXIT_CODE -eq 0)
{
Write-Output "Build Succeeded!!"
} else
{
Write-Output "Build failed, with exit code $BUILD_EXIT_CODE"
}
Write-Output ""
Write-Output "###########################"
Write-Output "# Build output #"
Write-Output "###########################"
Write-Output ""
Get-ChildItem $Env:BUILD_PATH_FULL
Write-Output ""
break
}
Start-Sleep -Seconds 3
} }
# TODO: Determine if we need to set permissions on any files
#
# Results
#
Write-Output ""
Write-Output "###########################"
Write-Output "# Build output #"
Write-Output "###########################"
Write-Output ""
Get-ChildItem $Env:BUILD_PATH_FULL
Write-Output ""

View File

@ -1,52 +1,18 @@
Get-Process
# Copy .upmconfig.toml if it exists
if (Test-Path "C:\githubhome\.upmconfig.toml") {
Write-Host "Copying .upmconfig.toml to $Env:USERPROFILE\.upmconfig.toml"
Copy-Item -Path "C:\githubhome\.upmconfig.toml" -Destination "$Env:USERPROFILE\.upmconfig.toml" -Force
} else {
Write-Host "No .upmconfig.toml found at C:\githubhome"
}
# Import any necessary registry keys, ie: location of windows 10 sdk # Import any necessary registry keys, ie: location of windows 10 sdk
# No guarantee that there will be any necessary registry keys, ie: tvOS # No guarantee that there will be any necessary registry keys, ie: tvOS
Get-ChildItem -Path c:\regkeys -File | ForEach-Object { reg import $_.fullname } Get-ChildItem -Path c:\regkeys -File | Foreach {reg import $_.fullname}
# Register the Visual Studio installation so Unity can find it # Register the Visual Studio installation so Unity can find it
regsvr32 C:\ProgramData\Microsoft\VisualStudio\Setup\x64\Microsoft.VisualStudio.Setup.Configuration.Native.dll regsvr32 C:\ProgramData\Microsoft\VisualStudio\Setup\x64\Microsoft.VisualStudio.Setup.Configuration.Native.dll
# Kill the regsvr process
Get-Process -Name regsvr32 | ForEach-Object { Stop-Process -Id $_.Id -Force }
# Setup Git Credentials # Setup Git Credentials
. "c:\steps\set_gitcredential.ps1" & "c:\steps\set_gitcredential.ps1"
if ($env:ENABLE_GPU -eq "true") {
# Install LLVMpipe software graphics driver
. "c:\steps\install_llvmpipe.ps1"
}
# Activate Unity # Activate Unity
if ($env:SKIP_ACTIVATION -ne "true") { & "c:\steps\activate.ps1"
. "c:\steps\activate.ps1"
# If we didn't activate successfully, exit with the exit code from the activation step.
if ($ACTIVATION_EXIT_CODE -ne 0) {
exit $ACTIVATION_EXIT_CODE
}
}
else {
Write-Host "Skipping activation"
}
# Build the project # Build the project
. "c:\steps\build.ps1" & "c:\steps\build.ps1"
# Free the seat for the activated license # Free the seat for the activated license
if ($env:SKIP_ACTIVATION -ne "true") { & "c:\steps\return_license.ps1"
. "c:\steps\return_license.ps1"
}
Get-Process
exit $BUILD_EXIT_CODE

View File

@ -1,56 +0,0 @@
$Private:repo = "mmozeiko/build-mesa"
$Private:downloadPath = "$Env:TEMP\mesa.zip"
$Private:extractPath = "$Env:TEMP\mesa"
$Private:destinationPath = "$Env:UNITY_PATH\Editor\"
$Private:version = "25.1.0"
$LLVMPIPE_INSTALLED = "false"
try {
# Get the release info from GitHub API (version fixed to decrease probability of breakage)
$releaseUrl = "https://api.github.com/repos/$repo/releases/tags/$version"
$release = Invoke-RestMethod -Uri $releaseUrl -Headers @{ "User-Agent" = "PowerShell" }
# Get the download URL for the zip asset
$zipUrl = $release.assets | Where-Object { $_.name -like "mesa-llvmpipe-x64*.zip" } | Select-Object -First 1 -ExpandProperty browser_download_url
if (-not $zipUrl) {
throw "No zip file found in the latest release."
}
# Download the zip file
Write-Host "Downloading $zipUrl..."
Invoke-WebRequest -Uri $zipUrl -OutFile $downloadPath
# Create extraction directory if it doesn't exist
if (-not (Test-Path $extractPath)) {
New-Item -ItemType Directory -Path $extractPath | Out-Null
}
# Extract the zip file
Write-Host "Extracting $downloadPath to $extractPath..."
Expand-Archive -Path $downloadPath -DestinationPath $extractPath -Force
# Create destination directory if it doesn't exist
if (-not (Test-Path $destinationPath)) {
New-Item -ItemType Directory -Path $destinationPath | Out-Null
}
# Copy extracted files to destination
Write-Host "Copying files to $destinationPath..."
Copy-Item -Path "$extractPath\*" -Destination $destinationPath -Recurse -Force
Write-Host "Successfully downloaded, extracted, and copied Mesa files to $destinationPath"
$LLVMPIPE_INSTALLED = "true"
} catch {
Write-Error "An error occurred: $_"
} finally {
# Clean up temporary files
if (Test-Path $downloadPath) {
Remove-Item $downloadPath -Force
}
if (Test-Path $extractPath) {
Remove-Item $extractPath -Recurse -Force
}
}

View File

@ -1,61 +1,7 @@
# Return the active Unity license # Return the active Unity license
& "C:\Program Files\Unity\Hub\Editor\$Env:UNITY_VERSION\Editor\Unity.exe" -batchmode -quit -nographics `
Write-Output "" -username $Env:UNITY_EMAIL `
Write-Output "###########################" -password $Env:UNITY_PASSWORD `
Write-Output "# Return License #" -returnlicense `
Write-Output "###########################" -projectPath "c:/BlankProject" `
Write-Output "" -logfile | Out-Host
if (($null -ne ${env:UNITY_LICENSING_SERVER}))
{
Write-Output "Returning floating license: ""$env:FLOATING_LICENSE"""
Start-Process -FilePath "$Env:UNITY_PATH\Editor\Data\Resources\Licensing\Client\Unity.Licensing.Client.exe" `
-ArgumentList "--return-floating ""$env:FLOATING_LICENSE"" " `
-NoNewWindow `
-Wait
}
elseif (($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($null -ne ${env:UNITY_PASSWORD}))
{
#
# SERIAL LICENSE MODE
#
# This will return the license that is currently in use.
#
$RETURN_LICENSE_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH/Editor/Unity.exe" `
-NoNewWindow `
-PassThru `
-ArgumentList "-batchmode `
-quit `
-nographics `
-username $Env:UNITY_EMAIL `
-password $Env:UNITY_PASSWORD `
-returnlicense `
-projectPath c:/BlankProject `
-logfile -"
# Cache the handle so exit code works properly
# https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait
$unityHandle = $RETURN_LICENSE_OUTPUT.Handle
while ($true) {
if ($RETURN_LICENSE_OUTPUT.HasExited) {
$RETURN_LICENSE_EXIT_CODE = $RETURN_LICENSE_OUTPUT.ExitCode
# Display results
if ($RETURN_LICENSE_EXIT_CODE -eq 0)
{
Write-Output "License Return Succeeded"
} else
{
Write-Output "License Return failed, with exit code $RETURN_LICENSE_EXIT_CODE"
Write-Output "::warning ::License Return failed! If this is a Pro License you might need to manually `
free the seat in your Unity admin panel or you might run out of seats to activate with."
}
break
}
Start-Sleep -Seconds 3
}
}

View File

@ -1,16 +1,16 @@
if ($null -eq ${env:GIT_PRIVATE_TOKEN}) { if ([string]::IsNullOrEmpty($env:GIT_PRIVATE_TOKEN)) {
Write-Host "GIT_PRIVATE_TOKEN unset skipping" Write-Host "GIT_PRIVATE_TOKEN unset skipping"
} }
else { else {
Write-Host "GIT_PRIVATE_TOKEN is set configuring git credentials" Write-Host "GIT_PRIVATE_TOKEN is set configuring git credentials"
git config --global credential.helper store git config --global credential.helper store
git config --global --replace-all url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/" git config --global --replace-all "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
git config --global --add url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com" git config --global --add "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com"
git config --global --add url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "https://github.com/" git config --global --add "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "https://github.com/"
git config --global url."https://ssh:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/" git config --global "url.https://ssh:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
git config --global url."https://git:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com:" git config --global "url.https://git:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com:"
} }
Write-Host "---------- git config --list -------------" Write-Host "---------- git config --list -------------"

View File

@ -7,14 +7,14 @@
"author": "Webber <webber@takken.io>", "author": "Webber <webber@takken.io>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"prepare": "lefthook install", "prepare": "lefthook install && npx husky uninstall -y",
"build": "yarn && tsc && ncc build lib --source-map --license licenses.txt", "build": "yarn && tsc && ncc build lib --source-map --license licenses.txt",
"lint": "prettier --check \"src/**/*.{js,ts}\" && eslint src/**/*.ts", "lint": "prettier --check \"src/**/*.{js,ts}\" && eslint src/**/*.ts",
"format": "prettier --write \"src/**/*.{js,ts}\"", "format": "prettier --write \"src/**/*.{js,ts}\"",
"cli": "yarn ts-node src/index.ts -m cli", "cli": "yarn ts-node src/index.ts -m cli",
"gcp-secrets-tests": "cross-env providerStrategy=aws cloudRunnerTests=true inputPullCommand=\"gcp-secret-manager\" populateOverride=true pullInputList=UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD yarn test -i -t \"cloud runner\"", "gcp-secrets-tests": "cross-env providerStrategy=aws cloudRunnerTests=true readInputOverrideCommand=\"gcp-secret-manager\" populateOverride=true readInputFromOverrideList=UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD yarn test -i -t \"cloud runner\"",
"gcp-secrets-cli": "cross-env cloudRunnerTests=true USE_IL2CPP=false inputPullCommand=\"gcp-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --pullInputList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD", "gcp-secrets-cli": "cross-env cloudRunnerTests=true readInputOverrideCommand=\"gcp-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --readInputFromOverrideList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
"aws-secrets-cli": "cross-env cloudRunnerTests=true inputPullCommand=\"aws-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --pullInputList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD", "aws-secrets-cli": "cross-env cloudRunnerTests=true readInputOverrideCommand=\"aws-secret-manager\" yarn ts-node src/index.ts -m cli --populateOverride true --readInputFromOverrideList UNITY_EMAIL,UNITY_SERIAL,UNITY_PASSWORD",
"cli-aws": "cross-env providerStrategy=aws yarn run test-cli", "cli-aws": "cross-env providerStrategy=aws yarn run test-cli",
"cli-k8s": "cross-env providerStrategy=k8s yarn run test-cli", "cli-k8s": "cross-env providerStrategy=k8s yarn run test-cli",
"test-cli": "cross-env cloudRunnerTests=true yarn ts-node src/index.ts -m cli --projectPath test-project", "test-cli": "cross-env cloudRunnerTests=true yarn ts-node src/index.ts -m cli --projectPath test-project",
@ -28,33 +28,27 @@
"node": ">=18.x" "node": ">=18.x"
}, },
"dependencies": { "dependencies": {
"@actions/cache": "^4.0.0", "@actions/cache": "^3.1.3",
"@actions/core": "^1.11.1", "@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.0",
"@actions/github": "^6.0.0", "@actions/github": "^5.0.0",
"@aws-sdk/client-cloudformation": "^3.777.0",
"@aws-sdk/client-cloudwatch-logs": "^3.777.0",
"@aws-sdk/client-ecs": "^3.778.0",
"@aws-sdk/client-kinesis": "^3.777.0",
"@aws-sdk/client-s3": "^3.779.0",
"@kubernetes/client-node": "^0.16.3", "@kubernetes/client-node": "^0.16.3",
"@octokit/core": "^5.1.0", "@octokit/core": "^3.5.1",
"async-wait-until": "^2.0.12", "async-wait-until": "^2.0.12",
"aws-sdk": "^2.1081.0", "aws-sdk": "^2.1081.0",
"base-64": "^1.0.0", "base-64": "^1.0.0",
"commander": "^9.0.0", "commander": "^9.0.0",
"commander-ts": "^0.2.0", "commander-ts": "^0.2.0",
"kubernetes-client": "^9.0.0", "kubernetes-client": "^9.0.0",
"md5": "^2.3.0",
"nanoid": "^3.3.1", "nanoid": "^3.3.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"semver": "^7.5.2", "semver": "^7.5.2",
"ts-md5": "^1.3.1",
"unity-changeset": "^2.0.0", "unity-changeset": "^2.0.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"yaml": "^2.2.2" "yaml": "^2.2.2"
}, },
"devDependencies": { "devDependencies": {
"@evilmartians/lefthook": "^1.2.9",
"@types/base-64": "^1.0.0", "@types/base-64": "^1.0.0",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/node": "^17.0.23", "@types/node": "^17.0.23",
@ -73,10 +67,9 @@
"jest-circus": "^27.5.1", "jest-circus": "^27.5.1",
"jest-fail-on-console": "^3.0.2", "jest-fail-on-console": "^3.0.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lefthook": "^1.6.1",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"ts-jest": "^27.1.3", "ts-jest": "^27.1.3",
"ts-node": "10.8.1", "ts-node": "10.4.0",
"typescript": "4.7.4", "typescript": "4.7.4",
"yarn-audit-fix": "^9.3.8" "yarn-audit-fix": "^9.3.8"
}, },
@ -84,4 +77,4 @@
"node": "20.5.1", "node": "20.5.1",
"yarn": "1.22.19" "yarn": "1.22.19"
} }
} }

View File

@ -1,15 +0,0 @@
echo "installing game-ci cli"
if exist %UserProfile%\AppData\LocalLow\game-ci\ (
echo Installed Updating
git -C %UserProfile%\AppData\LocalLow\game-ci\ fetch
git -C %UserProfile%\AppData\LocalLow\game-ci\ reset --hard
git -C %UserProfile%\AppData\LocalLow\game-ci\ pull
git -C %UserProfile%\AppData\LocalLow\game-ci\ branch
) else (
echo Not Installed Downloading...
mkdir %UserProfile%\AppData\LocalLow\game-ci\
git clone https://github.com/game-ci/unity-builder %UserProfile%\AppData\LocalLow\game-ci\
)
call yarn --cwd %UserProfile%\AppData\LocalLow\game-ci\ install
call yarn --cwd %UserProfile%\AppData\LocalLow\game-ci\ run gcp-secrets-cli %* --projectPath %cd% --awsStackName game-ci-cli

View File

@ -19,35 +19,23 @@ async function runMain() {
const buildParameters = await BuildParameters.create(); const buildParameters = await BuildParameters.create();
const baseImage = new ImageTag(buildParameters); const baseImage = new ImageTag(buildParameters);
let exitCode = -1;
if (buildParameters.providerStrategy === 'local') { if (buildParameters.providerStrategy === 'local') {
core.info('Building locally'); core.info('Building locally');
await PlatformSetup.setup(buildParameters, actionFolder); await PlatformSetup.setup(buildParameters, actionFolder);
exitCode = if (process.platform === 'darwin') {
process.platform === 'darwin' MacBuilder.run(actionFolder);
? await MacBuilder.run(actionFolder) } else {
: await Docker.run(baseImage.toString(), { await Docker.run(baseImage.toString(), { workspace, actionFolder, ...buildParameters });
workspace, }
actionFolder,
...buildParameters,
});
} else { } else {
await CloudRunner.run(buildParameters, baseImage.toString()); await CloudRunner.run(buildParameters, baseImage.toString());
exitCode = 0;
} }
// Set output // Set output
await Output.setBuildVersion(buildParameters.buildVersion); await Output.setBuildVersion(buildParameters.buildVersion);
await Output.setAndroidVersionCode(buildParameters.androidVersionCode); await Output.setAndroidVersionCode(buildParameters.androidVersionCode);
await Output.setEngineExitCode(exitCode);
if (exitCode !== 0) {
core.setFailed(`Build failed with exit code ${exitCode}`);
}
} catch (error) { } catch (error) {
core.setFailed((error as Error).message); core.setFailed((error as Error).message);
} }
} }
runMain(); runMain();

View File

@ -71,12 +71,6 @@ describe('BuildParameters', () => {
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue })); await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ projectPath: mockValue }));
}); });
it('returns the build profile', async () => {
const mockValue = 'path/to/build_profile.asset';
jest.spyOn(Input, 'buildProfile', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildProfile: mockValue }));
});
it('returns the build name', async () => { it('returns the build name', async () => {
const mockValue = 'someBuildName'; const mockValue = 'someBuildName';
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue); jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue);

View File

@ -12,7 +12,6 @@ import { Cli } from './cli/cli';
import GitHub from './github'; import GitHub from './github';
import CloudRunnerOptions from './cloud-runner/options/cloud-runner-options'; import CloudRunnerOptions from './cloud-runner/options/cloud-runner-options';
import CloudRunner from './cloud-runner/cloud-runner'; import CloudRunner from './cloud-runner/cloud-runner';
import * as core from '@actions/core';
class BuildParameters { class BuildParameters {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
@ -22,18 +21,14 @@ class BuildParameters {
public customImage!: string; public customImage!: string;
public unitySerial!: string; public unitySerial!: string;
public unityLicensingServer!: string; public unityLicensingServer!: string;
public skipActivation!: string;
public runnerTempPath!: string; public runnerTempPath!: string;
public targetPlatform!: string; public targetPlatform!: string;
public projectPath!: string; public projectPath!: string;
public buildProfile!: string;
public buildName!: string; public buildName!: string;
public buildPath!: string; public buildPath!: string;
public buildFile!: string; public buildFile!: string;
public buildMethod!: string; public buildMethod!: string;
public buildVersion!: string; public buildVersion!: string;
public manualExit!: boolean;
public enableGpu!: boolean;
public androidVersionCode!: string; public androidVersionCode!: string;
public androidKeystoreName!: string; public androidKeystoreName!: string;
public androidKeystoreBase64!: string; public androidKeystoreBase64!: string;
@ -44,11 +39,6 @@ class BuildParameters {
public androidSdkManagerParameters!: string; public androidSdkManagerParameters!: string;
public androidExportType!: string; public androidExportType!: string;
public androidSymbolType!: string; public androidSymbolType!: string;
public dockerCpuLimit!: string;
public dockerMemoryLimit!: string;
public dockerIsolationMode!: string;
public containerRegistryRepository!: string;
public containerRegistryImageVersion!: string;
public customParameters!: string; public customParameters!: string;
public sshAgent!: string; public sshAgent!: string;
@ -62,7 +52,6 @@ class BuildParameters {
public kubeVolumeSize!: string; public kubeVolumeSize!: string;
public kubeVolume!: string; public kubeVolume!: string;
public kubeStorageClass!: string; public kubeStorageClass!: string;
public runAsHostUser!: string;
public chownFilesTo!: string; public chownFilesTo!: string;
public commandHooks!: string; public commandHooks!: string;
public pullInputList!: string[]; public pullInputList!: string[];
@ -126,12 +115,10 @@ class BuildParameters {
if (!Input.unitySerial && GitHub.githubInputEnabled) { if (!Input.unitySerial && GitHub.githubInputEnabled) {
// No serial was present, so it is a personal license that we need to convert // No serial was present, so it is a personal license that we need to convert
if (!Input.unityLicense) { if (!Input.unityLicense) {
throw new Error( throw new Error(`Missing Unity License File and no Serial was found. If this
`Missing Unity License File and no Serial was found. If this
is a personal license, make sure to follow the activation is a personal license, make sure to follow the activation
steps and set the UNITY_LICENSE GitHub secret or enter a Unity steps and set the UNITY_LICENSE GitHub secret or enter a Unity
serial number inside the UNITY_SERIAL GitHub secret.`, serial number inside the UNITY_SERIAL GitHub secret.`);
);
} }
unitySerial = this.getSerialFromLicenseFile(Input.unityLicense); unitySerial = this.getSerialFromLicenseFile(Input.unityLicense);
} else { } else {
@ -139,28 +126,19 @@ class BuildParameters {
} }
} }
if (unitySerial !== undefined && unitySerial.length === 27) {
core.setSecret(unitySerial);
core.setSecret(`${unitySerial.slice(0, -4)}XXXX`);
}
return { return {
editorVersion, editorVersion,
customImage: Input.customImage, customImage: Input.customImage,
unitySerial, unitySerial,
unityLicensingServer: Input.unityLicensingServer, unityLicensingServer: Input.unityLicensingServer,
skipActivation: Input.skipActivation,
runnerTempPath: Input.runnerTempPath, runnerTempPath: Input.runnerTempPath,
targetPlatform: Input.targetPlatform, targetPlatform: Input.targetPlatform,
projectPath: Input.projectPath, projectPath: Input.projectPath,
buildProfile: Input.buildProfile,
buildName: Input.buildName, buildName: Input.buildName,
buildPath: `${Input.buildsPath}/${Input.targetPlatform}`, buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
buildFile, buildFile,
buildMethod: Input.buildMethod, buildMethod: Input.buildMethod,
buildVersion, buildVersion,
manualExit: Input.manualExit,
enableGpu: Input.enableGpu,
androidVersionCode, androidVersionCode,
androidKeystoreName: Input.androidKeystoreName, androidKeystoreName: Input.androidKeystoreName,
androidKeystoreBase64: Input.androidKeystoreBase64, androidKeystoreBase64: Input.androidKeystoreBase64,
@ -174,14 +152,8 @@ class BuildParameters {
customParameters: Input.customParameters, customParameters: Input.customParameters,
sshAgent: Input.sshAgent, sshAgent: Input.sshAgent,
sshPublicKeysDirectoryPath: Input.sshPublicKeysDirectoryPath, sshPublicKeysDirectoryPath: Input.sshPublicKeysDirectoryPath,
gitPrivateToken: Input.gitPrivateToken ?? (await GithubCliReader.GetGitHubAuthToken()), gitPrivateToken: Input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
runAsHostUser: Input.runAsHostUser,
chownFilesTo: Input.chownFilesTo, chownFilesTo: Input.chownFilesTo,
dockerCpuLimit: Input.dockerCpuLimit,
dockerMemoryLimit: Input.dockerMemoryLimit,
dockerIsolationMode: Input.dockerIsolationMode,
containerRegistryRepository: Input.containerRegistryRepository,
containerRegistryImageVersion: Input.containerRegistryImageVersion,
providerStrategy: CloudRunnerOptions.providerStrategy, providerStrategy: CloudRunnerOptions.providerStrategy,
buildPlatform: CloudRunnerOptions.buildPlatform, buildPlatform: CloudRunnerOptions.buildPlatform,
kubeConfig: CloudRunnerOptions.kubeConfig, kubeConfig: CloudRunnerOptions.kubeConfig,
@ -196,7 +168,7 @@ class BuildParameters {
branch: Input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()), branch: Input.branch.replace('/head', '') || (await GitRepoReader.GetBranch()),
cloudRunnerBranch: CloudRunnerOptions.cloudRunnerBranch.split('/').reverse()[0], cloudRunnerBranch: CloudRunnerOptions.cloudRunnerBranch.split('/').reverse()[0],
cloudRunnerDebug: CloudRunnerOptions.cloudRunnerDebug, cloudRunnerDebug: CloudRunnerOptions.cloudRunnerDebug,
githubRepo: (Input.githubRepo ?? (await GitRepoReader.GetRemote())) || 'game-ci/unity-builder', githubRepo: Input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder',
isCliMode: Cli.isCliMode, isCliMode: Cli.isCliMode,
awsStackName: CloudRunnerOptions.awsStackName, awsStackName: CloudRunnerOptions.awsStackName,
gitSha: Input.gitSha, gitSha: Input.gitSha,

View File

@ -10,6 +10,8 @@ import { LfsHashing } from '../cloud-runner/services/utility/lfs-hashing';
import { RemoteClient } from '../cloud-runner/remote-client'; import { RemoteClient } from '../cloud-runner/remote-client';
import CloudRunnerOptionsReader from '../cloud-runner/options/cloud-runner-options-reader'; import CloudRunnerOptionsReader from '../cloud-runner/options/cloud-runner-options-reader';
import GitHub from '../github'; import GitHub from '../github';
import { CloudRunnerFolders } from '../cloud-runner/options/cloud-runner-folders';
import { CloudRunnerSystem } from '../cloud-runner/services/core/cloud-runner-system';
import { OptionValues } from 'commander'; import { OptionValues } from 'commander';
import { InputKey } from '../input'; import { InputKey } from '../input';
@ -52,7 +54,6 @@ export class Cli {
program.option('--cachePushTo <cachePushTo>', 'cache push to caching folder'); program.option('--cachePushTo <cachePushTo>', 'cache push to caching folder');
program.option('--artifactName <artifactName>', 'caching artifact name'); program.option('--artifactName <artifactName>', 'caching artifact name');
program.option('--select <select>', 'select a particular resource'); program.option('--select <select>', 'select a particular resource');
program.option('--logFile <logFile>', 'output to log file (log stream only)');
program.parse(process.argv); program.parse(process.argv);
Cli.options = program.opts(); Cli.options = program.opts();
@ -109,7 +110,7 @@ export class Cli {
const buildParameter = await BuildParameters.create(); const buildParameter = await BuildParameters.create();
const baseImage = new ImageTag(buildParameter); const baseImage = new ImageTag(buildParameter);
return (await CloudRunner.run(buildParameter, baseImage.toString())).BuildResults; return await CloudRunner.run(buildParameter, baseImage.toString());
} }
@CliFunction(`async-workflow`, `runs a cloud runner build`) @CliFunction(`async-workflow`, `runs a cloud runner build`)
@ -118,7 +119,7 @@ export class Cli {
const baseImage = new ImageTag(buildParameter); const baseImage = new ImageTag(buildParameter);
await CloudRunner.setup(buildParameter); await CloudRunner.setup(buildParameter);
return (await CloudRunner.run(buildParameter, baseImage.toString())).BuildResults; return await CloudRunner.run(buildParameter, baseImage.toString());
} }
@CliFunction(`checks-update`, `runs a cloud runner build`) @CliFunction(`checks-update`, `runs a cloud runner build`)
@ -172,4 +173,31 @@ export class Cli {
return await CloudRunner.Provider.watchWorkflow(); return await CloudRunner.Provider.watchWorkflow();
} }
@CliFunction(`remote-cli-post-build`, `runs a cloud runner build`)
public static async PostCLIBuild(): Promise<string> {
core.info(`Running POST build tasks`);
await Caching.PushToCache(
CloudRunnerFolders.ToLinuxFolder(`${CloudRunnerFolders.cacheFolderForCacheKeyFull}/Library`),
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.libraryFolderAbsolute),
`lib-${CloudRunner.buildParameters.buildGuid}`,
);
await Caching.PushToCache(
CloudRunnerFolders.ToLinuxFolder(`${CloudRunnerFolders.cacheFolderForCacheKeyFull}/build`),
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectBuildFolderAbsolute),
`build-${CloudRunner.buildParameters.buildGuid}`,
);
if (!BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters)) {
await CloudRunnerSystem.Run(
`rm -r ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)}`,
);
}
await RemoteClient.runCustomHookFiles(`after-build`);
return new Promise((result) => result(``));
}
} }

View File

@ -16,7 +16,6 @@ import LocalDockerCloudRunner from './providers/docker';
import GitHub from '../github'; import GitHub from '../github';
import SharedWorkspaceLocking from './services/core/shared-workspace-locking'; import SharedWorkspaceLocking from './services/core/shared-workspace-locking';
import { FollowLogStreamService } from './services/core/follow-log-stream-service'; import { FollowLogStreamService } from './services/core/follow-log-stream-service';
import CloudRunnerResult from './services/core/cloud-runner-result';
class CloudRunner { class CloudRunner {
public static Provider: ProviderInterface; public static Provider: ProviderInterface;
@ -84,16 +83,15 @@ class CloudRunner {
} }
static async run(buildParameters: BuildParameters, baseImage: string) { static async run(buildParameters: BuildParameters, baseImage: string) {
if (baseImage.includes(`undefined`)) {
throw new Error(`baseImage is undefined`);
}
await CloudRunner.setup(buildParameters); await CloudRunner.setup(buildParameters);
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('Setup shared cloud runner resources');
await CloudRunner.Provider.setupWorkflow( await CloudRunner.Provider.setupWorkflow(
CloudRunner.buildParameters.buildGuid, CloudRunner.buildParameters.buildGuid,
CloudRunner.buildParameters, CloudRunner.buildParameters,
CloudRunner.buildParameters.branch, CloudRunner.buildParameters.branch,
CloudRunner.defaultSecrets, CloudRunner.defaultSecrets,
); );
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
try { try {
if (buildParameters.maxRetainedWorkspaces > 0) { if (buildParameters.maxRetainedWorkspaces > 0) {
CloudRunner.lockedWorkspace = SharedWorkspaceLocking.NewWorkspaceName(); CloudRunner.lockedWorkspace = SharedWorkspaceLocking.NewWorkspaceName();
@ -116,7 +114,11 @@ class CloudRunner {
CloudRunner.lockedWorkspace = ``; CloudRunner.lockedWorkspace = ``;
} }
} }
await CloudRunner.updateStatusWithBuildParameters(); const content = { ...CloudRunner.buildParameters };
content.gitPrivateToken = ``;
content.unitySerial = ``;
const jsonContent = JSON.stringify(content, undefined, 4);
await GitHub.updateGitHubCheck(jsonContent, CloudRunner.buildParameters.buildGuid);
const output = await new WorkflowCompositionRoot().run( const output = await new WorkflowCompositionRoot().run(
new CloudRunnerStepParameters( new CloudRunnerStepParameters(
baseImage, baseImage,
@ -124,15 +126,16 @@ class CloudRunner {
CloudRunner.defaultSecrets, CloudRunner.defaultSecrets,
), ),
); );
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('Cleanup shared cloud runner resources');
await CloudRunner.Provider.cleanupWorkflow( await CloudRunner.Provider.cleanupWorkflow(
CloudRunner.buildParameters.buildGuid,
CloudRunner.buildParameters, CloudRunner.buildParameters,
CloudRunner.buildParameters.branch, CloudRunner.buildParameters.branch,
CloudRunner.defaultSecrets, CloudRunner.defaultSecrets,
); );
CloudRunnerLogger.log(`Cleanup complete`);
if (!CloudRunner.buildParameters.isCliMode) core.endGroup(); if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
if (buildParameters.asyncWorkflow && this.isCloudRunnerEnvironment && this.isCloudRunnerAsyncEnvironment) { await GitHub.updateGitHubCheck(CloudRunner.buildParameters.buildGuid, `success`, `success`, `completed`);
await GitHub.updateGitHubCheck(CloudRunner.buildParameters.buildGuid, `success`, `success`, `completed`);
}
if (BuildParameters.shouldUseRetainedWorkspaceMode(buildParameters)) { if (BuildParameters.shouldUseRetainedWorkspaceMode(buildParameters)) {
const workspace = CloudRunner.lockedWorkspace || ``; const workspace = CloudRunner.lockedWorkspace || ``;
@ -159,7 +162,7 @@ class CloudRunner {
CloudRunner.Provider.garbageCollect(``, true, buildParameters.garbageMaxAge, true, true); CloudRunner.Provider.garbageCollect(``, true, buildParameters.garbageMaxAge, true, true);
} }
return new CloudRunnerResult(buildParameters, output, true, true, false); return output;
} catch (error: any) { } catch (error: any) {
CloudRunnerLogger.log(JSON.stringify(error, undefined, 4)); CloudRunnerLogger.log(JSON.stringify(error, undefined, 4));
await GitHub.updateGitHubCheck( await GitHub.updateGitHubCheck(
@ -173,15 +176,5 @@ class CloudRunner {
throw error; throw error;
} }
} }
private static async updateStatusWithBuildParameters() {
const content = { ...CloudRunner.buildParameters };
content.gitPrivateToken = ``;
content.unitySerial = ``;
content.unityEmail = ``;
content.unityPassword = ``;
const jsonContent = JSON.stringify(content, undefined, 4);
await GitHub.updateGitHubCheck(jsonContent, CloudRunner.buildParameters.buildGuid);
}
} }
export default CloudRunner; export default CloudRunner;

View File

@ -9,7 +9,12 @@ export class CloudRunnerError {
CloudRunnerLogger.error(JSON.stringify(error, undefined, 4)); CloudRunnerLogger.error(JSON.stringify(error, undefined, 4));
core.setFailed('Cloud Runner failed'); core.setFailed('Cloud Runner failed');
if (CloudRunner.Provider !== undefined) { if (CloudRunner.Provider !== undefined) {
await CloudRunner.Provider.cleanupWorkflow(buildParameters, buildParameters.branch, secrets); await CloudRunner.Provider.cleanupWorkflow(
buildParameters.buildGuid,
buildParameters,
buildParameters.branch,
secrets,
);
} }
} }
} }

View File

@ -103,14 +103,14 @@ class CloudRunnerOptions {
static get buildPlatform(): string { static get buildPlatform(): string {
const input = CloudRunnerOptions.getInput('buildPlatform'); const input = CloudRunnerOptions.getInput('buildPlatform');
if (input && input !== '') { if (input) {
return input; return input;
} }
if (CloudRunnerOptions.providerStrategy !== 'local') { if (CloudRunnerOptions.providerStrategy !== 'local') {
return 'linux'; return 'linux';
} }
return process.platform; return ``;
} }
static get cloudRunnerBranch(): string { static get cloudRunnerBranch(): string {

View File

@ -1,18 +1,6 @@
import CloudRunnerLogger from '../../services/core/cloud-runner-logger'; import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
import * as core from '@actions/core'; import * as core from '@actions/core';
import { import * as SDK from 'aws-sdk';
CloudFormation,
CreateStackCommand,
CreateStackCommandInput,
DescribeStacksCommand,
DescribeStacksCommandInput,
ListStacksCommand,
Parameter,
UpdateStackCommand,
UpdateStackCommandInput,
waitUntilStackCreateComplete,
waitUntilStackUpdateComplete,
} from '@aws-sdk/client-cloudformation';
import { BaseStackFormation } from './cloud-formations/base-stack-formation'; import { BaseStackFormation } from './cloud-formations/base-stack-formation';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
@ -22,49 +10,51 @@ export class AWSBaseStack {
} }
private baseStackName: string; private baseStackName: string;
async setupBaseStack(CF: CloudFormation) { async setupBaseStack(CF: SDK.CloudFormation) {
const baseStackName = this.baseStackName; const baseStackName = this.baseStackName;
const baseStack = BaseStackFormation.formation; const baseStack = BaseStackFormation.formation;
// Cloud Formation Input // Cloud Formation Input
const describeStackInput: DescribeStacksCommandInput = { const describeStackInput: SDK.CloudFormation.DescribeStacksInput = {
StackName: baseStackName, StackName: baseStackName,
}; };
const parametersWithoutHash: Parameter[] = [{ ParameterKey: 'EnvironmentName', ParameterValue: baseStackName }]; const parametersWithoutHash: SDK.CloudFormation.Parameter[] = [
{ ParameterKey: 'EnvironmentName', ParameterValue: baseStackName },
];
const parametersHash = crypto const parametersHash = crypto
.createHash('md5') .createHash('md5')
.update(baseStack + JSON.stringify(parametersWithoutHash)) .update(baseStack + JSON.stringify(parametersWithoutHash))
.digest('hex'); .digest('hex');
const parameters: Parameter[] = [ const parameters: SDK.CloudFormation.Parameter[] = [
...parametersWithoutHash, ...parametersWithoutHash,
...[{ ParameterKey: 'Version', ParameterValue: parametersHash }], ...[{ ParameterKey: 'Version', ParameterValue: parametersHash }],
]; ];
const updateInput: UpdateStackCommandInput = { const updateInput: SDK.CloudFormation.UpdateStackInput = {
StackName: baseStackName, StackName: baseStackName,
TemplateBody: baseStack, TemplateBody: baseStack,
Parameters: parameters, Parameters: parameters,
Capabilities: ['CAPABILITY_IAM'], Capabilities: ['CAPABILITY_IAM'],
}; };
const createStackInput: CreateStackCommandInput = { const createStackInput: SDK.CloudFormation.CreateStackInput = {
StackName: baseStackName, StackName: baseStackName,
TemplateBody: baseStack, TemplateBody: baseStack,
Parameters: parameters, Parameters: parameters,
Capabilities: ['CAPABILITY_IAM'], Capabilities: ['CAPABILITY_IAM'],
}; };
const stacks = await CF.send( const stacks = await CF.listStacks({
new ListStacksCommand({ StackStatusFilter: ['UPDATE_COMPLETE', 'CREATE_COMPLETE', 'ROLLBACK_COMPLETE'] }), StackStatusFilter: ['UPDATE_COMPLETE', 'CREATE_COMPLETE', 'ROLLBACK_COMPLETE'],
); }).promise();
const stackNames = stacks.StackSummaries?.map((x) => x.StackName) || []; const stackNames = stacks.StackSummaries?.map((x) => x.StackName) || [];
const stackExists: Boolean = stackNames.includes(baseStackName) || false; const stackExists: Boolean = stackNames.includes(baseStackName) || false;
const describeStack = async () => { const describeStack = async () => {
return await CF.send(new DescribeStacksCommand(describeStackInput)); return await CF.describeStacks(describeStackInput).promise();
}; };
try { try {
if (!stackExists) { if (!stackExists) {
CloudRunnerLogger.log(`${baseStackName} stack does not exist (${JSON.stringify(stackNames)})`); CloudRunnerLogger.log(`${baseStackName} stack does not exist (${JSON.stringify(stackNames)})`);
await CF.send(new CreateStackCommand(createStackInput)); await CF.createStack(createStackInput).promise();
CloudRunnerLogger.log(`created stack (version: ${parametersHash})`); CloudRunnerLogger.log(`created stack (version: ${parametersHash})`);
} }
const CFState = await describeStack(); const CFState = await describeStack();
@ -75,13 +65,7 @@ export class AWSBaseStack {
const stackVersion = stack.Parameters?.find((x) => x.ParameterKey === 'Version')?.ParameterValue; const stackVersion = stack.Parameters?.find((x) => x.ParameterKey === 'Version')?.ParameterValue;
if (stack.StackStatus === 'CREATE_IN_PROGRESS') { if (stack.StackStatus === 'CREATE_IN_PROGRESS') {
await waitUntilStackCreateComplete( await CF.waitFor('stackCreateComplete', describeStackInput).promise();
{
client: CF,
maxWaitTime: 200,
},
describeStackInput,
);
} }
if (stackExists) { if (stackExists) {
@ -89,7 +73,7 @@ export class AWSBaseStack {
if (parametersHash !== stackVersion) { if (parametersHash !== stackVersion) {
CloudRunnerLogger.log(`Attempting update of base stack`); CloudRunnerLogger.log(`Attempting update of base stack`);
try { try {
await CF.send(new UpdateStackCommand(updateInput)); await CF.updateStack(updateInput).promise();
} catch (error: any) { } catch (error: any) {
if (error['message'].includes('No updates are to be performed')) { if (error['message'].includes('No updates are to be performed')) {
CloudRunnerLogger.log(`No updates are to be performed`); CloudRunnerLogger.log(`No updates are to be performed`);
@ -109,13 +93,7 @@ export class AWSBaseStack {
); );
} }
if (stack.StackStatus === 'UPDATE_IN_PROGRESS') { if (stack.StackStatus === 'UPDATE_IN_PROGRESS') {
await waitUntilStackUpdateComplete( await CF.waitFor('stackUpdateComplete', describeStackInput).promise();
{
client: CF,
maxWaitTime: 200,
},
describeStackInput,
);
} }
} }
CloudRunnerLogger.log('base stack is now ready'); CloudRunnerLogger.log('base stack is now ready');

View File

@ -1,15 +1,15 @@
import CloudRunnerLogger from '../../services/core/cloud-runner-logger'; import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
import { CloudFormation, DescribeStackEventsCommand } from '@aws-sdk/client-cloudformation'; import * as SDK from 'aws-sdk';
import * as core from '@actions/core'; import * as core from '@actions/core';
import CloudRunner from '../../cloud-runner'; import CloudRunner from '../../cloud-runner';
export class AWSError { export class AWSError {
static async handleStackCreationFailure(error: any, CF: CloudFormation, taskDefStackName: string) { static async handleStackCreationFailure(error: any, CF: SDK.CloudFormation, taskDefStackName: string) {
CloudRunnerLogger.log('aws error: '); CloudRunnerLogger.log('aws error: ');
core.error(JSON.stringify(error, undefined, 4)); core.error(JSON.stringify(error, undefined, 4));
if (CloudRunner.buildParameters.cloudRunnerDebug) { if (CloudRunner.buildParameters.cloudRunnerDebug) {
CloudRunnerLogger.log('Getting events and resources for task stack'); CloudRunnerLogger.log('Getting events and resources for task stack');
const events = (await CF.send(new DescribeStackEventsCommand({ StackName: taskDefStackName }))).StackEvents; const events = (await CF.describeStackEvents({ StackName: taskDefStackName }).promise()).StackEvents;
CloudRunnerLogger.log(JSON.stringify(events, undefined, 4)); CloudRunnerLogger.log(JSON.stringify(events, undefined, 4));
} }
} }

View File

@ -1,12 +1,4 @@
import { import * as SDK from 'aws-sdk';
CloudFormation,
CreateStackCommand,
CreateStackCommandInput,
DescribeStackResourcesCommand,
DescribeStacksCommand,
ListStacksCommand,
waitUntilStackCreateComplete,
} from '@aws-sdk/client-cloudformation';
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def'; import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def';
import CloudRunnerSecret from '../../options/cloud-runner-secret'; import CloudRunnerSecret from '../../options/cloud-runner-secret';
import { AWSCloudFormationTemplates } from './aws-cloud-formation-templates'; import { AWSCloudFormationTemplates } from './aws-cloud-formation-templates';
@ -24,7 +16,7 @@ export class AWSJobStack {
} }
public async setupCloudFormations( public async setupCloudFormations(
CF: CloudFormation, CF: SDK.CloudFormation,
buildGuid: string, buildGuid: string,
image: string, image: string,
entrypoint: string[], entrypoint: string[],
@ -127,7 +119,7 @@ export class AWSJobStack {
let previousStackExists = true; let previousStackExists = true;
while (previousStackExists) { while (previousStackExists) {
previousStackExists = false; previousStackExists = false;
const stacks = await CF.send(new ListStacksCommand({})); const stacks = await CF.listStacks().promise();
if (!stacks.StackSummaries) { if (!stacks.StackSummaries) {
throw new Error('Faild to get stacks'); throw new Error('Faild to get stacks');
} }
@ -140,7 +132,7 @@ export class AWSJobStack {
} }
} }
} }
const createStackInput: CreateStackCommandInput = { const createStackInput: SDK.CloudFormation.CreateStackInput = {
StackName: taskDefStackName, StackName: taskDefStackName,
TemplateBody: taskDefCloudFormation, TemplateBody: taskDefCloudFormation,
Capabilities: ['CAPABILITY_IAM'], Capabilities: ['CAPABILITY_IAM'],
@ -148,15 +140,9 @@ export class AWSJobStack {
}; };
try { try {
CloudRunnerLogger.log(`Creating job aws formation ${taskDefStackName}`); CloudRunnerLogger.log(`Creating job aws formation ${taskDefStackName}`);
await CF.send(new CreateStackCommand(createStackInput)); await CF.createStack(createStackInput).promise();
await waitUntilStackCreateComplete( await CF.waitFor('stackCreateComplete', { StackName: taskDefStackName }).promise();
{ const describeStack = await CF.describeStacks({ StackName: taskDefStackName }).promise();
client: CF,
maxWaitTime: 200,
},
{ StackName: taskDefStackName },
);
const describeStack = await CF.send(new DescribeStacksCommand({ StackName: taskDefStackName }));
for (const parameter of parameters) { for (const parameter of parameters) {
if (!describeStack.Stacks?.[0].Parameters?.some((x) => x.ParameterKey === parameter.ParameterKey)) { if (!describeStack.Stacks?.[0].Parameters?.some((x) => x.ParameterKey === parameter.ParameterKey)) {
throw new Error(`Parameter ${parameter.ParameterKey} not found in stack`); throw new Error(`Parameter ${parameter.ParameterKey} not found in stack`);
@ -167,7 +153,7 @@ export class AWSJobStack {
throw error; throw error;
} }
const createCleanupStackInput: CreateStackCommandInput = { const createCleanupStackInput: SDK.CloudFormation.CreateStackInput = {
StackName: `${taskDefStackName}-cleanup`, StackName: `${taskDefStackName}-cleanup`,
TemplateBody: CleanupCronFormation.formation, TemplateBody: CleanupCronFormation.formation,
Capabilities: ['CAPABILITY_IAM'], Capabilities: ['CAPABILITY_IAM'],
@ -197,7 +183,7 @@ export class AWSJobStack {
if (CloudRunnerOptions.useCleanupCron) { if (CloudRunnerOptions.useCleanupCron) {
try { try {
CloudRunnerLogger.log(`Creating job cleanup formation`); CloudRunnerLogger.log(`Creating job cleanup formation`);
await CF.send(new CreateStackCommand(createCleanupStackInput)); await CF.createStack(createCleanupStackInput).promise();
// await CF.waitFor('stackCreateComplete', { StackName: createCleanupStackInput.StackName }).promise(); // await CF.waitFor('stackCreateComplete', { StackName: createCleanupStackInput.StackName }).promise();
} catch (error) { } catch (error) {
@ -207,15 +193,12 @@ export class AWSJobStack {
} }
const taskDefResources = ( const taskDefResources = (
await CF.send( await CF.describeStackResources({
new DescribeStackResourcesCommand({ StackName: taskDefStackName,
StackName: taskDefStackName, }).promise()
}),
)
).StackResources; ).StackResources;
const baseResources = (await CF.send(new DescribeStackResourcesCommand({ StackName: this.baseStackName }))) const baseResources = (await CF.describeStackResources({ StackName: this.baseStackName }).promise()).StackResources;
.StackResources;
return { return {
taskDefStackName, taskDefStackName,

View File

@ -1,19 +1,4 @@
import { import * as AWS from 'aws-sdk';
DescribeTasksCommand,
ECS,
RunTaskCommand,
RunTaskCommandInput,
Task,
waitUntilTasksRunning,
} from '@aws-sdk/client-ecs';
import {
DescribeStreamCommand,
DescribeStreamCommandOutput,
GetRecordsCommand,
GetRecordsCommandOutput,
GetShardIteratorCommand,
Kinesis,
} from '@aws-sdk/client-kinesis';
import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable'; import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable';
import * as core from '@actions/core'; import * as core from '@actions/core';
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def'; import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def';
@ -27,8 +12,8 @@ import CloudRunnerOptions from '../../options/cloud-runner-options';
import GitHub from '../../../github'; import GitHub from '../../../github';
class AWSTaskRunner { class AWSTaskRunner {
public static ECS: ECS; public static ECS: AWS.ECS;
public static Kinesis: Kinesis; public static Kinesis: AWS.Kinesis;
private static readonly encodedUnderscore = `$252F`; private static readonly encodedUnderscore = `$252F`;
static async runTask( static async runTask(
taskDef: CloudRunnerAWSTaskDef, taskDef: CloudRunnerAWSTaskDef,
@ -75,7 +60,7 @@ class AWSTaskRunner {
throw new Error(`Container Overrides length must be at most 8192`); throw new Error(`Container Overrides length must be at most 8192`);
} }
const task = await AWSTaskRunner.ECS.send(new RunTaskCommand(runParameters as RunTaskCommandInput)); const task = await AWSTaskRunner.ECS.runTask(runParameters).promise();
const taskArn = task.tasks?.[0].taskArn || ''; const taskArn = task.tasks?.[0].taskArn || '';
CloudRunnerLogger.log('Cloud runner job is starting'); CloudRunnerLogger.log('Cloud runner job is starting');
await AWSTaskRunner.waitUntilTaskRunning(taskArn, cluster); await AWSTaskRunner.waitUntilTaskRunning(taskArn, cluster);
@ -123,13 +108,7 @@ class AWSTaskRunner {
private static async waitUntilTaskRunning(taskArn: string, cluster: string) { private static async waitUntilTaskRunning(taskArn: string, cluster: string) {
try { try {
await waitUntilTasksRunning( await AWSTaskRunner.ECS.waitFor('tasksRunning', { tasks: [taskArn], cluster }).promise();
{
client: AWSTaskRunner.ECS,
maxWaitTime: 120,
},
{ tasks: [taskArn], cluster },
);
} catch (error_) { } catch (error_) {
const error = error_ as Error; const error = error_ as Error;
await new Promise((resolve) => setTimeout(resolve, 3000)); await new Promise((resolve) => setTimeout(resolve, 3000));
@ -145,7 +124,10 @@ class AWSTaskRunner {
} }
static async describeTasks(clusterName: string, taskArn: string) { static async describeTasks(clusterName: string, taskArn: string) {
const tasks = await AWSTaskRunner.ECS.send(new DescribeTasksCommand({ cluster: clusterName, tasks: [taskArn] })); const tasks = await AWSTaskRunner.ECS.describeTasks({
cluster: clusterName,
tasks: [taskArn],
}).promise();
if (tasks.tasks?.[0]) { if (tasks.tasks?.[0]) {
return tasks.tasks?.[0]; return tasks.tasks?.[0];
} else { } else {
@ -187,7 +169,9 @@ class AWSTaskRunner {
output: string, output: string,
shouldCleanup: boolean, shouldCleanup: boolean,
) { ) {
const records = await AWSTaskRunner.Kinesis.send(new GetRecordsCommand({ ShardIterator: iterator })); const records = await AWSTaskRunner.Kinesis.getRecords({
ShardIterator: iterator,
}).promise();
iterator = records.NextShardIterator || ''; iterator = records.NextShardIterator || '';
({ shouldReadLogs, output, shouldCleanup } = AWSTaskRunner.logRecords( ({ shouldReadLogs, output, shouldCleanup } = AWSTaskRunner.logRecords(
records, records,
@ -200,7 +184,7 @@ class AWSTaskRunner {
return { iterator, shouldReadLogs, output, shouldCleanup }; return { iterator, shouldReadLogs, output, shouldCleanup };
} }
private static checkStreamingShouldContinue(taskData: Task, timestamp: number, shouldReadLogs: boolean) { private static checkStreamingShouldContinue(taskData: AWS.ECS.Task, timestamp: number, shouldReadLogs: boolean) {
if (taskData?.lastStatus === 'UNKNOWN') { if (taskData?.lastStatus === 'UNKNOWN') {
CloudRunnerLogger.log('## Cloud runner job unknwon'); CloudRunnerLogger.log('## Cloud runner job unknwon');
} }
@ -220,17 +204,15 @@ class AWSTaskRunner {
} }
private static logRecords( private static logRecords(
records: GetRecordsCommandOutput, records: AWS.Kinesis.GetRecordsOutput,
iterator: string, iterator: string,
shouldReadLogs: boolean, shouldReadLogs: boolean,
output: string, output: string,
shouldCleanup: boolean, shouldCleanup: boolean,
) { ) {
if ((records.Records ?? []).length > 0 && iterator) { if (records.Records.length > 0 && iterator) {
for (const record of records.Records ?? []) { for (const record of records.Records) {
const json = JSON.parse( const json = JSON.parse(zlib.gunzipSync(Buffer.from(record.Data as string, 'base64')).toString('utf8'));
zlib.gunzipSync(Buffer.from(record.Data as unknown as string, 'base64')).toString('utf8'),
);
if (json.messageType === 'DATA_MESSAGE') { if (json.messageType === 'DATA_MESSAGE') {
for (const logEvent of json.logEvents) { for (const logEvent of json.logEvents) {
({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration( ({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration(
@ -248,19 +230,19 @@ class AWSTaskRunner {
} }
private static async getLogStream(kinesisStreamName: string) { private static async getLogStream(kinesisStreamName: string) {
return await AWSTaskRunner.Kinesis.send(new DescribeStreamCommand({ StreamName: kinesisStreamName })); return await AWSTaskRunner.Kinesis.describeStream({
StreamName: kinesisStreamName,
}).promise();
} }
private static async getLogIterator(stream: DescribeStreamCommandOutput) { private static async getLogIterator(stream: AWS.Kinesis.DescribeStreamOutput) {
return ( return (
( (
await AWSTaskRunner.Kinesis.send( await AWSTaskRunner.Kinesis.getShardIterator({
new GetShardIteratorCommand({ ShardIteratorType: 'TRIM_HORIZON',
ShardIteratorType: 'TRIM_HORIZON', StreamName: stream.StreamDescription.StreamName,
StreamName: stream.StreamDescription?.StreamName ?? '', ShardId: stream.StreamDescription.Shards[0].ShardId,
ShardId: stream.StreamDescription?.Shards?.[0]?.ShardId || '', }).promise()
}),
)
).ShardIterator || '' ).ShardIterator || ''
); );
} }

View File

@ -1,9 +1,6 @@
import CloudRunner from '../../../cloud-runner';
export class TaskDefinitionFormation { export class TaskDefinitionFormation {
public static readonly description: string = `Game CI Cloud Runner Task Stack`; public static readonly description: string = `Game CI Cloud Runner Task Stack`;
public static get formation(): string { public static readonly formation: string = `AWSTemplateFormatVersion: 2010-09-09
return `AWSTemplateFormatVersion: 2010-09-09
Description: ${TaskDefinitionFormation.description} Description: ${TaskDefinitionFormation.description}
Parameters: Parameters:
EnvironmentName: EnvironmentName:
@ -29,11 +26,11 @@ Parameters:
Default: 80 Default: 80
Description: What port number the application inside the docker container is binding to Description: What port number the application inside the docker container is binding to
ContainerCpu: ContainerCpu:
Default: ${CloudRunner.buildParameters.containerCpu} Default: 1024
Type: Number Type: Number
Description: How much CPU to give the container. 1024 is 1 CPU Description: How much CPU to give the container. 1024 is 1 CPU
ContainerMemory: ContainerMemory:
Default: ${CloudRunner.buildParameters.containerMemory} Default: 4096
Type: Number Type: Number
Description: How much memory in megabytes to give the container Description: How much memory in megabytes to give the container
BUILDGUID: BUILDGUID:
@ -95,7 +92,7 @@ Resources:
EFSVolumeConfiguration: EFSVolumeConfiguration:
FilesystemId: FilesystemId:
'Fn::ImportValue': !Sub '${'${EnvironmentName}'}:EfsFileStorageId' 'Fn::ImportValue': !Sub '${'${EnvironmentName}'}:EfsFileStorageId'
TransitEncryption: DISABLED TransitEncryption: ENABLED
RequiresCompatibilities: RequiresCompatibilities:
- FARGATE - FARGATE
ExecutionRoleArn: ExecutionRoleArn:
@ -138,7 +135,6 @@ Resources:
DependsOn: DependsOn:
- LogGroup - LogGroup
`; `;
}
public static streamLogs = ` public static streamLogs = `
SubscriptionFilter: SubscriptionFilter:
Type: 'AWS::Logs::SubscriptionFilter' Type: 'AWS::Logs::SubscriptionFilter'

View File

@ -1,9 +1,9 @@
import { StackResource } from '@aws-sdk/client-cloudformation'; import * as AWS from 'aws-sdk';
class CloudRunnerAWSTaskDef { class CloudRunnerAWSTaskDef {
public taskDefStackName!: string; public taskDefStackName!: string;
public taskDefCloudFormation!: string; public taskDefCloudFormation!: string;
public taskDefResources: StackResource[] | undefined; public taskDefResources: AWS.CloudFormation.StackResources | undefined;
public baseResources: StackResource[] | undefined; public baseResources: AWS.CloudFormation.StackResources | undefined;
} }
export default CloudRunnerAWSTaskDef; export default CloudRunnerAWSTaskDef;

View File

@ -1,6 +1,4 @@
import { CloudFormation, DeleteStackCommand, waitUntilStackDeleteComplete } from '@aws-sdk/client-cloudformation'; import * as SDK from 'aws-sdk';
import { ECS as ECSClient } from '@aws-sdk/client-ecs';
import { Kinesis } from '@aws-sdk/client-kinesis';
import CloudRunnerSecret from '../../options/cloud-runner-secret'; import CloudRunnerSecret from '../../options/cloud-runner-secret';
import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable'; import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable';
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def'; import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def';
@ -59,6 +57,8 @@ class AWSBuildEnvironment implements ProviderInterface {
} }
async cleanupWorkflow( async cleanupWorkflow(
// eslint-disable-next-line no-unused-vars
buildGuid: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
buildParameters: BuildParameters, buildParameters: BuildParameters,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@ -77,7 +77,7 @@ class AWSBuildEnvironment implements ProviderInterface {
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[], defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) { ) {
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const CF = new CloudFormation({ region: Input.region }); const CF = new SDK.CloudFormation();
await new AwsBaseStack(this.baseStackName).setupBaseStack(CF); await new AwsBaseStack(this.baseStackName).setupBaseStack(CF);
} }
@ -91,10 +91,10 @@ class AWSBuildEnvironment implements ProviderInterface {
secrets: CloudRunnerSecret[], secrets: CloudRunnerSecret[],
): Promise<string> { ): Promise<string> {
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const ECS = new ECSClient({ region: Input.region }); const ECS = new SDK.ECS();
const CF = new CloudFormation({ region: Input.region }); const CF = new SDK.CloudFormation();
AwsTaskRunner.ECS = ECS; AwsTaskRunner.ECS = ECS;
AwsTaskRunner.Kinesis = new Kinesis({ region: Input.region }); AwsTaskRunner.Kinesis = new SDK.Kinesis();
CloudRunnerLogger.log(`AWS Region: ${CF.config.region}`); CloudRunnerLogger.log(`AWS Region: ${CF.config.region}`);
const entrypoint = ['/bin/sh']; const entrypoint = ['/bin/sh'];
const startTimeMs = Date.now(); const startTimeMs = Date.now();
@ -131,31 +131,23 @@ class AWSBuildEnvironment implements ProviderInterface {
} }
} }
async cleanupResources(CF: CloudFormation, taskDef: CloudRunnerAWSTaskDef) { async cleanupResources(CF: SDK.CloudFormation, taskDef: CloudRunnerAWSTaskDef) {
CloudRunnerLogger.log('Cleanup starting'); CloudRunnerLogger.log('Cleanup starting');
await CF.send(new DeleteStackCommand({ StackName: taskDef.taskDefStackName })); await CF.deleteStack({
StackName: taskDef.taskDefStackName,
}).promise();
if (CloudRunnerOptions.useCleanupCron) { if (CloudRunnerOptions.useCleanupCron) {
await CF.send(new DeleteStackCommand({ StackName: `${taskDef.taskDefStackName}-cleanup` })); await CF.deleteStack({
StackName: `${taskDef.taskDefStackName}-cleanup`,
}).promise();
} }
await waitUntilStackDeleteComplete( await CF.waitFor('stackDeleteComplete', {
{ StackName: taskDef.taskDefStackName,
client: CF, }).promise();
maxWaitTime: 200, await CF.waitFor('stackDeleteComplete', {
}, StackName: `${taskDef.taskDefStackName}-cleanup`,
{ }).promise();
StackName: taskDef.taskDefStackName,
},
);
await waitUntilStackDeleteComplete(
{
client: CF,
maxWaitTime: 200,
},
{
StackName: `${taskDef.taskDefStackName}-cleanup`,
},
);
CloudRunnerLogger.log(`Deleted Stack: ${taskDef.taskDefStackName}`); CloudRunnerLogger.log(`Deleted Stack: ${taskDef.taskDefStackName}`);
CloudRunnerLogger.log('Cleanup complete'); CloudRunnerLogger.log('Cleanup complete');
} }

View File

@ -1,11 +1,4 @@
import { import AWS from 'aws-sdk';
CloudFormation,
DeleteStackCommand,
DeleteStackCommandInput,
DescribeStackResourcesCommand,
} from '@aws-sdk/client-cloudformation';
import { CloudWatchLogs, DeleteLogGroupCommand } from '@aws-sdk/client-cloudwatch-logs';
import { ECS, StopTaskCommand } from '@aws-sdk/client-ecs';
import Input from '../../../../input'; import Input from '../../../../input';
import CloudRunnerLogger from '../../../services/core/cloud-runner-logger'; import CloudRunnerLogger from '../../../services/core/cloud-runner-logger';
import { TaskService } from './task-service'; import { TaskService } from './task-service';
@ -19,9 +12,9 @@ export class GarbageCollectionService {
public static async cleanup(deleteResources = false, OneDayOlderOnly: boolean = false) { public static async cleanup(deleteResources = false, OneDayOlderOnly: boolean = false) {
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const CF = new CloudFormation({ region: Input.region }); const CF = new AWS.CloudFormation();
const ecs = new ECS({ region: Input.region }); const ecs = new AWS.ECS();
const cwl = new CloudWatchLogs({ region: Input.region }); const cwl = new AWS.CloudWatchLogs();
const taskDefinitionsInUse = new Array(); const taskDefinitionsInUse = new Array();
const tasks = await TaskService.getTasks(); const tasks = await TaskService.getTasks();
@ -30,14 +23,14 @@ export class GarbageCollectionService {
taskDefinitionsInUse.push(taskElement.taskDefinitionArn); taskDefinitionsInUse.push(taskElement.taskDefinitionArn);
if (deleteResources && (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(taskElement.createdAt!))) { if (deleteResources && (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(taskElement.createdAt!))) {
CloudRunnerLogger.log(`Stopping task ${taskElement.containers?.[0].name}`); CloudRunnerLogger.log(`Stopping task ${taskElement.containers?.[0].name}`);
await ecs.send(new StopTaskCommand({ task: taskElement.taskArn || '', cluster: element })); await ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise();
} }
} }
const jobStacks = await TaskService.getCloudFormationJobStacks(); const jobStacks = await TaskService.getCloudFormationJobStacks();
for (const element of jobStacks) { for (const element of jobStacks) {
if ( if (
(await CF.send(new DescribeStackResourcesCommand({ StackName: element.StackName }))).StackResources?.some( (await CF.describeStackResources({ StackName: element.StackName }).promise()).StackResources?.some(
(x) => x.ResourceType === 'AWS::ECS::TaskDefinition' && taskDefinitionsInUse.includes(x.PhysicalResourceId), (x) => x.ResourceType === 'AWS::ECS::TaskDefinition' && taskDefinitionsInUse.includes(x.PhysicalResourceId),
) )
) { ) {
@ -46,10 +39,7 @@ export class GarbageCollectionService {
return; return;
} }
if ( if (deleteResources && (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(element.CreationTime))) {
deleteResources &&
(!OneDayOlderOnly || (element.CreationTime && GarbageCollectionService.isOlderThan1day(element.CreationTime)))
) {
if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') { if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') {
CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`); CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`);
@ -57,8 +47,8 @@ export class GarbageCollectionService {
} }
CloudRunnerLogger.log(`Deleting ${element.StackName}`); CloudRunnerLogger.log(`Deleting ${element.StackName}`);
const deleteStackInput: DeleteStackCommandInput = { StackName: element.StackName }; const deleteStackInput: AWS.CloudFormation.DeleteStackInput = { StackName: element.StackName };
await CF.send(new DeleteStackCommand(deleteStackInput)); await CF.deleteStack(deleteStackInput).promise();
} }
} }
const logGroups = await TaskService.getLogGroups(); const logGroups = await TaskService.getLogGroups();
@ -68,7 +58,7 @@ export class GarbageCollectionService {
(!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(new Date(element.creationTime!))) (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(new Date(element.creationTime!)))
) { ) {
CloudRunnerLogger.log(`Deleting ${element.logGroupName}`); CloudRunnerLogger.log(`Deleting ${element.logGroupName}`);
await cwl.send(new DeleteLogGroupCommand({ logGroupName: element.logGroupName || '' })); await cwl.deleteLogGroup({ logGroupName: element.logGroupName || '' }).promise();
} }
} }

View File

@ -1,31 +1,12 @@
import { import AWS from 'aws-sdk';
CloudFormation,
DescribeStackResourcesCommand,
DescribeStacksCommand,
ListStacksCommand,
StackSummary,
} from '@aws-sdk/client-cloudformation';
import {
CloudWatchLogs,
DescribeLogGroupsCommand,
DescribeLogGroupsCommandInput,
LogGroup,
} from '@aws-sdk/client-cloudwatch-logs';
import {
DescribeTasksCommand,
DescribeTasksCommandInput,
ECS,
ListClustersCommand,
ListTasksCommand,
ListTasksCommandInput,
Task,
} from '@aws-sdk/client-ecs';
import { ListObjectsCommand, ListObjectsCommandInput, S3 } from '@aws-sdk/client-s3';
import Input from '../../../../input'; import Input from '../../../../input';
import CloudRunnerLogger from '../../../services/core/cloud-runner-logger'; import CloudRunnerLogger from '../../../services/core/cloud-runner-logger';
import { BaseStackFormation } from '../cloud-formations/base-stack-formation'; import { BaseStackFormation } from '../cloud-formations/base-stack-formation';
import AwsTaskRunner from '../aws-task-runner'; import AwsTaskRunner from '../aws-task-runner';
import { ListObjectsRequest } from 'aws-sdk/clients/s3';
import CloudRunner from '../../../cloud-runner'; import CloudRunner from '../../../cloud-runner';
import { StackSummaries } from 'aws-sdk/clients/cloudformation';
import { LogGroups } from 'aws-sdk/clients/cloudwatchlogs';
export class TaskService { export class TaskService {
static async watch() { static async watch() {
@ -39,24 +20,20 @@ export class TaskService {
return output; return output;
} }
public static async getCloudFormationJobStacks() { public static async getCloudFormationJobStacks() {
const result: StackSummary[] = []; const result: StackSummaries = [];
CloudRunnerLogger.log(``); CloudRunnerLogger.log(``);
CloudRunnerLogger.log(`List Cloud Formation Stacks`); CloudRunnerLogger.log(`List Cloud Formation Stacks`);
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const CF = new CloudFormation({ region: Input.region }); const CF = new AWS.CloudFormation();
const stacks = const stacks =
(await CF.send(new ListStacksCommand({}))).StackSummaries?.filter( (await CF.listStacks().promise()).StackSummaries?.filter(
(_x) => (_x) =>
_x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription !== BaseStackFormation.baseStackDecription, _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription !== BaseStackFormation.baseStackDecription,
) || []; ) || [];
CloudRunnerLogger.log(``); CloudRunnerLogger.log(``);
CloudRunnerLogger.log(`Cloud Formation Stacks ${stacks.length}`); CloudRunnerLogger.log(`Cloud Formation Stacks ${stacks.length}`);
for (const element of stacks) { for (const element of stacks) {
if (!element.CreationTime) { const ageDate: Date = new Date(Date.now() - element.CreationTime.getTime());
CloudRunnerLogger.log(`${element.StackName} due to undefined CreationTime`);
}
const ageDate: Date = new Date(Date.now() - (element.CreationTime?.getTime() ?? 0));
CloudRunnerLogger.log( CloudRunnerLogger.log(
`Task Stack ${element.StackName} - Age D${Math.floor( `Task Stack ${element.StackName} - Age D${Math.floor(
@ -66,18 +43,14 @@ export class TaskService {
result.push(element); result.push(element);
} }
const baseStacks = const baseStacks =
(await CF.send(new ListStacksCommand({}))).StackSummaries?.filter( (await CF.listStacks().promise()).StackSummaries?.filter(
(_x) => (_x) =>
_x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === BaseStackFormation.baseStackDecription, _x.StackStatus !== 'DELETE_COMPLETE' && _x.TemplateDescription === BaseStackFormation.baseStackDecription,
) || []; ) || [];
CloudRunnerLogger.log(``); CloudRunnerLogger.log(``);
CloudRunnerLogger.log(`Base Stacks ${baseStacks.length}`); CloudRunnerLogger.log(`Base Stacks ${baseStacks.length}`);
for (const element of baseStacks) { for (const element of baseStacks) {
if (!element.CreationTime) { const ageDate: Date = new Date(Date.now() - element.CreationTime.getTime());
CloudRunnerLogger.log(`${element.StackName} due to undefined CreationTime`);
}
const ageDate: Date = new Date(Date.now() - (element.CreationTime?.getTime() ?? 0));
CloudRunnerLogger.log( CloudRunnerLogger.log(
`Task Stack ${element.StackName} - Age D${Math.floor( `Task Stack ${element.StackName} - Age D${Math.floor(
@ -91,22 +64,22 @@ export class TaskService {
return result; return result;
} }
public static async getTasks() { public static async getTasks() {
const result: { taskElement: Task; element: string }[] = []; const result: { taskElement: AWS.ECS.Task; element: string }[] = [];
CloudRunnerLogger.log(``); CloudRunnerLogger.log(``);
CloudRunnerLogger.log(`List Tasks`); CloudRunnerLogger.log(`List Tasks`);
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const ecs = new ECS({ region: Input.region }); const ecs = new AWS.ECS();
const clusters = (await ecs.send(new ListClustersCommand({}))).clusterArns || []; const clusters = (await ecs.listClusters().promise()).clusterArns || [];
CloudRunnerLogger.log(`Task Clusters ${clusters.length}`); CloudRunnerLogger.log(`Task Clusters ${clusters.length}`);
for (const element of clusters) { for (const element of clusters) {
const input: ListTasksCommandInput = { const input: AWS.ECS.ListTasksRequest = {
cluster: element, cluster: element,
}; };
const list = (await ecs.send(new ListTasksCommand(input))).taskArns || []; const list = (await ecs.listTasks(input).promise()).taskArns || [];
if (list.length > 0) { if (list.length > 0) {
const describeInput: DescribeTasksCommandInput = { tasks: list, cluster: element }; const describeInput: AWS.ECS.DescribeTasksRequest = { tasks: list, cluster: element };
const describeList = (await ecs.send(new DescribeTasksCommand(describeInput))).tasks || []; const describeList = (await ecs.describeTasks(describeInput).promise()).tasks || [];
if (describeList.length === 0) { if (describeList.length === 0) {
CloudRunnerLogger.log(`No Tasks`); CloudRunnerLogger.log(`No Tasks`);
continue; continue;
@ -132,48 +105,37 @@ export class TaskService {
} }
public static async awsDescribeJob(job: string) { public static async awsDescribeJob(job: string) {
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const CF = new CloudFormation({ region: Input.region }); const CF = new AWS.CloudFormation();
try { const stack = (await CF.listStacks().promise()).StackSummaries?.find((_x) => _x.StackName === job) || undefined;
const stack = const stackInfo = (await CF.describeStackResources({ StackName: job }).promise()) || undefined;
(await CF.send(new ListStacksCommand({}))).StackSummaries?.find((_x) => _x.StackName === job) || undefined; const stackInfo2 = (await CF.describeStacks({ StackName: job }).promise()) || undefined;
const stackInfo = (await CF.send(new DescribeStackResourcesCommand({ StackName: job }))) || undefined; if (stack === undefined) {
const stackInfo2 = (await CF.send(new DescribeStacksCommand({ StackName: job }))) || undefined; throw new Error('stack not defined');
if (stack === undefined) { }
throw new Error('stack not defined'); const ageDate: Date = new Date(Date.now() - stack.CreationTime.getTime());
} const message = `
if (!stack.CreationTime) {
CloudRunnerLogger.log(`${stack.StackName} due to undefined CreationTime`);
}
const ageDate: Date = new Date(Date.now() - (stack.CreationTime?.getTime() ?? 0));
const message = `
Task Stack ${stack.StackName} Task Stack ${stack.StackName}
Age D${Math.floor(ageDate.getHours() / 24)} H${ageDate.getHours()} M${ageDate.getMinutes()} Age D${Math.floor(ageDate.getHours() / 24)} H${ageDate.getHours()} M${ageDate.getMinutes()}
${JSON.stringify(stack, undefined, 4)} ${JSON.stringify(stack, undefined, 4)}
${JSON.stringify(stackInfo, undefined, 4)} ${JSON.stringify(stackInfo, undefined, 4)}
${JSON.stringify(stackInfo2, undefined, 4)} ${JSON.stringify(stackInfo2, undefined, 4)}
`; `;
CloudRunnerLogger.log(message); CloudRunnerLogger.log(message);
return message; return message;
} catch (error) {
CloudRunnerLogger.error(
`Failed to describe job ${job}: ${error instanceof Error ? error.message : String(error)}`,
);
throw error;
}
} }
public static async getLogGroups() { public static async getLogGroups() {
const result: Array<LogGroup> = []; const result: LogGroups = [];
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const ecs = new CloudWatchLogs(); const ecs = new AWS.CloudWatchLogs();
let logStreamInput: DescribeLogGroupsCommandInput = { let logStreamInput: AWS.CloudWatchLogs.DescribeLogGroupsRequest = {
/* logGroupNamePrefix: 'game-ci' */ /* logGroupNamePrefix: 'game-ci' */
}; };
let logGroupsDescribe = await ecs.send(new DescribeLogGroupsCommand(logStreamInput)); let logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise();
const logGroups = logGroupsDescribe.logGroups || []; const logGroups = logGroupsDescribe.logGroups || [];
while (logGroupsDescribe.nextToken) { while (logGroupsDescribe.nextToken) {
logStreamInput = { /* logGroupNamePrefix: 'game-ci',*/ nextToken: logGroupsDescribe.nextToken }; logStreamInput = { /* logGroupNamePrefix: 'game-ci',*/ nextToken: logGroupsDescribe.nextToken };
logGroupsDescribe = await ecs.send(new DescribeLogGroupsCommand(logStreamInput)); logGroupsDescribe = await ecs.describeLogGroups(logStreamInput).promise();
logGroups.push(...(logGroupsDescribe?.logGroups || [])); logGroups.push(...(logGroupsDescribe?.logGroups || []));
} }
@ -197,12 +159,11 @@ export class TaskService {
} }
public static async getLocks() { public static async getLocks() {
process.env.AWS_REGION = Input.region; process.env.AWS_REGION = Input.region;
const s3 = new S3({ region: Input.region }); const s3 = new AWS.S3();
const listRequest: ListObjectsCommandInput = { const listRequest: ListObjectsRequest = {
Bucket: CloudRunner.buildParameters.awsStackName, Bucket: CloudRunner.buildParameters.awsStackName,
}; };
const results = await s3.listObjects(listRequest).promise();
const results = await s3.send(new ListObjectsCommand(listRequest));
return results.Contents || []; return results.Contents || [];
} }

View File

@ -41,6 +41,7 @@ class LocalDockerCloudRunner implements ProviderInterface {
return new Promise((result) => result(``)); return new Promise((result) => result(``));
} }
async cleanupWorkflow( async cleanupWorkflow(
buildGuid: string,
buildParameters: BuildParameters, buildParameters: BuildParameters,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
branchName: string, branchName: string,
@ -132,7 +133,7 @@ cp -a ${sharedFolder}. /github/workspace/cloud-runner-cache/
if (fs.existsSync(`${workspace}/cloud-runner-cache`)) { if (fs.existsSync(`${workspace}/cloud-runner-cache`)) {
await CloudRunnerSystem.Run(`ls ${workspace}/cloud-runner-cache && du -sh ${workspace}/cloud-runner-cache`); await CloudRunnerSystem.Run(`ls ${workspace}/cloud-runner-cache && du -sh ${workspace}/cloud-runner-cache`);
} }
const exitCode = await Docker.run( await Docker.run(
image, image,
{ workspace, actionFolder, ...this.buildParameters }, { workspace, actionFolder, ...this.buildParameters },
false, false,
@ -149,14 +150,9 @@ cp -a ${sharedFolder}. /github/workspace/cloud-runner-cache/
}, },
}, },
true, true,
false,
); );
// Docker doesn't exit on fail now so adding this to ensure behavior is unchanged
// TODO: Is there a helpful way to consume the exit code or is it best to except
if (exitCode !== 0) {
throw new Error(`Build failed with exit code ${exitCode}`);
}
return myOutput; return myOutput;
} }
} }

View File

@ -14,17 +14,12 @@ import { CoreV1Api } from '@kubernetes/client-node';
import CloudRunner from '../../cloud-runner'; import CloudRunner from '../../cloud-runner';
import { ProviderResource } from '../provider-resource'; import { ProviderResource } from '../provider-resource';
import { ProviderWorkflow } from '../provider-workflow'; import { ProviderWorkflow } from '../provider-workflow';
import { RemoteClientLogger } from '../../remote-client/remote-client-logger';
import { KubernetesRole } from './kubernetes-role';
import { CloudRunnerSystem } from '../../services/core/cloud-runner-system';
class Kubernetes implements ProviderInterface { class Kubernetes implements ProviderInterface {
public static Instance: Kubernetes; public static Instance: Kubernetes;
public kubeConfig!: k8s.KubeConfig; public kubeConfig!: k8s.KubeConfig;
public kubeClient!: k8s.CoreV1Api; public kubeClient!: k8s.CoreV1Api;
public kubeClientApps!: k8s.AppsV1Api;
public kubeClientBatch!: k8s.BatchV1Api; public kubeClientBatch!: k8s.BatchV1Api;
public rbacAuthorizationV1Api!: k8s.RbacAuthorizationV1Api;
public buildGuid: string = ''; public buildGuid: string = '';
public buildParameters!: BuildParameters; public buildParameters!: BuildParameters;
public pvcName: string = ''; public pvcName: string = '';
@ -35,7 +30,6 @@ class Kubernetes implements ProviderInterface {
public containerName: string = ''; public containerName: string = '';
public cleanupCronJobName: string = ''; public cleanupCronJobName: string = '';
public serviceAccountName: string = ''; public serviceAccountName: string = '';
public ip: string = '';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
constructor(buildParameters: BuildParameters) { constructor(buildParameters: BuildParameters) {
@ -43,30 +37,11 @@ class Kubernetes implements ProviderInterface {
this.kubeConfig = new k8s.KubeConfig(); this.kubeConfig = new k8s.KubeConfig();
this.kubeConfig.loadFromDefault(); this.kubeConfig.loadFromDefault();
this.kubeClient = this.kubeConfig.makeApiClient(k8s.CoreV1Api); this.kubeClient = this.kubeConfig.makeApiClient(k8s.CoreV1Api);
this.kubeClientApps = this.kubeConfig.makeApiClient(k8s.AppsV1Api);
this.kubeClientBatch = this.kubeConfig.makeApiClient(k8s.BatchV1Api); this.kubeClientBatch = this.kubeConfig.makeApiClient(k8s.BatchV1Api);
this.rbacAuthorizationV1Api = this.kubeConfig.makeApiClient(k8s.RbacAuthorizationV1Api);
this.namespace = 'default'; this.namespace = 'default';
CloudRunnerLogger.log('Loaded default Kubernetes configuration for this environment'); CloudRunnerLogger.log('Loaded default Kubernetes configuration for this environment');
} }
async PushLogUpdate(logs: string) {
// push logs to nginx file server via 'LOG_SERVICE_IP' env var
const ip = process.env[`LOG_SERVICE_IP`];
if (ip === undefined) {
RemoteClientLogger.logWarning(`LOG_SERVICE_IP not set, skipping log push`);
return;
}
const url = `http://${ip}/api/log`;
RemoteClientLogger.log(`Pushing logs to ${url}`);
// logs to base64
logs = Buffer.from(logs).toString('base64');
const response = await CloudRunnerSystem.Run(`curl -X POST -d "${logs}" ${url}`, false, true);
RemoteClientLogger.log(`Pushed logs to ${url} ${response}`);
}
async listResources(): Promise<ProviderResource[]> { async listResources(): Promise<ProviderResource[]> {
const pods = await this.kubeClient.listNamespacedPod(this.namespace); const pods = await this.kubeClient.listNamespacedPod(this.namespace);
const serviceAccounts = await this.kubeClient.listNamespacedServiceAccount(this.namespace); const serviceAccounts = await this.kubeClient.listNamespacedServiceAccount(this.namespace);
@ -140,10 +115,9 @@ class Kubernetes implements ProviderInterface {
CloudRunnerLogger.log('Cloud Runner K8s workflow!'); CloudRunnerLogger.log('Cloud Runner K8s workflow!');
// Setup // Setup
const id = const id = BuildParameters.shouldUseRetainedWorkspaceMode(this.buildParameters)
BuildParameters && BuildParameters.shouldUseRetainedWorkspaceMode(this.buildParameters) ? CloudRunner.lockedWorkspace
? CloudRunner.lockedWorkspace : this.buildParameters.buildGuid;
: this.buildParameters.buildGuid;
this.pvcName = `unity-builder-pvc-${id}`; this.pvcName = `unity-builder-pvc-${id}`;
await KubernetesStorage.createPersistentVolumeClaim( await KubernetesStorage.createPersistentVolumeClaim(
this.buildParameters, this.buildParameters,
@ -163,7 +137,10 @@ class Kubernetes implements ProviderInterface {
CloudRunnerLogger.log('Watching pod until running'); CloudRunnerLogger.log('Watching pod until running');
await KubernetesTaskRunner.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace); await KubernetesTaskRunner.watchUntilPodRunning(this.kubeClient, this.podName, this.namespace);
CloudRunnerLogger.log('Pod is running'); CloudRunnerLogger.log('Pod running, streaming logs');
CloudRunnerLogger.log(
`Starting logs follow for pod: ${this.podName} container: ${this.containerName} namespace: ${this.namespace} pvc: ${this.pvcName} ${CloudRunner.buildParameters.kubeVolumeSize}/${CloudRunner.buildParameters.containerCpu}/${CloudRunner.buildParameters.containerMemory}`,
);
output += await KubernetesTaskRunner.runTask( output += await KubernetesTaskRunner.runTask(
this.kubeConfig, this.kubeConfig,
this.kubeClient, this.kubeClient,
@ -255,12 +232,8 @@ class Kubernetes implements ProviderInterface {
this.jobName, this.jobName,
k8s, k8s,
this.containerName, this.containerName,
this.ip,
); );
await new Promise((promise) => setTimeout(promise, 15000)); await new Promise((promise) => setTimeout(promise, 15000));
// await KubernetesRole.createRole(this.serviceAccountName, this.namespace, this.rbacAuthorizationV1Api);
const result = await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec); const result = await this.kubeClientBatch.createNamespacedJob(this.namespace, jobSpec);
CloudRunnerLogger.log(`Build job created`); CloudRunnerLogger.log(`Build job created`);
await new Promise((promise) => setTimeout(promise, 5000)); await new Promise((promise) => setTimeout(promise, 5000));
@ -284,7 +257,6 @@ class Kubernetes implements ProviderInterface {
try { try {
await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace); await this.kubeClientBatch.deleteNamespacedJob(this.jobName, this.namespace);
await this.kubeClient.deleteNamespacedPod(this.podName, this.namespace); await this.kubeClient.deleteNamespacedPod(this.podName, this.namespace);
await KubernetesRole.deleteRole(this.serviceAccountName, this.namespace, this.rbacAuthorizationV1Api);
} catch (error: any) { } catch (error: any) {
CloudRunnerLogger.log(`Failed to cleanup`); CloudRunnerLogger.log(`Failed to cleanup`);
if (error.response.body.reason !== `NotFound`) { if (error.response.body.reason !== `NotFound`) {
@ -303,13 +275,14 @@ class Kubernetes implements ProviderInterface {
} }
async cleanupWorkflow( async cleanupWorkflow(
buildGuid: string,
buildParameters: BuildParameters, buildParameters: BuildParameters,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
branchName: string, branchName: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[], defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
) { ) {
if (BuildParameters && BuildParameters.shouldUseRetainedWorkspaceMode(buildParameters)) { if (BuildParameters.shouldUseRetainedWorkspaceMode(buildParameters)) {
return; return;
} }
CloudRunnerLogger.log(`deleting PVC`); CloudRunnerLogger.log(`deleting PVC`);

View File

@ -20,7 +20,6 @@ class KubernetesJobSpecFactory {
jobName: string, jobName: string,
k8s: any, k8s: any,
containerName: string, containerName: string,
ip: string = '',
) { ) {
const job = new k8s.V1Job(); const job = new k8s.V1Job();
job.apiVersion = 'batch/v1'; job.apiVersion = 'batch/v1';
@ -82,7 +81,6 @@ class KubernetesJobSpecFactory {
return environmentVariable; return environmentVariable;
}), }),
{ name: 'LOG_SERVICE_IP', value: ip },
], ],
volumeMounts: [ volumeMounts: [
{ {
@ -94,8 +92,9 @@ class KubernetesJobSpecFactory {
preStop: { preStop: {
exec: { exec: {
command: [ command: [
`wait 60s; 'bin/bash',
cd /data/builder/action/steps; '-c',
`cd /data/builder/action/steps;
chmod +x /return_license.sh; chmod +x /return_license.sh;
/return_license.sh;`, /return_license.sh;`,
], ],
@ -109,16 +108,6 @@ class KubernetesJobSpecFactory {
}, },
}; };
if (process.env['CLOUD_RUNNER_MINIKUBE']) {
job.spec.template.spec.volumes[0] = {
name: 'build-mount',
hostPath: {
path: `/data`,
type: `Directory`,
},
};
}
job.spec.template.spec.containers[0].resources.requests[`ephemeral-storage`] = '10Gi'; job.spec.template.spec.containers[0].resources.requests[`ephemeral-storage`] = '10Gi';
return job; return job;

View File

@ -1,53 +0,0 @@
import { RbacAuthorizationV1Api } from '@kubernetes/client-node';
class KubernetesRole {
static async createRole(serviceAccountName: string, namespace: string, rbac: RbacAuthorizationV1Api) {
// create admin kubernetes role and role binding
const roleBinding = {
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'RoleBinding',
metadata: {
name: `${serviceAccountName}-admin`,
namespace,
},
subjects: [
{
kind: 'ServiceAccount',
name: serviceAccountName,
namespace,
},
],
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'Role',
name: `${serviceAccountName}-admin`,
},
};
const role = {
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'Role',
metadata: {
name: `${serviceAccountName}-admin`,
namespace,
},
rules: [
{
apiGroups: ['*'],
resources: ['*'],
verbs: ['*'],
},
],
};
const roleBindingResponse = await rbac.createNamespacedRoleBinding(namespace, roleBinding);
const roleResponse = await rbac.createNamespacedRole(namespace, role);
return { roleBindingResponse, roleResponse };
}
public static async deleteRole(serviceAccountName: string, namespace: string, rbac: RbacAuthorizationV1Api) {
await rbac.deleteNamespacedRoleBinding(`${serviceAccountName}-admin`, namespace);
await rbac.deleteNamespacedRole(`${serviceAccountName}-admin`, namespace);
}
}
export { KubernetesRole };

View File

@ -9,7 +9,7 @@ class KubernetesServiceAccount {
serviceAccount.metadata = { serviceAccount.metadata = {
name: serviceAccountName, name: serviceAccountName,
}; };
serviceAccount.automountServiceAccountToken = true; serviceAccount.automountServiceAccountToken = false;
return kubeClient.createNamespacedServiceAccount(namespace, serviceAccount); return kubeClient.createNamespacedServiceAccount(namespace, serviceAccount);
} }

View File

@ -7,6 +7,7 @@ import KubernetesPods from './kubernetes-pods';
import { FollowLogStreamService } from '../../services/core/follow-log-stream-service'; import { FollowLogStreamService } from '../../services/core/follow-log-stream-service';
class KubernetesTaskRunner { class KubernetesTaskRunner {
static lastReceivedTimestamp: number = 0;
static readonly maxRetry: number = 3; static readonly maxRetry: number = 3;
static lastReceivedMessage: string = ``; static lastReceivedMessage: string = ``;
@ -21,33 +22,38 @@ class KubernetesTaskRunner {
let output = ''; let output = '';
let shouldReadLogs = true; let shouldReadLogs = true;
let shouldCleanup = true; let shouldCleanup = true;
let sinceTime = ``;
let retriesAfterFinish = 0; let retriesAfterFinish = 0;
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
await new Promise((resolve) => setTimeout(resolve, 3000)); await new Promise((resolve) => setTimeout(resolve, 3000));
const lastReceivedMessage =
KubernetesTaskRunner.lastReceivedTimestamp > 0
? `\nLast Log Message "${this.lastReceivedMessage}" ${this.lastReceivedTimestamp}`
: ``;
CloudRunnerLogger.log( CloudRunnerLogger.log(
`Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace} ${CloudRunner.buildParameters.kubeVolumeSize}/${CloudRunner.buildParameters.containerCpu}/${CloudRunner.buildParameters.containerMemory}`, `Streaming logs from pod: ${podName} container: ${containerName} namespace: ${namespace} ${CloudRunner.buildParameters.kubeVolumeSize}/${CloudRunner.buildParameters.containerCpu}/${CloudRunner.buildParameters.containerMemory}\n${lastReceivedMessage}`,
); );
if (KubernetesTaskRunner.lastReceivedTimestamp > 0) {
const currentDate = new Date(KubernetesTaskRunner.lastReceivedTimestamp);
const dateTimeIsoString = currentDate.toISOString();
sinceTime = ` --since-time="${dateTimeIsoString}"`;
}
let extraFlags = ``; let extraFlags = ``;
extraFlags += (await KubernetesPods.IsPodRunning(podName, namespace, kubeClient)) extraFlags += (await KubernetesPods.IsPodRunning(podName, namespace, kubeClient))
? ` -f -c ${containerName}` ? ` -f -c ${containerName}`
: ` --previous`; : ` --previous`;
let lastMessageSeenIncludedInChunk = false;
let lastMessageSeen = false;
const callback = (outputChunk: string) => { let logs;
output += outputChunk;
// split output chunk and handle per line
for (const chunk of outputChunk.split(`\n`)) {
({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration(
chunk,
shouldReadLogs,
shouldCleanup,
output,
));
}
};
try { try {
await CloudRunnerSystem.Run(`kubectl logs ${podName}${extraFlags}`, false, true, callback); logs = await CloudRunnerSystem.Run(
`kubectl logs ${podName}${extraFlags} --timestamps${sinceTime}`,
false,
true,
);
} catch (error: any) { } catch (error: any) {
await new Promise((resolve) => setTimeout(resolve, 3000)); await new Promise((resolve) => setTimeout(resolve, 3000));
const continueStreaming = await KubernetesPods.IsPodRunning(podName, namespace, kubeClient); const continueStreaming = await KubernetesPods.IsPodRunning(podName, namespace, kubeClient);
@ -62,6 +68,34 @@ class KubernetesTaskRunner {
} }
throw error; throw error;
} }
const splitLogs = logs.split(`\n`);
for (const chunk of splitLogs) {
if (
chunk.replace(/\s/g, ``) === KubernetesTaskRunner.lastReceivedMessage.replace(/\s/g, ``) &&
KubernetesTaskRunner.lastReceivedMessage.replace(/\s/g, ``) !== ``
) {
CloudRunnerLogger.log(`Previous log message found ${chunk}`);
lastMessageSeenIncludedInChunk = true;
}
}
for (const chunk of splitLogs) {
const newDate = Date.parse(`${chunk.toString().split(`Z `)[0]}Z`);
if (chunk.replace(/\s/g, ``) === KubernetesTaskRunner.lastReceivedMessage.replace(/\s/g, ``)) {
lastMessageSeen = true;
}
if (lastMessageSeenIncludedInChunk && !lastMessageSeen) {
continue;
}
const message = CloudRunner.buildParameters.cloudRunnerDebug ? chunk : chunk.split(`Z `)[1];
KubernetesTaskRunner.lastReceivedMessage = chunk;
KubernetesTaskRunner.lastReceivedTimestamp = newDate;
({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration(
message,
shouldReadLogs,
shouldCleanup,
output,
));
}
if (FollowLogStreamService.DidReceiveEndOfTransmission) { if (FollowLogStreamService.DidReceiveEndOfTransmission) {
CloudRunnerLogger.log('end of log stream'); CloudRunnerLogger.log('end of log stream');
break; break;
@ -72,14 +106,14 @@ class KubernetesTaskRunner {
} }
static async watchUntilPodRunning(kubeClient: CoreV1Api, podName: string, namespace: string) { static async watchUntilPodRunning(kubeClient: CoreV1Api, podName: string, namespace: string) {
let waitComplete: boolean = false; let success: boolean = false;
let message = ``; let message = ``;
CloudRunnerLogger.log(`Watching ${podName} ${namespace}`); CloudRunnerLogger.log(`Watching ${podName} ${namespace}`);
await waitUntil( await waitUntil(
async () => { async () => {
const status = await kubeClient.readNamespacedPodStatus(podName, namespace); const status = await kubeClient.readNamespacedPodStatus(podName, namespace);
const phase = status?.body.status?.phase; const phase = status?.body.status?.phase;
waitComplete = phase !== 'Pending'; success = phase === 'Running';
message = `Phase:${status.body.status?.phase} \n Reason:${ message = `Phase:${status.body.status?.phase} \n Reason:${
status.body.status?.conditions?.[0].reason || '' status.body.status?.conditions?.[0].reason || ''
} \n Message:${status.body.status?.conditions?.[0].message || ''}`; } \n Message:${status.body.status?.conditions?.[0].message || ''}`;
@ -99,7 +133,7 @@ class KubernetesTaskRunner {
// 4, // 4,
// ), // ),
// ); // );
if (waitComplete || phase !== 'Pending') return true; if (success || phase !== 'Pending') return true;
return false; return false;
}, },
@ -108,11 +142,11 @@ class KubernetesTaskRunner {
intervalBetweenAttempts: 15000, intervalBetweenAttempts: 15000,
}, },
); );
if (!waitComplete) { if (!success) {
CloudRunnerLogger.log(message); CloudRunnerLogger.log(message);
} }
return waitComplete; return success;
} }
} }

View File

@ -32,6 +32,8 @@ class LocalCloudRunner implements ProviderInterface {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
cleanupWorkflow( cleanupWorkflow(
// eslint-disable-next-line no-unused-vars
buildGuid: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
buildParameters: BuildParameters, buildParameters: BuildParameters,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars

View File

@ -6,6 +6,8 @@ import { ProviderWorkflow } from './provider-workflow';
export interface ProviderInterface { export interface ProviderInterface {
cleanupWorkflow( cleanupWorkflow(
// eslint-disable-next-line no-unused-vars
buildGuid: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
buildParameters: BuildParameters, buildParameters: BuildParameters,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars

View File

@ -25,6 +25,8 @@ class TestCloudRunner implements ProviderInterface {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
cleanupWorkflow( cleanupWorkflow(
// eslint-disable-next-line no-unused-vars
buildGuid: string,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
buildParameters: BuildParameters, buildParameters: BuildParameters,
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars

View File

@ -80,7 +80,7 @@ export class Caching {
} }
await CloudRunnerSystem.Run( await CloudRunnerSystem.Run(
`tar -cf ${cacheArtifactName}.tar${compressionSuffix} "${path.basename(sourceFolder)}"`, `tar -cf ${cacheArtifactName}.tar${compressionSuffix} ${path.basename(sourceFolder)}`,
); );
await CloudRunnerSystem.Run(`du ${cacheArtifactName}.tar${compressionSuffix}`); await CloudRunnerSystem.Run(`du ${cacheArtifactName}.tar${compressionSuffix}`);
assert(await fileExists(`${cacheArtifactName}.tar${compressionSuffix}`), 'cache archive exists'); assert(await fileExists(`${cacheArtifactName}.tar${compressionSuffix}`), 'cache archive exists');

View File

@ -12,83 +12,16 @@ import { CloudRunnerSystem } from '../services/core/cloud-runner-system';
import YAML from 'yaml'; import YAML from 'yaml';
import GitHub from '../../github'; import GitHub from '../../github';
import BuildParameters from '../../build-parameters'; import BuildParameters from '../../build-parameters';
import { Cli } from '../../cli/cli';
import CloudRunnerOptions from '../options/cloud-runner-options';
export class RemoteClient { export class RemoteClient {
@CliFunction(`remote-cli-pre-build`, `sets up a repository, usually before a game-ci build`) @CliFunction(`remote-cli-pre-build`, `sets up a repository, usually before a game-ci build`)
static async setupRemoteClient() { static async runRemoteClientJob() {
CloudRunnerLogger.log(`bootstrap game ci cloud runner...`); CloudRunnerLogger.log(`bootstrap game ci cloud runner...`);
if (!(await RemoteClient.handleRetainedWorkspace())) { if (!(await RemoteClient.handleRetainedWorkspace())) {
await RemoteClient.bootstrapRepository(); await RemoteClient.bootstrapRepository();
} }
await RemoteClient.replaceLargePackageReferencesWithSharedReferences();
await RemoteClient.runCustomHookFiles(`before-build`); await RemoteClient.runCustomHookFiles(`before-build`);
} }
@CliFunction('remote-cli-log-stream', `log stream from standard input`)
public static async remoteClientLogStream() {
const logFile = Cli.options!['logFile'];
process.stdin.resume();
process.stdin.setEncoding('utf8');
let lingeringLine = '';
process.stdin.on('data', (chunk) => {
const lines = chunk.toString().split('\n');
lines[0] = lingeringLine + lines[0];
lingeringLine = lines.pop() || '';
for (const element of lines) {
if (CloudRunnerOptions.providerStrategy !== 'k8s') {
CloudRunnerLogger.log(element);
} else {
fs.appendFileSync(logFile, element);
CloudRunnerLogger.log(element);
}
}
});
process.stdin.on('end', () => {
if (CloudRunnerOptions.providerStrategy !== 'k8s') {
CloudRunnerLogger.log(lingeringLine);
} else {
fs.appendFileSync(logFile, lingeringLine);
CloudRunnerLogger.log(lingeringLine);
}
});
}
@CliFunction(`remote-cli-post-build`, `runs a cloud runner build`)
public static async remoteClientPostBuild(): Promise<string> {
RemoteClientLogger.log(`Running POST build tasks`);
await Caching.PushToCache(
CloudRunnerFolders.ToLinuxFolder(`${CloudRunnerFolders.cacheFolderForCacheKeyFull}/Library`),
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.libraryFolderAbsolute),
`lib-${CloudRunner.buildParameters.buildGuid}`,
);
await Caching.PushToCache(
CloudRunnerFolders.ToLinuxFolder(`${CloudRunnerFolders.cacheFolderForCacheKeyFull}/build`),
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectBuildFolderAbsolute),
`build-${CloudRunner.buildParameters.buildGuid}`,
);
if (!BuildParameters.shouldUseRetainedWorkspaceMode(CloudRunner.buildParameters)) {
await CloudRunnerSystem.Run(
`rm -r ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.uniqueCloudRunnerJobFolderAbsolute)}`,
);
}
await RemoteClient.runCustomHookFiles(`after-build`);
// WIP - need to give the pod permissions to create config map
await RemoteClientLogger.handleLogManagementPostJob();
return new Promise((result) => result(``));
}
static async runCustomHookFiles(hookLifecycle: string) { static async runCustomHookFiles(hookLifecycle: string) {
RemoteClientLogger.log(`RunCustomHookFiles: ${hookLifecycle}`); RemoteClientLogger.log(`RunCustomHookFiles: ${hookLifecycle}`);
const gameCiCustomHooksPath = path.join(CloudRunnerFolders.repoPathAbsolute, `game-ci`, `hooks`); const gameCiCustomHooksPath = path.join(CloudRunnerFolders.repoPathAbsolute, `game-ci`, `hooks`);
@ -114,6 +47,7 @@ export class RemoteClient {
`mkdir -p ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.cacheFolderForCacheKeyFull)}`, `mkdir -p ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.cacheFolderForCacheKeyFull)}`,
); );
await RemoteClient.cloneRepoWithoutLFSFiles(); await RemoteClient.cloneRepoWithoutLFSFiles();
await RemoteClient.replaceLargePackageReferencesWithSharedReferences();
await RemoteClient.sizeOfFolder( await RemoteClient.sizeOfFolder(
'repo before lfs cache pull', 'repo before lfs cache pull',
CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute), CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute),

View File

@ -1,18 +1,8 @@
import CloudRunnerLogger from '../services/core/cloud-runner-logger'; import CloudRunnerLogger from '../services/core/cloud-runner-logger';
import fs from 'node:fs';
import path from 'node:path';
import CloudRunner from '../cloud-runner';
import CloudRunnerOptions from '../options/cloud-runner-options';
export class RemoteClientLogger { export class RemoteClientLogger {
private static get LogFilePath() {
return path.join(`/home`, `job-log.txt`);
}
public static log(message: string) { public static log(message: string) {
const finalMessage = `[Client] ${message}`; CloudRunnerLogger.log(`[Client] ${message}`);
this.appendToFile(finalMessage);
CloudRunnerLogger.log(finalMessage);
} }
public static logCliError(message: string) { public static logCliError(message: string) {
@ -26,57 +16,4 @@ export class RemoteClientLogger {
public static logWarning(message: string) { public static logWarning(message: string) {
CloudRunnerLogger.logWarning(message); CloudRunnerLogger.logWarning(message);
} }
public static appendToFile(message: string) {
if (CloudRunner.isCloudRunnerEnvironment) {
fs.appendFileSync(RemoteClientLogger.LogFilePath, `${message}\n`);
}
}
public static async handleLogManagementPostJob() {
if (CloudRunnerOptions.providerStrategy !== 'k8s') {
return;
}
CloudRunnerLogger.log(`Collected Logs`);
// check for log file not existing
if (!fs.existsSync(RemoteClientLogger.LogFilePath)) {
CloudRunnerLogger.log(`Log file does not exist`);
// check if CloudRunner.isCloudRunnerEnvironment is true, log
if (!CloudRunner.isCloudRunnerEnvironment) {
CloudRunnerLogger.log(`Cloud Runner is not running in a cloud environment, not collecting logs`);
}
return;
}
CloudRunnerLogger.log(`Log file exist`);
await new Promise((resolve) => setTimeout(resolve, 1));
// let hashedLogs = fs.readFileSync(RemoteClientLogger.LogFilePath).toString();
//
// hashedLogs = md5(hashedLogs);
//
// for (let index = 0; index < 3; index++) {
// CloudRunnerLogger.log(`LOGHASH: ${hashedLogs}`);
// const logs = fs.readFileSync(RemoteClientLogger.LogFilePath).toString();
// CloudRunnerLogger.log(`LOGS: ${Buffer.from(logs).toString('base64')}`);
// CloudRunnerLogger.log(
// `Game CI's "Cloud Runner System" will cancel the log when it has successfully received the log data to verify all logs have been received.`,
// );
//
// // wait for 15 seconds to allow the log to be sent
// await new Promise((resolve) => setTimeout(resolve, 15000));
// }
}
public static HandleLog(message: string): boolean {
if (RemoteClientLogger.value !== '') {
RemoteClientLogger.value += `\n`;
}
RemoteClientLogger.value += message;
return false;
}
static value: string = '';
} }

View File

@ -1,24 +0,0 @@
import BuildParameters from '../../../build-parameters';
class CloudRunnerResult {
public BuildParameters: BuildParameters;
public BuildResults: string;
public BuildSucceeded: boolean;
public BuildFinished: boolean;
public LibraryCacheUsed: boolean;
public constructor(
buildParameters: BuildParameters,
buildResults: string,
buildSucceeded: boolean,
buildFinished: boolean,
libraryCacheUsed: boolean,
) {
this.BuildParameters = buildParameters;
this.BuildResults = buildResults;
this.BuildSucceeded = buildSucceeded;
this.BuildFinished = buildFinished;
this.LibraryCacheUsed = libraryCacheUsed;
}
}
export default CloudRunnerResult;

View File

@ -16,13 +16,7 @@ export class CloudRunnerSystem {
}); });
} }
public static async Run( public static async Run(command: string, suppressError = false, suppressLogs = false) {
command: string,
suppressError = false,
suppressLogs = false,
// eslint-disable-next-line no-unused-vars
outputCallback?: (output: string) => void,
) {
for (const element of command.split(`\n`)) { for (const element of command.split(`\n`)) {
if (!suppressLogs) { if (!suppressLogs) {
RemoteClientLogger.log(element); RemoteClientLogger.log(element);
@ -31,7 +25,7 @@ export class CloudRunnerSystem {
return await new Promise<string>((promise, throwError) => { return await new Promise<string>((promise, throwError) => {
let output = ''; let output = '';
const child = exec(command, { maxBuffer: 1024 * 10000 }, (error, stdout, stderr) => { const child = exec(command, (error, stdout, stderr) => {
if (!suppressError && error) { if (!suppressError && error) {
RemoteClientLogger.log(error.toString()); RemoteClientLogger.log(error.toString());
throwError(error); throwError(error);
@ -44,9 +38,6 @@ export class CloudRunnerSystem {
output += diagnosticOutput; output += diagnosticOutput;
} }
const outputChunk = `${stdout}`; const outputChunk = `${stdout}`;
if (outputCallback) {
outputCallback(outputChunk);
}
output += outputChunk; output += outputChunk;
}); });
child.on('close', (code) => { child.on('close', (code) => {

View File

@ -146,8 +146,7 @@ export class TaskParameterSerializer {
array = TaskParameterSerializer.tryAddInput(array, 'UNITY_SERIAL'); array = TaskParameterSerializer.tryAddInput(array, 'UNITY_SERIAL');
array = TaskParameterSerializer.tryAddInput(array, 'UNITY_EMAIL'); array = TaskParameterSerializer.tryAddInput(array, 'UNITY_EMAIL');
array = TaskParameterSerializer.tryAddInput(array, 'UNITY_PASSWORD'); array = TaskParameterSerializer.tryAddInput(array, 'UNITY_PASSWORD');
array = TaskParameterSerializer.tryAddInput(array, 'UNITY_LICENSE');
// array = TaskParameterSerializer.tryAddInput(array, 'UNITY_LICENSE');
array = TaskParameterSerializer.tryAddInput(array, 'GIT_PRIVATE_TOKEN'); array = TaskParameterSerializer.tryAddInput(array, 'GIT_PRIVATE_TOKEN');
return array; return array;

View File

@ -1,5 +1,6 @@
import YAML from 'yaml'; import YAML from 'yaml';
import CloudRunner from '../../cloud-runner'; import CloudRunner from '../../cloud-runner';
import * as core from '@actions/core';
import { CustomWorkflow } from '../../workflows/custom-workflow'; import { CustomWorkflow } from '../../workflows/custom-workflow';
import { RemoteClientLogger } from '../../remote-client/remote-client-logger'; import { RemoteClientLogger } from '../../remote-client/remote-client-logger';
import path from 'node:path'; import path from 'node:path';
@ -236,11 +237,13 @@ export class ContainerHookService {
]; ];
if (steps.length > 0) { if (steps.length > 0) {
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('post build steps');
output += await CustomWorkflow.runContainerJob( output += await CustomWorkflow.runContainerJob(
steps, steps,
cloudRunnerStepState.environment, cloudRunnerStepState.environment,
cloudRunnerStepState.secrets, cloudRunnerStepState.secrets,
); );
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
} }
return output; return output;
@ -253,11 +256,13 @@ export class ContainerHookService {
]; ];
if (steps.length > 0) { if (steps.length > 0) {
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('pre build steps');
output += await CustomWorkflow.runContainerJob( output += await CustomWorkflow.runContainerJob(
steps, steps,
cloudRunnerStepState.environment, cloudRunnerStepState.environment,
cloudRunnerStepState.secrets, cloudRunnerStepState.secrets,
); );
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
} }
return output; return output;

View File

@ -24,17 +24,11 @@ describe('Cloud Runner Async Workflows', () => {
unityVersion: UnityVersioning.read('test-project'), unityVersion: UnityVersioning.read('test-project'),
asyncCloudRunner: `true`, asyncCloudRunner: `true`,
githubChecks: `true`, githubChecks: `true`,
providerStrategy: 'k8s',
buildPlatform: 'linux',
targetPlatform: 'StandaloneLinux64',
}); });
const baseImage = new ImageTag(buildParameter); const baseImage = new ImageTag(buildParameter);
// Run the job // Run the job
await CloudRunner.run(buildParameter, baseImage.toString()); await CloudRunner.run(buildParameter, baseImage.toString());
// wait for 15 seconds
await new Promise((resolve) => setTimeout(resolve, 1000 * 60 * 12));
}, 1_000_000_000); }, 1_000_000_000);
} }
}); });

View File

@ -34,7 +34,6 @@ describe('Cloud Runner Sync Environments', () => {
versioning: 'None', versioning: 'None',
projectPath: 'test-project', projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'), unityVersion: UnityVersioning.read('test-project'),
targetPlatform: 'StandaloneWindows64',
customJob: ` customJob: `
- name: 'step 1' - name: 'step 1'
image: 'ubuntu' image: 'ubuntu'
@ -45,12 +44,9 @@ describe('Cloud Runner Sync Environments', () => {
`, `,
}); });
const baseImage = new ImageTag(buildParameter); const baseImage = new ImageTag(buildParameter);
if (baseImage.toString().includes('undefined')) {
throw new Error(`Base image is undefined`);
}
// Run the job // Run the job
const file = (await CloudRunner.run(buildParameter, baseImage.toString())).BuildResults; const file = await CloudRunner.run(buildParameter, baseImage.toString());
// Assert results // Assert results
// expect(file).toContain(JSON.stringify(buildParameter)); // expect(file).toContain(JSON.stringify(buildParameter));

View File

@ -1,59 +0,0 @@
import { BuildParameters } from '../..';
import CloudRunner from '../cloud-runner';
import UnityVersioning from '../../unity-versioning';
import { Cli } from '../../cli/cli';
import CloudRunnerOptions from '../options/cloud-runner-options';
import setups from './cloud-runner-suite.test';
import { OptionValues } from 'commander';
import GitHub from '../../github';
export const TIMEOUT_INFINITE = 1e9;
async function CreateParameters(overrides: OptionValues | undefined) {
if (overrides) Cli.options = overrides;
return BuildParameters.create();
}
describe('Cloud Runner Github Checks', () => {
setups();
it('Responds', () => {});
if (CloudRunnerOptions.cloudRunnerDebug) {
it(
'Check Handling Direct',
async () => {
// Setup parameters
const buildParameter = await CreateParameters({
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
await CloudRunner.setup(buildParameter);
CloudRunner.buildParameters.githubCheckId = await GitHub.createGitHubCheck(`direct create`);
await GitHub.updateGitHubCheck(`1 ${new Date().toISOString()}`, `direct`);
await GitHub.updateGitHubCheck(`2 ${new Date().toISOString()}`, `direct`, `success`, `completed`);
},
TIMEOUT_INFINITE,
);
it(
'Check Handling Via Async Workflow',
async () => {
// Setup parameters
const buildParameter = await CreateParameters({
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
GitHub.forceAsyncTest = true;
await CloudRunner.setup(buildParameter);
CloudRunner.buildParameters.githubCheckId = await GitHub.createGitHubCheck(`async create`);
await GitHub.updateGitHubCheck(`1 ${new Date().toISOString()}`, `async`);
await GitHub.updateGitHubCheck(`2 ${new Date().toISOString()}`, `async`, `success`, `completed`);
GitHub.forceAsyncTest = false;
},
TIMEOUT_INFINITE,
);
}
});

View File

@ -30,7 +30,6 @@ commands: echo "test"`;
projectPath: 'test-project', projectPath: 'test-project',
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')), unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
targetPlatform: 'StandaloneLinux64', targetPlatform: 'StandaloneLinux64',
image: 'ubuntu',
cacheKey: `test-case-${uuidv4()}`, cacheKey: `test-case-${uuidv4()}`,
}; };
CloudRunner.setup(await CreateParameters(overrides)); CloudRunner.setup(await CreateParameters(overrides));
@ -52,7 +51,6 @@ commands: echo "test"`;
it('Should be 1 before and 1 after hook', async () => { it('Should be 1 before and 1 after hook', async () => {
const overrides = { const overrides = {
versioning: 'None', versioning: 'None',
image: 'ubuntu',
projectPath: 'test-project', projectPath: 'test-project',
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')), unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
targetPlatform: 'StandaloneLinux64', targetPlatform: 'StandaloneLinux64',
@ -74,7 +72,6 @@ commands: echo "test"`;
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')), unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
targetPlatform: 'StandaloneLinux64', targetPlatform: 'StandaloneLinux64',
cacheKey: `test-case-${uuidv4()}`, cacheKey: `test-case-${uuidv4()}`,
image: 'ubuntu',
containerHookFiles: `my-test-step-pre-build,my-test-step-post-build`, containerHookFiles: `my-test-step-pre-build,my-test-step-post-build`,
commandHookFiles: `my-test-hook-pre-build,my-test-hook-post-build`, commandHookFiles: `my-test-hook-pre-build,my-test-hook-post-build`,
}; };
@ -97,8 +94,7 @@ commands: echo "test"`;
}; };
const buildParameter2 = await CreateParameters(overrides); const buildParameter2 = await CreateParameters(overrides);
const baseImage2 = new ImageTag(buildParameter2); const baseImage2 = new ImageTag(buildParameter2);
const results2Object = await CloudRunner.run(buildParameter2, baseImage2.toString()); const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
const results2 = results2Object.BuildResults;
CloudRunnerLogger.log(`run 2 succeeded`); CloudRunnerLogger.log(`run 2 succeeded`);
const buildContainsBuildSucceeded = results2.includes('Build succeeded'); const buildContainsBuildSucceeded = results2.includes('Build succeeded');

View File

@ -1,51 +0,0 @@
import { BuildParameters, ImageTag } from '../..';
import UnityVersioning from '../../unity-versioning';
import { Cli } from '../../cli/cli';
import GitHub from '../../github';
import setups from './cloud-runner-suite.test';
async function CreateParameters(overrides: any) {
if (overrides) {
Cli.options = overrides;
}
const originalValue = GitHub.githubInputEnabled;
GitHub.githubInputEnabled = false;
const results = await BuildParameters.create();
GitHub.githubInputEnabled = originalValue;
delete Cli.options;
return results;
}
describe('Cloud Runner Image', () => {
setups();
const testSecretName = 'testSecretName';
const testSecretValue = 'testSecretValue';
it('Can create valid image from normal config', async () => {
// Setup parameters
const buildParameter = await CreateParameters({
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
targetPlatform: 'StandaloneWindows64',
customJob: `
- name: 'step 1'
image: 'ubuntu'
commands: 'printenv'
secrets:
- name: '${testSecretName}'
value: '${testSecretValue}'
`,
});
const baseImage = new ImageTag(buildParameter);
if (buildParameter.targetPlatform === undefined) {
throw new Error(`target platform includes undefined`);
}
if (baseImage.toString().includes('undefined')) {
throw new Error(`Base image ${baseImage.toString()} includes undefined`);
}
if (baseImage.toString().includes('NaN')) {
throw new Error(`Base image ${baseImage.toString()} includes nan`);
}
}, 1_000_000_000);
});

View File

@ -32,8 +32,7 @@ describe('Cloud Runner pre-built S3 steps', () => {
}; };
const buildParameter2 = await CreateParameters(overrides); const buildParameter2 = await CreateParameters(overrides);
const baseImage2 = new ImageTag(buildParameter2); const baseImage2 = new ImageTag(buildParameter2);
const results2Object = await CloudRunner.run(buildParameter2, baseImage2.toString()); const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
const results2 = results2Object.BuildResults;
CloudRunnerLogger.log(`run 2 succeeded`); CloudRunnerLogger.log(`run 2 succeeded`);
const build2ContainsBuildSucceeded = results2.includes('Build succeeded'); const build2ContainsBuildSucceeded = results2.includes('Build succeeded');

View File

@ -24,13 +24,11 @@ describe('Cloud Runner Caching', () => {
it('Run one build it should not use cache, run subsequent build which should use cache', async () => { it('Run one build it should not use cache, run subsequent build which should use cache', async () => {
const overrides = { const overrides = {
versioning: 'None', versioning: 'None',
image: 'ubuntu',
projectPath: 'test-project', projectPath: 'test-project',
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')), unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
targetPlatform: 'StandaloneLinux64', targetPlatform: 'StandaloneLinux64',
cacheKey: `test-case-${uuidv4()}`, cacheKey: `test-case-${uuidv4()}`,
containerHookFiles: `debug-cache`, containerHookFiles: `debug-cache`,
cloudRunnerBranch: `cloud-runner-develop`,
}; };
if (CloudRunnerOptions.providerStrategy === `k8s`) { if (CloudRunnerOptions.providerStrategy === `k8s`) {
overrides.containerHookFiles += `,aws-s3-pull-cache,aws-s3-upload-cache`; overrides.containerHookFiles += `,aws-s3-pull-cache,aws-s3-upload-cache`;
@ -39,8 +37,7 @@ describe('Cloud Runner Caching', () => {
expect(buildParameter.projectPath).toEqual(overrides.projectPath); expect(buildParameter.projectPath).toEqual(overrides.projectPath);
const baseImage = new ImageTag(buildParameter); const baseImage = new ImageTag(buildParameter);
const resultsObject = await CloudRunner.run(buildParameter, baseImage.toString()); const results = await CloudRunner.run(buildParameter, baseImage.toString());
const results = resultsObject.BuildResults;
const libraryString = 'Rebuilding Library because the asset database could not be found!'; const libraryString = 'Rebuilding Library because the asset database could not be found!';
const cachePushFail = 'Did not push source folder to cache because it was empty Library'; const cachePushFail = 'Did not push source folder to cache because it was empty Library';
const buildSucceededString = 'Build succeeded'; const buildSucceededString = 'Build succeeded';
@ -66,12 +63,12 @@ describe('Cloud Runner Caching', () => {
buildParameter2.cacheKey = buildParameter.cacheKey; buildParameter2.cacheKey = buildParameter.cacheKey;
const baseImage2 = new ImageTag(buildParameter2); const baseImage2 = new ImageTag(buildParameter2);
const results2Object = await CloudRunner.run(buildParameter2, baseImage2.toString()); const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
const results2 = results2Object.BuildResults;
CloudRunnerLogger.log(`run 2 succeeded`); CloudRunnerLogger.log(`run 2 succeeded`);
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey); const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
const build2ContainsBuildSucceeded = results2.includes(buildSucceededString); const build2ContainsBuildSucceeded = results2.includes(buildSucceededString);
const build2NotContainsNoLibraryMessage = !results2.includes(libraryString);
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes( const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
'There is 0 files/dir in the cache pulled contents for Library', 'There is 0 files/dir in the cache pulled contents for Library',
); );
@ -80,13 +77,10 @@ describe('Cloud Runner Caching', () => {
); );
expect(build2ContainsCacheKey).toBeTruthy(); expect(build2ContainsCacheKey).toBeTruthy();
expect(results2).toContain('Activation successful');
expect(build2ContainsBuildSucceeded).toBeTruthy(); expect(build2ContainsBuildSucceeded).toBeTruthy();
expect(results2).toContain(buildSucceededString);
const splitResults = results2.split('Activation successful');
expect(splitResults[splitResults.length - 1]).not.toContain(libraryString);
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy(); expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy(); expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
expect(build2NotContainsNoLibraryMessage).toBeTruthy();
}, 1_000_000_000); }, 1_000_000_000);
} }
}); });

View File

@ -29,8 +29,7 @@ describe('Cloud Runner Retain Workspace', () => {
expect(buildParameter.projectPath).toEqual(overrides.projectPath); expect(buildParameter.projectPath).toEqual(overrides.projectPath);
const baseImage = new ImageTag(buildParameter); const baseImage = new ImageTag(buildParameter);
const resultsObject = await CloudRunner.run(buildParameter, baseImage.toString()); const results = await CloudRunner.run(buildParameter, baseImage.toString());
const results = resultsObject.BuildResults;
const libraryString = 'Rebuilding Library because the asset database could not be found!'; const libraryString = 'Rebuilding Library because the asset database could not be found!';
const cachePushFail = 'Did not push source folder to cache because it was empty Library'; const cachePushFail = 'Did not push source folder to cache because it was empty Library';
const buildSucceededString = 'Build succeeded'; const buildSucceededString = 'Build succeeded';
@ -52,8 +51,7 @@ describe('Cloud Runner Retain Workspace', () => {
buildParameter2.cacheKey = buildParameter.cacheKey; buildParameter2.cacheKey = buildParameter.cacheKey;
const baseImage2 = new ImageTag(buildParameter2); const baseImage2 = new ImageTag(buildParameter2);
const results2Object = await CloudRunner.run(buildParameter2, baseImage2.toString()); const results2 = await CloudRunner.run(buildParameter2, baseImage2.toString());
const results2 = results2Object.BuildResults;
CloudRunnerLogger.log(`run 2 succeeded`); CloudRunnerLogger.log(`run 2 succeeded`);
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey); const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
@ -61,6 +59,7 @@ describe('Cloud Runner Retain Workspace', () => {
const build2ContainsRetainedWorkspacePhrase = results2.includes(`Retained Workspace:`); const build2ContainsRetainedWorkspacePhrase = results2.includes(`Retained Workspace:`);
const build2ContainsWorkspaceExistsAlreadyPhrase = results2.includes(`Retained Workspace Already Exists!`); const build2ContainsWorkspaceExistsAlreadyPhrase = results2.includes(`Retained Workspace Already Exists!`);
const build2ContainsBuildSucceeded = results2.includes(buildSucceededString); const build2ContainsBuildSucceeded = results2.includes(buildSucceededString);
const build2NotContainsNoLibraryMessage = !results2.includes(libraryString);
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes( const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
'There is 0 files/dir in the cache pulled contents for Library', 'There is 0 files/dir in the cache pulled contents for Library',
); );
@ -75,8 +74,7 @@ describe('Cloud Runner Retain Workspace', () => {
expect(build2ContainsBuildSucceeded).toBeTruthy(); expect(build2ContainsBuildSucceeded).toBeTruthy();
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy(); expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy(); expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
const splitResults = results2.split('Activation successful'); expect(build2NotContainsNoLibraryMessage).toBeTruthy();
expect(splitResults[splitResults.length - 1]).not.toContain(libraryString);
}, 1_000_000_000); }, 1_000_000_000);
afterAll(async () => { afterAll(async () => {
await SharedWorkspaceLocking.CleanupWorkspace(CloudRunner.lockedWorkspace || ``, CloudRunner.buildParameters); await SharedWorkspaceLocking.CleanupWorkspace(CloudRunner.lockedWorkspace || ``, CloudRunner.buildParameters);

View File

@ -1,56 +0,0 @@
import CloudRunner from '../../cloud-runner';
import UnityVersioning from '../../../unity-versioning';
import { Cli } from '../../../cli/cli';
import CloudRunnerLogger from '../../services/core/cloud-runner-logger';
import { v4 as uuidv4 } from 'uuid';
import CloudRunnerOptions from '../../options/cloud-runner-options';
import setups from '../cloud-runner-suite.test';
import BuildParameters from '../../../build-parameters';
import ImageTag from '../../../image-tag';
async function CreateParameters(overrides: any) {
if (overrides) {
Cli.options = overrides;
}
return await BuildParameters.create();
}
describe('Cloud Runner Kubernetes', () => {
it('Responds', () => {});
setups();
if (CloudRunnerOptions.cloudRunnerDebug) {
it('Run one build it using K8s without error', async () => {
if (CloudRunnerOptions.providerStrategy !== `k8s`) {
return;
}
process.env.USE_IL2CPP = 'false';
const overrides = {
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
targetPlatform: 'StandaloneLinux64',
cacheKey: `test-case-${uuidv4()}`,
providerStrategy: 'k8s',
buildPlatform: 'linux',
};
const buildParameter = await CreateParameters(overrides);
expect(buildParameter.projectPath).toEqual(overrides.projectPath);
const baseImage = new ImageTag(buildParameter);
const resultsObject = await CloudRunner.run(buildParameter, baseImage.toString());
const results = resultsObject.BuildResults;
const libraryString = 'Rebuilding Library because the asset database could not be found!';
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
const buildSucceededString = 'Build succeeded';
expect(results).toContain('Collected Logs');
expect(results).toContain(libraryString);
expect(results).toContain(buildSucceededString);
expect(results).not.toContain(cachePushFail);
CloudRunnerLogger.log(`run 1 succeeded`);
}, 1_000_000_000);
}
});

View File

@ -41,11 +41,6 @@ node /builder/dist/index.js -m async-workflow`,
[ [
...secrets, ...secrets,
...[ ...[
{
ParameterKey: `GITHUB_TOKEN`,
EnvironmentVariable: `GITHUB_TOKEN`,
ParameterValue: process.env.GITHUB_TOKEN || ``,
},
{ {
ParameterKey: `AWS_ACCESS_KEY_ID`, ParameterKey: `AWS_ACCESS_KEY_ID`,
EnvironmentVariable: `AWS_ACCESS_KEY_ID`, EnvironmentVariable: `AWS_ACCESS_KEY_ID`,

View File

@ -2,6 +2,7 @@ import CloudRunnerLogger from '../services/core/cloud-runner-logger';
import { CloudRunnerFolders } from '../options/cloud-runner-folders'; import { CloudRunnerFolders } from '../options/cloud-runner-folders';
import { CloudRunnerStepParameters } from '../options/cloud-runner-step-parameters'; import { CloudRunnerStepParameters } from '../options/cloud-runner-step-parameters';
import { WorkflowInterface } from './workflow-interface'; import { WorkflowInterface } from './workflow-interface';
import * as core from '@actions/core';
import { CommandHookService } from '../services/hooks/command-hook-service'; import { CommandHookService } from '../services/hooks/command-hook-service';
import path from 'node:path'; import path from 'node:path';
import CloudRunner from '../cloud-runner'; import CloudRunner from '../cloud-runner';
@ -20,6 +21,8 @@ export class BuildAutomationWorkflow implements WorkflowInterface {
output += await ContainerHookService.RunPreBuildSteps(cloudRunnerStepState); output += await ContainerHookService.RunPreBuildSteps(cloudRunnerStepState);
CloudRunnerLogger.logWithTime('Configurable pre build step(s) time'); CloudRunnerLogger.logWithTime('Configurable pre build step(s) time');
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('build');
CloudRunnerLogger.log(baseImage); CloudRunnerLogger.log(baseImage);
CloudRunnerLogger.logLine(` `); CloudRunnerLogger.logLine(` `);
CloudRunnerLogger.logLine('Starting build automation job'); CloudRunnerLogger.logLine('Starting build automation job');
@ -33,6 +36,7 @@ export class BuildAutomationWorkflow implements WorkflowInterface {
cloudRunnerStepState.environment, cloudRunnerStepState.environment,
cloudRunnerStepState.secrets, cloudRunnerStepState.secrets,
); );
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
CloudRunnerLogger.logWithTime('Build time'); CloudRunnerLogger.logWithTime('Build time');
output += await ContainerHookService.RunPostBuildSteps(cloudRunnerStepState); output += await ContainerHookService.RunPostBuildSteps(cloudRunnerStepState);
@ -54,14 +58,11 @@ export class BuildAutomationWorkflow implements WorkflowInterface {
path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', `index.js`), path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', `index.js`),
); );
return `echo "cloud runner build workflow starting" return `apt-get update > /dev/null
apt-get update > /dev/null
apt-get install -y curl tar tree npm git-lfs jq git > /dev/null apt-get install -y curl tar tree npm git-lfs jq git > /dev/null
npm --version
npm i -g n > /dev/null npm i -g n > /dev/null
npm i -g semver > /dev/null n 16.15.1 > /dev/null
npm install --global yarn > /dev/null npm --version
n 20.8.0
node --version node --version
${setupHooks.filter((x) => x.hook.includes(`before`)).map((x) => x.commands) || ' '} ${setupHooks.filter((x) => x.hook.includes(`before`)).map((x) => x.commands) || ' '}
export GITHUB_WORKSPACE="${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute)}" export GITHUB_WORKSPACE="${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.repoPathAbsolute)}"
@ -90,7 +91,6 @@ export class BuildAutomationWorkflow implements WorkflowInterface {
return `export GIT_DISCOVERY_ACROSS_FILESYSTEM=1 return `export GIT_DISCOVERY_ACROSS_FILESYSTEM=1
${cloneBuilderCommands} ${cloneBuilderCommands}
echo "log start" >> /home/job-log.txt
node ${builderPath} -m remote-cli-pre-build`; node ${builderPath} -m remote-cli-pre-build`;
} }
@ -98,7 +98,7 @@ node ${builderPath} -m remote-cli-pre-build`;
const distFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist'); const distFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist');
const ubuntuPlatformsFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', 'platforms', 'ubuntu'); const ubuntuPlatformsFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', 'platforms', 'ubuntu');
return ` return `echo "game ci cloud runner initalized"
mkdir -p ${`${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectBuildFolderAbsolute)}/build`} mkdir -p ${`${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectBuildFolderAbsolute)}/build`}
cd ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectPathAbsolute)} cd ${CloudRunnerFolders.ToLinuxFolder(CloudRunnerFolders.projectPathAbsolute)}
cp -r "${CloudRunnerFolders.ToLinuxFolder(path.join(distFolder, 'default-build-script'))}" "/UnityBuilderAction" cp -r "${CloudRunnerFolders.ToLinuxFolder(path.join(distFolder, 'default-build-script'))}" "/UnityBuilderAction"
@ -107,8 +107,9 @@ node ${builderPath} -m remote-cli-pre-build`;
chmod -R +x "/entrypoint.sh" chmod -R +x "/entrypoint.sh"
chmod -R +x "/steps" chmod -R +x "/steps"
echo "game ci start" echo "game ci start"
echo "game ci start" >> /home/job-log.txt /entrypoint.sh
/entrypoint.sh | node ${builderPath} -m remote-cli-log-stream --logFile /home/job-log.txt echo "game ci caching results"
chmod +x ${builderPath}
node ${builderPath} -m remote-cli-post-build`; node ${builderPath} -m remote-cli-post-build`;
} }
} }

View File

@ -1,7 +1,8 @@
import { execWithErrorCheck } from './exec-with-error-check';
import ImageEnvironmentFactory from './image-environment-factory'; import ImageEnvironmentFactory from './image-environment-factory';
import { existsSync, mkdirSync } from 'node:fs'; import { existsSync, mkdirSync } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { ExecOptions, exec } from '@actions/exec'; import { ExecOptions } from '@actions/exec';
import { DockerParameters, StringKeyValuePair } from './shared-types'; import { DockerParameters, StringKeyValuePair } from './shared-types';
class Docker { class Docker {
@ -11,9 +12,11 @@ class Docker {
silent: boolean = false, silent: boolean = false,
overrideCommands: string = '', overrideCommands: string = '',
additionalVariables: StringKeyValuePair[] = [], additionalVariables: StringKeyValuePair[] = [],
options: ExecOptions = {}, // eslint-disable-next-line unicorn/no-useless-undefined
options: ExecOptions | undefined = undefined,
entrypointBash: boolean = false, entrypointBash: boolean = false,
): Promise<number> { errorWhenMissingUnityBuildResults: boolean = true,
) {
let runCommand = ''; let runCommand = '';
switch (process.platform) { switch (process.platform) {
case 'linux': case 'linux':
@ -21,15 +24,13 @@ class Docker {
break; break;
case 'win32': case 'win32':
runCommand = this.getWindowsCommand(image, parameters); runCommand = this.getWindowsCommand(image, parameters);
break;
default:
throw new Error(`Operation system, ${process.platform}, is not supported yet.`);
} }
if (options) {
options.silent = silent; options.silent = silent;
options.ignoreReturnCode = true; await execWithErrorCheck(runCommand, undefined, options, errorWhenMissingUnityBuildResults);
} else {
return await exec(runCommand, undefined, options); await execWithErrorCheck(runCommand, undefined, { silent }, errorWhenMissingUnityBuildResults);
}
} }
static getLinuxCommand( static getLinuxCommand(
@ -47,8 +48,6 @@ class Docker {
sshPublicKeysDirectoryPath, sshPublicKeysDirectoryPath,
gitPrivateToken, gitPrivateToken,
dockerWorkspacePath, dockerWorkspacePath,
dockerCpuLimit,
dockerMemoryLimit,
} = parameters; } = parameters;
const githubHome = path.join(runnerTempPath, '_github_home'); const githubHome = path.join(runnerTempPath, '_github_home');
@ -61,6 +60,7 @@ class Docker {
--workdir ${dockerWorkspacePath} \ --workdir ${dockerWorkspacePath} \
--rm \ --rm \
${ImageEnvironmentFactory.getEnvVarString(parameters, additionalVariables)} \ ${ImageEnvironmentFactory.getEnvVarString(parameters, additionalVariables)} \
--env UNITY_SERIAL \
--env GITHUB_WORKSPACE=${dockerWorkspacePath} \ --env GITHUB_WORKSPACE=${dockerWorkspacePath} \
--env GIT_CONFIG_EXTENSIONS \ --env GIT_CONFIG_EXTENSIONS \
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \ ${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \
@ -72,9 +72,6 @@ class Docker {
--volume "${actionFolder}/platforms/ubuntu/steps:/steps:z" \ --volume "${actionFolder}/platforms/ubuntu/steps:/steps:z" \
--volume "${actionFolder}/platforms/ubuntu/entrypoint.sh:/entrypoint.sh:z" \ --volume "${actionFolder}/platforms/ubuntu/entrypoint.sh:/entrypoint.sh:z" \
--volume "${actionFolder}/unity-config:/usr/share/unity3d/config/:z" \ --volume "${actionFolder}/unity-config:/usr/share/unity3d/config/:z" \
--volume "${actionFolder}/BlankProject":"/BlankProject:z" \
--cpus=${dockerCpuLimit} \
--memory=${dockerMemoryLimit} \
${sshAgent ? `--volume ${sshAgent}:/ssh-agent` : ''} \ ${sshAgent ? `--volume ${sshAgent}:/ssh-agent` : ''} \
${ ${
sshAgent && !sshPublicKeysDirectoryPath sshAgent && !sshPublicKeysDirectoryPath
@ -89,40 +86,23 @@ class Docker {
} }
static getWindowsCommand(image: string, parameters: DockerParameters): string { static getWindowsCommand(image: string, parameters: DockerParameters): string {
const { const { workspace, actionFolder, unitySerial, gitPrivateToken, dockerWorkspacePath } = parameters;
workspace,
actionFolder,
runnerTempPath,
gitPrivateToken,
dockerWorkspacePath,
dockerCpuLimit,
dockerMemoryLimit,
dockerIsolationMode,
} = parameters;
const githubHome = path.join(runnerTempPath, '_github_home');
if (!existsSync(githubHome)) mkdirSync(githubHome);
return `docker run \ return `docker run \
--workdir c:${dockerWorkspacePath} \ --workdir c:${dockerWorkspacePath} \
--rm \ --rm \
${ImageEnvironmentFactory.getEnvVarString(parameters)} \ ${ImageEnvironmentFactory.getEnvVarString(parameters)} \
--env UNITY_SERIAL="${unitySerial}" \
--env GITHUB_WORKSPACE=c:${dockerWorkspacePath} \ --env GITHUB_WORKSPACE=c:${dockerWorkspacePath} \
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \ ${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \
--volume "${workspace}":"c:${dockerWorkspacePath}" \ --volume "${workspace}":"c:${dockerWorkspacePath}" \
--volume "${githubHome}":"C:/githubhome" \
--volume "c:/regkeys":"c:/regkeys" \ --volume "c:/regkeys":"c:/regkeys" \
--volume "C:/Program Files/Microsoft Visual Studio":"C:/Program Files/Microsoft Visual Studio" \
--volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \ --volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
--volume "C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \ --volume "C:/Program Files (x86)/Windows Kits":"C:/Program Files (x86)/Windows Kits" \
--volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \ --volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
--volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \ --volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
--volume "${actionFolder}/platforms/windows":"c:/steps" \ --volume "${actionFolder}/platforms/windows":"c:/steps" \
--volume "${actionFolder}/unity-config":"C:/ProgramData/Unity/config" \
--volume "${actionFolder}/BlankProject":"c:/BlankProject" \ --volume "${actionFolder}/BlankProject":"c:/BlankProject" \
--cpus=${dockerCpuLimit} \
--memory=${dockerMemoryLimit} \
--isolation=${dockerIsolationMode} \
${image} \ ${image} \
powershell c:/steps/entrypoint.ps1`; powershell c:/steps/entrypoint.ps1`;
} }

View File

@ -0,0 +1,29 @@
import { getExecOutput, ExecOptions } from '@actions/exec';
export async function execWithErrorCheck(
commandLine: string,
arguments_?: string[],
options?: ExecOptions,
errorWhenMissingUnityBuildResults: boolean = true,
): Promise<number> {
const result = await getExecOutput(commandLine, arguments_, options);
if (!errorWhenMissingUnityBuildResults) {
return result.exitCode;
}
// Check for errors in the Build Results section
const match = result.stdout.match(/^#\s*Build results\s*#(.*)^Size:/ms);
if (match) {
const buildResults = match[1];
const errorMatch = buildResults.match(/^Errors:\s*(\d+)$/m);
if (errorMatch && Number.parseInt(errorMatch[1], 10) !== 0) {
throw new Error(`There was an error building the project. Please read the logs for details.`);
}
} else {
throw new Error(`There was an error building the project. Please read the logs for details.`);
}
return result.exitCode;
}

View File

@ -11,7 +11,6 @@ class GitHub {
private static startedDate: string; private static startedDate: string;
private static endedDate: string; private static endedDate: string;
static result: string = ``; static result: string = ``;
static forceAsyncTest: boolean;
private static get octokitDefaultToken() { private static get octokitDefaultToken() {
return new Octokit({ return new Octokit({
auth: process.env.GITHUB_TOKEN, auth: process.env.GITHUB_TOKEN,
@ -52,7 +51,7 @@ class GitHub {
} }
GitHub.startedDate = new Date().toISOString(); GitHub.startedDate = new Date().toISOString();
CloudRunnerLogger.log(`Creating github check`); CloudRunnerLogger.log(`Creating inital github check`);
const data = { const data = {
owner: GitHub.owner, owner: GitHub.owner,
repo: GitHub.repo, repo: GitHub.repo,
@ -79,8 +78,6 @@ class GitHub {
}; };
const result = await GitHub.createGitHubCheckRequest(data); const result = await GitHub.createGitHubCheckRequest(data);
CloudRunnerLogger.log(`Creating github check ${result.status}`);
return result.data.id.toString(); return result.data.id.toString();
} }
@ -130,7 +127,7 @@ class GitHub {
data.conclusion = result; data.conclusion = result;
} }
await (CloudRunner.isCloudRunnerAsyncEnvironment || GitHub.forceAsyncTest await (CloudRunner.isCloudRunnerAsyncEnvironment
? GitHub.runUpdateAsyncChecksWorkflow(data, `update`) ? GitHub.runUpdateAsyncChecksWorkflow(data, `update`)
: GitHub.updateGitHubCheckRequest(data)); : GitHub.updateGitHubCheckRequest(data));
} }
@ -177,46 +174,38 @@ class GitHub {
static async triggerWorkflowOnComplete(triggerWorkflowOnComplete: string[]) { static async triggerWorkflowOnComplete(triggerWorkflowOnComplete: string[]) {
const isLocalAsync = CloudRunner.buildParameters.asyncWorkflow && !CloudRunner.isCloudRunnerAsyncEnvironment; const isLocalAsync = CloudRunner.buildParameters.asyncWorkflow && !CloudRunner.isCloudRunnerAsyncEnvironment;
if (isLocalAsync || triggerWorkflowOnComplete === undefined || triggerWorkflowOnComplete.length === 0) { if (isLocalAsync) {
return; return;
} }
try { const workflowsResult = await GitHub.octokitPAT.request(`GET /repos/{owner}/{repo}/actions/workflows`, {
const workflowsResult = await GitHub.octokitPAT.request(`GET /repos/{owner}/{repo}/actions/workflows`, { owner: GitHub.owner,
repo: GitHub.repo,
});
const workflows = workflowsResult.data.workflows;
CloudRunnerLogger.log(`Got ${workflows.length} workflows`);
for (const element of triggerWorkflowOnComplete) {
let selectedId = ``;
for (let index = 0; index < workflowsResult.data.total_count; index++) {
if (workflows[index].name === element) {
selectedId = workflows[index].id.toString();
}
}
if (selectedId === ``) {
core.info(JSON.stringify(workflows));
throw new Error(`no workflow with name "${GitHub.asyncChecksApiWorkflowName}"`);
}
await GitHub.octokitPAT.request(`POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches`, {
owner: GitHub.owner, owner: GitHub.owner,
repo: GitHub.repo, repo: GitHub.repo,
// eslint-disable-next-line camelcase
workflow_id: selectedId,
ref: CloudRunnerOptions.branch,
inputs: {
buildGuid: CloudRunner.buildParameters.buildGuid,
},
}); });
const workflows = workflowsResult.data.workflows;
CloudRunnerLogger.log(`Got ${workflows.length} workflows`);
for (const element of triggerWorkflowOnComplete) {
let selectedId = ``;
for (let index = 0; index < workflowsResult.data.total_count; index++) {
if (workflows[index].name === element) {
selectedId = workflows[index].id.toString();
}
}
if (selectedId === ``) {
core.info(JSON.stringify(workflows));
throw new Error(`no workflow with name "${GitHub.asyncChecksApiWorkflowName}"`);
}
await GitHub.octokitPAT.request(`POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches`, {
owner: GitHub.owner,
repo: GitHub.repo,
// eslint-disable-next-line camelcase
workflow_id: selectedId,
ref: CloudRunnerOptions.branch,
inputs: {
buildGuid: CloudRunner.buildParameters.buildGuid,
},
});
}
} catch {
core.info(`github workflow complete hook not found`);
} }
} }
public static async getCheckStatus() {
return await GitHub.octokitDefaultToken.request(`GET /repos/{owner}/{repo}/check-runs/{check_run_id}`);
}
} }
export default GitHub; export default GitHub;

View File

@ -1,3 +1,4 @@
import { ReadLicense } from './input-readers/test-license-reader';
import { DockerParameters, StringKeyValuePair } from './shared-types'; import { DockerParameters, StringKeyValuePair } from './shared-types';
class ImageEnvironmentFactory { class ImageEnvironmentFactory {
@ -22,50 +23,32 @@ class ImageEnvironmentFactory {
public static getEnvironmentVariables(parameters: DockerParameters, additionalVariables: StringKeyValuePair[] = []) { public static getEnvironmentVariables(parameters: DockerParameters, additionalVariables: StringKeyValuePair[] = []) {
let environmentVariables: StringKeyValuePair[] = [ let environmentVariables: StringKeyValuePair[] = [
{ name: 'UNITY_LICENSE', value: process.env.UNITY_LICENSE || ReadLicense() },
{ name: 'UNITY_LICENSE_FILE', value: process.env.UNITY_LICENSE_FILE },
{ name: 'UNITY_EMAIL', value: process.env.UNITY_EMAIL }, { name: 'UNITY_EMAIL', value: process.env.UNITY_EMAIL },
{ name: 'UNITY_PASSWORD', value: process.env.UNITY_PASSWORD }, { name: 'UNITY_PASSWORD', value: process.env.UNITY_PASSWORD },
{ name: 'UNITY_SERIAL', value: parameters.unitySerial }, { name: 'UNITY_SERIAL', value: parameters.unitySerial },
{ { name: 'UNITY_LICENSING_SERVER', value: parameters.unityLicensingServer },
name: 'UNITY_LICENSING_SERVER',
value: parameters.unityLicensingServer,
},
{ name: 'SKIP_ACTIVATION', value: parameters.skipActivation },
{ name: 'UNITY_VERSION', value: parameters.editorVersion }, { name: 'UNITY_VERSION', value: parameters.editorVersion },
{ { name: 'USYM_UPLOAD_AUTH_TOKEN', value: process.env.USYM_UPLOAD_AUTH_TOKEN },
name: 'USYM_UPLOAD_AUTH_TOKEN',
value: process.env.USYM_UPLOAD_AUTH_TOKEN,
},
{ name: 'PROJECT_PATH', value: parameters.projectPath }, { name: 'PROJECT_PATH', value: parameters.projectPath },
{ name: 'BUILD_PROFILE', value: parameters.buildProfile },
{ name: 'BUILD_TARGET', value: parameters.targetPlatform }, { name: 'BUILD_TARGET', value: parameters.targetPlatform },
{ name: 'BUILD_NAME', value: parameters.buildName }, { name: 'BUILD_NAME', value: parameters.buildName },
{ name: 'BUILD_PATH', value: parameters.buildPath }, { name: 'BUILD_PATH', value: parameters.buildPath },
{ name: 'BUILD_FILE', value: parameters.buildFile }, { name: 'BUILD_FILE', value: parameters.buildFile },
{ name: 'BUILD_METHOD', value: parameters.buildMethod }, { name: 'BUILD_METHOD', value: parameters.buildMethod },
{ name: 'MANUAL_EXIT', value: parameters.manualExit },
{ name: 'ENABLE_GPU', value: parameters.enableGpu },
{ name: 'VERSION', value: parameters.buildVersion }, { name: 'VERSION', value: parameters.buildVersion },
{ name: 'ANDROID_VERSION_CODE', value: parameters.androidVersionCode }, { name: 'ANDROID_VERSION_CODE', value: parameters.androidVersionCode },
{ name: 'ANDROID_KEYSTORE_NAME', value: parameters.androidKeystoreName }, { name: 'ANDROID_KEYSTORE_NAME', value: parameters.androidKeystoreName },
{ { name: 'ANDROID_KEYSTORE_BASE64', value: parameters.androidKeystoreBase64 },
name: 'ANDROID_KEYSTORE_BASE64',
value: parameters.androidKeystoreBase64,
},
{ name: 'ANDROID_KEYSTORE_PASS', value: parameters.androidKeystorePass }, { name: 'ANDROID_KEYSTORE_PASS', value: parameters.androidKeystorePass },
{ name: 'ANDROID_KEYALIAS_NAME', value: parameters.androidKeyaliasName }, { name: 'ANDROID_KEYALIAS_NAME', value: parameters.androidKeyaliasName },
{ name: 'ANDROID_KEYALIAS_PASS', value: parameters.androidKeyaliasPass }, { name: 'ANDROID_KEYALIAS_PASS', value: parameters.androidKeyaliasPass },
{ { name: 'ANDROID_TARGET_SDK_VERSION', value: parameters.androidTargetSdkVersion },
name: 'ANDROID_TARGET_SDK_VERSION', { name: 'ANDROID_SDK_MANAGER_PARAMETERS', value: parameters.androidSdkManagerParameters },
value: parameters.androidTargetSdkVersion,
},
{
name: 'ANDROID_SDK_MANAGER_PARAMETERS',
value: parameters.androidSdkManagerParameters,
},
{ name: 'ANDROID_EXPORT_TYPE', value: parameters.androidExportType }, { name: 'ANDROID_EXPORT_TYPE', value: parameters.androidExportType },
{ name: 'ANDROID_SYMBOL_TYPE', value: parameters.androidSymbolType }, { name: 'ANDROID_SYMBOL_TYPE', value: parameters.androidSymbolType },
{ name: 'CUSTOM_PARAMETERS', value: parameters.customParameters }, { name: 'CUSTOM_PARAMETERS', value: parameters.customParameters },
{ name: 'RUN_AS_HOST_USER', value: parameters.runAsHostUser },
{ name: 'CHOWN_FILES_TO', value: parameters.chownFilesTo }, { name: 'CHOWN_FILES_TO', value: parameters.chownFilesTo },
{ name: 'GITHUB_REF', value: process.env.GITHUB_REF }, { name: 'GITHUB_REF', value: process.env.GITHUB_REF },
{ name: 'GITHUB_SHA', value: process.env.GITHUB_SHA }, { name: 'GITHUB_SHA', value: process.env.GITHUB_SHA },
@ -84,19 +67,25 @@ class ImageEnvironmentFactory {
]; ];
if (parameters.providerStrategy === 'local-docker') { if (parameters.providerStrategy === 'local-docker') {
for (const element of additionalVariables) { for (const element of additionalVariables) {
if (!environmentVariables.some((x) => element?.name === x?.name)) { if (
environmentVariables.find(
(x) => element !== undefined && element.name !== undefined && x.name === element.name,
) === undefined
) {
environmentVariables.push(element); environmentVariables.push(element);
} }
} }
for (const variable of environmentVariables) { for (const variable of environmentVariables) {
if (!environmentVariables.some((x) => variable?.name === x?.name)) { if (
environmentVariables.find(
(x) => variable !== undefined && variable.name !== undefined && x.name === variable.name,
) === undefined
) {
environmentVariables = environmentVariables.filter((x) => x !== variable); environmentVariables = environmentVariables.filter((x) => x !== variable);
} }
} }
} }
if (parameters.sshAgent) { if (parameters.sshAgent) environmentVariables.push({ name: 'SSH_AUTH_SOCK', value: '/ssh-agent' });
environmentVariables.push({ name: 'SSH_AUTH_SOCK', value: '/ssh-agent' });
}
return environmentVariables; return environmentVariables;
} }

View File

@ -2,14 +2,14 @@ import ImageTag from './image-tag';
describe('ImageTag', () => { describe('ImageTag', () => {
const testImageParameters = { const testImageParameters = {
editorVersion: '2099.9.9f9', editorVersion: '2099.9.f9f9',
targetPlatform: 'Test', targetPlatform: 'Test',
builderPlatform: '', builderPlatform: '',
containerRegistryRepository: 'unityci/editor',
containerRegistryImageVersion: '3',
}; };
const defaults = { const defaults = {
repository: 'unityci',
name: 'editor',
image: 'unityci/editor', image: 'unityci/editor',
}; };
@ -21,19 +21,16 @@ describe('ImageTag', () => {
it('accepts parameters and sets the right properties', () => { it('accepts parameters and sets the right properties', () => {
const image = new ImageTag(testImageParameters); const image = new ImageTag(testImageParameters);
expect(image.repository).toStrictEqual('unityci/editor'); expect(image.repository).toStrictEqual('unityci');
expect(image.name).toStrictEqual('editor');
expect(image.editorVersion).toStrictEqual(testImageParameters.editorVersion); expect(image.editorVersion).toStrictEqual(testImageParameters.editorVersion);
expect(image.targetPlatform).toStrictEqual(testImageParameters.targetPlatform); expect(image.targetPlatform).toStrictEqual(testImageParameters.targetPlatform);
expect(image.builderPlatform).toStrictEqual(testImageParameters.builderPlatform); expect(image.builderPlatform).toStrictEqual(testImageParameters.builderPlatform);
}); });
test.each(['2000.0.0f0', '2011.1.11f1', '6000.0.0f1'])('accepts %p version format', (version) => { test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', (version) => {
expect( expect(
() => () => new ImageTag({ editorVersion: version, targetPlatform: testImageParameters.targetPlatform }),
new ImageTag({
editorVersion: version,
targetPlatform: testImageParameters.targetPlatform,
}),
).not.toThrow(); ).not.toThrow();
}); });
@ -49,65 +46,48 @@ describe('ImageTag', () => {
describe('toString', () => { describe('toString', () => {
it('returns the correct version', () => { it('returns the correct version', () => {
const image = new ImageTag({ const image = new ImageTag({ editorVersion: '2099.1.1111', targetPlatform: testImageParameters.targetPlatform });
editorVersion: '2099.1.1111f1',
targetPlatform: testImageParameters.targetPlatform,
containerRegistryRepository: 'unityci/editor',
containerRegistryImageVersion: '3',
});
switch (process.platform) { switch (process.platform) {
case 'win32': case 'win32':
expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2099.1.1111f1-3`); expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2099.1.1111-2`);
break; break;
case 'linux': case 'linux':
expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2099.1.1111f1-3`); expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2099.1.1111-2`);
break; break;
} }
}); });
it('returns customImage if given', () => { it('returns customImage if given', () => {
const image = new ImageTag({ const image = new ImageTag({
editorVersion: '2099.1.1111f1', editorVersion: '2099.1.1111',
targetPlatform: testImageParameters.targetPlatform, targetPlatform: testImageParameters.targetPlatform,
customImage: `${defaults.image}:2099.1.1111@347598437689743986`, customImage: `${defaults.image}:2099.1.1111@347598437689743986`,
containerRegistryRepository: 'unityci/editor',
containerRegistryImageVersion: '3',
}); });
expect(image.toString()).toStrictEqual(image.customImage); expect(image.toString()).toStrictEqual(image.customImage);
}); });
it('returns the specific build platform', () => { it('returns the specific build platform', () => {
const image = new ImageTag({ const image = new ImageTag({ editorVersion: '2019.2.11f1', targetPlatform: 'WebGL' });
editorVersion: '2019.2.11f1',
targetPlatform: 'WebGL',
containerRegistryRepository: 'unityci/editor',
containerRegistryImageVersion: '3',
});
switch (process.platform) { switch (process.platform) {
case 'win32': case 'win32':
expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2019.2.11f1-webgl-3`); expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2019.2.11f1-webgl-2`);
break; break;
case 'linux': case 'linux':
expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2019.2.11f1-webgl-3`); expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2019.2.11f1-webgl-2`);
break; break;
} }
}); });
it('returns no specific build platform for generic targetPlatforms', () => { it('returns no specific build platform for generic targetPlatforms', () => {
const image = new ImageTag({ const image = new ImageTag({ editorVersion: '2019.2.11f1', targetPlatform: 'NoTarget' });
editorVersion: '2019.2.11f1',
targetPlatform: 'NoTarget',
containerRegistryRepository: 'unityci/editor',
containerRegistryImageVersion: '3',
});
switch (process.platform) { switch (process.platform) {
case 'win32': case 'win32':
expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2019.2.11f1-3`); expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2019.2.11f1-2`);
break; break;
case 'linux': case 'linux':
expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2019.2.11f1-3`); expect(image.toString()).toStrictEqual(`${defaults.image}:ubuntu-2019.2.11f1-2`);
break; break;
} }
}); });

View File

@ -2,6 +2,8 @@ import Platform from './platform';
class ImageTag { class ImageTag {
public repository: string; public repository: string;
public name: string;
public cloudRunnerBuilderPlatform!: string;
public editorVersion: string; public editorVersion: string;
public targetPlatform: string; public targetPlatform: string;
public builderPlatform: string; public builderPlatform: string;
@ -10,15 +12,7 @@ class ImageTag {
public imagePlatformPrefix: string; public imagePlatformPrefix: string;
constructor(imageProperties: { [key: string]: string }) { constructor(imageProperties: { [key: string]: string }) {
const { const { editorVersion, targetPlatform, customImage, cloudRunnerBuilderPlatform } = imageProperties;
editorVersion,
targetPlatform,
customImage,
buildPlatform,
containerRegistryRepository,
containerRegistryImageVersion,
providerStrategy,
} = imageProperties;
if (!ImageTag.versionPattern.test(editorVersion)) { if (!ImageTag.versionPattern.test(editorVersion)) {
throw new Error(`Invalid version "${editorVersion}".`); throw new Error(`Invalid version "${editorVersion}".`);
@ -29,20 +23,21 @@ class ImageTag {
this.customImage = customImage; this.customImage = customImage;
// Or // Or
this.repository = containerRegistryRepository; this.repository = 'unityci';
this.name = 'editor';
this.editorVersion = editorVersion; this.editorVersion = editorVersion;
this.targetPlatform = targetPlatform; this.targetPlatform = targetPlatform;
this.builderPlatform = ImageTag.getTargetPlatformToTargetPlatformSuffixMap( this.cloudRunnerBuilderPlatform = cloudRunnerBuilderPlatform;
targetPlatform, const isCloudRunnerLocal = cloudRunnerBuilderPlatform === 'local' || cloudRunnerBuilderPlatform === undefined;
editorVersion, this.builderPlatform = ImageTag.getTargetPlatformToTargetPlatformSuffixMap(targetPlatform, editorVersion);
providerStrategy, this.imagePlatformPrefix = ImageTag.getImagePlatformPrefixes(
isCloudRunnerLocal ? process.platform : cloudRunnerBuilderPlatform,
); );
this.imagePlatformPrefix = ImageTag.getImagePlatformPrefixes(buildPlatform); this.imageRollingVersion = 2; // Will automatically roll to the latest non-breaking version.
this.imageRollingVersion = Number(containerRegistryImageVersion); // Will automatically roll to the latest non-breaking version.
} }
static get versionPattern(): RegExp { static get versionPattern(): RegExp {
return /^\d+\.\d+\.\d+[a-z]\d+$/; return /^(20\d{2}\.\d\.\w{3,4}|3)$/;
} }
static get targetPlatformSuffixes() { static get targetPlatformSuffixes() {
@ -58,16 +53,11 @@ class ImageTag {
android: 'android', android: 'android',
ios: 'ios', ios: 'ios',
tvos: 'appletv', tvos: 'appletv',
visionos: 'visionos',
facebook: 'facebook', facebook: 'facebook',
}; };
} }
static getImagePlatformPrefixes(platform: string): string { static getImagePlatformPrefixes(platform: string): string {
if (!platform || platform === '') {
platform = process.platform;
}
switch (platform) { switch (platform) {
case 'win32': case 'win32':
return 'windows'; return 'windows';
@ -78,26 +68,9 @@ class ImageTag {
} }
} }
static getTargetPlatformToTargetPlatformSuffixMap( static getTargetPlatformToTargetPlatformSuffixMap(platform: string, version: string): string {
platform: string, const { generic, webgl, mac, windows, windowsIl2cpp, wsaPlayer, linux, linuxIl2cpp, android, ios, tvos, facebook } =
version: string, ImageTag.targetPlatformSuffixes;
providerStrategy: string,
): string {
const {
generic,
webgl,
mac,
windows,
windowsIl2cpp,
wsaPlayer,
linux,
linuxIl2cpp,
android,
ios,
tvos,
visionos,
facebook,
} = ImageTag.targetPlatformSuffixes;
const [major, minor] = version.split('.').map((digit) => Number(digit)); const [major, minor] = version.split('.').map((digit) => Number(digit));
@ -113,10 +86,8 @@ class ImageTag {
if (major >= 2020 || (major === 2019 && minor >= 3)) { if (major >= 2020 || (major === 2019 && minor >= 3)) {
return windowsIl2cpp; return windowsIl2cpp;
} else { } else {
throw new Error( throw new Error(`Windows-based builds are only supported on 2019.3.X+ versions of Unity.
`Windows-based builds are only supported on 2019.3.X+ versions of Unity. If you are trying to build for windows-mono, please use a Linux based OS.`);
If you are trying to build for windows-mono, please use a Linux based OS.`,
);
} }
} }
@ -124,11 +95,7 @@ class ImageTag {
case Platform.types.StandaloneLinux64: { case Platform.types.StandaloneLinux64: {
// Unity versions before 2019.3 do not support il2cpp // Unity versions before 2019.3 do not support il2cpp
if (major >= 2020 || (major === 2019 && minor >= 3)) { if (major >= 2020 || (major === 2019 && minor >= 3)) {
if (providerStrategy === 'local') { return linuxIl2cpp;
return linuxIl2cpp;
} else {
return process.env.USE_IL2CPP === 'true' ? linuxIl2cpp : linux;
}
} }
return linux; return linux;
@ -150,17 +117,11 @@ class ImageTag {
case Platform.types.XboxOne: case Platform.types.XboxOne:
return windows; return windows;
case Platform.types.tvOS: case Platform.types.tvOS:
if (process.platform !== 'win32' && process.platform !== 'darwin') { if (process.platform !== 'win32') {
throw new Error(`tvOS can only be built on Windows or macOS base OS`); throw new Error(`tvOS can only be built on a windows base OS`);
} }
return tvos; return tvos;
case Platform.types.VisionOS:
if (process.platform !== 'darwin') {
throw new Error(`visionOS can only be built on a macOS base OS`);
}
return visionos;
case Platform.types.Switch: case Platform.types.Switch:
return windows; return windows;
@ -193,7 +154,7 @@ class ImageTag {
} }
get image(): string { get image(): string {
return `${this.repository}`.replace(/^\/+/, ''); return `${this.repository}/${this.name}`.replace(/^\/+/, '');
} }
toString(): string { toString(): string {
@ -201,7 +162,7 @@ class ImageTag {
if (customImage) return customImage; if (customImage) return customImage;
return `${image}:${tag}`; return `${image}:${tag}`; // '0' here represents the docker repo version
} }
} }

View File

@ -59,19 +59,6 @@ describe('Input', () => {
}); });
}); });
describe('buildProfile', () => {
it('returns the default value', () => {
expect(Input.buildProfile).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = 'path/to/build_profile.asset';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildProfile).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildName', () => { describe('buildName', () => {
it('returns the default value', () => { it('returns the default value', () => {
expect(Input.buildName).toStrictEqual(Input.targetPlatform); expect(Input.buildName).toStrictEqual(Input.targetPlatform);
@ -117,42 +104,6 @@ describe('Input', () => {
}); });
}); });
describe('manualExit', () => {
it('returns the default value', () => {
expect(Input.manualExit).toStrictEqual(false);
});
it('returns true when string true is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
expect(Input.manualExit).toStrictEqual(true);
expect(spy).toHaveBeenCalledTimes(1);
});
it('returns false when string false is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
expect(Input.manualExit).toStrictEqual(false);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('enableGpu', () => {
it('returns the default value', () => {
expect(Input.enableGpu).toStrictEqual(false);
});
it('returns true when string true is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('true');
expect(Input.enableGpu).toStrictEqual(true);
expect(spy).toHaveBeenCalledTimes(1);
});
it('returns false when string false is passed', () => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue('false');
expect(Input.enableGpu).toStrictEqual(false);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('versioningStrategy', () => { describe('versioningStrategy', () => {
it('returns the default value', () => { it('returns the default value', () => {
expect(Input.versioningStrategy).toStrictEqual('Semantic'); expect(Input.versioningStrategy).toStrictEqual('Semantic');

View File

@ -4,7 +4,6 @@ import { Cli } from './cli/cli';
import CloudRunnerQueryOverride from './cloud-runner/options/cloud-runner-query-override'; import CloudRunnerQueryOverride from './cloud-runner/options/cloud-runner-query-override';
import Platform from './platform'; import Platform from './platform';
import GitHub from './github'; import GitHub from './github';
import os from 'node:os';
import * as core from '@actions/core'; import * as core from '@actions/core';
@ -46,11 +45,11 @@ class Input {
} }
static get region(): string { static get region(): string {
return Input.getInput('region') ?? 'eu-west-2'; return Input.getInput('region') || 'eu-west-2';
} }
static get githubRepo(): string | undefined { static get githubRepo(): string | undefined {
return Input.getInput('GITHUB_REPOSITORY') ?? Input.getInput('GITHUB_REPO') ?? undefined; return Input.getInput('GITHUB_REPOSITORY') || Input.getInput('GITHUB_REPO') || undefined;
} }
static get branch(): string { static get branch(): string {
@ -74,19 +73,19 @@ class Input {
} }
static get runNumber(): string { static get runNumber(): string {
return Input.getInput('GITHUB_RUN_NUMBER') ?? '0'; return Input.getInput('GITHUB_RUN_NUMBER') || '0';
} }
static get targetPlatform(): string { static get targetPlatform(): string {
return Input.getInput('targetPlatform') ?? Platform.default; return Input.getInput('targetPlatform') || Platform.default;
} }
static get unityVersion(): string { static get unityVersion(): string {
return Input.getInput('unityVersion') ?? 'auto'; return Input.getInput('unityVersion') || 'auto';
} }
static get customImage(): string { static get customImage(): string {
return Input.getInput('customImage') ?? ''; return Input.getInput('customImage') || '';
} }
static get projectPath(): string { static get projectPath(): string {
@ -107,124 +106,104 @@ class Input {
return rawProjectPath.replace(/\/$/, ''); return rawProjectPath.replace(/\/$/, '');
} }
static get buildProfile(): string {
return Input.getInput('buildProfile') ?? '';
}
static get runnerTempPath(): string { static get runnerTempPath(): string {
return Input.getInput('RUNNER_TEMP') ?? ''; return Input.getInput('RUNNER_TEMP') || '';
} }
static get buildName(): string { static get buildName(): string {
return Input.getInput('buildName') ?? Input.targetPlatform; return Input.getInput('buildName') || Input.targetPlatform;
} }
static get buildsPath(): string { static get buildsPath(): string {
return Input.getInput('buildsPath') ?? 'build'; return Input.getInput('buildsPath') || 'build';
} }
static get unityLicensingServer(): string { static get unityLicensingServer(): string {
return Input.getInput('unityLicensingServer') ?? ''; return Input.getInput('unityLicensingServer') || '';
} }
static get buildMethod(): string { static get buildMethod(): string {
return Input.getInput('buildMethod') ?? ''; // Processed in docker file return Input.getInput('buildMethod') || ''; // Processed in docker file
}
static get manualExit(): boolean {
const input = Input.getInput('manualExit') ?? false;
return input === 'true';
}
static get enableGpu(): boolean {
const input = Input.getInput('enableGpu') ?? false;
return input === 'true';
} }
static get customParameters(): string { static get customParameters(): string {
return Input.getInput('customParameters') ?? ''; return Input.getInput('customParameters') || '';
} }
static get versioningStrategy(): string { static get versioningStrategy(): string {
return Input.getInput('versioning') ?? 'Semantic'; return Input.getInput('versioning') || 'Semantic';
} }
static get specifiedVersion(): string { static get specifiedVersion(): string {
return Input.getInput('version') ?? ''; return Input.getInput('version') || '';
} }
static get androidVersionCode(): string { static get androidVersionCode(): string {
return Input.getInput('androidVersionCode') ?? ''; return Input.getInput('androidVersionCode') || '';
} }
static get androidExportType(): string { static get androidExportType(): string {
return Input.getInput('androidExportType') ?? 'androidPackage'; return Input.getInput('androidExportType') || 'androidPackage';
} }
static get androidKeystoreName(): string { static get androidKeystoreName(): string {
return Input.getInput('androidKeystoreName') ?? ''; return Input.getInput('androidKeystoreName') || '';
} }
static get androidKeystoreBase64(): string { static get androidKeystoreBase64(): string {
return Input.getInput('androidKeystoreBase64') ?? ''; return Input.getInput('androidKeystoreBase64') || '';
} }
static get androidKeystorePass(): string { static get androidKeystorePass(): string {
return Input.getInput('androidKeystorePass') ?? ''; return Input.getInput('androidKeystorePass') || '';
} }
static get androidKeyaliasName(): string { static get androidKeyaliasName(): string {
return Input.getInput('androidKeyaliasName') ?? ''; return Input.getInput('androidKeyaliasName') || '';
} }
static get androidKeyaliasPass(): string { static get androidKeyaliasPass(): string {
return Input.getInput('androidKeyaliasPass') ?? ''; return Input.getInput('androidKeyaliasPass') || '';
} }
static get androidTargetSdkVersion(): string { static get androidTargetSdkVersion(): string {
return Input.getInput('androidTargetSdkVersion') ?? ''; return Input.getInput('androidTargetSdkVersion') || '';
} }
static get androidSymbolType(): string { static get androidSymbolType(): string {
return Input.getInput('androidSymbolType') ?? 'none'; return Input.getInput('androidSymbolType') || 'none';
} }
static get sshAgent(): string { static get sshAgent(): string {
return Input.getInput('sshAgent') ?? ''; return Input.getInput('sshAgent') || '';
} }
static get sshPublicKeysDirectoryPath(): string { static get sshPublicKeysDirectoryPath(): string {
return Input.getInput('sshPublicKeysDirectoryPath') ?? ''; return Input.getInput('sshPublicKeysDirectoryPath') || '';
} }
static get gitPrivateToken(): string | undefined { static get gitPrivateToken(): string | undefined {
return Input.getInput('gitPrivateToken'); return Input.getInput('gitPrivateToken');
} }
static get runAsHostUser(): string {
return Input.getInput('runAsHostUser')?.toLowerCase() ?? 'false';
}
static get chownFilesTo() { static get chownFilesTo() {
return Input.getInput('chownFilesTo') ?? ''; return Input.getInput('chownFilesTo') || '';
} }
static get allowDirtyBuild(): boolean { static get allowDirtyBuild(): boolean {
const input = Input.getInput('allowDirtyBuild') ?? false; const input = Input.getInput('allowDirtyBuild') || false;
return input === 'true'; return input === 'true';
} }
static get cacheUnityInstallationOnMac(): boolean { static get cacheUnityInstallationOnMac(): boolean {
const input = Input.getInput('cacheUnityInstallationOnMac') ?? false; const input = Input.getInput('cacheUnityInstallationOnMac') || false;
return input === 'true'; return input === 'true';
} }
static get unityHubVersionOnMac(): string { static get unityHubVersionOnMac(): string {
const input = Input.getInput('unityHubVersionOnMac') ?? ''; const input = Input.getInput('unityHubVersionOnMac') || '';
return input !== '' ? input : ''; return input !== '' ? input : '';
} }
@ -238,48 +217,7 @@ class Input {
} }
static get dockerWorkspacePath(): string { static get dockerWorkspacePath(): string {
return Input.getInput('dockerWorkspacePath') ?? '/github/workspace'; return Input.getInput('dockerWorkspacePath') || '/github/workspace';
}
static get dockerCpuLimit(): string {
return Input.getInput('dockerCpuLimit') ?? os.cpus().length.toString();
}
static get dockerMemoryLimit(): string {
const bytesInMegabyte = 1024 * 1024;
let memoryMultiplier;
switch (os.platform()) {
case 'linux':
memoryMultiplier = 0.95;
break;
case 'win32':
memoryMultiplier = 0.8;
break;
default:
memoryMultiplier = 0.75;
break;
}
return (
Input.getInput('dockerMemoryLimit') ?? `${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`
);
}
static get dockerIsolationMode(): string {
return Input.getInput('dockerIsolationMode') ?? 'default';
}
static get containerRegistryRepository(): string {
return Input.getInput('containerRegistryRepository') ?? 'unityci/editor';
}
static get containerRegistryImageVersion(): string {
return Input.getInput('containerRegistryImageVersion') ?? '3';
}
static get skipActivation(): string {
return Input.getInput('skipActivation')?.toLowerCase() ?? 'false';
} }
public static ToEnvVarFormat(input: string) { public static ToEnvVarFormat(input: string) {

View File

@ -1,10 +1,9 @@
import { exec } from '@actions/exec'; import { execWithErrorCheck } from './exec-with-error-check';
class MacBuilder { class MacBuilder {
public static async run(actionFolder: string, silent: boolean = false): Promise<number> { public static async run(actionFolder: string, silent: boolean = false) {
return await exec('bash', [`${actionFolder}/platforms/mac/entrypoint.sh`], { await execWithErrorCheck('bash', [`${actionFolder}/platforms/mac/entrypoint.sh`], {
silent, silent,
ignoreReturnCode: true,
}); });
} }
} }

View File

@ -8,10 +8,6 @@ class Output {
static async setAndroidVersionCode(androidVersionCode: string) { static async setAndroidVersionCode(androidVersionCode: string) {
core.setOutput('androidVersionCode', androidVersionCode); core.setOutput('androidVersionCode', androidVersionCode);
} }
static async setEngineExitCode(exitCode: number) {
core.setOutput('engineExitCode', exitCode);
}
} }
export default Output; export default Output;

View File

@ -47,10 +47,7 @@ class SetupMac {
// Ignoring return code because the log seems to overflow the internal buffer which triggers // Ignoring return code because the log seems to overflow the internal buffer which triggers
// a false error // a false error
const errorCode = await exec(command, undefined, { const errorCode = await exec(command, undefined, { silent, ignoreReturnCode: true });
silent,
ignoreReturnCode: true,
});
if (errorCode) { if (errorCode) {
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`); throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
} }
@ -67,9 +64,7 @@ class SetupMac {
private static async getLatestUnityHubVersion(): Promise<string> { private static async getLatestUnityHubVersion(): Promise<string> {
// Need to check if the latest version available is the same as the one we have cached // Need to check if the latest version available is the same as the one we have cached
const hubVersionCommand = `/bin/bash -c "brew info unity-hub | grep -o '[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+'"`; const hubVersionCommand = `/bin/bash -c "brew info unity-hub | grep -o '[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+'"`;
const result = await getExecOutput(hubVersionCommand, undefined, { const result = await getExecOutput(hubVersionCommand, undefined, { silent: true });
silent: true,
});
if (result.exitCode === 0 && result.stdout !== '') { if (result.exitCode === 0 && result.stdout !== '') {
return result.stdout; return result.stdout;
} }
@ -77,23 +72,6 @@ class SetupMac {
return ''; return '';
} }
private static getArchitectureParameters(): string[] {
const architectureArgument = [];
switch (process.arch) {
case 'x64':
architectureArgument.push('--architecture', 'x86_64');
break;
case 'arm64':
architectureArgument.push('--architecture', 'arm64');
break;
default:
throw new Error(`Unsupported architecture: ${process.arch}.`);
}
return architectureArgument;
}
private static getModuleParametersForTargetPlatform(targetPlatform: string): string[] { private static getModuleParametersForTargetPlatform(targetPlatform: string): string[] {
const moduleArgument = []; const moduleArgument = [];
switch (targetPlatform) { switch (targetPlatform) {
@ -101,10 +79,7 @@ class SetupMac {
moduleArgument.push('--module', 'ios'); moduleArgument.push('--module', 'ios');
break; break;
case 'tvOS': case 'tvOS':
moduleArgument.push('--module', 'appletv'); moduleArgument.push('--module', 'tvos');
break;
case 'VisionOS':
moduleArgument.push('--module', 'visionos');
break; break;
case 'StandaloneOSX': case 'StandaloneOSX':
moduleArgument.push('--module', 'mac-il2cpp'); moduleArgument.push('--module', 'mac-il2cpp');
@ -136,7 +111,6 @@ class SetupMac {
const unityChangeset = await getUnityChangeset(buildParameters.editorVersion); const unityChangeset = await getUnityChangeset(buildParameters.editorVersion);
const moduleArguments = SetupMac.getModuleParametersForTargetPlatform(buildParameters.targetPlatform); const moduleArguments = SetupMac.getModuleParametersForTargetPlatform(buildParameters.targetPlatform);
const architectureArguments = SetupMac.getArchitectureParameters();
const execArguments: string[] = [ const execArguments: string[] = [
'--', '--',
@ -145,16 +119,12 @@ class SetupMac {
...['--version', buildParameters.editorVersion], ...['--version', buildParameters.editorVersion],
...['--changeset', unityChangeset.changeset], ...['--changeset', unityChangeset.changeset],
...moduleArguments, ...moduleArguments,
...architectureArguments,
'--childModules', '--childModules',
]; ];
// Ignoring return code because the log seems to overflow the internal buffer which triggers // Ignoring return code because the log seems to overflow the internal buffer which triggers
// a false error // a false error
const errorCode = await exec(this.unityHubExecPath, execArguments, { const errorCode = await exec(this.unityHubExecPath, execArguments, { silent, ignoreReturnCode: true });
silent,
ignoreReturnCode: true,
});
if (errorCode) { if (errorCode) {
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`); throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
} }
@ -171,9 +141,7 @@ class SetupMac {
process.env.UNITY_VERSION = buildParameters.editorVersion; process.env.UNITY_VERSION = buildParameters.editorVersion;
process.env.UNITY_SERIAL = buildParameters.unitySerial; process.env.UNITY_SERIAL = buildParameters.unitySerial;
process.env.UNITY_LICENSING_SERVER = buildParameters.unityLicensingServer; process.env.UNITY_LICENSING_SERVER = buildParameters.unityLicensingServer;
process.env.SKIP_ACTIVATION = buildParameters.skipActivation;
process.env.PROJECT_PATH = buildParameters.projectPath; process.env.PROJECT_PATH = buildParameters.projectPath;
process.env.BUILD_PROFILE = buildParameters.buildProfile;
process.env.BUILD_TARGET = buildParameters.targetPlatform; process.env.BUILD_TARGET = buildParameters.targetPlatform;
process.env.BUILD_NAME = buildParameters.buildName; process.env.BUILD_NAME = buildParameters.buildName;
process.env.BUILD_PATH = buildParameters.buildPath; process.env.BUILD_PATH = buildParameters.buildPath;
@ -192,8 +160,6 @@ class SetupMac {
process.env.ANDROID_SYMBOL_TYPE = buildParameters.androidSymbolType; process.env.ANDROID_SYMBOL_TYPE = buildParameters.androidSymbolType;
process.env.CUSTOM_PARAMETERS = buildParameters.customParameters; process.env.CUSTOM_PARAMETERS = buildParameters.customParameters;
process.env.CHOWN_FILES_TO = buildParameters.chownFilesTo; process.env.CHOWN_FILES_TO = buildParameters.chownFilesTo;
process.env.MANUAL_EXIT = buildParameters.manualExit.toString();
process.env.ENABLE_GPU = buildParameters.enableGpu.toString();
} }
} }

View File

@ -4,14 +4,9 @@ import { BuildParameters } from '..';
class ValidateWindows { class ValidateWindows {
public static validate(buildParameters: BuildParameters) { public static validate(buildParameters: BuildParameters) {
ValidateWindows.validateWindowsPlatformRequirements(buildParameters.targetPlatform); ValidateWindows.validateWindowsPlatformRequirements(buildParameters.targetPlatform);
if (!(process.env.UNITY_EMAIL && process.env.UNITY_PASSWORD)) {
const { unityLicensingServer } = buildParameters; throw new Error(`Unity email and password must be set for Windows based builds to
const hasLicensingCredentials = process.env.UNITY_EMAIL && process.env.UNITY_PASSWORD; authenticate the license. Make sure to set them inside UNITY_EMAIL
const hasValidLicensingStrategy = hasLicensingCredentials || unityLicensingServer;
if (!hasValidLicensingStrategy) {
throw new Error(`Unity email and password or alternatively a Unity licensing server url must be set for
Windows based builds to authenticate the license. Make sure to set them inside UNITY_EMAIL
and UNITY_PASSWORD in Github Secrets and pass them into the environment.`); and UNITY_PASSWORD in Github Secrets and pass them into the environment.`);
} }
} }

View File

@ -16,7 +16,6 @@ class Platform {
PS4: 'PS4', PS4: 'PS4',
XboxOne: 'XboxOne', XboxOne: 'XboxOne',
tvOS: 'tvOS', tvOS: 'tvOS',
VisionOS: 'VisionOS',
Switch: 'Switch', Switch: 'Switch',
// Unsupported // Unsupported

Some files were not shown because too many files have changed in this diff Show More