Merge remote-tracking branch 'game-ci/main' into cloud-runner-develop

pull/496/head
Frostebite 2023-03-07 16:14:53 +00:00
commit e075f22e5c
90 changed files with 2646 additions and 1706 deletions

View File

@ -6,6 +6,7 @@
<!-- please check all items and add your own -->
- [x] Read the contribution [guide](../CONTRIBUTING.md) and accept the [code](../CODE_OF_CONDUCT.md) of conduct
- [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
- [ ] Readme (updated or not needed)
- [ ] Tests (added, updated or not needed)

View File

@ -0,0 +1,84 @@
name: Builds - MacOS
on:
workflow_dispatch:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
buildForAllPlatformsWindows:
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
- 2019.4.40f1 # Minimum version for IL2CPP
- 2020.1.17f1
- 2020.2.7f1
- 2020.3.44f1
- 2021.1.28f1
- 2021.2.19f1
- 2021.3.18f1
- 2022.1.24f1
- 2022.2.6f1
targetPlatform:
- StandaloneOSX # Build a MacOS executable
steps:
###########################
# Checkout #
###########################
- uses: actions/checkout@v3
with:
lfs: true
###########################
# Cache #
###########################
- uses: actions/cache@v3
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-macos-${{ matrix.targetPlatform }}
restore-keys: |
Library-${{ matrix.projectPath }}-macos-
Library-
###########################
# Set Scripting Backend #
###########################
- name: Set Scripting Backend To il2cpp
run: |
mv -f "./test-project/ProjectSettings/ProjectSettingsIl2cpp.asset" "./test-project/ProjectSettings/ProjectSettings.asset"
###########################
# Build #
###########################
- uses: ./
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
# We use dirty build because we are replacing the default project settings file above
allowDirtyBuild: true
###########################
# Upload #
###########################
- uses: actions/upload-artifact@v3
with:
name: Build MacOS (${{ matrix.unityVersion }})
path: build
retention-days: 14

View File

@ -1,11 +1,18 @@
name: Builds
name: Builds - Ubuntu
on:
push: { branches: [main] }
workflow_dispatch:
push:
branches:
- main
pull_request:
paths-ignore:
- '.github/**'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
UNITY_LICENSE:
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License
@ -34,23 +41,37 @@ env:
jobs:
buildForAllPlatformsUbuntu:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
exclude:
- targetPlatform: Android
unityVersion: 2022.2.7f1
cloudRunnerCluster:
# - local-docker
- local
projectPath:
- test-project
unityVersion:
- 2019.2.11f1
- 2018.3.14f1
- 2018.4.36f1
- 2019.1.14f1
- 2019.2.21f1
- 2019.3.15f1
- 2019.4.40f1
- 2020.2.7f1
- 2020.3.45f1
- 2021.1.28f1
- 2021.2.19f1
- 2021.3.19f1
- 2022.1.24f1
- 2022.2.7f1
targetPlatform:
- StandaloneOSX # Build a macOS standalone (Intel 64-bit).
- StandaloneWindows64 # Build a Windows 64-bit standalone.
- StandaloneLinux64 # Build a Linux 64-bit standalone.
- StandaloneOSX # Build a macOS standalone (Intel 64-bit) with mono backend.
- StandaloneWindows64 # Build a Windows 64-bit standalone with mono backend.
- StandaloneLinux64 # Build a Linux 64-bit standalone with mono backend.
- iOS # Build an iOS player.
- Android # Build an Android .apk.
- WebGL # WebGL.
@ -64,14 +85,14 @@ jobs:
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
lfs: true
###########################
# Cache #
###########################
- uses: actions/cache@v2
- uses: actions/cache@v3
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-ubuntu-${{ matrix.targetPlatform }}
@ -93,7 +114,7 @@ jobs:
###########################
# Upload #
###########################
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: Build Ubuntu (${{ matrix.unityVersion }})
path: build

View File

@ -0,0 +1,135 @@
name: Builds - Windows
on:
workflow_dispatch:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
buildForAllPlatformsWindows:
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }}
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
- 2019.3.15f1 # Minimum version for IL2CPP
- 2019.4.40f1
- 2020.1.17f1
- 2020.2.7f1
- 2020.3.44f1
- 2021.3.18f1 # 2021.1 and 2021.2 seem to have IL2CPP issues
- 2022.1.24f1
- 2022.2.6f1
targetPlatform:
- StandaloneWindows64 # Build a Windows 64-bit standalone.
- StandaloneWindows # Build a Windows 32-bit standalone.
- WSAPlayer # Build a UWP App
- tvOS # Build an Apple TV XCode project
steps:
###########################
# Checkout #
###########################
- uses: actions/checkout@v3
with:
lfs: true
###########################
# Cache #
###########################
- uses: actions/cache@v3
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-windows-${{ matrix.targetPlatform }}
restore-keys: |
Library-${{ matrix.projectPath }}-windows-
Library-
###########################
# Set Scripting Backend #
###########################
- name: Set Scripting Backend To il2cpp
run: |
Move-Item -Path "./test-project/ProjectSettings/ProjectSettingsIl2cpp.asset" -Destination "./test-project/ProjectSettings/ProjectSettings.asset" -Force
###########################
# Build #
###########################
- name: Build
uses: ./
id: build-1
continue-on-error: true
timeout-minutes: 30
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above
- name: Sleep for Retry
if: ${{ steps.build-1.outcome == 'failure' }}
run: |
Start-Sleep -s 120
- name: Build Retry 1
uses: ./
id: build-2
continue-on-error: true
timeout-minutes: 30
if: steps.build-1.outcome == 'failure'
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above
- name: Sleep for Retry
if: ${{ steps.build-1.outcome == 'failure' && steps.build-2.outcome == 'failure' }}
run: |
Start-Sleep -s 240
- name: Build Retry 2
uses: ./
id: build-3
timeout-minutes: 30
if: ${{ steps.build-1.outcome == 'failure' && steps.build-2.outcome == 'failure' }}
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above
###########################
# Upload #
###########################
- uses: actions/upload-artifact@v3
with:
name: Build Windows (${{ matrix.unityVersion }})
path: build
retention-days: 14

View File

@ -15,13 +15,13 @@ jobs:
cleanupCloudRunner:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
if: github.event.event_type != 'pull_request_target'
with:
lfs: true
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: 12.x
node-version: 16.x
- run: yarn
- run: yarn run cli --help
env:

View File

@ -55,7 +55,7 @@ jobs:
- k8s
steps:
- name: Checkout (default)
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
lfs: false
- name: Configure AWS Credentials
@ -164,7 +164,7 @@ jobs:
- Android # Build an Android .apk.
steps:
- name: Checkout (default)
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
lfs: false
- run: yarn
@ -180,7 +180,7 @@ jobs:
cloudRunnerCluster: ${{ matrix.cloudRunnerCluster }}
- 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 }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.cloudRunnerCluster }} Build (${{ matrix.targetPlatform }})
path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}

View File

@ -7,6 +7,10 @@ on:
env:
CODECOV_TOKEN: '2f2eb890-30e2-4724-83eb-7633832cf0de'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
tests:
name: Tests

View File

@ -1,74 +0,0 @@
name: Mac Builds
on:
push:
branches:
- main
env:
UNITY_LICENSE: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"576562626572264761624c65526f7578\"/>\n <Binding Key=\"2\" Value=\"576562626572264761624c65526f7578\"/>\n </MachineBindings>\n <MachineID Value=\"D7nTUnjNAmtsUMcnoyrqkgIbYdM=\"/>\n <SerialHash Value=\"2033b8ac3e6faa3742ca9f0bfae44d18f2a96b80\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUJHUlgtWEQ0RS1aQ1dWLUM1SlctR0RIQg==\"/>\n <SerialMasked Value=\"F4-BGRX-XD4E-ZCWV-C5JW-XXXX\"/>\n <StartDate Value=\"2021-02-08T00:00:00\"/>\n <UpdateDate Value=\"2021-02-09T00:34:57\"/>\n <InitialActivationDate Value=\"2021-02-08T00:34:56\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2018.4.30f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n <Entitlement Ns=\"unity_editor\" Tag=\"DarkSkin\" Type=\"EDITOR_FEATURE\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>m0Db8UK+ktnOLJBtHybkfetpcKo=</DigestValue></Reference></SignedInfo><SignatureValue>o/pUbSQAukz7+ZYAWhnA0AJbIlyyCPL7bKVEM2lVqbrXt7cyey+umkCXamuOgsWPVUKBMkXtMH8L\n5etLmD0getWIhTGhzOnDCk+gtIPfL4jMo9tkEuOCROQAXCci23VFscKcrkB+3X6h4wEOtA2APhOY\nB+wvC794o8/82ffjP79aVAi57rp3Wmzx+9pe9yMwoJuljAy2sc2tIMgdQGWVmOGBpQm3JqsidyzI\nJWG2kjnc7pDXK9pwYzXoKiqUqqrut90d+kQqRyv7MSZXR50HFqD/LI69h68b7P8Bjo3bPXOhNXGR\n9YCoemH6EkfCJxp2gIjzjWW+l2Hj2EsFQi8YXw==</SignatureValue></Signature></root>"
jobs:
buildForAllPlatformsWindows:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
- 2020.3.24f1
targetPlatform:
- StandaloneOSX # Build a MacOS executable
steps:
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
with:
lfs: true
###########################
# Cache #
###########################
- uses: actions/cache@v2
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-macos-${{ matrix.targetPlatform }}
restore-keys: |
Library-${{ matrix.projectPath }}-macos-
Library-
###########################
# Set Scripting Backend #
###########################
- name: Set Scripting Backend To il2cpp
run: |
mv -f "./test-project/ProjectSettings/ProjectSettingsIl2cpp.asset" "./test-project/ProjectSettings/ProjectSettings.asset"
###########################
# Build #
###########################
- uses: ./
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
# We use dirty build because we are replacing the default project settings file above
allowDirtyBuild: true
###########################
# Upload #
###########################
- uses: actions/upload-artifact@v2
with:
name: Build MacOS (${{ matrix.unityVersion }})
path: build
retention-days: 14

View File

@ -1,77 +0,0 @@
name: Windows Builds
on:
push:
branches:
- main
env:
UNITY_LICENSE: "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>\n <License id=\"Terms\">\n <MachineBindings>\n <Binding Key=\"1\" Value=\"576562626572264761624c65526f7578\"/>\n <Binding Key=\"2\" Value=\"576562626572264761624c65526f7578\"/>\n </MachineBindings>\n <MachineID Value=\"D7nTUnjNAmtsUMcnoyrqkgIbYdM=\"/>\n <SerialHash Value=\"2033b8ac3e6faa3742ca9f0bfae44d18f2a96b80\"/>\n <Features>\n <Feature Value=\"33\"/>\n <Feature Value=\"1\"/>\n <Feature Value=\"12\"/>\n <Feature Value=\"2\"/>\n <Feature Value=\"24\"/>\n <Feature Value=\"3\"/>\n <Feature Value=\"36\"/>\n <Feature Value=\"17\"/>\n <Feature Value=\"19\"/>\n <Feature Value=\"62\"/>\n </Features>\n <DeveloperData Value=\"AQAAAEY0LUJHUlgtWEQ0RS1aQ1dWLUM1SlctR0RIQg==\"/>\n <SerialMasked Value=\"F4-BGRX-XD4E-ZCWV-C5JW-XXXX\"/>\n <StartDate Value=\"2021-02-08T00:00:00\"/>\n <UpdateDate Value=\"2021-02-09T00:34:57\"/>\n <InitialActivationDate Value=\"2021-02-08T00:34:56\"/>\n <LicenseVersion Value=\"6.x\"/>\n <ClientProvidedVersion Value=\"2018.4.30f1\"/>\n <AlwaysOnline Value=\"false\"/>\n <Entitlements>\n <Entitlement Ns=\"unity_editor\" Tag=\"UnityPersonal\" Type=\"EDITOR\" ValidTo=\"9999-12-31T00:00:00\"/>\n <Entitlement Ns=\"unity_editor\" Tag=\"DarkSkin\" Type=\"EDITOR_FEATURE\" ValidTo=\"9999-12-31T00:00:00\"/>\n </Entitlements>\n </License>\n<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><Reference URI=\"#Terms\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>m0Db8UK+ktnOLJBtHybkfetpcKo=</DigestValue></Reference></SignedInfo><SignatureValue>o/pUbSQAukz7+ZYAWhnA0AJbIlyyCPL7bKVEM2lVqbrXt7cyey+umkCXamuOgsWPVUKBMkXtMH8L\n5etLmD0getWIhTGhzOnDCk+gtIPfL4jMo9tkEuOCROQAXCci23VFscKcrkB+3X6h4wEOtA2APhOY\nB+wvC794o8/82ffjP79aVAi57rp3Wmzx+9pe9yMwoJuljAy2sc2tIMgdQGWVmOGBpQm3JqsidyzI\nJWG2kjnc7pDXK9pwYzXoKiqUqqrut90d+kQqRyv7MSZXR50HFqD/LI69h68b7P8Bjo3bPXOhNXGR\n9YCoemH6EkfCJxp2gIjzjWW+l2Hj2EsFQi8YXw==</SignatureValue></Signature></root>"
jobs:
buildForAllPlatformsWindows:
name: Build for ${{ matrix.targetPlatform }} on version ${{ matrix.unityVersion }}
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
projectPath:
- test-project
unityVersion:
- 2020.3.24f1
targetPlatform:
- StandaloneWindows64 # Build a Windows 64-bit standalone.
- StandaloneWindows # Build a Windows 32-bit standalone.
- WSAPlayer # Build a UWP App
- tvOS # Build an Apple TV XCode project
steps:
###########################
# Checkout #
###########################
- uses: actions/checkout@v2
with:
lfs: true
###########################
# Cache #
###########################
- uses: actions/cache@v2
with:
path: ${{ matrix.projectPath }}/Library
key: Library-${{ matrix.projectPath }}-windows-${{ matrix.targetPlatform }}
restore-keys: |
Library-${{ matrix.projectPath }}-windows-
Library-
###########################
# Set Scripting Backend #
###########################
- name: Set Scripting Backend To il2cpp
run: |
Move-Item -Path "./test-project/ProjectSettings/ProjectSettingsIl2cpp.asset" -Destination "./test-project/ProjectSettings/ProjectSettings.asset" -Force
###########################
# Build #
###########################
- uses: ./
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
with:
projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above
###########################
# Upload #
###########################
- uses: actions/upload-artifact@v2
with:
name: Build Windows (${{ matrix.unityVersion }})
path: build
retention-days: 14

1
.npmrc 100644
View File

@ -0,0 +1 @@
engine-strict=true

