Compare commits

..

14 Commits
v4.2.3 ... main

Author SHA1 Message Date
David Finol
9e91ca9749
Update unity version for macOS (#712)
* Update Unity version

* Test updating unity version for mac
2025-06-10 09:03:26 -04:00
Eric_Lian
9cd9f7e0e7
fix: androidCreateSymbols has been deprecated (#701) 2025-06-08 21:21:32 -05:00
David Finol
0b822c28fb
Update Unity version (#711) 2025-06-08 11:00:16 -04:00
Daniel Lupiañez Casares
65607f9ebb
Adds support for .upmconfig.toml in Windows Docker images (#705)
* Supports github_home in windows-latest

* Attempt at copying from specific volume

* Adding some more logging

* Fix and compiles index.js

* Debugging and some other approach

* Another attempt at debugging

* Testing two more approaches

* Try only copying the file

* Cleanup

* Updates index.js, index.js.map and licenses.txt

After `yarn` + `npm run build`

* Update index.js.map
2025-06-07 16:11:18 -05:00
Daniel Lupiañez Casares
a1ebdb7abd
Adds support for VisionOS in UnityHub in macos (#710)
* Adds support for VisionOS in UnityHub in macos

* Adds support for VisionOS in UnityHub in macos

* Syncs index.js.map
2025-06-07 20:20:18 +02:00
Daniel Lupiañez Casares
3b26780ddf
Adds build support for tvOS in macos-latest (#709)
* Removes limit for tvOS only in Windows

* Fix UnityHub argument for tvOS

* Allows macos as a build platform for tvOS
2025-06-07 18:08:47 +02:00
Kirill Artemov
819c2511e0
Added install_llvmpipe script to replace -nographics in Windows builds (#706)
* Added install_llvmpipe script

* Replace ternary with a regular condition

* Revert files I haven't changed

* Pin llvmpipe version, expand test matrix with a single enableGPU target

* Fixed parameter name

* EnableGPU false by default

* Fixed nitpick

* Fixed scripts

* Pass enableGpu into tests properly

* Fixed script

* Append With GPU to build name

* Fix expression
2025-05-17 19:17:08 +02:00
Matheus Costa
81ed299e10
Feat/migrate aws sdk v3 (#698)
* chore(cloud-runner): migrate/replace deps aws-sdk v2 to v3

* chore(aws): refactor aws services to support SDK v3

* chore(aws): refactor aws runner to support SDK v3

* chore(aws): update dist

* fix(aws): error handling wrap try/catch to avoid unhandled promise rejections.

* fix(aws): keeping the syntax simpler for arrays
2025-04-10 22:48:14 +02:00
Michael Buhler
9d6bdcbdc5
feat: add buildProfile parameter (#685)
* feat: add `buildProfile` parameter

add new `buildProfile` action param, which will be passed into
Unity as the `-activeBuildProfile ...` CLI param.

closes https://github.com/game-ci/unity-builder/issues/674

* ci: add tests for Unity 6 and build profiles
2025-02-17 11:41:38 -06:00
Egorrko
3ae9ec8536
Update @actions/cache and @actions/core to support actions/upload-artifact: v4 dependency (#688)
* Bump versions of @actions/cache, @actions/core to support actions/upload-artifact: v4 dependency. Bump version actions/upload-artifact in repo actions.

* Add UNITY_LICENSE secret to CI workflows.
2025-02-08 17:14:07 +01:00
zdickinsonfugro
83c85328dd
Removed all instances of interpolated strings from editor scripts so we don't get compiler errors on old versions of .NET in Unity 2018 (#633)
Co-authored-by: Zac <zac@dickinson.xyz>
Co-authored-by: Andrew Kahr <22359829+AndrewKahr@users.noreply.github.com>
Co-authored-by: David Finol <davidmfinol@gmail.com>
2024-10-10 09:02:39 -05:00
Boris Proshin
b11b6a6f2c
Fix getVersionDescription() to prioritize version tags over non-version tags (#673)
* Fix getVersionDescription() to prioritize version tags over non-version tags

This fix modifies the getVersionDescription() method to ensure it only considers valid version tags when describing the current version. It retrieves all tags merged into the current branch, filters them based on a version-compatible regex, and uses the most recent valid version tag for description. If no valid tags are found, it falls back to the default description behavior. This resolves the issue of incorrect tags being used when multiple tags are present.

* Update versioning.ts

Rewrote getting the description for the last valid tag using `rev-list` and `rev-parse`

* Fix formatting

* Revert "dist"

This reverts commit bd58cbedf7.

* Revert "dist"

This reverts commit bd58cbedf7.
2024-10-08 00:06:50 +02:00
Filip Kajzer
461ecf7cea
fix(windows): replacing of urls if gitPrivateToken is set (#648) 2024-05-17 10:21:33 -05:00
Pierre Lataillade
f2250e958e
Enable unity licensing server for Windows (#638)
* Enable unity licensing server for windows

* Clarify validating logic with explicit variables
2024-03-26 20:11:33 +01:00
53 changed files with 2217 additions and 269 deletions

View File

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

View File

@ -18,7 +18,7 @@ jobs:
projectPath: projectPath:
- test-project - test-project
unityVersion: unityVersion:
- 2021.3.32f1 - 2021.3.45f1
- 2022.3.13f1 - 2022.3.13f1
- 2023.2.2f1 - 2023.2.2f1
targetPlatform: targetPlatform:
@ -59,6 +59,7 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}

View File

@ -36,7 +36,8 @@ env:
jobs: jobs:
buildForAllPlatformsUbuntu: buildForAllPlatformsUbuntu:
name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }} name:
"${{ matrix.targetPlatform }} on ${{ matrix.unityVersion}}${{startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}"
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@ -91,6 +92,12 @@ jobs:
- targetPlatform: StandaloneWindows64 - targetPlatform: StandaloneWindows64
additionalParameters: -standaloneBuildSubtarget Server additionalParameters: -standaloneBuildSubtarget Server
buildWithIl2cpp: true buildWithIl2cpp: true
include:
- unityVersion: 6000.0.36f1
targetPlatform: WebGL
- unityVersion: 6000.0.36f1
targetPlatform: WebGL
buildProfile: 'Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset'
steps: steps:
- name: Clear Space for Android Build - name: Clear Space for Android Build
@ -136,6 +143,7 @@ jobs:
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
buildProfile: ${{ matrix.buildProfile }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
@ -158,6 +166,7 @@ jobs:
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
buildProfile: ${{ matrix.buildProfile }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
@ -179,6 +188,7 @@ jobs:
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
buildProfile: ${{ matrix.buildProfile }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }}
@ -191,7 +201,6 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: name:
'Build ${{ matrix.targetPlatform }} on Ubuntu (${{ matrix.unityVersion }}_il2cpp_${{ matrix.buildWithIl2cpp "Build ${{ matrix.targetPlatform }}${{ startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }} on Ubuntu (${{ matrix.unityVersion }}_il2cpp_${{ matrix.buildWithIl2cpp }}_params_${{ matrix.additionalParameters }})"
}}_params_${{ matrix.additionalParameters }})'
path: build path: build
retention-days: 14 retention-days: 14

View File

@ -26,6 +26,14 @@ jobs:
- StandaloneWindows64 # Build a Windows 64-bit standalone. - StandaloneWindows64 # Build a Windows 64-bit standalone.
- WSAPlayer # Build a UWP App - WSAPlayer # Build a UWP App
- tvOS # Build an Apple TV XCode project - tvOS # Build an Apple TV XCode project
enableGpu:
- false
include:
# Additionally test enableGpu build for a standalone windows target
- projectPath: test-project
unityVersion: 2023.2.2f1
targetPlatform: StandaloneWindows64
enableGpu: true
steps: steps:
########################### ###########################
@ -65,11 +73,13 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
enableGpu: ${{ matrix.enableGpu }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above # We use dirty build because we are replacing the default project settings file above
@ -89,11 +99,13 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
enableGpu: ${{ matrix.enableGpu }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above # We use dirty build because we are replacing the default project settings file above
@ -112,11 +124,13 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with: with:
buildName: 'GameCI Test Build' buildName: 'GameCI Test Build'
projectPath: ${{ matrix.projectPath }} projectPath: ${{ matrix.projectPath }}
unityVersion: ${{ matrix.unityVersion }} unityVersion: ${{ matrix.unityVersion }}
targetPlatform: ${{ matrix.targetPlatform }} targetPlatform: ${{ matrix.targetPlatform }}
enableGpu: ${{ matrix.enableGpu }}
customParameters: -profile SomeProfile -someBoolean -someValue exampleValue customParameters: -profile SomeProfile -someBoolean -someValue exampleValue
allowDirtyBuild: true allowDirtyBuild: true
# We use dirty build because we are replacing the default project settings file above # We use dirty build because we are replacing the default project settings file above
@ -126,6 +140,6 @@ jobs:
########################### ###########################
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: Build ${{ matrix.targetPlatform }} on Windows (${{ matrix.unityVersion }}) name: Build ${{ matrix.targetPlatform }} on Windows (${{ matrix.unityVersion }})${{ matrix.enableGpu && ' With GPU' || '' }}
path: build path: build
retention-days: 14 retention-days: 14

View File

@ -190,6 +190,7 @@ jobs:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }} GIT_PRIVATE_TOKEN: ${{ secrets.GIT_PRIVATE_TOKEN }}
@ -201,7 +202,7 @@ jobs:
providerStrategy: ${{ matrix.providerStrategy }} providerStrategy: ${{ matrix.providerStrategy }}
- run: | - run: |
cp ./cloud-runner-cache/cache/${{ steps.unity-build.outputs.CACHE_KEY }}/build/${{ steps.unity-build.outputs.BUILD_ARTIFACT }} ${{ steps.unity-build.outputs.BUILD_ARTIFACT }} cp ./cloud-runner-cache/cache/${{ steps.unity-build.outputs.CACHE_KEY }}/build/${{ steps.unity-build.outputs.BUILD_ARTIFACT }} ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.providerStrategy }} Build (${{ matrix.targetPlatform }}) name: ${{ matrix.providerStrategy }} Build (${{ matrix.targetPlatform }})
path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }} path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}

View File

@ -18,7 +18,11 @@ inputs:
projectPath: projectPath:
required: false required: false
default: '' default: ''
description: 'Relative path to the project to be built.' description: 'Path to the project to be built, relative to the repository root.'
buildProfile:
required: false
default: ''
description: 'Path to the build profile to activate, relative to the project root.'
buildName: buildName:
required: false required: false
default: '' default: ''

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ namespace UnityBuilderAction.Reporting
prefix = "error"; prefix = "error";
break; break;
} }
Console.WriteLine($"{Environment.NewLine}::{prefix} ::{condition}{Environment.NewLine}{stackTrace}"); Console.WriteLine(Environment.NewLine + "::" + prefix + "::" + condition + Environment.NewLine + stackTrace);
} }
} }
} }