25
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,25 @@
{
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Jest Test",
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
"args": [
"--collectCoverage=false",
"--colors",
"--config",
"${workspaceRoot}/jest.config.js",
"--runInBand",
"--runTestsByPath",
"${relativeFile}",
"--testPathPattern=${fileDirname}",
"--testTimeout=10000000"
],
"outputCapture": "std",
"internalConsoleOptions": "openOnSessionStart",
"envFile": "${workspaceRoot}/.env",
"skipFiles": ["${workspaceRoot}/../../node_modules/**/*", "<node_internals>/**/*"]
}
]
}

View File

@ -10,8 +10,9 @@ Part of the <a href="https://game.ci">GameCI</a> open source project.
<br />
<br />
[![Actions status](https://github.com/game-ci/unity-builder/workflows/Builds/badge.svg?event=push&branch=main)](https://github.com/game-ci/unity-builder/actions?query=branch%3Amain+event%3Apush+workflow%3A%22Builds)
[![lgtm - code quality](https://img.shields.io/lgtm/grade/javascript/g/webbertakken/unity-builder.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/webbertakken/unity-builder/context:javascript)
[![Builds - Ubuntu](https://github.com/game-ci/unity-builder/actions/workflows/build-tests-ubuntu.yml/badge.svg)](https://github.com/game-ci/unity-builder/actions/workflows/build-tests-ubuntu.yml)
[![Builds - Windows](https://github.com/game-ci/unity-builder/actions/workflows/build-tests-windows.yml/badge.svg)](https://github.com/game-ci/unity-builder/actions/workflows/build-tests-windows.yml)
[![Builds - MacOS](https://github.com/game-ci/unity-builder/actions/workflows/build-tests-mac.yml/badge.svg)](https://github.com/game-ci/unity-builder/actions/workflows/build-tests-mac.yml)
[![codecov - test coverage](https://codecov.io/gh/game-ci/unity-builder/branch/master/graph/badge.svg)](https://codecov.io/gh/game-ci/unity-builder)
<br />
<br />

View File

@ -22,7 +22,7 @@ inputs:
buildName:
required: false
default: ''
description: 'Name of the build.'
description: 'Name of the build. Should not include a file extension.'
buildsPath:
required: false
default: ''
@ -50,7 +50,13 @@ inputs:
androidAppBundle:
required: false
default: 'false'
description: 'Whether to build .aab instead of .apk'
description: '[Deprecated] Use androidExportType instead. Whether to build .aab instead of .apk'
androidExportType:
required: false
default: ''
description:
'The android export type. Should be androidPackage for apk, androidAppBundle for aab, or androidStudioProject for
an android studio project.'
androidKeystoreName:
required: false
default: ''
@ -75,6 +81,10 @@ inputs:
required: false
default: ''
description: 'The android target API level.'
androidSymbolType:
required: false
default: 'none'
description: 'The android symbol type to export. Should be "none", "public" or "debugging".'
sshAgent:
required: false
default: ''
@ -187,6 +197,17 @@ inputs:
description:
'[CloudRunner] Whether or not to watch the build to the end. Can be used for especially long running jobs e.g
imports or self-hosted ephemeral runners.'
cacheUnityInstallationOnMac:
default: 'false'
required: false
description: 'Whether to cache the Unity hub and editor installation on MacOS'
unityHubVersionOnMac:
default: ''
required: false
description:
'The version of Unity Hub to install on MacOS (e.g. 3.4.0). Defaults to latest available on brew if empty string
or nothing is specified.'
outputs:
volume:
description: 'The Persistent Volume (PV) where the build artifacts have been stored by Kubernetes'

View File

@ -1,14 +1,14 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using System.Reflection;
namespace UnityBuilderAction.Input
{
public class AndroidSettings
public static class AndroidSettings
{
public static void Apply(Dictionary<string, string> options)
{
EditorUserBuildSettings.buildAppBundle = options["customBuildPath"].EndsWith(".aab");
#if UNITY_2019_1_OR_NEWER
if (options.TryGetValue("androidKeystoreName", out string keystoreName) && !string.IsNullOrEmpty(keystoreName))
{
@ -16,13 +16,21 @@ namespace UnityBuilderAction.Input
PlayerSettings.Android.keystoreName = keystoreName;
}
#endif
if (options.TryGetValue("androidKeystorePass", out string keystorePass) && !string.IsNullOrEmpty(keystorePass))
// Can't use out variable declaration as Unity 2018 doesn't support it
string keystorePass;
if (options.TryGetValue("androidKeystorePass", out keystorePass) && !string.IsNullOrEmpty(keystorePass))
PlayerSettings.Android.keystorePass = keystorePass;
if (options.TryGetValue("androidKeyaliasName", out string keyaliasName) && !string.IsNullOrEmpty(keyaliasName))
string keyaliasName;
if (options.TryGetValue("androidKeyaliasName", out keyaliasName) && !string.IsNullOrEmpty(keyaliasName))
PlayerSettings.Android.keyaliasName = keyaliasName;
if (options.TryGetValue("androidKeyaliasPass", out string keyaliasPass) && !string.IsNullOrEmpty(keyaliasPass))
string keyaliasPass;
if (options.TryGetValue("androidKeyaliasPass", out keyaliasPass) && !string.IsNullOrEmpty(keyaliasPass))
PlayerSettings.Android.keyaliasPass = keyaliasPass;
if (options.TryGetValue("androidTargetSdkVersion", out string androidTargetSdkVersion) && !string.IsNullOrEmpty(androidTargetSdkVersion))
string androidTargetSdkVersion;
if (options.TryGetValue("androidTargetSdkVersion", out androidTargetSdkVersion) && !string.IsNullOrEmpty(androidTargetSdkVersion))
{
var targetSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto;
try
@ -36,6 +44,62 @@ namespace UnityBuilderAction.Input
}
PlayerSettings.Android.targetSdkVersion = targetSdkVersion;
}
string androidExportType;
if (options.TryGetValue("androidExportType", out androidExportType) && !string.IsNullOrEmpty(androidExportType))
{
// Only exists in 2018.3 and above
PropertyInfo buildAppBundle = typeof(EditorUserBuildSettings)
.GetProperty("buildAppBundle", BindingFlags.Public | BindingFlags.Static);
switch (androidExportType)
{
case "androidStudioProject":
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
if (buildAppBundle != null)
buildAppBundle.SetValue(null, false);
break;
case "androidAppBundle":
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
if (buildAppBundle != null)
buildAppBundle.SetValue(null, true);
break;
case "androidPackage":
EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
if (buildAppBundle != null)
buildAppBundle.SetValue(null, false);
break;
}
}
string symbolType;
if (options.TryGetValue("androidSymbolType", out symbolType) && !string.IsNullOrEmpty(symbolType))
{
#if UNITY_2021_1_OR_NEWER
switch (symbolType)
{
case "public":
EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Public;
break;
case "debugging":
EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Debugging;
break;
case "none":
EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Disabled;
break;
}
#elif UNITY_2019_2_OR_NEWER
switch (symbolType)
{
case "public":
case "debugging":
EditorUserBuildSettings.androidCreateSymbolsZip = true;
break;
case "none":
EditorUserBuildSettings.androidCreateSymbolsZip = false;
break;
}
#endif
}
}
}
}

View File

@ -12,14 +12,17 @@ namespace UnityBuilderAction.Input
public static Dictionary<string, string> GetValidatedOptions()
{
ParseCommandLineArguments(out var validatedOptions);
Dictionary<string, string> validatedOptions;
ParseCommandLineArguments(out validatedOptions);
if (!validatedOptions.TryGetValue("projectPath", out var projectPath)) {
string projectPath;
if (!validatedOptions.TryGetValue("projectPath", out projectPath)) {
Console.WriteLine("Missing argument -projectPath");
EditorApplication.Exit(110);
}
if (!validatedOptions.TryGetValue("buildTarget", out var buildTarget)) {
string buildTarget;
if (!validatedOptions.TryGetValue("buildTarget", out buildTarget)) {
Console.WriteLine("Missing argument -buildTarget");
EditorApplication.Exit(120);
}
@ -28,13 +31,15 @@ namespace UnityBuilderAction.Input
EditorApplication.Exit(121);
}
if (!validatedOptions.TryGetValue("customBuildPath", out var customBuildPath)) {
string customBuildPath;
if (!validatedOptions.TryGetValue("customBuildPath", out customBuildPath)) {
Console.WriteLine("Missing argument -customBuildPath");
EditorApplication.Exit(130);
}
const string defaultCustomBuildName = "TestBuild";
if (!validatedOptions.TryGetValue("customBuildName", out var customBuildName)) {
string customBuildName;
if (!validatedOptions.TryGetValue("customBuildName", out customBuildName)) {
Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
validatedOptions.Add("customBuildName", defaultCustomBuildName);
} else if (customBuildName == "") {

View File

@ -106,7 +106,8 @@ namespace UnityBuilderAction.Versioning
using (var process = new System.Diagnostics.Process()) {
string workingDirectory = UnityEngine.Application.dataPath;
int exitCode = process.Run(application, arguments, workingDirectory, out string output, out string errors);
string output, errors;
int exitCode = process.Run(application, arguments, workingDirectory, out output, out errors);
if (exitCode != 0) { throw new GitException(exitCode, errors); }
return output;

1314
dist/licenses.txt generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ echo "Requesting activation"
# Activate license
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
-logFile /dev/stdout \
-logFile - \
-batchmode \
-nographics \
-quit \

View File

@ -76,8 +76,10 @@ fi
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"
ANDROID_INSTALL_LOCATION="/Applications/Unity/Hub/Editor/$UNITY_VERSION/PlaybackEngines/AndroidPlayer"
export JAVA_HOME="$ANDROID_INSTALL_LOCATION/OpenJDK"
export ANDROID_HOME="$ANDROID_INSTALL_LOCATION/SDK"
yes | "$ANDROID_HOME/tools/bin/sdkmanager" "$ANDROID_SDK_MANAGER_PARAMETERS"
echo "Updated Android SDK."
else
echo "Not updating Android SDK."
@ -126,6 +128,7 @@ echo ""
# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
-logFile - \
-quit \
-batchmode \
-nographics \
@ -144,8 +147,9 @@ echo ""
-androidKeyaliasName "$ANDROID_KEYALIAS_NAME" \
-androidKeyaliasPass "$ANDROID_KEYALIAS_PASS" \
-androidTargetSdkVersion "$ANDROID_TARGET_SDK_VERSION" \
$CUSTOM_PARAMETERS \
> "$UNITY_PROJECT_PATH/out.log" 2>&1
-androidExportType "$ANDROID_EXPORT_TYPE" \
-androidSymbolType "$ANDROID_SYMBOL_TYPE" \
$CUSTOM_PARAMETERS
# Catch exit code
BUILD_EXIT_CODE=$?

View File

@ -5,7 +5,7 @@ echo "Changing to \"$ACTIVATE_LICENSE_PATH\" directory."
pushd "$ACTIVATE_LICENSE_PATH"
/Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity \
-logFile /dev/stdout \
-logFile - \
-batchmode \
-nographics \
-quit \

View File

@ -133,6 +133,8 @@ unity-editor \
-androidKeyaliasName "$ANDROID_KEYALIAS_NAME" \
-androidKeyaliasPass "$ANDROID_KEYALIAS_PASS" \
-androidTargetSdkVersion "$ANDROID_TARGET_SDK_VERSION" \
-androidExportType "$ANDROID_EXPORT_TYPE" \
-androidSymbolType "$ANDROID_SYMBOL_TYPE" \
$CUSTOM_PARAMETERS
# Catch exit code

View File

@ -126,11 +126,13 @@ $_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUST
-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
# Catch exit code
$Env:BUILD_EXIT_CODE=$?
$Env:BUILD_EXIT_CODE=$LastExitCode
# Display results
if ($Env:BUILD_EXIT_CODE -eq 0)

2
dist/sourcemap-register.js generated vendored

File diff suppressed because one or more lines are too long

View File

@ -1,60 +0,0 @@
"use strict";
/* eslint-disable no-process-exit */
const util = require("util");
const { JSDOM } = require("../../../..");
const { READY_STATES } = require("./xhr-utils");
const idlUtils = require("../generated/utils");
const tough = require("tough-cookie");
const dom = new JSDOM();
const xhr = new dom.window.XMLHttpRequest();
const xhrImpl = idlUtils.implForWrapper(xhr);
const chunks = [];
process.stdin.on("data", chunk => {
chunks.push(chunk);
});
process.stdin.on("end", () => {
const buffer = Buffer.concat(chunks);
const flag = JSON.parse(buffer.toString());
if (flag.body && flag.body.type === "Buffer" && flag.body.data) {
flag.body = Buffer.from(flag.body.data);
}
if (flag.cookieJar) {
flag.cookieJar = tough.CookieJar.fromJSON(flag.cookieJar);
}
flag.synchronous = false;
Object.assign(xhrImpl.flag, flag);
const { properties } = xhrImpl;
xhrImpl.readyState = READY_STATES.OPENED;
try {
xhr.addEventListener("loadend", () => {
if (properties.error) {
properties.error = properties.error.stack || util.inspect(properties.error);
}
process.stdout.write(JSON.stringify({
responseURL: xhrImpl.responseURL,
status: xhrImpl.status,
statusText: xhrImpl.statusText,
properties
}), () => {
process.exit(0);
});
}, false);
xhr.send(flag.body);
} catch (error) {
properties.error += error.stack || util.inspect(error);
process.stdout.write(JSON.stringify({
responseURL: xhrImpl.responseURL,
status: xhrImpl.status,
statusText: xhrImpl.statusText,
properties
}), () => {
process.exit(0);
});
}
});

View File

@ -18,7 +18,6 @@ module.exports = {
transform: {
'^.+\\.ts$': 'ts-jest',
},
autoRun: false,
// Indicates whether each individual test should be reported during the run
verbose: true,

View File

@ -24,7 +24,11 @@
"test-i-aws": "cross-env cloudRunnerTests=true cloudRunnerCluster=aws yarn test -i -t \"cloud runner\"",
"test-i-k8s": "cross-env cloudRunnerTests=true cloudRunnerCluster=k8s yarn test -i -t \"cloud runner\""
},
"engines": {
"node": ">=16.x"
},
"dependencies": {
"@actions/cache": "^3.1.3",
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.0",
"@actions/github": "^5.0.0",
@ -39,19 +43,21 @@
"nanoid": "^3.3.1",
"reflect-metadata": "^0.1.13",
"semver": "^7.3.5",
"unity-changeset": "^1.6.0",
"unity-changeset": "^2.0.0",
"uuid": "^8.3.2",
"yaml": "^1.10.2"
},
"devDependencies": {
"@arkweid/lefthook": "^0.7.7",
"@evilmartians/lefthook": "^1.2.9",
"@types/base-64": "^1.0.0",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@types/semver": "^7.3.9",
"@types/uuid": "^9.0.0",
"@typescript-eslint/parser": "4.8.1",
"@vercel/ncc": "^0.33.3",
"@vercel/ncc": "^0.36.1",
"cross-env": "^7.0.3",
"eslint": "7.17.0",
"eslint": "^7.23.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-github": "^4.1.1",
"eslint-plugin-jest": "24.1.3",
@ -59,11 +65,12 @@
"eslint-plugin-unicorn": "28.0.2",
"jest": "^27.5.1",
"jest-circus": "^27.5.1",
"jest-fail-on-console": "^2.3.0",
"jest-fail-on-console": "^3.0.2",
"js-yaml": "^4.1.0",
"prettier": "^2.5.1",
"ts-jest": "^27.1.3",
"ts-node": "10.4.0",
"typescript": "4.1.3"
"typescript": "4.1.3",
"yarn-audit-fix": "^9.3.8"
}
}

View File

@ -3,6 +3,7 @@ import { Action, BuildParameters, Cache, CloudRunner, Docker, ImageTag, Output }
import { Cli } from './model/cli/cli';
import MacBuilder from './model/mac-builder';
import PlatformSetup from './model/platform-setup';
async function runMain() {
try {
if (Cli.InitCliMode()) {
@ -18,16 +19,16 @@ async function runMain() {
const buildParameters = await BuildParameters.create();
const baseImage = new ImageTag(buildParameters);
if (buildParameters.cloudRunnerCluster !== 'local') {
await CloudRunner.run(buildParameters, baseImage.toString());
} else {
if (buildParameters.cloudRunnerCluster === 'local') {
core.info('Building locally');
await PlatformSetup.setup(buildParameters, actionFolder);
if (process.platform === 'darwin') {
MacBuilder.run(actionFolder, workspace, buildParameters);
MacBuilder.run(actionFolder);
} else {
await Docker.run(baseImage, { workspace, actionFolder, ...buildParameters });
await Docker.run(baseImage.toString(), { workspace, actionFolder, ...buildParameters });
}
} else {
await CloudRunner.run(buildParameters, baseImage.toString());
}
// Set output

View File

@ -1,4 +1,4 @@
import { stat } from 'fs/promises';
import { stat } from 'node:fs/promises';
describe('Integrity tests', () => {
describe('package-lock.json', () => {

View File

@ -1,5 +1,5 @@
import path from 'path';
import fs from 'fs';
import path from 'node:path';
import fs from 'node:fs';
import Action from './action';
describe('Action', () => {

View File

@ -1,23 +1,27 @@
import path from 'path';
import path from 'node:path';
class Action {
static get supportedPlatforms() {
static get supportedPlatforms(): string[] {
return ['linux', 'win32', 'darwin'];
}
static get isRunningLocally() {
static get isRunningLocally(): boolean {
return process.env.RUNNER_WORKSPACE === undefined;
}
static get isRunningFromSource() {
static get isRunningFromSource(): boolean {
return path.basename(__dirname) === 'model';
}
static get canonicalName() {
static get canonicalName(): string {
if (Action.isRunningFromSource) {
return path.basename(path.dirname(path.join(path.dirname(__filename), '/..')));
}
return 'unity-builder';
}
static get rootFolder() {
static get rootFolder(): string {
if (Action.isRunningFromSource) {
return path.dirname(path.dirname(path.dirname(__filename)));
}
@ -25,12 +29,12 @@ class Action {
return path.dirname(path.dirname(__filename));
}
static get actionFolder() {
static get actionFolder(): string {
return `${Action.rootFolder}/dist`;
}
static get workspace() {
return process.env.GITHUB_WORKSPACE;
static get workspace(): string {
return process.env.GITHUB_WORKSPACE!;
}
static checkCompatibility() {

View File

@ -3,15 +3,15 @@ import AndroidVersioning from './android-versioning';
describe('Android Versioning', () => {
describe('versionToVersionCode', () => {
it('defaults to 0 when versioning strategy is none', () => {
expect(AndroidVersioning.versionToVersionCode('none')).toBe(0);
expect(AndroidVersioning.versionToVersionCode('none')).toBe('0');
});
it('defaults to 1 when version is not a valid semver', () => {
expect(AndroidVersioning.versionToVersionCode('abcd')).toBe(1);
expect(AndroidVersioning.versionToVersionCode('abcd')).toBe('1');
});
it('returns a number', () => {
expect(AndroidVersioning.versionToVersionCode('123.456.789')).toBe(123456789);
expect(AndroidVersioning.versionToVersionCode('123.456.789')).toBe('123456789');
});
it('throw when generated version code is too large', () => {
@ -21,11 +21,11 @@ describe('Android Versioning', () => {
describe('determineVersionCode', () => {
it('defaults to parsed version', () => {
expect(AndroidVersioning.determineVersionCode('1.2.3', '')).toBe(1002003);
expect(AndroidVersioning.determineVersionCode('1.2.3', '')).toBe('1002003');
});
it('use specified code', () => {
expect(AndroidVersioning.determineVersionCode('1.2.3', 2)).toBe(2);
expect(AndroidVersioning.determineVersionCode('1.2.3', '2')).toBe('2');
});
});

View File

@ -2,19 +2,19 @@ import * as core from '@actions/core';
import * as semver from 'semver';
export default class AndroidVersioning {
static determineVersionCode(version, inputVersionCode) {
if (!inputVersionCode) {
static determineVersionCode(version: string, inputVersionCode: string): string {
if (inputVersionCode === '') {
return AndroidVersioning.versionToVersionCode(version);
}
return inputVersionCode;
}
static versionToVersionCode(version) {
static versionToVersionCode(version: string): string {
if (version === 'none') {
core.info(`Versioning strategy is set to ${version}, so android version code should not be applied.`);
return 0;
return '0';
}
const parsedVersion = semver.parse(version);
@ -22,7 +22,7 @@ export default class AndroidVersioning {
if (!parsedVersion) {
core.warning(`Could not parse "${version}" to semver, defaulting android version code to 1`);
return 1;
return '1';
}
// The greatest value Google Plays allows is 2100000000.
@ -36,10 +36,10 @@ export default class AndroidVersioning {
}
core.info(`Using android versionCode ${versionCode}`);
return versionCode;
return versionCode.toString();
}
static determineSdkManagerParameters(targetSdkVersion) {
static determineSdkManagerParameters(targetSdkVersion: string) {
const parsedVersion = Number.parseInt(targetSdkVersion.slice(-2), 10);
return Number.isNaN(parsedVersion) ? '' : `platforms;android-${parsedVersion}`;

View File

@ -33,7 +33,7 @@ describe('BuildParameters', () => {
it('determines the unity version only once', async () => {
jest.spyOn(UnityVersioning, 'determineUnityVersion').mockImplementation(() => '2019.2.11f1');
await BuildParameters.create();
await expect(UnityVersioning.determineUnityVersion).toHaveBeenCalledTimes(1);
expect(UnityVersioning.determineUnityVersion).toHaveBeenCalledTimes(1);
});
it('returns the android version code with provided input', async () => {
@ -47,13 +47,15 @@ describe('BuildParameters', () => {
it('returns the android version code from version by default', async () => {
const mockValue = '';
jest.spyOn(Input, 'androidVersionCode', 'get').mockReturnValue(mockValue);
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidVersionCode: 1003037 }));
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ androidVersionCode: '1003037' }),
);
});
it('determines the android sdk manager parameters only once', async () => {
jest.spyOn(AndroidVersioning, 'determineSdkManagerParameters').mockImplementation(() => 'platforms;android-30');
await BuildParameters.create();
await expect(AndroidVersioning.determineSdkManagerParameters).toHaveBeenCalledTimes(1);
expect(AndroidVersioning.determineSdkManagerParameters).toHaveBeenCalledTimes(1);
});
it('returns the targetPlatform', async () => {
@ -95,34 +97,41 @@ describe('BuildParameters', () => {
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ buildFile: mockValue }));
});
test.each([Platform.types.StandaloneWindows, Platform.types.StandaloneWindows64])(
'appends exe for %s',
async (targetPlatform) => {
test.each`
targetPlatform | expectedExtension | androidExportType
${Platform.types.Android} | ${'.apk'} | ${'androidPackage'}
${Platform.types.Android} | ${'.aab'} | ${'androidAppBundle'}
${Platform.types.Android} | ${''} | ${'androidStudioProject'}
${Platform.types.StandaloneWindows} | ${'.exe'} | ${'n/a'}
${Platform.types.StandaloneWindows64} | ${'.exe'} | ${'n/a'}
`(
'appends $expectedExtension for $targetPlatform with androidExportType $androidExportType',
async ({ targetPlatform, expectedExtension, androidExportType }) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidExportType', 'get').mockReturnValue(androidExportType);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.exe` }),
expect.objectContaining({ buildFile: `${targetPlatform}${expectedExtension}` }),
);
},
);
test.each([Platform.types.Android])('appends apk for %s', async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(false);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.apk` }),
);
});
test.each([Platform.types.Android])('appends aab for %s', async (targetPlatform) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidAppBundle', 'get').mockReturnValue(true);
await expect(BuildParameters.create()).resolves.toEqual(
expect.objectContaining({ buildFile: `${targetPlatform}.aab` }),
);
});
test.each`
targetPlatform | androidSymbolType
${Platform.types.Android} | ${'none'}
${Platform.types.Android} | ${'public'}
${Platform.types.Android} | ${'debugging'}
${Platform.types.StandaloneWindows} | ${'none'}
${Platform.types.StandaloneWindows64} | ${'none'}
`(
'androidSymbolType is set to $androidSymbolType when targetPlatform is $targetPlatform and input targetSymbolType is $androidSymbolType',
async ({ targetPlatform, androidSymbolType }) => {
jest.spyOn(Input, 'targetPlatform', 'get').mockReturnValue(targetPlatform);
jest.spyOn(Input, 'androidSymbolType', 'get').mockReturnValue(androidSymbolType);
jest.spyOn(Input, 'buildName', 'get').mockReturnValue(targetPlatform);
await expect(BuildParameters.create()).resolves.toEqual(expect.objectContaining({ androidSymbolType }));
},
);
it('returns the build method', async () => {
const mockValue = 'Namespace.ClassName.BuildMethod';

View File

@ -13,11 +13,14 @@ import GitHub from './github';
import CloudRunnerOptions from './cloud-runner/cloud-runner-options';
class BuildParameters {
// eslint-disable-next-line no-undef
[key: string]: any;
public editorVersion!: string;
public customImage!: string;
public unitySerial!: string;
public unityLicensingServer!: string;
public runnerTempPath: string | undefined;
public runnerTempPath!: string;
public targetPlatform!: string;
public projectPath!: string;
public buildName!: string;
@ -33,6 +36,9 @@ class BuildParameters {
public androidKeyaliasPass!: string;
public androidTargetSdkVersion!: string;
public androidSdkManagerParameters!: string;
public androidExportType!: string;
public androidSymbolType!: string;
public customParameters!: string;
public sshAgent!: string;
public cloudRunnerCluster!: string;
@ -40,8 +46,8 @@ class BuildParameters {
public gitPrivateToken!: string;
public awsStackName!: string;
public kubeConfig!: string;
public cloudRunnerMemory!: string;
public cloudRunnerCpu!: string;
public cloudRunnerMemory!: string | undefined;
public cloudRunnerCpu!: string | undefined;
public kubeVolumeSize!: string;
public kubeVolume!: string;
public kubeStorageClass!: string;
@ -61,7 +67,7 @@ class BuildParameters {
public logId!: string;
public buildGuid!: string;
public cloudRunnerBranch!: string;
public cloudRunnerDebug!: boolean;
public cloudRunnerDebug!: boolean | undefined;
public cloudRunnerBuilderPlatform!: string | undefined;
public isCliMode!: boolean;
public retainWorkspace!: boolean;
@ -76,29 +82,43 @@ class BuildParameters {
public triggerWorkflowOnComplete!: string[];
public cloudRunnerDebugSkipLFS!: boolean;
public cloudRunnerDebugSkipCache!: boolean;
public cacheUnityInstallationOnMac!: boolean;
public unityHubVersionOnMac!: string;
static async create(): Promise<BuildParameters> {
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidAppBundle);
const buildFile = this.parseBuildFile(Input.buildName, Input.targetPlatform, Input.androidExportType);
const editorVersion = UnityVersioning.determineUnityVersion(Input.projectPath, Input.unityVersion);
const buildVersion = await Versioning.determineBuildVersion(Input.versioningStrategy, Input.specifiedVersion);
const androidVersionCode = AndroidVersioning.determineVersionCode(buildVersion, Input.androidVersionCode);
const androidSdkManagerParameters = AndroidVersioning.determineSdkManagerParameters(Input.androidTargetSdkVersion);
// Todo - Don't use process.env directly, that's what the input model class is for.
// ---
const androidSymbolExportType = Input.androidSymbolType;
if (Platform.isAndroid(Input.targetPlatform)) {
switch (androidSymbolExportType) {
case 'none':
case 'public':
case 'debugging':
break;
default:
throw new Error(
`Invalid androidSymbolType: ${Input.androidSymbolType}. Must be one of: none, public, debugging`,
);
}
}
let unitySerial = '';
if (Input.unityLicensingServer === '') {
if (!process.env.UNITY_SERIAL && GitHub.githubInputEnabled) {
if (!Input.unitySerial && GitHub.githubInputEnabled) {
// No serial was present, so it is a personal license that we need to convert
if (!process.env.UNITY_LICENSE) {
if (!Input.unityLicense) {
throw new Error(`Missing Unity License File and no Serial was found. If this
is a personal license, make sure to follow the activation
steps and set the UNITY_LICENSE GitHub secret or enter a Unity
serial number inside the UNITY_SERIAL GitHub secret.`);
}
unitySerial = this.getSerialFromLicenseFile(process.env.UNITY_LICENSE);
unitySerial = this.getSerialFromLicenseFile(Input.unityLicense);
} else {
unitySerial = process.env.UNITY_SERIAL!;
unitySerial = Input.unitySerial!;
}
}
@ -107,7 +127,7 @@ class BuildParameters {
customImage: Input.customImage,
unitySerial,
unityLicensingServer: Input.unityLicensingServer,
runnerTempPath: process.env.RUNNER_TEMP,
runnerTempPath: Input.runnerTempPath,
targetPlatform: Input.targetPlatform,
projectPath: Input.projectPath,
buildName: Input.buildName,
@ -123,6 +143,8 @@ class BuildParameters {
androidKeyaliasPass: Input.androidKeyaliasPass,
androidTargetSdkVersion: Input.androidTargetSdkVersion,
androidSdkManagerParameters,
androidExportType: Input.androidExportType,
androidSymbolType: androidSymbolExportType,
customParameters: Input.customParameters,
sshAgent: Input.sshAgent,
gitPrivateToken: Input.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
@ -165,22 +187,35 @@ class BuildParameters {
triggerWorkflowOnComplete: CloudRunnerOptions.triggerWorkflowOnComplete,
cloudRunnerDebugSkipLFS: CloudRunnerOptions.cloudRunnerDebugSkipLFS,
cloudRunnerDebugSkipCache: CloudRunnerOptions.cloudRunnerDebugSkipCache,
cacheUnityInstallationOnMac: Input.cacheUnityInstallationOnMac,
unityHubVersionOnMac: Input.unityHubVersionOnMac,
};
}
static parseBuildFile(filename, platform, androidAppBundle) {
static parseBuildFile(filename: string, platform: string, androidExportType: string): string {
if (Platform.isWindows(platform)) {
return `${filename}.exe`;
}
if (Platform.isAndroid(platform)) {
return androidAppBundle ? `${filename}.aab` : `${filename}.apk`;
switch (androidExportType) {
case `androidPackage`:
return `${filename}.apk`;
case `androidAppBundle`:
return `${filename}.aab`;
case `androidStudioProject`:
return filename;
default:
throw new Error(
`Unknown Android Export Type: ${androidExportType}. Must be one of androidPackage for apk, androidAppBundle for aab, androidStudioProject for android project`,
);
}
}
return filename;
}
static getSerialFromLicenseFile(license) {
static getSerialFromLicenseFile(license: string) {
const startKey = `<DeveloperData Value="`;
const endKey = `"/>`;
const startIndex = license.indexOf(startKey) + startKey.length;

View File

@ -1,5 +1,5 @@
import * as core from '@actions/core';
import fs from 'fs';
import fs from 'node:fs';
import Action from './action';
import Project from './project';

View File

@ -16,7 +16,7 @@ export class CliFunctionsRepository {
});
}
public static GetCliFunctions(key) {
public static GetCliFunctions(key: any) {
const results = CliFunctionsRepository.targets.find((x) => x.key === key);
if (results === undefined || results.length === 0) {
throw new Error(`no CLI mode found for ${key}`);

View File

@ -13,13 +13,15 @@ import GitHub from '../github';
import { TaskParameterSerializer } from '../cloud-runner/services/task-parameter-serializer';
import { CloudRunnerFolders } from '../cloud-runner/services/cloud-runner-folders';
import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system';
import { OptionValues } from 'commander';
import { InputKey } from '../input';
export class Cli {
public static options;
public static options: OptionValues | undefined;
static get isCliMode() {
return Cli.options !== undefined && Cli.options.mode !== undefined && Cli.options.mode !== '';
}
public static query(key, alternativeKey) {
public static query(key: string, alternativeKey: string) {
if (Cli.options && Cli.options[key] !== undefined) {
return Cli.options[key];
}
@ -61,15 +63,15 @@ export class Cli {
static async RunCli(): Promise<void> {
GitHub.githubInputEnabled = false;
if (Cli.options['populateOverride'] === `true`) {
if (Cli.options!['populateOverride'] === `true`) {
await CloudRunnerQueryOverride.PopulateQueryOverrideInput();
}
if (Cli.options['logInput']) {
if (Cli.options!['logInput']) {
Cli.logInput();
}
const results = CliFunctionsRepository.GetCliFunctions(Cli.options.mode);
const results = CliFunctionsRepository.GetCliFunctions(Cli.options?.mode);
CloudRunnerLogger.log(`Entrypoint: ${results.key}`);
Cli.options.versioning = 'None';
Cli.options!.versioning = 'None';
const buildParameter = TaskParameterSerializer.readBuildParameterFromEnvironment();
CloudRunnerLogger.log(`Build Params:
@ -88,14 +90,15 @@ export class Cli {
const properties = CloudRunnerOptionsReader.GetProperties();
for (const element of properties) {
if (
Input[element] !== undefined &&
Input[element] !== '' &&
typeof Input[element] !== `function` &&
element in Input &&
Input[element as InputKey] !== undefined &&
Input[element as InputKey] !== '' &&
typeof Input[element as InputKey] !== `function` &&
element !== 'length' &&
element !== 'cliOptions' &&
element !== 'prototype'
) {
core.info(`${element} ${Input[element]}`);
core.info(`${element} ${Input[element as InputKey]}`);
}
}
core.info(`\n`);

View File

@ -2,7 +2,7 @@ import CloudRunnerLogger from '../../services/cloud-runner-logger';
import * as core from '@actions/core';
import * as SDK from 'aws-sdk';
import { BaseStackFormation } from './cloud-formations/base-stack-formation';
const crypto = require('crypto');
import crypto from 'node:crypto';
export class AWSBaseStack {
constructor(baseStackName: string) {

View File

@ -1,7 +1,7 @@
import { TaskDefinitionFormation } from './cloud-formations/task-definition-formation';
export class AWSCloudFormationTemplates {
public static getParameterTemplate(p1) {
public static getParameterTemplate(p1: string) {
return `
${p1}:
Type: String
@ -9,7 +9,7 @@ export class AWSCloudFormationTemplates {
`;
}
public static getSecretTemplate(p1) {
public static getSecretTemplate(p1: string) {
return `
${p1}Secret:
Type: AWS::SecretsManager::Secret
@ -19,14 +19,14 @@ export class AWSCloudFormationTemplates {
`;
}
public static getSecretDefinitionTemplate(p1, p2) {
public static getSecretDefinitionTemplate(p1: string, p2: string) {
return `
- Name: '${p1}'
ValueFrom: !Ref ${p2}Secret
`;
}
public static insertAtTemplate(template, insertionKey, insertion) {
public static insertAtTemplate(template: string, insertionKey: string, insertion: string) {
const index = template.search(insertionKey) + insertionKey.length + '\n'.length;
template = [template.slice(0, index), insertion, template.slice(index)].join('');

View File

@ -2,7 +2,7 @@ import * as AWS from 'aws-sdk';
import CloudRunnerEnvironmentVariable from '../../services/cloud-runner-environment-variable';
import * as core from '@actions/core';
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def';
import * as zlib from 'zlib';
import * as zlib from 'node:zlib';
import CloudRunnerLogger from '../../services/cloud-runner-logger';
import { Input } from '../../..';
import CloudRunner from '../../cloud-runner';
@ -197,22 +197,19 @@ class AWSTaskRunner {
}
private static logRecords(
records,
records: AWS.Kinesis.GetRecordsOutput,
iterator: string,
shouldReadLogs: boolean,
output: string,
shouldCleanup: boolean,
) {
if (records.Records.length > 0 && iterator) {
for (let index = 0; index < records.Records.length; index++) {
const json = JSON.parse(
zlib.gunzipSync(Buffer.from(records.Records[index].Data as string, 'base64')).toString('utf8'),
);
for (const record of records.Records) {
const json = JSON.parse(zlib.gunzipSync(Buffer.from(record.Data as string, 'base64')).toString('utf8'));
if (json.messageType === 'DATA_MESSAGE') {
for (let logEventsIndex = 0; logEventsIndex < json.logEvents.length; logEventsIndex++) {
const message = json.logEvents[logEventsIndex].message;
for (const logEvent of json.logEvents) {
({ shouldReadLogs, shouldCleanup, output } = FollowLogStreamService.handleIteration(
message,
logEvent.message,
shouldReadLogs,
shouldCleanup,
output,
@ -231,7 +228,7 @@ class AWSTaskRunner {
}).promise();
}
private static async getLogIterator(stream) {
private static async getLogIterator(stream: AWS.Kinesis.DescribeStreamOutput) {
return (
(
await AWSTaskRunner.Kinesis.getShardIterator({

View File

@ -4,7 +4,7 @@ import CloudRunnerLogger from '../../../services/cloud-runner-logger';
import { TaskService } from './task-service';
export class GarbageCollectionService {
static isOlderThan1day(date: any) {
static isOlderThan1day(date: Date) {
const ageDate = new Date(date.getTime() - Date.now());
return ageDate.getDay() > 0;
@ -17,14 +17,16 @@ export class GarbageCollectionService {
const cwl = new AWS.CloudWatchLogs();
const taskDefinitionsInUse = new Array();
const tasks = await TaskService.getTasks();
for (const task of tasks) {
const { taskElement, element } = task;
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}`);
await ecs.stopTask({ task: taskElement.taskArn || '', cluster: element }).promise();
}
}
const jobStacks = await TaskService.getCloudFormationJobStacks();
for (const element of jobStacks) {
if (
@ -36,13 +38,15 @@ export class GarbageCollectionService {
return;
}
if (deleteResources && (!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(element.CreationTime))) {
if (element.StackName === 'game-ci' || element.TemplateDescription === 'Game-CI base stack') {
CloudRunnerLogger.log(`Skipping ${element.StackName} ignore list`);
return;
}
CloudRunnerLogger.log(`Deleting ${element.logGroupName}`);
CloudRunnerLogger.log(`Deleting ${element.StackName}`);
const deleteStackInput: AWS.CloudFormation.DeleteStackInput = { StackName: element.StackName };
await CF.deleteStack(deleteStackInput).promise();
}
@ -51,7 +55,7 @@ export class GarbageCollectionService {
for (const element of logGroups) {
if (
deleteResources &&
(!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(new Date(element.createdAt)))
(!OneDayOlderOnly || GarbageCollectionService.isOlderThan1day(new Date(element.creationTime!)))
) {
CloudRunnerLogger.log(`Deleting ${element.logGroupName}`);
await cwl.deleteLogGroup({ logGroupName: element.logGroupName || '' }).promise();

View File

@ -5,6 +5,8 @@ import { BaseStackFormation } from '../cloud-formations/base-stack-formation';
import AwsTaskRunner from '../aws-task-runner';
import { ListObjectsRequest } from 'aws-sdk/clients/s3';
import CloudRunner from '../../../cloud-runner';
import { StackSummaries } from 'aws-sdk/clients/cloudformation';
import { LogGroups } from 'aws-sdk/clients/cloudwatchlogs';
export class TaskService {
static async watch() {
@ -18,7 +20,7 @@ export class TaskService {
return output;
}
public static async getCloudFormationJobStacks() {
const result: any[] = [];
const result: StackSummaries = [];
CloudRunnerLogger.log(``);
CloudRunnerLogger.log(`List Cloud Formation Stacks`);
process.env.AWS_REGION = Input.region;
@ -62,7 +64,7 @@ export class TaskService {
return result;
}
public static async getTasks() {
const result: any[] = [];
const result: { taskElement: AWS.ECS.Task; element: string }[] = [];
CloudRunnerLogger.log(``);
CloudRunnerLogger.log(`List Tasks`);
process.env.AWS_REGION = Input.region;
@ -123,7 +125,7 @@ export class TaskService {
return message;
}
public static async getLogGroups() {
const result: any[] = [];
const result: LogGroups = [];
process.env.AWS_REGION = Input.region;
const ecs = new AWS.CloudWatchLogs();
let logStreamInput: AWS.CloudWatchLogs.DescribeLogGroupsRequest = {

View File

@ -16,10 +16,10 @@ class KubernetesJobSpecFactory {
secrets: CloudRunnerSecret[],
buildGuid: string,
buildParameters: BuildParameters,
secretName,
pvcName,
jobName,
k8s,
secretName: string,
pvcName: string,
jobName: string,
k8s: any,
) {
environment.push(
...[

View File

@ -2,7 +2,7 @@ import { CoreV1Api } from '@kubernetes/client-node';
import CloudRunnerSecret from '../../services/cloud-runner-secret';
import * as k8s from '@kubernetes/client-node';
import CloudRunnerLogger from '../../services/cloud-runner-logger';
const base64 = require('base-64');
import * as base64 from 'base-64';
class KubernetesSecret {
static async createSecret(

View File

@ -3,7 +3,7 @@ import * as core from '@actions/core';
import * as k8s from '@kubernetes/client-node';
import BuildParameters from '../../../build-parameters';
import CloudRunnerLogger from '../../services/cloud-runner-logger';
import { IncomingMessage } from 'http';
import { IncomingMessage } from 'node:http';
import GitHub from '../../../github';
class KubernetesStorage {

View File

@ -14,7 +14,7 @@ export interface ProviderInterface {
branchName: string,
// eslint-disable-next-line no-unused-vars
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
);
): any;
setupWorkflow(
// eslint-disable-next-line no-unused-vars
buildGuid: string,
@ -24,7 +24,7 @@ export interface ProviderInterface {
branchName: string,
// eslint-disable-next-line no-unused-vars
defaultSecretsArray: { ParameterKey: string; EnvironmentVariable: string; ParameterValue: string }[],
);
): any;
runTaskInWorkflow(
// eslint-disable-next-line no-unused-vars
buildGuid: string,

View File

@ -1,6 +1,6 @@
import { assert } from 'console';
import fs from 'fs';
import path from 'path';
import { assert } from 'node:console';
import fs from 'node:fs';
import path from 'node:path';
import CloudRunner from '../cloud-runner';
import CloudRunnerLogger from '../services/cloud-runner-logger';
import { CloudRunnerFolders } from '../services/cloud-runner-folders';
@ -10,7 +10,7 @@ import { RemoteClientLogger } from './remote-client-logger';
import { Cli } from '../../cli/cli';
import { CliFunction } from '../../cli/cli-functions-repository';
// eslint-disable-next-line github/no-then
const fileExists = async (fpath) => !!(await fs.promises.stat(fpath).catch(() => false));
const fileExists = async (fpath: fs.PathLike) => !!(await fs.promises.stat(fpath).catch(() => false));
export class Caching {
@CliFunction(`cache-push`, `push to cache`)
@ -19,9 +19,9 @@ export class Caching {
const buildParameter = JSON.parse(process.env.BUILD_PARAMETERS || '{}');
CloudRunner.buildParameters = buildParameter;
await Caching.PushToCache(
Cli.options['cachePushTo'],
Cli.options['cachePushFrom'],
Cli.options['artifactName'] || '',
Cli.options!['cachePushTo'],
Cli.options!['cachePushFrom'],
Cli.options!['artifactName'] || '',
);
} catch (error: any) {
CloudRunnerLogger.log(`${error}`);
@ -34,9 +34,9 @@ export class Caching {
const buildParameter = JSON.parse(process.env.BUILD_PARAMETERS || '{}');
CloudRunner.buildParameters = buildParameter;
await Caching.PullFromCache(
Cli.options['cachePushFrom'],
Cli.options['cachePushTo'],
Cli.options['artifactName'] || '',
Cli.options!['cachePushFrom'],
Cli.options!['cachePushTo'],
Cli.options!['artifactName'] || '',
);
} catch (error: any) {
CloudRunnerLogger.log(`${error}`);

View File

@ -1,11 +1,11 @@
import fs from 'fs';
import fs from 'node:fs';
import CloudRunner from '../cloud-runner';
import { CloudRunnerFolders } from '../services/cloud-runner-folders';
import { Caching } from './caching';
import { LfsHashing } from '../services/lfs-hashing';
import { RemoteClientLogger } from './remote-client-logger';
import path from 'path';
import { assert } from 'console';
import path from 'node:path';
import { assert } from 'node:console';
import CloudRunnerLogger from '../services/cloud-runner-logger';
import { CliFunction } from '../../cli/cli-functions-repository';
import { CloudRunnerSystem } from '../services/cloud-runner-system';

View File

@ -13,7 +13,7 @@ export class RemoteClientLogger {
CloudRunnerLogger.log(`[Client][Diagnostic] ${message}`);
}
public static logWarning(message) {
public static logWarning(message: string) {
CloudRunnerLogger.logWarning(message);
}
}

View File

@ -3,11 +3,12 @@ import CloudRunner from '../cloud-runner';
import * as core from '@actions/core';
import { CustomWorkflow } from '../workflows/custom-workflow';
import { RemoteClientLogger } from '../remote-client/remote-client-logger';
import path from 'path';
import * as fs from 'fs';
import path from 'node:path';
import fs from 'node:fs';
import Input from '../../input';
import CloudRunnerOptions from '../cloud-runner-options';
import { CustomStep } from './custom-step';
import { CloudRunnerStepState } from '../cloud-runner-step-state';
export class CloudRunnerCustomSteps {
static GetCustomStepsFromFiles(hookLifecycle: string): CustomStep[] {
@ -183,13 +184,13 @@ export class CloudRunnerCustomSteps {
return results;
}
private static ConvertYamlSecrets(object) {
private static ConvertYamlSecrets(object: CustomStep) {
if (object.secrets === undefined) {
object.secrets = [];
return;
}
object.secrets = object.secrets.map((x) => {
object.secrets = object.secrets.map((x: { [key: string]: any }) => {
return {
ParameterKey: x.name,
EnvironmentVariable: Input.ToEnvVarFormat(x.name),
@ -229,7 +230,7 @@ export class CloudRunnerCustomSteps {
return object;
}
static async RunPostBuildSteps(cloudRunnerStepState) {
static async RunPostBuildSteps(cloudRunnerStepState: CloudRunnerStepState) {
let output = ``;
const steps: CustomStep[] = [
...CloudRunnerCustomSteps.ParseSteps(CloudRunner.buildParameters.postBuildSteps),
@ -248,7 +249,7 @@ export class CloudRunnerCustomSteps {
return output;
}
static async RunPreBuildSteps(cloudRunnerStepState) {
static async RunPreBuildSteps(cloudRunnerStepState: CloudRunnerStepState) {
let output = ``;
const steps: CustomStep[] = [
...CloudRunnerCustomSteps.ParseSteps(CloudRunner.buildParameters.preBuildSteps),

View File

@ -1,4 +1,4 @@
import path from 'path';
import path from 'node:path';
import CloudRunnerOptions from '../cloud-runner-options';
import CloudRunner from './../cloud-runner';

View File

@ -2,7 +2,7 @@ import Input from '../../input';
import { GenericInputReader } from '../../input-readers/generic-input-reader';
import CloudRunnerOptions from '../cloud-runner-options';
const formatFunction = (value, arguments_) => {
const formatFunction = (value: string, arguments_: any[]) => {
for (const element of arguments_) {
value = value.replace(`{${element.key}}`, element.value);
}
@ -11,11 +11,11 @@ const formatFunction = (value, arguments_) => {
};
class CloudRunnerQueryOverride {
static queryOverrides: any;
static queryOverrides: { [key: string]: string } | undefined;
// TODO accept premade secret sources or custom secret source definition yamls
public static query(key, alternativeKey) {
public static query(key: string, alternativeKey: string) {
if (CloudRunnerQueryOverride.queryOverrides && CloudRunnerQueryOverride.queryOverrides[key] !== undefined) {
return CloudRunnerQueryOverride.queryOverrides[key];
}
@ -30,7 +30,7 @@ class CloudRunnerQueryOverride {
return;
}
private static shouldUseOverride(query) {
private static shouldUseOverride(query: string) {
if (CloudRunnerOptions.readInputOverrideCommand() !== '') {
if (CloudRunnerOptions.readInputFromOverrideList() !== '') {
const doesInclude =
@ -44,7 +44,7 @@ class CloudRunnerQueryOverride {
}
}
private static async queryOverride(query) {
private static async queryOverride(query: string) {
if (!this.shouldUseOverride(query)) {
throw new Error(`Should not be trying to run override query on ${query}`);
}
@ -56,7 +56,7 @@ class CloudRunnerQueryOverride {
public static async PopulateQueryOverrideInput() {
const queries = CloudRunnerOptions.readInputFromOverrideList().split(',');
CloudRunnerQueryOverride.queryOverrides = new Array();
CloudRunnerQueryOverride.queryOverrides = {};
for (const element of queries) {
if (CloudRunnerQueryOverride.shouldUseOverride(element)) {
CloudRunnerQueryOverride.queryOverrides[element] = await CloudRunnerQueryOverride.queryOverride(element);

View File

@ -1,9 +1,9 @@
import CloudRunnerSecret from './cloud-runner-secret';
export class CustomStep {
public commands;
public commands!: string;
public secrets: CloudRunnerSecret[] = new Array<CloudRunnerSecret>();
public name;
public name!: string;
public image: string = `ubuntu`;
public hook!: string;
}

View File

@ -1,5 +1,5 @@
import { CloudRunnerSystem } from './cloud-runner-system';
import * as fs from 'fs';
import fs from 'node:fs';
import CloudRunnerLogger from './cloud-runner-logger';
import CloudRunnerOptions from '../cloud-runner-options';
import BuildParameters from '../../build-parameters';

View File

@ -44,20 +44,21 @@ export class TaskParameterSerializer {
x.name = TaskParameterSerializer.ToEnvVarFormat(x.name);
x.value = `${x.value}`;
if (buildParameters.cloudRunnerDebug && Number(x.name) === Number.NaN) {
if (buildParameters.cloudRunnerDebug && Number.isNaN(Number(x.name))) {
core.info(`[ERROR] found a number in task param serializer ${JSON.stringify(x)}`);
}
return x;
}),
(item) => item.name,
(item: CloudRunnerEnvironmentVariable) => item.name,
);
return result;
}
static uniqBy(a, key) {
const seen = {};
// eslint-disable-next-line no-unused-vars
static uniqBy(a: CloudRunnerEnvironmentVariable[], key: (parameters: CloudRunnerEnvironmentVariable) => string) {
const seen: { [key: string]: boolean } = {};
return a.filter(function (item) {
const k = key(item);
@ -89,23 +90,23 @@ export class TaskParameterSerializer {
return TaskParameterSerializer.serializeFromType(Input);
}
public static ToEnvVarFormat(input): string {
public static ToEnvVarFormat(input: string): string {
return CloudRunnerOptions.ToEnvVarFormat(input);
}
public static UndoEnvVarFormat(element): string {
public static UndoEnvVarFormat(element: string): string {
return this.camelize(element.replace('GAMECI_', '').toLowerCase().replace(/_+/g, ' '));
}
private static camelize(string) {
private static camelize(string: string) {
return string
.replace(/^\w|[A-Z]|\b\w/g, function (word, index) {
.replace(/(^\w)|([A-Z])|(\b\w)/g, function (word: string, index: number) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
})
.replace(/\s+/g, '');
}
private static serializeFromObject(buildParameters) {
private static serializeFromObject(buildParameters: any) {
const array: any[] = [];
const keys = Object.getOwnPropertyNames(buildParameters).filter((x) => !this.blocked.has(x));
for (const element of keys) {
@ -124,7 +125,7 @@ export class TaskParameterSerializer {
return array;
}
private static serializeFromType(type) {
private static serializeFromType(type: any) {
const array: any[] = [];
const input = CloudRunnerOptionsReader.GetProperties();
for (const element of input) {
@ -149,14 +150,15 @@ export class TaskParameterSerializer {
return array;
}
private static getValue(key) {
private static getValue(key: string) {
return CloudRunnerQueryOverride.queryOverrides !== undefined &&
CloudRunnerQueryOverride.queryOverrides[key] !== undefined
? CloudRunnerQueryOverride.queryOverrides[key]
: process.env[key];
}
s;
private static tryAddInput(array, key): CloudRunnerSecret[] {
private static tryAddInput(array: CloudRunnerSecret[], key: string): CloudRunnerSecret[] {
const value = TaskParameterSerializer.getValue(key);
if (value !== undefined && value !== '' && value !== 'null') {
array.push({

View File

@ -4,8 +4,9 @@ import UnityVersioning from '../../unity-versioning';
import { Cli } from '../../cli/cli';
import CloudRunnerOptions from '../cloud-runner-options';
import setups from './cloud-runner-suite.test';
import { OptionValues } from 'commander';
async function CreateParameters(overrides) {
async function CreateParameters(overrides: OptionValues | undefined) {
if (overrides) Cli.options = overrides;
return BuildParameters.create();

View File

@ -1,5 +1,5 @@
import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import BuildParameters from '../../build-parameters';
import { Cli } from '../../cli/cli';
import UnityVersioning from '../../unity-versioning';

View File

@ -7,8 +7,9 @@ import { v4 as uuidv4 } from 'uuid';
import CloudRunnerOptions from '../cloud-runner-options';
import setups from './cloud-runner-suite.test';
import { CloudRunnerSystem } from '../services/cloud-runner-system';
import { OptionValues } from 'commander';
async function CreateParameters(overrides) {
async function CreateParameters(overrides: OptionValues | undefined) {
if (overrides) {
Cli.options = overrides;
}

View File

@ -4,56 +4,48 @@ import { CloudRunnerStepState } from '../cloud-runner-step-state';
import { WorkflowInterface } from './workflow-interface';
import * as core from '@actions/core';
import { CloudRunnerCustomHooks } from '../services/cloud-runner-custom-hooks';
import path from 'path';
import path from 'node:path';
import CloudRunner from '../cloud-runner';
import CloudRunnerOptions from '../cloud-runner-options';
import { CloudRunnerCustomSteps } from '../services/cloud-runner-custom-steps';
export class BuildAutomationWorkflow implements WorkflowInterface {
async run(cloudRunnerStepState: CloudRunnerStepState) {
try {
return await BuildAutomationWorkflow.standardBuildAutomation(cloudRunnerStepState.image, cloudRunnerStepState);
} catch (error) {
throw error;
}
return await BuildAutomationWorkflow.standardBuildAutomation(cloudRunnerStepState.image, cloudRunnerStepState);
}
private static async standardBuildAutomation(baseImage: any, cloudRunnerStepState: CloudRunnerStepState) {
private static async standardBuildAutomation(baseImage: string, cloudRunnerStepState: CloudRunnerStepState) {
// TODO accept post and pre build steps as yaml files in the repo
try {
CloudRunnerLogger.log(`Cloud Runner is running standard build automation`);
CloudRunnerLogger.log(`Cloud Runner is running standard build automation`);
let output = '';
let output = '';
output += await CloudRunnerCustomSteps.RunPreBuildSteps(cloudRunnerStepState);
CloudRunnerLogger.logWithTime('Configurable pre build step(s) time');
output += await CloudRunnerCustomSteps.RunPreBuildSteps(cloudRunnerStepState);
CloudRunnerLogger.logWithTime('Configurable pre build step(s) time');
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('build');
CloudRunnerLogger.log(baseImage.toString());
CloudRunnerLogger.logLine(` `);
CloudRunnerLogger.logLine('Starting build automation job');
if (!CloudRunner.buildParameters.isCliMode) core.startGroup('build');
CloudRunnerLogger.log(baseImage);
CloudRunnerLogger.logLine(` `);
CloudRunnerLogger.logLine('Starting build automation job');
output += await CloudRunner.Provider.runTaskInWorkflow(
CloudRunner.buildParameters.buildGuid,
baseImage.toString(),
BuildAutomationWorkflow.BuildWorkflow,
`/${CloudRunnerFolders.buildVolumeFolder}`,
`/${CloudRunnerFolders.buildVolumeFolder}/`,
cloudRunnerStepState.environment,
cloudRunnerStepState.secrets,
);
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
CloudRunnerLogger.logWithTime('Build time');
output += await CloudRunner.Provider.runTaskInWorkflow(
CloudRunner.buildParameters.buildGuid,
baseImage.toString(),
BuildAutomationWorkflow.BuildWorkflow,
`/${CloudRunnerFolders.buildVolumeFolder}`,
`/${CloudRunnerFolders.buildVolumeFolder}/`,
cloudRunnerStepState.environment,
cloudRunnerStepState.secrets,
);
if (!CloudRunner.buildParameters.isCliMode) core.endGroup();
CloudRunnerLogger.logWithTime('Build time');
output += await CloudRunnerCustomSteps.RunPostBuildSteps(cloudRunnerStepState);
CloudRunnerLogger.logWithTime('Configurable post build step(s) time');
output += await CloudRunnerCustomSteps.RunPostBuildSteps(cloudRunnerStepState);
CloudRunnerLogger.logWithTime('Configurable post build step(s) time');
CloudRunnerLogger.log(`Cloud Runner finished running standard build automation`);
CloudRunnerLogger.log(`Cloud Runner finished running standard build automation`);
return output;
} catch (error) {
throw error;
}
return output;
}
private static get BuildWorkflow() {
@ -85,7 +77,7 @@ export class BuildAutomationWorkflow implements WorkflowInterface {
${BuildAutomationWorkflow.TreeCommand}`;
}
private static setupCommands(builderPath) {
private static setupCommands(builderPath: string) {
const commands = `mkdir -p ${CloudRunnerFolders.ToLinuxFolder(
CloudRunnerFolders.builderPathAbsolute,
)} && git clone -q -b ${CloudRunner.buildParameters.cloudRunnerBranch} ${
@ -105,7 +97,7 @@ echo "bootstrap game ci cloud runner..."
node ${builderPath} -m remote-cli-pre-build`;
}
private static BuildCommands(builderPath) {
private static BuildCommands(builderPath: string) {
const distFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist');
const ubuntuPlatformsFolder = path.join(CloudRunnerFolders.builderPathAbsolute, 'dist', 'platforms', 'ubuntu');

View File

@ -4,5 +4,5 @@ export interface WorkflowInterface {
run(
// eslint-disable-next-line no-unused-vars
cloudRunnerStepState: CloudRunnerStepState,
);
): Promise<string>;
}

View File

@ -1,16 +1,19 @@
import { exec } from '@actions/exec';
import { execWithErrorCheck } from './exec-with-error-check';
import ImageEnvironmentFactory from './image-environment-factory';
import { existsSync, mkdirSync } from 'fs';
import path from 'path';
import { existsSync, mkdirSync } from 'node:fs';
import path from 'node:path';
import { ExecOptions } from '@actions/exec';
import { DockerParameters, StringKeyValuePair } from './shared-types';
class Docker {
static async run(
image,
parameters,
silent = false,
overrideCommands = '',
additionalVariables: any[] = [],
options: any = false,
image: string,
parameters: DockerParameters,
silent: boolean = false,
overrideCommands: string = '',
additionalVariables: StringKeyValuePair[] = [],
// eslint-disable-next-line unicorn/no-useless-undefined
options: ExecOptions | undefined = undefined,
entrypointBash: boolean = false,
) {
let runCommand = '';
@ -21,19 +24,19 @@ class Docker {
case 'win32':
runCommand = this.getWindowsCommand(image, parameters);
}
if (options !== false) {
if (options) {
options.silent = silent;
await exec(runCommand, undefined, options);
await execWithErrorCheck(runCommand, undefined, options);
} else {
await exec(runCommand, undefined, { silent });
await execWithErrorCheck(runCommand, undefined, { silent });
}
}
static getLinuxCommand(
image,
parameters,
overrideCommands = '',
additionalVariables: any[] = [],
image: string,
parameters: DockerParameters,
overrideCommands: string = '',
additionalVariables: StringKeyValuePair[] = [],
entrypointBash: boolean = false,
): string {
const { workspace, actionFolder, runnerTempPath, sshAgent, gitPrivateToken } = parameters;
@ -67,7 +70,7 @@ class Docker {
"${overrideCommands !== '' ? overrideCommands : `/entrypoint.sh`}"`;
}
static getWindowsCommand(image: any, parameters: any): string {
static getWindowsCommand(image: string, parameters: DockerParameters): string {
const { workspace, actionFolder, unitySerial, gitPrivateToken } = parameters;
return `docker run \

View File

@ -0,0 +1,24 @@
import { getExecOutput, ExecOptions } from '@actions/exec';
export async function execWithErrorCheck(
commandLine: string,
arguments_?: string[],
options?: ExecOptions,
): Promise<number> {
const result = await getExecOutput(commandLine, arguments_, options);
// 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

@ -3,6 +3,7 @@ import CloudRunner from './cloud-runner/cloud-runner';
import CloudRunnerOptions from './cloud-runner/cloud-runner-options';
import * as core from '@actions/core';
import { Octokit } from '@octokit/core';
class GitHub {
private static readonly asyncChecksApiWorkflowName = `Async Checks API`;
public static githubInputEnabled: boolean = true;
@ -44,8 +45,8 @@ class GitHub {
return CloudRunnerOptions.githubRepoName;
}
public static async createGitHubCheck(summary) {
if (!CloudRunnerOptions.githubChecks || CloudRunner.isCloudRunnerEnvironment) {
public static async createGitHubCheck(summary: string) {
if (!CloudRunnerOptions.githubChecks) {
return ``;
}
GitHub.startedDate = new Date().toISOString();
@ -81,8 +82,7 @@ class GitHub {
}
public static async updateGitHubCheck(longDescription, summary, result = `neutral`, status = `in_progress`) {
const isLocalAsync = CloudRunner.buildParameters.asyncWorkflow && !CloudRunner.isCloudRunnerAsyncEnvironment;
if (!CloudRunnerOptions.githubChecks || isLocalAsync) {
if (!CloudRunnerOptions.githubChecks) {
return;
}
GitHub.longDescriptionContent += `\n${longDescription}`;
@ -129,15 +129,15 @@ class GitHub {
await GitHub.updateGitHubCheckRequest(data);
}
public static async updateGitHubCheckRequest(data) {
public static async updateGitHubCheckRequest(data: any) {
return await GitHub.octokitDefaultToken.request(`PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}`, data);
}
public static async createGitHubCheckRequest(data) {
public static async createGitHubCheckRequest(data: any) {
return await GitHub.octokitDefaultToken.request(`POST /repos/{owner}/{repo}/check-runs`, data);
}
public static async runUpdateAsyncChecksWorkflow(data, mode) {
public static async runUpdateAsyncChecksWorkflow(data: any, mode: string) {
if (mode === `create`) {
throw new Error(`Not supported: only use update`);
}

View File

@ -1,13 +1,8 @@
import BuildParameters from './build-parameters';
import { ReadLicense } from './input-readers/test-license-reader';
class Parameter {
public name;
public value;
}
import { DockerParameters, StringKeyValuePair } from './shared-types';
class ImageEnvironmentFactory {
public static getEnvVarString(parameters, additionalVariables: any[] = []) {
public static getEnvVarString(parameters: DockerParameters, additionalVariables: StringKeyValuePair[] = []) {
const environmentVariables = ImageEnvironmentFactory.getEnvironmentVariables(parameters, additionalVariables);
let string = '';
for (const p of environmentVariables) {
@ -25,8 +20,9 @@ class ImageEnvironmentFactory {
return string;
}
public static getEnvironmentVariables(parameters: BuildParameters, additionalVariables: any[] = []) {
let environmentVariables: Parameter[] = [
public static getEnvironmentVariables(parameters: DockerParameters, additionalVariables: 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 },
@ -50,6 +46,8 @@ class ImageEnvironmentFactory {
{ name: 'ANDROID_KEYALIAS_PASS', value: parameters.androidKeyaliasPass },
{ name: 'ANDROID_TARGET_SDK_VERSION', value: parameters.androidTargetSdkVersion },
{ name: 'ANDROID_SDK_MANAGER_PARAMETERS', value: parameters.androidSdkManagerParameters },
{ name: 'ANDROID_EXPORT_TYPE', value: parameters.androidExportType },
{ name: 'ANDROID_SYMBOL_TYPE', value: parameters.androidSymbolType },
{ name: 'CUSTOM_PARAMETERS', value: parameters.customParameters },
{ name: 'CHOWN_FILES_TO', value: parameters.chownFilesTo },
{ name: 'GITHUB_REF', value: process.env.GITHUB_REF },

View File

@ -1,7 +1,7 @@
import ImageTag from './image-tag';
describe('ImageTag', () => {
const some = {
const testImageParameters = {
editorVersion: '2099.9.f9f9',
targetPlatform: 'Test',
builderPlatform: '',
@ -15,38 +15,38 @@ describe('ImageTag', () => {
describe('constructor', () => {
it('can be called', () => {
const { targetPlatform } = some;
expect(() => new ImageTag({ targetPlatform })).not.toThrow();
expect(() => new ImageTag(testImageParameters)).not.toThrow();
});
it('accepts parameters and sets the right properties', () => {
const image = new ImageTag(some);
const image = new ImageTag(testImageParameters);
expect(image.repository).toStrictEqual('unityci');
expect(image.name).toStrictEqual('editor');
expect(image.editorVersion).toStrictEqual(some.editorVersion);
expect(image.targetPlatform).toStrictEqual(some.targetPlatform);
expect(image.builderPlatform).toStrictEqual(some.builderPlatform);
expect(image.editorVersion).toStrictEqual(testImageParameters.editorVersion);
expect(image.targetPlatform).toStrictEqual(testImageParameters.targetPlatform);
expect(image.builderPlatform).toStrictEqual(testImageParameters.builderPlatform);
});
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', (version) => {
expect(() => new ImageTag({ editorVersion: version, targetPlatform: some.targetPlatform })).not.toThrow();
expect(
() => new ImageTag({ editorVersion: version, targetPlatform: testImageParameters.targetPlatform }),
).not.toThrow();
});
test.each(['some version', ''])('throws for incorrect version %p', (editorVersion) => {
const { targetPlatform } = some;
const { targetPlatform } = testImageParameters;
expect(() => new ImageTag({ editorVersion, targetPlatform })).toThrow();
});
test.each([undefined, 'nonExisting'])('throws for unsupported target %p', (targetPlatform) => {
test.each(['nonExisting'])('throws for unsupported target %p', (targetPlatform) => {
expect(() => new ImageTag({ targetPlatform })).toThrow();
});
});
describe('toString', () => {
it('returns the correct version', () => {
const image = new ImageTag({ editorVersion: '2099.1.1111', targetPlatform: some.targetPlatform });
const image = new ImageTag({ editorVersion: '2099.1.1111', targetPlatform: testImageParameters.targetPlatform });
switch (process.platform) {
case 'win32':
expect(image.toString()).toStrictEqual(`${defaults.image}:windows-2099.1.1111-1`);
@ -59,7 +59,7 @@ describe('ImageTag', () => {
it('returns customImage if given', () => {
const image = new ImageTag({
editorVersion: '2099.1.1111',
targetPlatform: some.targetPlatform,
targetPlatform: testImageParameters.targetPlatform,
customImage: `${defaults.image}:2099.1.1111@347598437689743986`,
});
@ -80,7 +80,7 @@ describe('ImageTag', () => {
});
it('returns no specific build platform for generic targetPlatforms', () => {
const image = new ImageTag({ targetPlatform: 'NoTarget' });
const image = new ImageTag({ editorVersion: '2019.2.11f1', targetPlatform: 'NoTarget' });
switch (process.platform) {
case 'win32':

View File

@ -1,21 +1,18 @@
import Platform from './platform';
import BuildParameters from './build-parameters';
import Input from './input';
class ImageTag {
public repository: string;
public name: string;
public cloudRunnerBuilderPlatform!: string | undefined;
public cloudRunnerBuilderPlatform!: string;
public editorVersion: string;
public targetPlatform: any;
public targetPlatform: string;
public builderPlatform: string;
public customImage: any;
public customImage: string;
public imageRollingVersion: number;
public imagePlatformPrefix: string;
constructor(imageProperties: Partial<BuildParameters>) {
const { editorVersion = '2019.2.11f1', targetPlatform, customImage, cloudRunnerBuilderPlatform } = imageProperties;
constructor(imageProperties: { [key: string]: string }) {
const { editorVersion, targetPlatform, customImage, cloudRunnerBuilderPlatform } = imageProperties;
if (!ImageTag.versionPattern.test(editorVersion)) {
throw new Error(`Invalid version "${editorVersion}".`);
@ -39,8 +36,8 @@ class ImageTag {
this.imageRollingVersion = 1; // Will automatically roll to the latest non-breaking version.
}
static get versionPattern() {
return /^20\d{2}\.\d\.\w{3,4}|3$/;
static get versionPattern(): RegExp {
return /^(20\d{2}\.\d\.\w{3,4}|3)$/;
}
static get targetPlatformSuffixes() {
@ -60,7 +57,7 @@ class ImageTag {
};
}
static getImagePlatformPrefixes(platform) {
static getImagePlatformPrefixes(platform: string): string {
switch (platform) {
case 'win32':
return 'windows';
@ -71,7 +68,7 @@ class ImageTag {
}
}
static getTargetPlatformToTargetPlatformSuffixMap(platform, version) {
static getTargetPlatformToTargetPlatformSuffixMap(platform: string, version: string): string {
const { generic, webgl, mac, windows, windowsIl2cpp, wsaPlayer, linux, linuxIl2cpp, android, ios, tvos, facebook } =
ImageTag.targetPlatformSuffixes;
@ -84,7 +81,7 @@ class ImageTag {
case Platform.types.StandaloneWindows:
case Platform.types.StandaloneWindows64:
// Can only build windows-il2cpp on a windows based system
if (Input.useIL2Cpp && process.platform === 'win32') {
if (process.platform === 'win32') {
// Unity versions before 2019.3 do not support il2cpp
if (major >= 2020 || (major === 2019 && minor >= 3)) {
return windowsIl2cpp;
@ -97,7 +94,7 @@ class ImageTag {
return windows;
case Platform.types.StandaloneLinux64: {
// Unity versions before 2019.3 do not support il2cpp
if ((Input.useIL2Cpp && major >= 2020) || (major === 2019 && minor >= 3)) {
if (major >= 2020 || (major === 2019 && minor >= 3)) {
return linuxIl2cpp;
}
@ -150,17 +147,17 @@ class ImageTag {
}
}
get tag() {
get tag(): string {
const versionAndPlatform = `${this.editorVersion}-${this.builderPlatform}`.replace(/-+$/, '');
return `${this.imagePlatformPrefix}-${versionAndPlatform}-${this.imageRollingVersion}`;
}
get image() {
get image(): string {
return `${this.repository}/${this.name}`.replace(/^\/+/, '');
}
toString() {
toString(): string {
const { image, tag, customImage } = this;
if (customImage) return customImage;

View File

@ -1,10 +1,22 @@
import * as Index from '.';
interface ExportedModules {
[key: string]: any;
Action: any;
BuildParameters: any;
Cache: any;
Docker: any;
ImageTag: any;
Input: any;
Platform: any;
Project: any;
Unity: any;
}
const exportedModules: ExportedModules = Index;
describe('Index', () => {
test.each(['Action', 'BuildParameters', 'Cache', 'Docker', 'ImageTag', 'Input', 'Platform', 'Project', 'Unity'])(
'exports %s',
(exportedModule) => {
expect(Index[exportedModule]).toBeDefined();
},
);
test.each(Object.keys(exportedModules))('exports %s', (exportedModule) => {
expect(exportedModules[exportedModule]).toBeDefined();
});
});

View File

@ -1,5 +1,5 @@
import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import YAML from 'yaml';
export class ActionYamlReader {

View File

@ -2,7 +2,7 @@ import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system'
import CloudRunnerOptions from '../cloud-runner/cloud-runner-options';
export class GenericInputReader {
public static async Run(command) {
public static async Run(command: string) {
if (CloudRunnerOptions.cloudRunnerCluster === 'local') {
return '';
}

View File

@ -1,5 +1,5 @@
import { assert } from 'console';
import fs from 'fs';
import { assert } from 'node:console';
import fs from 'node:fs';
import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system';
import CloudRunnerLogger from '../cloud-runner/services/cloud-runner-logger';
import CloudRunnerOptions from '../cloud-runner/cloud-runner-options';

View File

@ -1,9 +1,9 @@
import path from 'path';
import fs from 'fs';
import path from 'node:path';
import fs from 'node:fs';
import YAML from 'yaml';
import CloudRunnerOptions from '../cloud-runner/cloud-runner-options';
export function ReadLicense() {
export function ReadLicense(): string {
if (CloudRunnerOptions.cloudRunnerCluster === 'local') {
return '';
}

View File

@ -161,6 +161,82 @@ describe('Input', () => {
});
});
describe('androidExportType', () => {
it('returns the default value', () => {
expect(Input.androidExportType).toStrictEqual('androidPackage');
});
// TODO: Remove "and androidAppBundle is not set" in v3
test.each`
input | expected
${'androidPackage'} | ${'androidPackage'}
${'androidAppBundle'} | ${'androidAppBundle'}
${'androidStudioProject'} | ${'androidStudioProject'}
`('returns $expected when $input is passed and androidAppBundle is not set', ({ input, expected }) => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue(input);
expect(Input.androidExportType).toStrictEqual(expected);
expect(spy).toHaveBeenCalledTimes(1);
});
// TODO: Remove in v3
test.each`
input | expected
${'androidPackage'} | ${'androidPackage'}
${'androidAppBundle'} | ${'androidAppBundle'}
${'androidStudioProject'} | ${'androidStudioProject'}
`('returns $expected when $input is passed and overrides androidAppBundle if it is set', ({ input, expected }) => {
const spy = jest.spyOn(Input, 'getInput');
spy.mockImplementationOnce(() => {
return input;
});
spy.mockImplementationOnce(() => {
return 'true';
});
expect(Input.androidExportType).toStrictEqual(expected);
expect(spy).toHaveBeenCalledTimes(1);
});
// TODO: Remove in v3
test.each`
input | expected
${'true'} | ${'androidAppBundle'}
${'false'} | ${'androidPackage'}
`(
'returns $expected when androidExportType is undefined and androidAppBundle is set to $input',
({ input, expected }) => {
const spy = jest.spyOn(Input, 'getInput');
spy.mockImplementationOnce(() => {
return '';
});
spy.mockImplementationOnce(() => {
return input;
});
expect(Input.androidExportType).toStrictEqual(expected);
expect(spy).toHaveBeenCalledTimes(2);
},
);
});
describe('androidSymbolType', () => {
it('returns the default value', () => {
expect(Input.androidSymbolType).toStrictEqual('none');
});
test.each`
input | expected
${'none'} | ${'none'}
${'public'} | ${'public'}
${'debugging'} | ${'debugging'}
`('returns $expected when $input is passed', ({ input, expected }) => {
const spy = jest.spyOn(core, 'getInput').mockReturnValue(input);
expect(Input.androidExportType).toStrictEqual(expected);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('androidKeystoreName', () => {
it('returns the default value', () => {
expect(Input.androidKeystoreName).toStrictEqual('');

View File

@ -1,11 +1,13 @@
import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import { Cli } from './cli/cli';
import CloudRunnerQueryOverride from './cloud-runner/services/cloud-runner-query-override';
import Platform from './platform';
import GitHub from './github';
const core = require('@actions/core');
import * as core from '@actions/core';
export type InputKey = keyof typeof Input;
/**
* Input variables specified in workflows using "with" prop.
@ -15,7 +17,7 @@ const core = require('@actions/core');
* Todo: rename to UserInput and remove anything that is not direct input from the user / ci workflow
*/
class Input {
public static getInput(query) {
public static getInput(query: string): string | undefined {
if (GitHub.githubInputEnabled) {
const coreInput = core.getInput(query);
if (coreInput && coreInput !== '') {
@ -34,153 +36,201 @@ class Input {
}
if (process.env[query] !== undefined) {
return process.env[query];
return process.env[query]!;
}
if (alternativeQuery !== query && process.env[alternativeQuery] !== undefined) {
return process.env[alternativeQuery];
return process.env[alternativeQuery]!;
}
return;
}
static get region(): string {
return Input.getInput('region') || 'eu-west-2';
}
static get githubRepo() {
static get githubRepo(): string | undefined {
return Input.getInput('GITHUB_REPOSITORY') || Input.getInput('GITHUB_REPO') || undefined;
}
static get branch() {
static get branch(): string {
if (Input.getInput(`GITHUB_REF`)) {
return Input.getInput(`GITHUB_REF`).replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
return Input.getInput(`GITHUB_REF`)!.replace('refs/', '').replace(`head/`, '').replace(`heads/`, '');
} else if (Input.getInput('branch')) {
return Input.getInput('branch');
return Input.getInput('branch')!;
} else {
return '';
}
}
static get gitSha() {
static get gitSha(): string {
if (Input.getInput(`GITHUB_SHA`)) {
return Input.getInput(`GITHUB_SHA`);
} else if (Input.getInput(`GitSha`)) {
return Input.getInput(`GitSha`);
return Input.getInput(`GITHUB_SHA`)!;
} else if (Input.getInput(`GitSHA`)) {
return Input.getInput(`GitSHA`)!;
}
return '';
}
static get useIL2Cpp() {
return Input.getInput(`useIL2Cpp`) || true;
static get runNumber(): string {
return Input.getInput('GITHUB_RUN_NUMBER') || '0';
}
static get runNumber() {
return Input.getInput('GITHUB_RUN_NUMBER') || Input.getInput('runNumber') || '0';
}
static get targetPlatform() {
static get targetPlatform(): string {
return Input.getInput('targetPlatform') || Platform.default;
}
static get unityVersion() {
static get unityVersion(): string {
return Input.getInput('unityVersion') || 'auto';
}
static get customImage() {
static get customImage(): string {
return Input.getInput('customImage') || '';
}
static get projectPath() {
static get projectPath(): string {
const input = Input.getInput('projectPath');
const rawProjectPath = input
? input
: fs.existsSync(path.join('test-project', 'ProjectSettings', 'ProjectVersion.txt')) &&
!fs.existsSync(path.join('ProjectSettings', 'ProjectVersion.txt'))
? 'test-project'
: '.';
let rawProjectPath;
if (input) {
rawProjectPath = input;
} else if (
fs.existsSync(path.join('test-project', 'ProjectSettings', 'ProjectVersion.txt')) &&
!fs.existsSync(path.join('ProjectSettings', 'ProjectVersion.txt'))
) {
rawProjectPath = 'test-project';
} else {
rawProjectPath = '.';
}
return rawProjectPath.replace(/\/$/, '');
}
static get buildName() {
return Input.getInput('buildName') || this.targetPlatform;
static get runnerTempPath(): string {
return Input.getInput('RUNNER_TEMP') || '';
}
static get buildsPath() {
static get buildName(): string {
return Input.getInput('buildName') || Input.targetPlatform;
}
static get buildsPath(): string {
return Input.getInput('buildsPath') || 'build';
}
static get unityLicensingServer() {
static get unityLicensingServer(): string {
return Input.getInput('unityLicensingServer') || '';
}
static get buildMethod() {
static get buildMethod(): string {
return Input.getInput('buildMethod') || ''; // Processed in docker file
}
static get customParameters() {
static get customParameters(): string {
return Input.getInput('customParameters') || '';
}
static get versioningStrategy() {
static get versioningStrategy(): string {
return Input.getInput('versioning') || 'Semantic';
}
static get specifiedVersion() {
static get specifiedVersion(): string {
return Input.getInput('version') || '';
}
static get androidVersionCode() {
return Input.getInput('androidVersionCode');
static get androidVersionCode(): string {
return Input.getInput('androidVersionCode') || '';
}
static get androidAppBundle() {
static get androidAppBundle(): boolean {
core.warning('androidAppBundle is deprecated, please use androidExportType instead');
const input = Input.getInput('androidAppBundle') || false;
return input === 'true';
}
static get androidKeystoreName() {
static get androidExportType(): string {
// TODO: remove this in V3
const exportType = Input.getInput('androidExportType') || '';
if (exportType !== '') {
return exportType;
}
return Input.androidAppBundle ? 'androidAppBundle' : 'androidPackage';
// End TODO
// Use this in V3 when androidAppBundle is removed
// return Input.getInput('androidExportType') || 'androidPackage';
}
static get androidKeystoreName(): string {
return Input.getInput('androidKeystoreName') || '';
}
static get androidKeystoreBase64() {
static get androidKeystoreBase64(): string {
return Input.getInput('androidKeystoreBase64') || '';
}
static get androidKeystorePass() {
static get androidKeystorePass(): string {
return Input.getInput('androidKeystorePass') || '';
}
static get androidKeyaliasName() {
static get androidKeyaliasName(): string {
return Input.getInput('androidKeyaliasName') || '';
}
static get androidKeyaliasPass() {
static get androidKeyaliasPass(): string {
return Input.getInput('androidKeyaliasPass') || '';
}
static get androidTargetSdkVersion() {
static get androidTargetSdkVersion(): string {
return Input.getInput('androidTargetSdkVersion') || '';
}
static get sshAgent() {
static get androidSymbolType(): string {
return Input.getInput('androidSymbolType') || 'none';
}
static get sshAgent(): string {
return Input.getInput('sshAgent') || '';
}
static get gitPrivateToken() {
return Input.getInput('gitPrivateToken') || false;
static get gitPrivateToken(): string | undefined {
return Input.getInput('gitPrivateToken');
}
static get chownFilesTo() {
return Input.getInput('chownFilesTo') || '';
}
static get allowDirtyBuild() {
static get allowDirtyBuild(): boolean {
const input = Input.getInput('allowDirtyBuild') || false;
return input === 'true';
}
static get cacheUnityInstallationOnMac(): boolean {
const input = Input.getInput('cacheUnityInstallationOnMac') || false;
return input === 'true';
}
static get unityHubVersionOnMac(): string {
const input = Input.getInput('unityHubVersionOnMac') || '';
return input !== '' ? input : '';
}
static get unitySerial(): string | undefined {
return Input.getInput('UNITY_SERIAL');
}
static get unityLicense(): string | undefined {
return Input.getInput('UNITY_LICENSE');
}
public static ToEnvVarFormat(input: string) {
if (input.toUpperCase() === input) {
return input;

View File

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

View File

@ -1,12 +1,12 @@
const core = require('@actions/core');
import * as core from '@actions/core';
class Output {
static async setBuildVersion(buildVersion) {
await core.setOutput('buildVersion', buildVersion);
static async setBuildVersion(buildVersion: string) {
core.setOutput('buildVersion', buildVersion);
}
static async setAndroidVersionCode(androidVersionCode) {
await core.setOutput('androidVersionCode', androidVersionCode);
static async setAndroidVersionCode(androidVersionCode: string) {
core.setOutput('androidVersionCode', androidVersionCode);
}
}

View File

@ -1,4 +1,4 @@
import fs from 'fs';
import fs from 'node:fs';
import * as core from '@actions/core';
import { BuildParameters } from '.';
import { SetupMac, SetupWindows, SetupAndroid } from './platform-setup/';

View File

@ -1,5 +1,5 @@
import fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
import { BuildParameters } from '..';
class SetupAndroid {

View File

@ -1,54 +1,49 @@
import { BuildParameters } from '..';
import { getUnityChangeset } from 'unity-changeset';
import { exec } from '@actions/exec';
import fs from 'fs';
import { exec, getExecOutput } from '@actions/exec';
import { restoreCache, saveCache } from '@actions/cache';
import fs from 'node:fs';
class SetupMac {
static unityHubPath = `"/Applications/Unity Hub.app/Contents/MacOS/Unity Hub"`;
static unityHubBasePath = `/Applications/"Unity Hub.app"`;
static unityHubExecPath = `${SetupMac.unityHubBasePath}/Contents/MacOS/"Unity Hub"`;
public static async setup(buildParameters: BuildParameters, actionFolder: string) {
const unityEditorPath = `/Applications/Unity/Hub/Editor/${buildParameters.editorVersion}/Unity.app/Contents/MacOS/Unity`;
// Only install unity if the editor doesn't already exist
if (!fs.existsSync(this.unityHubExecPath)) {
await SetupMac.installUnityHub(buildParameters);
}
if (!fs.existsSync(unityEditorPath)) {
await SetupMac.installUnityHub();
await SetupMac.installUnity(buildParameters);
}
await SetupMac.setEnvironmentVariables(buildParameters, actionFolder);
}
private static async installUnityHub(silent = false) {
const command = 'brew install unity-hub';
if (!fs.existsSync(this.unityHubPath)) {
// Ignoring return code because the log seems to overflow the internal buffer which triggers
// a false error
const errorCode = await exec(command, undefined, { silent, ignoreReturnCode: true });
if (errorCode) {
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
private static async installUnityHub(buildParameters: BuildParameters, silent = false) {
// Can't use quotes in the cache package so we need a different path
const unityHubCachePath = `/Applications/Unity\\ Hub.app`;
const targetHubVersion =
buildParameters.unityHubVersionOnMac !== ''
? buildParameters.unityHubVersionOnMac
: await SetupMac.getLatestUnityHubVersion();
const restoreKey = `Cache-MacOS-UnityHub@${targetHubVersion}`;
if (buildParameters.cacheUnityInstallationOnMac) {
const cacheId = await restoreCache([unityHubCachePath], restoreKey);
if (cacheId) {
// Cache restored successfully, unity hub is installed now
return;
}
}
}
private static async installUnity(buildParameters: BuildParameters, silent = false) {
const unityChangeset = await getUnityChangeset(buildParameters.editorVersion);
let command = `${this.unityHubPath} -- --headless install \
--version ${buildParameters.editorVersion} \
--changeset ${unityChangeset.changeset} `;
switch (buildParameters.targetPlatform) {
case 'iOS':
command += `--module ios `;
break;
case 'StandaloneOSX':
command += `--module mac-il2cpp `;
break;
case 'android':
command += `--module android `;
break;
}
command += `--childModules`;
const commandSuffix = buildParameters.unityHubVersionOnMac !== '' ? `@${buildParameters.unityHubVersionOnMac}` : '';
const command = `brew install unity-hub${commandSuffix}`;
// Ignoring return code because the log seems to overflow the internal buffer which triggers
// a false error
@ -56,6 +51,83 @@ class SetupMac {
if (errorCode) {
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
}
if (buildParameters.cacheUnityInstallationOnMac) {
await saveCache([unityHubCachePath], restoreKey);
}
}
/**
* Gets the latest version of Unity Hub available on brew
* @returns The latest version of Unity Hub available on brew
*/
private static async getLatestUnityHubVersion(): Promise<string> {
// 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 result = await getExecOutput(hubVersionCommand, undefined, { silent: true });
if (result.exitCode === 0 && result.stdout !== '') {
return result.stdout;
}
return '';
}
private static getModuleParametersForTargetPlatform(targetPlatform: string): string {
let moduleArgument = '';
switch (targetPlatform) {
case 'iOS':
moduleArgument += `--module ios `;
break;
case 'tvOS':
moduleArgument += '--module tvos ';
break;
case 'StandaloneOSX':
moduleArgument += `--module mac-il2cpp `;
break;
case 'Android':
moduleArgument += `--module android `;
break;
case 'WebGL':
moduleArgument += '--module webgl ';
break;
default:
throw new Error(`Unsupported module for target platform: ${targetPlatform}.`);
}
return moduleArgument;
}
private static async installUnity(buildParameters: BuildParameters, silent = false) {
const unityEditorPath = `/Applications/Unity/Hub/Editor/${buildParameters.editorVersion}`;
const key = `Cache-MacOS-UnityEditor-With-Module-${buildParameters.targetPlatform}@${buildParameters.editorVersion}`;
if (buildParameters.cacheUnityInstallationOnMac) {
const cacheId = await restoreCache([unityEditorPath], key);
if (cacheId) {
// Cache restored successfully, unity editor is installed now
return;
}
}
const unityChangeset = await getUnityChangeset(buildParameters.editorVersion);
const moduleArgument = SetupMac.getModuleParametersForTargetPlatform(buildParameters.targetPlatform);
const command = `${this.unityHubExecPath} -- --headless install \
--version ${buildParameters.editorVersion} \
--changeset ${unityChangeset.changeset} \
${moduleArgument} \
--childModules `;
// Ignoring return code because the log seems to overflow the internal buffer which triggers
// a false error
const errorCode = await exec(command, undefined, { silent, ignoreReturnCode: true });
if (errorCode) {
throw new Error(`There was an error installing the Unity Editor. See logs above for details.`);
}
if (buildParameters.cacheUnityInstallationOnMac) {
await saveCache([unityEditorPath], key);
}
}
private static async setEnvironmentVariables(buildParameters: BuildParameters, actionFolder: string) {
@ -80,6 +152,8 @@ class SetupMac {
process.env.ANDROID_KEYALIAS_PASS = buildParameters.androidKeyaliasPass;
process.env.ANDROID_TARGET_SDK_VERSION = buildParameters.androidTargetSdkVersion;
process.env.ANDROID_SDK_MANAGER_PARAMETERS = buildParameters.androidSdkManagerParameters;
process.env.ANDROID_EXPORT_TYPE = buildParameters.androidExportType;
process.env.ANDROID_SYMBOL_TYPE = buildParameters.androidSymbolType;
process.env.CUSTOM_PARAMETERS = buildParameters.customParameters;
process.env.CHOWN_FILES_TO = buildParameters.chownFilesTo;
}

View File

@ -1,5 +1,5 @@
import { exec } from '@actions/exec';
import fs from 'fs';
import fs from 'node:fs';
import { BuildParameters } from '..';
class SetupWindows {
@ -9,7 +9,7 @@ class SetupWindows {
await SetupWindows.setupWindowsRun(targetPlatform);
}
private static async setupWindowsRun(targetPlatform, silent = false) {
private static async setupWindowsRun(targetPlatform: string, silent: boolean = false) {
if (!fs.existsSync('c:/regkeys')) {
fs.mkdirSync('c:/regkeys');
}
@ -24,7 +24,7 @@ class SetupWindows {
}
}
private static async generateWinSDKRegKeys(silent = false) {
private static async generateWinSDKRegKeys(silent: boolean = false) {
// Export registry keys that point to the Windows 10 SDK
const exportWinSDKRegKeysCommand =
'reg export "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0" c:/regkeys/winsdk.reg /y';

View File

@ -1,4 +1,4 @@
import fs from 'fs';
import fs from 'node:fs';
import { BuildParameters } from '..';
class ValidateWindows {
@ -11,16 +11,10 @@ class ValidateWindows {
}
}
private static validateWindowsPlatformRequirements(platform) {
private static validateWindowsPlatformRequirements(platform: string) {
switch (platform) {
case 'StandaloneWindows':
this.checkForVisualStudio();
this.checkForWin10SDK();
break;
case 'StandaloneWindows64':
this.checkForVisualStudio();
this.checkForWin10SDK();
break;
case 'WSAPlayer':
this.checkForVisualStudio();
this.checkForWin10SDK();

View File

@ -30,7 +30,7 @@ class Platform {
};
}
static isWindows(platform) {
static isWindows(platform: string) {
switch (platform) {
case Platform.types.StandaloneWindows:
case Platform.types.StandaloneWindows64:
@ -40,7 +40,7 @@ class Platform {
}
}
static isAndroid(platform) {
static isAndroid(platform: string) {
switch (platform) {
case Platform.types.Android:
return true;

View File

@ -0,0 +1,6 @@
export class StringKeyValuePair {
public name!: string;
public value!: string;
}
export type DockerParameters = { [key: string]: any };

View File

@ -1,21 +1,21 @@
import * as core from '@actions/core';
import { exec } from '@actions/exec';
import { exec, ExecListeners } from '@actions/exec';
class System {
static async run(command, arguments_: any = [], options = {}, shouldLog = true) {
static async run(command: string, arguments_: string[] = [], options = {}, shouldLog = true) {
let result = '';
let error = '';
let debug = '';
const listeners = {
stdout: (dataBuffer) => {
const listeners: ExecListeners = {
stdout: (dataBuffer: Buffer) => {
result += dataBuffer.toString();
},
stderr: (dataBuffer) => {
stderr: (dataBuffer: Buffer) => {
error += dataBuffer.toString();
},
debug: (dataString) => {
debug += dataString.toString();
debug: (dataString: string) => {
debug += dataString;
},
};
@ -33,7 +33,7 @@ class System {
}
};
const throwContextualError = (message) => {
const throwContextualError = (message: string) => {
let commandAsString = command;
if (Array.isArray(arguments_)) {
commandAsString += ` ${arguments_.join(' ')}`;

View File

@ -1,12 +1,12 @@
import * as fs from 'fs';
import path from 'path';
import fs from 'node:fs';
import path from 'node:path';
export default class UnityVersioning {
static get versionPattern() {
return /20\d{2}\.\d\.\w{3,4}|3/;
}
static determineUnityVersion(projectPath, unityVersion) {
static determineUnityVersion(projectPath: string, unityVersion: string) {
if (unityVersion === 'auto') {
return UnityVersioning.read(projectPath);
}
@ -14,7 +14,7 @@ export default class UnityVersioning {
return unityVersion;
}
static read(projectPath) {
static read(projectPath: string) {
const filePath = path.join(projectPath, 'ProjectSettings', 'ProjectVersion.txt');
if (!fs.existsSync(filePath)) {
throw new Error(`Project settings file not found at "${filePath}". Have you correctly set the projectPath?`);
@ -23,7 +23,7 @@ export default class UnityVersioning {
return UnityVersioning.parse(fs.readFileSync(filePath, 'utf8'));
}
static parse(projectVersionTxt) {
static parse(projectVersionTxt: string) {
const matches = projectVersionTxt.match(UnityVersioning.versionPattern);
if (!matches || matches.length === 0) {
throw new Error(`Failed to parse version from "${projectVersionTxt}".`);

View File

@ -37,7 +37,7 @@ describe('Versioning', () => {
describe('grepCompatibleInputVersionRegex', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const matchInputUsingGrep = async (input) => {
const matchInputUsingGrep = async (input: string) => {
const output = await System.run('sh', undefined, {
input: Buffer.from(`echo '${input}' | grep -E '${Versioning.grepCompatibleInputVersionRegex}'`),
silent: true,
@ -68,7 +68,7 @@ describe('Versioning', () => {
const reference = jest.spyOn(Versioning, 'ref', 'get').mockReturnValue('refs/heads/feature-branch-2');
expect(Versioning.branch).toStrictEqual('feature-branch-2');
expect(reference).toHaveBeenCalledTimes(2);
expect(reference).toHaveBeenCalledTimes(1);
});
it('prefers headRef over ref when set', () => {
@ -103,16 +103,6 @@ describe('Versioning', () => {
});
});
describe('isDirtyAllowed', () => {
it('does not throw', () => {
expect(() => Versioning.isDirtyAllowed).not.toThrow();
});
it('returns false by default', () => {
expect(Versioning.isDirtyAllowed).toStrictEqual(false);
});
});
describe('logging git diff', () => {
it('calls git diff', async () => {
// allowDirtyBuild: true
@ -140,28 +130,28 @@ describe('Versioning', () => {
describe('descriptionRegex1', () => {
it('is a valid regex', () => {
expect(Versioning.descriptionRegex1).toBeInstanceOf(RegExp);
expect(Versioning.descriptionRegexes[0]).toBeInstanceOf(RegExp);
});
test.each(['v1.1-1-g12345678', 'v0.1-2-g12345678', 'v0.0-500-gA9B6C3D0-dirty'])(
'is happy with valid %s',
(description) => {
expect(Versioning.descriptionRegex1.test(description)).toBeTruthy();
expect(Versioning.descriptionRegexes[0].test(description)).toBeTruthy();
},
);
test.each(['1.1-1-g12345678', '0.1-2-g12345678', '0.0-500-gA9B6C3D0-dirty'])(
'accepts valid semantic versions without v-prefix %s',
(description) => {
expect(Versioning.descriptionRegex1.test(description)).toBeTruthy();
expect(Versioning.descriptionRegexes[0].test(description)).toBeTruthy();
},
);
test.each(['v0', 'v0.1', 'v0.1.2', 'v0.1-2', 'v0.1-2-g'])('does not like %s', (description) => {
expect(Versioning.descriptionRegex1.test(description)).toBeFalsy();
expect(Versioning.descriptionRegexes[0].test(description)).toBeFalsy();
// Also, never expect without the v to work for any of these cases.
expect(Versioning.descriptionRegex1.test(description?.slice(1))).toBeFalsy();
expect(Versioning.descriptionRegexes[0].test(description?.slice(1))).toBeFalsy();
});
});

View File

@ -5,14 +5,6 @@ import Input from './input';
import System from './system';
export default class Versioning {
static get projectPath() {
return Input.projectPath;
}
static get isDirtyAllowed() {
return Input.allowDirtyBuild;
}
static get strategies() {
return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' };
}
@ -25,8 +17,7 @@ export default class Versioning {
* Get the branch name of the (related) branch
*/
static get branch() {
// Todo - use optional chaining (https://github.com/zeit/ncc/issues/534)
return this.headRef || (this.ref && this.ref.slice(11));
return this.headRef || this.ref?.slice(11);
}
/**
@ -62,52 +53,46 @@ export default class Versioning {
*/
static async logDiff() {
const diffCommand = `git --no-pager diff | head -n ${this.maxDiffLines.toString()}`;
await System.run('sh', undefined, {
input: Buffer.from(diffCommand),
silent: true,
});
await System.run(
'sh',
undefined,
{
input: Buffer.from(diffCommand),
silent: true,
},
false,
);
}
/**
* Regex to parse version description into separate fields
*/
static get descriptionRegex1() {
return /^v?([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g;
static get descriptionRegexes(): RegExp[] {
return [
/^v?([\d.]+)-(\d+)-g(\w+)-?(\w+)*/g,
/^v?([\d.]+-\w+)-(\d+)-g(\w+)-?(\w+)*/g,
/^v?([\d.]+-\w+\.\d+)-(\d+)-g(\w+)-?(\w+)*/g,
];
}
static get descriptionRegex2() {
return /^v?([\d.]+-\w+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static get descriptionRegex3() {
return /^v?([\d.]+-\w+\.\d+)-(\d+)-g(\w+)-?(\w+)*/g;
}
static async determineBuildVersion(strategy: string, inputVersion: string) {
static async determineBuildVersion(strategy: string, inputVersion: string): Promise<string> {
// Validate input
if (!Object.hasOwnProperty.call(this.strategies, strategy)) {
throw new ValidationError(`Versioning strategy should be one of ${Object.values(this.strategies).join(', ')}.`);
}
let version;
switch (strategy) {
case this.strategies.None:
version = 'none';
break;
return 'none';
case this.strategies.Custom:
version = inputVersion;
break;
return inputVersion;
case this.strategies.Semantic:
version = await this.generateSemanticVersion();
break;
return await this.generateSemanticVersion();
case this.strategies.Tag:
version = await this.generateTagVersion();
break;
return await this.generateTagVersion();
default:
throw new NotImplementedException(`Strategy ${strategy} is not implemented.`);
}
return version;
}
/**
@ -127,7 +112,7 @@ export default class Versioning {
await this.logDiff();
if ((await this.isDirty()) && !this.isDirtyAllowed) {
if ((await this.isDirty()) && !Input.allowDirtyBuild) {
throw new Error('Branch is dirty. Refusing to base semantic version on uncommitted changes');
}
@ -175,19 +160,9 @@ export default class Versioning {
*/
static async parseSemanticVersion() {
const description = await this.getVersionDescription();
try {
const [match, tag, commits, hash] = this.descriptionRegex1.exec(description) as RegExpExecArray;
return {
match,
tag,
commits,
hash,
};
} catch {
for (const descriptionRegex of Versioning.descriptionRegexes) {
try {
const [match, tag, commits, hash] = this.descriptionRegex2.exec(description) as RegExpExecArray;
const [match, tag, commits, hash] = descriptionRegex.exec(description) as RegExpExecArray;
return {
match,
@ -196,24 +171,13 @@ export default class Versioning {
hash,
};
} catch {
try {
const [match, tag, commits, hash] = this.descriptionRegex3.exec(description) as RegExpExecArray;
return {
match,
tag,
commits,
hash,
};
} catch {
core.warning(
`Failed to parse git describe output or version can not be determined through: "${description}".`,
);
return false;
}
continue;
}
}
core.warning(`Failed to parse git describe output or version can not be determined through: "${description}".`);
return false;
}
/**
@ -250,7 +214,7 @@ export default class Versioning {
* identifies the current commit.
*/
static async getVersionDescription() {
return this.git(['describe', '--long', '--tags', '--always', this.sha]);
return this.git(['describe', '--long', '--tags', '--always', this.sha!]);
}
/**
@ -283,7 +247,7 @@ export default class Versioning {
static async hasAnyVersionTags() {
const numberOfTagsAsString = await System.run('sh', undefined, {
input: Buffer.from(`git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`),
cwd: this.projectPath,
cwd: Input.projectPath,
silent: false,
});
@ -298,7 +262,7 @@ export default class Versioning {
* Note: HEAD should not be used, as it may be detached, resulting in an additional count.
*/
static async getTotalNumberOfCommits() {
const numberOfCommitsAsString = await this.git(['rev-list', '--count', this.sha]);
const numberOfCommitsAsString = await this.git(['rev-list', '--count', this.sha!]);
return Number.parseInt(numberOfCommitsAsString, 10);
}
@ -306,7 +270,7 @@ export default class Versioning {
/**
* Run git in the specified project path
*/
static async git(arguments_, options = {}) {
return System.run('git', arguments_, { cwd: this.projectPath, ...options });
static async git(arguments_: string[], options = {}) {
return System.run('git', arguments_, { cwd: Input.projectPath, ...options }, false);
}
}

View File

@ -13,7 +13,7 @@ PlayerSettings:
useOnDemandResources: 0
accelerometerFrequency: 60
companyName: DefaultCompany
productName: simple-test-project
productName: simpletestproject
defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0}
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
@ -165,6 +165,7 @@ PlayerSettings:
androidMaxAspectRatio: 2.1
applicationIdentifier:
Standalone: com.Company.ProductName
Android: com.DefaultCompany.simpletestproject
buildNumber: {}
AndroidBundleVersionCode: 1
AndroidMinSdkVersion: 16

View File

@ -13,7 +13,7 @@ PlayerSettings:
useOnDemandResources: 0
accelerometerFrequency: 60
companyName: DefaultCompany
productName: simple-test-project
productName: simpletestproject
defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0}
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
@ -154,11 +154,12 @@ PlayerSettings:
androidMaxAspectRatio: 2.1
applicationIdentifier:
Standalone: com.Company.ProductName
Android: com.DefaultCompany.simpletestproject
buildNumber:
Standalone: 0
iPhone: 0
tvOS: 0
overrideDefaultApplicationIdentifier: 1
overrideDefaultApplicationIdentifier: 0
AndroidBundleVersionCode: 1
AndroidMinSdkVersion: 19
AndroidTargetSdkVersion: 0

View File

@ -1,12 +1,12 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"outDir": "./lib" /* Redirect output structure to the directory. */,
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": false /* Re-enable after fixing compatibility */ /* Raise error on expressions and declarations with an implied 'any' type. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
},
"exclude": ["node_modules", "dist"]

1136
yarn.lock

File diff suppressed because it is too large Load Diff