View File

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

View File

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

BIN
dist/index.js generated vendored

Binary file not shown.

BIN
dist/index.js.map generated vendored

Binary file not shown.

BIN
dist/licenses.txt generated vendored

Binary file not shown.

View File

@ -19,6 +19,23 @@ echo "Using build name \"$BUILD_NAME\"."
echo "Using build target \"$BUILD_TARGET\"." echo "Using build target \"$BUILD_TARGET\"."
#
# Display the build profile
#
if [ -z "$BUILD_PROFILE" ]; then
# User has not provided a build profile
#
echo "Doing a default \"$BUILD_TARGET\" platform build."
#
else
# User has provided a path to a build profile `.asset` file
#
echo "Using build profile \"$BUILD_PROFILE\" relative to \"$UNITY_PROJECT_PATH\"."
#
fi
# #
# Display build path and file # Display build path and file
# #
@ -139,6 +156,7 @@ echo ""
-buildTarget "$BUILD_TARGET" \ -buildTarget "$BUILD_TARGET" \
-customBuildTarget "$BUILD_TARGET" \ -customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \ -customBuildPath "$CUSTOM_BUILD_PATH" \
-customBuildProfile "$BUILD_PROFILE" \
-executeMethod "$BUILD_METHOD" \ -executeMethod "$BUILD_METHOD" \
-buildVersion "$VERSION" \ -buildVersion "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \ -androidVersionCode "$ANDROID_VERSION_CODE" \

View File

@ -19,6 +19,22 @@ echo "Using build name \"$BUILD_NAME\"."
echo "Using build target \"$BUILD_TARGET\"." echo "Using build target \"$BUILD_TARGET\"."
#
# Display the build profile
#
if [ -z "$BUILD_PROFILE" ]; then
# User has not provided a build profile
#
echo "Doing a default \"$BUILD_TARGET\" platform build."
#
else
# User has provided a path to a build profile `.asset` file
#
echo "Using build profile \"$BUILD_PROFILE\" relative to \"$UNITY_PROJECT_PATH\"."
#
fi
# #
# Display build path and file # Display build path and file
# #
@ -112,6 +128,7 @@ unity-editor \
-buildTarget "$BUILD_TARGET" \ -buildTarget "$BUILD_TARGET" \
-customBuildTarget "$BUILD_TARGET" \ -customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \ -customBuildPath "$CUSTOM_BUILD_PATH" \
-customBuildProfile "$BUILD_PROFILE" \
-executeMethod "$BUILD_METHOD" \ -executeMethod "$BUILD_METHOD" \
-buildVersion "$VERSION" \ -buildVersion "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \ -androidVersionCode "$ANDROID_VERSION_CODE" \

View File

@ -50,6 +50,30 @@ if ( ($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($
Start-Sleep -Seconds 3 Start-Sleep -Seconds 3
} }
} }
elseif( ($null -ne ${env:UNITY_LICENSING_SERVER}))
{
#
# Custom Unity License Server
#
Write-Output "Adding licensing server config"
$ACTIVATION_OUTPUT = Start-Process -FilePath "$Env:UNITY_PATH\Editor\Data\Resources\Licensing\Client\Unity.Licensing.Client.exe" `
-ArgumentList "--acquire-floating" `
-NoNewWindow `
-PassThru `
-Wait `
-RedirectStandardOutput "license.txt"
$PARSEDFILE = (Get-Content "license.txt" | Select-String -AllMatches -Pattern '\".*?\"' | ForEach-Object { $_.Matches.Value }) -replace '"'
$env:FLOATING_LICENSE = $PARSEDFILE[1]
$FLOATING_LICENSE_TIMEOUT = $PARSEDFILE[3]
Write-Output "Acquired floating license: ""$env:FLOATING_LICENSE"" with timeout $FLOATING_LICENSE_TIMEOUT"
# Store the exit code from the verify command
$ACTIVATION_EXIT_CODE = $ACTIVATION_OUTPUT.ExitCode
}
else else
{ {
# #

View File

@ -16,6 +16,25 @@ Write-Output "$('Using build name "')$($Env:BUILD_NAME)$('".')"
Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')" Write-Output "$('Using build target "')$($Env:BUILD_TARGET)$('".')"
#
# Display the build profile
#
if ($Env:BUILD_PROFILE)
{
# User has provided a path to a build profile `.asset` file
#
Write-Output "$('Using build profile "')$($Env:BUILD_PROFILE)$('" relative to "')$($Env:UNITY_PROJECT_PATH)$('".')"
#
}
else
{
# User has not provided a build profile
#
Write-Output "$('Doing a default "')$($Env:BUILD_TARGET)$('" platform build.')"
#
}
# #
# Display build path and file # Display build path and file
# #
@ -129,13 +148,20 @@ Write-Output "# Building project #"
Write-Output "###########################" Write-Output "###########################"
Write-Output "" Write-Output ""
$unityGraphics = "-nographics"
if ($LLVMPIPE_INSTALLED -eq "true")
{
$unityGraphics = "-force-opengl"
}
# If $Env:CUSTOM_PARAMETERS contains spaces and is passed directly on the command line to Unity, powershell will wrap it # If $Env:CUSTOM_PARAMETERS contains spaces and is passed directly on the command line to Unity, powershell will wrap it
# in double quotes. To avoid this, parse $Env:CUSTOM_PARAMETERS into an array, while respecting any quotations within the string. # in double quotes. To avoid this, parse $Env:CUSTOM_PARAMETERS into an array, while respecting any quotations within the string.
$_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUSTOM_PARAMETERS) $_, $customParametersArray = Invoke-Expression('Write-Output -- "" ' + $Env:CUSTOM_PARAMETERS)
$unityArgs = @( $unityArgs = @(
"-quit", "-quit",
"-batchmode", "-batchmode",
"-nographics", $unityGraphics,
"-silent-crashes", "-silent-crashes",
"-customBuildName", "`"$Env:BUILD_NAME`"", "-customBuildName", "`"$Env:BUILD_NAME`"",
"-projectPath", "`"$Env:UNITY_PROJECT_PATH`"", "-projectPath", "`"$Env:UNITY_PROJECT_PATH`"",
@ -143,6 +169,7 @@ $unityArgs = @(
"-buildTarget", "`"$Env:BUILD_TARGET`"", "-buildTarget", "`"$Env:BUILD_TARGET`"",
"-customBuildTarget", "`"$Env:BUILD_TARGET`"", "-customBuildTarget", "`"$Env:BUILD_TARGET`"",
"-customBuildPath", "`"$Env:CUSTOM_BUILD_PATH`"", "-customBuildPath", "`"$Env:CUSTOM_BUILD_PATH`"",
"-customBuildProfile", "`"$Env:BUILD_PROFILE`"",
"-buildVersion", "`"$Env:VERSION`"", "-buildVersion", "`"$Env:VERSION`"",
"-androidVersionCode", "`"$Env:ANDROID_VERSION_CODE`"", "-androidVersionCode", "`"$Env:ANDROID_VERSION_CODE`"",
"-androidKeystorePass", "`"$Env:ANDROID_KEYSTORE_PASS`"", "-androidKeystorePass", "`"$Env:ANDROID_KEYSTORE_PASS`"",

View File

@ -1,5 +1,13 @@
Get-Process Get-Process
# Copy .upmconfig.toml if it exists
if (Test-Path "C:\githubhome\.upmconfig.toml") {
Write-Host "Copying .upmconfig.toml to $Env:USERPROFILE\.upmconfig.toml"
Copy-Item -Path "C:\githubhome\.upmconfig.toml" -Destination "$Env:USERPROFILE\.upmconfig.toml" -Force
} else {
Write-Host "No .upmconfig.toml found at C:\githubhome"
}
# Import any necessary registry keys, ie: location of windows 10 sdk # Import any necessary registry keys, ie: location of windows 10 sdk
# No guarantee that there will be any necessary registry keys, ie: tvOS # No guarantee that there will be any necessary registry keys, ie: tvOS
Get-ChildItem -Path c:\regkeys -File | ForEach-Object { reg import $_.fullname } Get-ChildItem -Path c:\regkeys -File | ForEach-Object { reg import $_.fullname }
@ -13,6 +21,11 @@ Get-Process -Name regsvr32 | ForEach-Object { Stop-Process -Id $_.Id -Force }
# Setup Git Credentials # Setup Git Credentials
. "c:\steps\set_gitcredential.ps1" . "c:\steps\set_gitcredential.ps1"
if ($env:ENABLE_GPU -eq "true") {
# Install LLVMpipe software graphics driver
. "c:\steps\install_llvmpipe.ps1"
}
# Activate Unity # Activate Unity
if ($env:SKIP_ACTIVATION -ne "true") { if ($env:SKIP_ACTIVATION -ne "true") {
. "c:\steps\activate.ps1" . "c:\steps\activate.ps1"

View File

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

View File

@ -6,7 +6,16 @@ Write-Output "# Return License #"
Write-Output "###########################" Write-Output "###########################"
Write-Output "" Write-Output ""
if (($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($null -ne ${env:UNITY_PASSWORD})) if (($null -ne ${env:UNITY_LICENSING_SERVER}))
{
Write-Output "Returning floating license: ""$env:FLOATING_LICENSE"""
Start-Process -FilePath "$Env:UNITY_PATH\Editor\Data\Resources\Licensing\Client\Unity.Licensing.Client.exe" `
-ArgumentList "--return-floating ""$env:FLOATING_LICENSE"" " `
-NoNewWindow `
-Wait
}
elseif (($null -ne ${env:UNITY_SERIAL}) -and ($null -ne ${env:UNITY_EMAIL}) -and ($null -ne ${env:UNITY_PASSWORD}))
{ {
# #
# SERIAL LICENSE MODE # SERIAL LICENSE MODE

View File

@ -5,12 +5,12 @@ else {
Write-Host "GIT_PRIVATE_TOKEN is set configuring git credentials" Write-Host "GIT_PRIVATE_TOKEN is set configuring git credentials"
git config --global credential.helper store git config --global credential.helper store
git config --global --replace-all "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/" git config --global --replace-all url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
git config --global --add "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com" git config --global --add url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com"
git config --global --add "url.https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "https://github.com/" git config --global --add url."https://token:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "https://github.com/"
git config --global "url.https://ssh:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/" git config --global url."https://ssh:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "ssh://git@github.com/"
git config --global "url.https://git:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com:" git config --global url."https://git:$env:GIT_PRIVATE_TOKEN@github.com/".insteadOf "git@github.com:"
} }
Write-Host "---------- git config --list -------------" Write-Host "---------- git config --list -------------"

View File

@ -28,10 +28,15 @@
"node": ">=18.x" "node": ">=18.x"
}, },
"dependencies": { "dependencies": {
"@actions/cache": "^3.2.4", "@actions/cache": "^4.0.0",
"@actions/core": "^1.10.1", "@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/github": "^6.0.0", "@actions/github": "^6.0.0",
"@aws-sdk/client-cloudformation": "^3.777.0",
"@aws-sdk/client-cloudwatch-logs": "^3.777.0",
"@aws-sdk/client-ecs": "^3.778.0",
"@aws-sdk/client-kinesis": "^3.777.0",
"@aws-sdk/client-s3": "^3.779.0",
"@kubernetes/client-node": "^0.16.3", "@kubernetes/client-node": "^0.16.3",
"@octokit/core": "^5.1.0", "@octokit/core": "^5.1.0",
"async-wait-until": "^2.0.12", "async-wait-until": "^2.0.12",
@ -79,4 +84,4 @@
"node": "20.5.1", "node": "20.5.1",
"yarn": "1.22.19" "yarn": "1.22.19"
} }
} }

View File

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

View File

@ -26,6 +26,7 @@ class BuildParameters {
public runnerTempPath!: string; public runnerTempPath!: string;
public targetPlatform!: string; public targetPlatform!: string;
public projectPath!: string; public projectPath!: string;
public buildProfile!: string;
public buildName!: string; public buildName!: string;
public buildPath!: string; public buildPath!: string;
public buildFile!: string; public buildFile!: string;
@ -152,6 +153,7 @@ class BuildParameters {
runnerTempPath: Input.runnerTempPath, runnerTempPath: Input.runnerTempPath,
targetPlatform: Input.targetPlatform, targetPlatform: Input.targetPlatform,
projectPath: Input.projectPath, projectPath: Input.projectPath,
buildProfile: Input.buildProfile,
buildName: Input.buildName, buildName: Input.buildName,
buildPath: `${Input.buildsPath}/${Input.targetPlatform}`, buildPath: `${Input.buildsPath}/${Input.targetPlatform}`,
buildFile, buildFile,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -92,6 +92,7 @@ class Docker {
const { const {
workspace, workspace,
actionFolder, actionFolder,
runnerTempPath,
gitPrivateToken, gitPrivateToken,
dockerWorkspacePath, dockerWorkspacePath,
dockerCpuLimit, dockerCpuLimit,
@ -99,6 +100,9 @@ class Docker {
dockerIsolationMode, dockerIsolationMode,
} = parameters; } = parameters;
const githubHome = path.join(runnerTempPath, '_github_home');
if (!existsSync(githubHome)) mkdirSync(githubHome);
return `docker run \ return `docker run \
--workdir c:${dockerWorkspacePath} \ --workdir c:${dockerWorkspacePath} \
--rm \ --rm \
@ -106,6 +110,7 @@ class Docker {
--env GITHUB_WORKSPACE=c:${dockerWorkspacePath} \ --env GITHUB_WORKSPACE=c:${dockerWorkspacePath} \
${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \ ${gitPrivateToken ? `--env GIT_PRIVATE_TOKEN="${gitPrivateToken}"` : ''} \
--volume "${workspace}":"c:${dockerWorkspacePath}" \ --volume "${workspace}":"c:${dockerWorkspacePath}" \
--volume "${githubHome}":"C:/githubhome" \
--volume "c:/regkeys":"c:/regkeys" \ --volume "c:/regkeys":"c:/regkeys" \
--volume "C:/Program Files/Microsoft Visual Studio":"C:/Program Files/Microsoft Visual Studio" \ --volume "C:/Program Files/Microsoft Visual Studio":"C:/Program Files/Microsoft Visual Studio" \
--volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \ --volume "C:/Program Files (x86)/Microsoft Visual Studio":"C:/Program Files (x86)/Microsoft Visual Studio" \
@ -113,6 +118,7 @@ class Docker {
--volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \ --volume "C:/ProgramData/Microsoft/VisualStudio":"C:/ProgramData/Microsoft/VisualStudio" \
--volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \ --volume "${actionFolder}/default-build-script":"c:/UnityBuilderAction" \
--volume "${actionFolder}/platforms/windows":"c:/steps" \ --volume "${actionFolder}/platforms/windows":"c:/steps" \
--volume "${actionFolder}/unity-config":"C:/ProgramData/Unity/config" \
--volume "${actionFolder}/BlankProject":"c:/BlankProject" \ --volume "${actionFolder}/BlankProject":"c:/BlankProject" \
--cpus=${dockerCpuLimit} \ --cpus=${dockerCpuLimit} \
--memory=${dockerMemoryLimit} \ --memory=${dockerMemoryLimit} \

View File

@ -36,6 +36,7 @@ class ImageEnvironmentFactory {
value: process.env.USYM_UPLOAD_AUTH_TOKEN, value: process.env.USYM_UPLOAD_AUTH_TOKEN,
}, },
{ name: 'PROJECT_PATH', value: parameters.projectPath }, { name: 'PROJECT_PATH', value: parameters.projectPath },
{ name: 'BUILD_PROFILE', value: parameters.buildProfile },
{ name: 'BUILD_TARGET', value: parameters.targetPlatform }, { name: 'BUILD_TARGET', value: parameters.targetPlatform },
{ name: 'BUILD_NAME', value: parameters.buildName }, { name: 'BUILD_NAME', value: parameters.buildName },
{ name: 'BUILD_PATH', value: parameters.buildPath }, { name: 'BUILD_PATH', value: parameters.buildPath },

View File

@ -58,6 +58,7 @@ class ImageTag {
android: 'android', android: 'android',
ios: 'ios', ios: 'ios',
tvos: 'appletv', tvos: 'appletv',
visionos: 'visionos',
facebook: 'facebook', facebook: 'facebook',
}; };
} }
@ -82,8 +83,21 @@ class ImageTag {
version: string, version: string,
providerStrategy: string, providerStrategy: string,
): string { ): string {
const { generic, webgl, mac, windows, windowsIl2cpp, wsaPlayer, linux, linuxIl2cpp, android, ios, tvos, facebook } = const {
ImageTag.targetPlatformSuffixes; generic,
webgl,
mac,
windows,
windowsIl2cpp,
wsaPlayer,
linux,
linuxIl2cpp,
android,
ios,
tvos,
visionos,
facebook,
} = ImageTag.targetPlatformSuffixes;
const [major, minor] = version.split('.').map((digit) => Number(digit)); const [major, minor] = version.split('.').map((digit) => Number(digit));
@ -136,11 +150,17 @@ class ImageTag {
case Platform.types.XboxOne: case Platform.types.XboxOne:
return windows; return windows;
case Platform.types.tvOS: case Platform.types.tvOS:
if (process.platform !== 'win32') { if (process.platform !== 'win32' && process.platform !== 'darwin') {
throw new Error(`tvOS can only be built on a windows base OS`); throw new Error(`tvOS can only be built on Windows or macOS base OS`);
} }
return tvos; return tvos;
case Platform.types.VisionOS:
if (process.platform !== 'darwin') {
throw new Error(`visionOS can only be built on a macOS base OS`);
}
return visionos;
case Platform.types.Switch: case Platform.types.Switch:
return windows; return windows;

View File

@ -59,6 +59,19 @@ describe('Input', () => {
}); });
}); });
describe('buildProfile', () => {
it('returns the default value', () => {
expect(Input.buildProfile).toStrictEqual('');
});
it('takes input from the users workflow', () => {
const mockValue = 'path/to/build_profile.asset';
const spy = jest.spyOn(core, 'getInput').mockReturnValue(mockValue);
expect(Input.buildProfile).toStrictEqual(mockValue);
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('buildName', () => { describe('buildName', () => {
it('returns the default value', () => { it('returns the default value', () => {
expect(Input.buildName).toStrictEqual(Input.targetPlatform); expect(Input.buildName).toStrictEqual(Input.targetPlatform);

View File

@ -107,6 +107,10 @@ class Input {
return rawProjectPath.replace(/\/$/, ''); return rawProjectPath.replace(/\/$/, '');
} }
static get buildProfile(): string {
return Input.getInput('buildProfile') ?? '';
}
static get runnerTempPath(): string { static get runnerTempPath(): string {
return Input.getInput('RUNNER_TEMP') ?? ''; return Input.getInput('RUNNER_TEMP') ?? '';
} }

View File

@ -101,7 +101,10 @@ class SetupMac {
moduleArgument.push('--module', 'ios'); moduleArgument.push('--module', 'ios');
break; break;
case 'tvOS': case 'tvOS':
moduleArgument.push('--module', 'tvos'); moduleArgument.push('--module', 'appletv');
break;
case 'VisionOS':
moduleArgument.push('--module', 'visionos');
break; break;
case 'StandaloneOSX': case 'StandaloneOSX':
moduleArgument.push('--module', 'mac-il2cpp'); moduleArgument.push('--module', 'mac-il2cpp');
@ -170,6 +173,7 @@ class SetupMac {
process.env.UNITY_LICENSING_SERVER = buildParameters.unityLicensingServer; process.env.UNITY_LICENSING_SERVER = buildParameters.unityLicensingServer;
process.env.SKIP_ACTIVATION = buildParameters.skipActivation; process.env.SKIP_ACTIVATION = buildParameters.skipActivation;
process.env.PROJECT_PATH = buildParameters.projectPath; process.env.PROJECT_PATH = buildParameters.projectPath;
process.env.BUILD_PROFILE = buildParameters.buildProfile;
process.env.BUILD_TARGET = buildParameters.targetPlatform; process.env.BUILD_TARGET = buildParameters.targetPlatform;
process.env.BUILD_NAME = buildParameters.buildName; process.env.BUILD_NAME = buildParameters.buildName;
process.env.BUILD_PATH = buildParameters.buildPath; process.env.BUILD_PATH = buildParameters.buildPath;

View File

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

View File

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

View File

@ -7,9 +7,9 @@ describe('Unity Versioning', () => {
}); });
it('parses from ProjectVersion.txt', () => { it('parses from ProjectVersion.txt', () => {
const projectVersionContents = `m_EditorVersion: 2021.3.4f1 const projectVersionContents = `m_EditorVersion: 2021.3.45f1
m_EditorVersionWithRevision: 2021.3.4f1 (cb45f9cae8b7)`; m_EditorVersionWithRevision: 2021.3.45f1 (cb45f9cae8b7)`;
expect(UnityVersioning.parse(projectVersionContents)).toBe('2021.3.4f1'); expect(UnityVersioning.parse(projectVersionContents)).toBe('2021.3.45f1');
}); });
it('parses Unity 6000 and newer from ProjectVersion.txt', () => { it('parses Unity 6000 and newer from ProjectVersion.txt', () => {
@ -25,13 +25,13 @@ describe('Unity Versioning', () => {
}); });
it('reads from test-project', () => { it('reads from test-project', () => {
expect(UnityVersioning.read('./test-project')).toBe('2021.3.4f1'); expect(UnityVersioning.read('./test-project')).toBe('2021.3.45f1');
}); });
}); });
describe('determineUnityVersion', () => { describe('determineUnityVersion', () => {
it('defaults to parsed version', () => { it('defaults to parsed version', () => {
expect(UnityVersioning.determineUnityVersion('./test-project', 'auto')).toBe('2021.3.4f1'); expect(UnityVersioning.determineUnityVersion('./test-project', 'auto')).toBe('2021.3.45f1');
}); });
it('use specified unityVersion', () => { it('use specified unityVersion', () => {

View File

@ -207,7 +207,21 @@ export default class Versioning {
* identifies the current commit. * identifies the current commit.
*/ */
static async getVersionDescription() { static async getVersionDescription() {
return this.git(['describe', '--long', '--tags', '--always', 'HEAD']); const versionTags = (await this.git(['tag', '--list', '--merged', 'HEAD', '--sort=-creatordate']))
.split('\n')
.filter((tag) => new RegExp(this.grepCompatibleInputVersionRegex).test(tag));
if (versionTags.length === 0) {
core.warning('No valid version tags found. Using fallback description.');
return this.git(['describe', '--long', '--tags', '--always', 'HEAD']);
}
const latestVersionTag = versionTags[0];
const commitsCount = (await this.git(['rev-list', `${latestVersionTag}..HEAD`, '--count'])).trim();
const commitHash = (await this.git(['rev-parse', '--short', 'HEAD'])).trim();
return `${latestVersionTag}-${commitsCount}-g${commitHash}`;
} }
/** /**

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 28bfc999a135648538355bfcb6a23aee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cd91492ed9aca40c49d42156a4a8f387
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0}
m_Name: Sample WebGL Build Profile
m_EditorClassIdentifier:
m_AssetVersion: 1
m_BuildTarget: 20
m_Subtarget: 0
m_PlatformId: 84a3bb9e7420477f885e98145999eb20
m_PlatformBuildProfile:
rid: 200022742090383361
m_OverrideGlobalSceneList: 0
m_Scenes: []
m_ScriptingDefines: []
m_PlayerSettingsYaml:
m_Settings: []
references:
version: 2
RefIds:
- rid: 200022742090383361
type: {class: WebGLPlatformSettings, ns: UnityEditor.WebGL, asm: UnityEditor.WebGL.Extensions}
data:
m_Development: 0
m_ConnectProfiler: 0
m_BuildWithDeepProfilingSupport: 0
m_AllowDebugging: 0
m_WaitForManagedDebugger: 0
m_ManagedDebuggerFixedPort: 0
m_ExplicitNullChecks: 0
m_ExplicitDivideByZeroChecks: 0
m_ExplicitArrayBoundsChecks: 0
m_CompressionType: -1
m_InstallInBuildFolder: 0
m_CodeOptimization: 0
m_WebGLClientBrowserPath:
m_WebGLClientBrowserType: 0
m_WebGLTextureSubtarget: 0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b9aac23ad2add4b439decb0cf65b0d68
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,7 @@
{ {
"dependencies": { "dependencies": {
"com.unity.burst": "1.6.6", "com.unity.burst": "1.8.22",
"com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.visualstudio": "2.0.23",
"com.unity.modules.ai": "1.0.0", "com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0", "com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0", "com.unity.modules.animation": "1.0.0",

View File

@ -1,11 +1,12 @@
{ {
"dependencies": { "dependencies": {
"com.unity.burst": { "com.unity.burst": {
"version": "1.6.6", "version": "1.8.22",
"depth": 0, "depth": 0,
"source": "registry", "source": "registry",
"dependencies": { "dependencies": {
"com.unity.mathematics": "1.2.1" "com.unity.mathematics": "1.2.1",
"com.unity.modules.jsonserialize": "1.0.0"
}, },
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
}, },
@ -17,7 +18,7 @@
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
}, },
"com.unity.ide.visualstudio": { "com.unity.ide.visualstudio": {
"version": "2.0.22", "version": "2.0.23",
"depth": 0, "depth": 0,
"source": "registry", "source": "registry",
"dependencies": { "dependencies": {
@ -33,7 +34,7 @@
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
}, },
"com.unity.test-framework": { "com.unity.test-framework": {
"version": "1.1.31", "version": "1.1.33",
"depth": 1, "depth": 1,
"source": "registry", "source": "registry",
"dependencies": { "dependencies": {

View File

@ -3,7 +3,7 @@
--- !u!129 &1 --- !u!129 &1
PlayerSettings: PlayerSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 23 serializedVersion: 24
productGUID: f3f6a917a3bba0046bb55998f8678f8c productGUID: f3f6a917a3bba0046bb55998f8678f8c
AndroidProfiler: 0 AndroidProfiler: 0
AndroidFilterTouchesWhenObscured: 0 AndroidFilterTouchesWhenObscured: 0
@ -48,6 +48,7 @@ PlayerSettings:
defaultScreenHeightWeb: 600 defaultScreenHeightWeb: 600
m_StereoRenderingPath: 0 m_StereoRenderingPath: 0
m_ActiveColorSpace: 0 m_ActiveColorSpace: 0
unsupportedMSAAFallback: 0
m_MTRendering: 1 m_MTRendering: 1
mipStripping: 0 mipStripping: 0
numberOfMipsStripped: 0 numberOfMipsStripped: 0
@ -74,6 +75,7 @@ PlayerSettings:
androidMinimumWindowWidth: 400 androidMinimumWindowWidth: 400
androidMinimumWindowHeight: 300 androidMinimumWindowHeight: 300
androidFullscreenMode: 1 androidFullscreenMode: 1
androidAutoRotationBehavior: 1
defaultIsNativeResolution: 1 defaultIsNativeResolution: 1
macRetinaSupport: 1 macRetinaSupport: 1
runInBackground: 1 runInBackground: 1
@ -121,6 +123,7 @@ PlayerSettings:
switchNVNOtherPoolsGranularity: 16777216 switchNVNOtherPoolsGranularity: 16777216
switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicTextureIDCount: 0
switchNVNMaxPublicSamplerIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0
switchMaxWorkerMultiple: 8
stadiaPresentMode: 0 stadiaPresentMode: 0
stadiaTargetFramerate: 0 stadiaTargetFramerate: 0
vulkanNumSwapchainBuffers: 3 vulkanNumSwapchainBuffers: 3
@ -180,10 +183,10 @@ PlayerSettings:
StripUnusedMeshComponents: 1 StripUnusedMeshComponents: 1
VertexChannelCompressionMask: 4054 VertexChannelCompressionMask: 4054
iPhoneSdkVersion: 988 iPhoneSdkVersion: 988
iOSTargetOSVersionString: 11.0 iOSTargetOSVersionString: 12.0
tvOSSdkVersion: 0 tvOSSdkVersion: 0
tvOSRequireExtendedGameController: 0 tvOSRequireExtendedGameController: 0
tvOSTargetOSVersionString: 11.0 tvOSTargetOSVersionString: 12.0
uIPrerenderedIcon: 0 uIPrerenderedIcon: 0
uIRequiresPersistentWiFi: 0 uIRequiresPersistentWiFi: 0
uIRequiresFullScreen: 1 uIRequiresFullScreen: 1
@ -247,6 +250,7 @@ PlayerSettings:
useCustomLauncherGradleManifest: 0 useCustomLauncherGradleManifest: 0
useCustomBaseGradleTemplate: 0 useCustomBaseGradleTemplate: 0
useCustomGradlePropertiesTemplate: 0 useCustomGradlePropertiesTemplate: 0
useCustomGradleSettingsTemplate: 0
useCustomProguardFile: 0 useCustomProguardFile: 0
AndroidTargetArchitectures: 3 AndroidTargetArchitectures: 3
AndroidTargetDevices: 0 AndroidTargetDevices: 0
@ -267,7 +271,6 @@ PlayerSettings:
banner: {fileID: 0} banner: {fileID: 0}
androidGamepadSupportLevel: 0 androidGamepadSupportLevel: 0
chromeosInputEmulation: 1 chromeosInputEmulation: 1
AndroidMinifyWithR8: 0
AndroidMinifyRelease: 0 AndroidMinifyRelease: 0
AndroidMinifyDebug: 0 AndroidMinifyDebug: 0
AndroidValidateAppBundleSize: 1 AndroidValidateAppBundleSize: 1
@ -516,6 +519,7 @@ PlayerSettings:
- m_BuildTarget: WebGL - m_BuildTarget: WebGL
m_StaticBatching: 0 m_StaticBatching: 0
m_DynamicBatching: 0 m_DynamicBatching: 0
m_BuildTargetShaderSettings: []
m_BuildTargetGraphicsJobs: m_BuildTargetGraphicsJobs:
- m_BuildTarget: MacStandaloneSupport - m_BuildTarget: MacStandaloneSupport
m_GraphicsJobs: 0 m_GraphicsJobs: 0
@ -567,6 +571,8 @@ PlayerSettings:
m_Devices: m_Devices:
- Oculus - Oculus
- OpenVR - OpenVR
m_DefaultShaderChunkSizeInMB: 16
m_DefaultShaderChunkCount: 0
openGLRequireES31: 0 openGLRequireES31: 0
openGLRequireES31AEP: 0 openGLRequireES31AEP: 0
openGLRequireES32: 0 openGLRequireES32: 0
@ -610,7 +616,7 @@ PlayerSettings:
switchSocketConcurrencyLimit: 14 switchSocketConcurrencyLimit: 14
switchScreenResolutionBehavior: 2 switchScreenResolutionBehavior: 2
switchUseCPUProfiler: 0 switchUseCPUProfiler: 0
switchUseGOLDLinker: 0 switchEnableFileSystemTrace: 0
switchLTOSetting: 0 switchLTOSetting: 0
switchApplicationID: 0x01004b9000490000 switchApplicationID: 0x01004b9000490000
switchNSODependencies: switchNSODependencies:
@ -687,7 +693,6 @@ PlayerSettings:
switchReleaseVersion: 0 switchReleaseVersion: 0
switchDisplayVersion: 1.0.0 switchDisplayVersion: 1.0.0
switchStartupUserAccount: 0 switchStartupUserAccount: 0
switchTouchScreenUsage: 0
switchSupportedLanguagesMask: 0 switchSupportedLanguagesMask: 0
switchLogoType: 0 switchLogoType: 0
switchApplicationErrorCodeCategory: switchApplicationErrorCodeCategory:
@ -729,6 +734,7 @@ PlayerSettings:
switchNativeFsCacheSize: 32 switchNativeFsCacheSize: 32
switchIsHoldTypeHorizontal: 0 switchIsHoldTypeHorizontal: 0
switchSupportedNpadCount: 8 switchSupportedNpadCount: 8
switchEnableTouchScreen: 1
switchSocketConfigEnabled: 0 switchSocketConfigEnabled: 0
switchTcpInitialSendBufferSize: 32 switchTcpInitialSendBufferSize: 32
switchTcpInitialReceiveBufferSize: 64 switchTcpInitialReceiveBufferSize: 64
@ -739,8 +745,8 @@ PlayerSettings:
switchSocketBufferEfficiency: 4 switchSocketBufferEfficiency: 4
switchSocketInitializeEnabled: 1 switchSocketInitializeEnabled: 1
switchNetworkInterfaceManagerInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1
switchPlayerConnectionEnabled: 1
switchUseNewStyleFilepaths: 0 switchUseNewStyleFilepaths: 0
switchUseLegacyFmodPriorities: 1
switchUseMicroSleepForYield: 1 switchUseMicroSleepForYield: 1
switchEnableRamDiskSupport: 0 switchEnableRamDiskSupport: 0
switchMicroSleepForYieldTime: 25 switchMicroSleepForYieldTime: 25
@ -815,6 +821,7 @@ PlayerSettings:
ps4videoRecordingFeaturesUsed: 0 ps4videoRecordingFeaturesUsed: 0
ps4contentSearchFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0
ps4CompatibilityPS5: 0 ps4CompatibilityPS5: 0
ps4AllowPS5Detection: 0
ps4GPU800MHz: 1 ps4GPU800MHz: 1
ps4attribEyeToEyeDistanceSettingVR: 0 ps4attribEyeToEyeDistanceSettingVR: 0
ps4IncludedModules: [] ps4IncludedModules: []
@ -839,6 +846,7 @@ PlayerSettings:
webGLLinkerTarget: 1 webGLLinkerTarget: 1
webGLThreadsSupport: 0 webGLThreadsSupport: 0
webGLDecompressionFallback: 0 webGLDecompressionFallback: 0
webGLPowerPreference: 2
scriptingDefineSymbols: {} scriptingDefineSymbols: {}
additionalCompilerArguments: {} additionalCompilerArguments: {}
platformArchitecture: {} platformArchitecture: {}
@ -847,7 +855,21 @@ PlayerSettings:
Server: 0 Server: 0
Standalone: 0 Standalone: 0
il2cppCompilerConfiguration: {} il2cppCompilerConfiguration: {}
managedStrippingLevel: {} managedStrippingLevel:
Android: 1
EmbeddedLinux: 1
GameCoreScarlett: 1
GameCoreXboxOne: 1
Lumin: 1
Nintendo Switch: 1
PS4: 1
PS5: 1
Stadia: 1
WebGL: 1
Windows Store Apps: 1
XboxOne: 1
iPhone: 1
tvOS: 1
incrementalIl2cppBuild: {} incrementalIl2cppBuild: {}
suppressCommonWarnings: 1 suppressCommonWarnings: 1
allowUnsafeCode: 0 allowUnsafeCode: 0
@ -863,11 +885,11 @@ PlayerSettings:
m_MobileRenderingPath: 1 m_MobileRenderingPath: 1
metroPackageName: Template3D metroPackageName: Template3D
metroPackageVersion: 1.0.0.0 metroPackageVersion: 1.0.0.0
metroCertificatePath: metroCertificatePath: C:\Users\david\Documents\GitHub\unity-builder\test-project\Assets\WSATestCertificate.pfx
metroCertificatePassword: metroCertificatePassword:
metroCertificateSubject: metroCertificateSubject: GameCI
metroCertificateIssuer: metroCertificateIssuer: GameCI
metroCertificateNotAfter: 0000000000000000 metroCertificateNotAfter: 00b8ac9241f7dc01
metroApplicationDescription: Template_3D metroApplicationDescription: Template_3D
wsaImages: {} wsaImages: {}
metroTileShortName: TestProject metroTileShortName: TestProject
@ -882,6 +904,7 @@ PlayerSettings:
metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1}
metroSplashScreenUseBackgroundColor: 0 metroSplashScreenUseBackgroundColor: 0
syncCapabilities: 0
platformCapabilities: {} platformCapabilities: {}
metroTargetDeviceFamilies: {} metroTargetDeviceFamilies: {}
metroFTAName: metroFTAName:
@ -931,6 +954,7 @@ PlayerSettings:
m_VersionName: m_VersionName:
apiCompatibilityLevel: 6 apiCompatibilityLevel: 6
activeInputHandler: 0 activeInputHandler: 0
windowsGamepadBackendHint: 0
cloudProjectId: cloudProjectId:
framebufferDepthMemorylessMode: 0 framebufferDepthMemorylessMode: 0
qualitySettingsNames: [] qualitySettingsNames: []

View File

@ -1,2 +1,2 @@
m_EditorVersion: 2021.3.4f1 m_EditorVersion: 2021.3.45f1
m_EditorVersionWithRevision: 2021.3.4f1 (cb45f9cae8b7) m_EditorVersionWithRevision: 2021.3.45f1 (0da89fac8e79)

1439
yarn.lock

File diff suppressed because it is too large Load Diff