From 9d6bdcbdc528580e5b97fc81039bb75bdbf84b57 Mon Sep 17 00:00:00 2001 From: Michael Buhler Date: Tue, 18 Feb 2025 00:41:38 +0700 Subject: [PATCH] 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 --- .github/workflows/build-tests-ubuntu.yml | 15 ++- action.yml | 6 +- .../Editor/UnityBuilderAction/Builder.cs | 109 +++++++++++------- dist/index.js | Bin 22602053 -> 22602337 bytes dist/index.js.map | Bin 15124436 -> 15124756 bytes dist/platforms/mac/steps/build.sh | 18 +++ dist/platforms/ubuntu/steps/build.sh | 17 +++ dist/platforms/windows/build.ps1 | 20 ++++ src/model/build-parameters.test.ts | 6 + src/model/build-parameters.ts | 2 + src/model/image-environment-factory.ts | 1 + src/model/input.test.ts | 13 +++ src/model/input.ts | 4 + src/model/platform-setup/setup-mac.ts | 1 + test-project/Assets/Settings.meta | 8 ++ .../Assets/Settings/Build Profiles.meta | 8 ++ .../Sample WebGL Build Profile.asset | 46 ++++++++ .../Sample WebGL Build Profile.asset.meta | 8 ++ 18 files changed, 239 insertions(+), 43 deletions(-) create mode 100644 test-project/Assets/Settings.meta create mode 100644 test-project/Assets/Settings/Build Profiles.meta create mode 100644 test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset create mode 100644 test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset.meta diff --git a/.github/workflows/build-tests-ubuntu.yml b/.github/workflows/build-tests-ubuntu.yml index 9e67536c..0125357c 100644 --- a/.github/workflows/build-tests-ubuntu.yml +++ b/.github/workflows/build-tests-ubuntu.yml @@ -36,7 +36,8 @@ env: jobs: buildForAllPlatformsUbuntu: - name: ${{ matrix.targetPlatform }} on ${{ matrix.unityVersion }} + name: + "${{ matrix.targetPlatform }} on ${{ matrix.unityVersion}}${{startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }}" runs-on: ubuntu-latest strategy: fail-fast: false @@ -91,6 +92,12 @@ jobs: - targetPlatform: StandaloneWindows64 additionalParameters: -standaloneBuildSubtarget Server buildWithIl2cpp: true + include: + - unityVersion: 6000.0.36f1 + targetPlatform: WebGL + - unityVersion: 6000.0.36f1 + targetPlatform: WebGL + buildProfile: 'Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset' steps: - name: Clear Space for Android Build @@ -136,6 +143,7 @@ jobs: with: buildName: 'GameCI Test Build' projectPath: ${{ matrix.projectPath }} + buildProfile: ${{ matrix.buildProfile }} unityVersion: ${{ matrix.unityVersion }} targetPlatform: ${{ matrix.targetPlatform }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} @@ -158,6 +166,7 @@ jobs: with: buildName: 'GameCI Test Build' projectPath: ${{ matrix.projectPath }} + buildProfile: ${{ matrix.buildProfile }} unityVersion: ${{ matrix.unityVersion }} targetPlatform: ${{ matrix.targetPlatform }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} @@ -179,6 +188,7 @@ jobs: with: buildName: 'GameCI Test Build' projectPath: ${{ matrix.projectPath }} + buildProfile: ${{ matrix.buildProfile }} unityVersion: ${{ matrix.unityVersion }} targetPlatform: ${{ matrix.targetPlatform }} customParameters: -profile SomeProfile -someBoolean -someValue exampleValue ${{ matrix.additionalParameters }} @@ -191,7 +201,6 @@ jobs: - uses: actions/upload-artifact@v4 with: name: - 'Build ${{ matrix.targetPlatform }} on Ubuntu (${{ matrix.unityVersion }}_il2cpp_${{ matrix.buildWithIl2cpp - }}_params_${{ matrix.additionalParameters }})' + "Build ${{ matrix.targetPlatform }}${{ startsWith(matrix.buildProfile, 'Assets') && ' (via Build Profile)' || '' }} on Ubuntu (${{ matrix.unityVersion }}_il2cpp_${{ matrix.buildWithIl2cpp }}_params_${{ matrix.additionalParameters }})" path: build retention-days: 14 diff --git a/action.yml b/action.yml index 7f58660e..791df50e 100644 --- a/action.yml +++ b/action.yml @@ -18,7 +18,11 @@ inputs: projectPath: required: false 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: required: false default: '' diff --git a/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs b/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs index 453dd1f4..ac8e7e8e 100644 --- a/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs +++ b/dist/default-build-script/Assets/Editor/UnityBuilderAction/Builder.cs @@ -6,6 +6,9 @@ using UnityBuilderAction.Reporting; using UnityBuilderAction.Versioning; using UnityEditor; using UnityEditor.Build.Reporting; +#if UNITY_6000_0_OR_NEWER +using UnityEditor.Build.Profile; +#endif using UnityEngine; namespace UnityBuilderAction @@ -17,47 +20,9 @@ namespace UnityBuilderAction // Gather values from args 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 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. // Version defines would be the best solution here, but Unity 2018 doesn't support that, // so we fall back to using reflection instead. @@ -78,6 +43,72 @@ namespace UnityBuilderAction } } + // 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(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 BuildReport buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions); diff --git a/dist/index.js b/dist/index.js index bb7e8cd3cefe611a4a05d7d6f5d5c5bbdf2c2c24..5111463f25e4d1fbcd3cb6433e17da0769b99013 100644 GIT binary patch delta 1615 zcmX}sXIRr$7>4mH`cKe+iXb4);NFS>H{t?CQ6lbLx1pfcnQE=ORB=a5hl+deg?klT zt+S=Wx+txqbz4Ur)%$UM_}$n0;hgh+I~hl^ty$^W*3B-vmt)AFe$K?c?vSvc5A%Jy z|3sHP*=08&2Xdyl?8c*Tiz7ZRIwsbWHP8t3I1&u=`x~CD1S8mU&uLiFlAK0TNB71| zi}}sN?ohvg`Ns@j?_R^5iGAKK^YlD!*wQW>HZo$YZ(oeEMcCXSDFK#u2TVTZqRFY= zW?0~bT*wV8yy1g9$cudNMSl3f9|aJAKm?&6f>8*CQ3Tpi48>tX2uh$NN})8$pe)Lv zJSw0fDxor}pem{%6xCrz7{XBlHBk$-Q3rKV4-u%3NHjo0G(uxEK@_6V6wMHW=4gRf zv_vb!!GYF@M;o+7JG4g!bVMg~Mi+ENH*`l2e1x9ph2H3czUYVk7yu^{@G%mRgn<}@ z!T1D2FciZu93wCiE_{kn7>&;`2A^Xr#$h}rU?L`AGQL1EzQhzvMGB^2I%Z%dW??qw zz>T?>hxzc}D}0S_@GTZ#Ar>JOi?IYtu?)+x0xPi!tFZ=au@3980UNOio3RC3u?^d? z13Qt1UD%C1*o%GGj{`V}LpY2h_zvmFz)>8-ah$+Oe2*V+3a4=fXK@ba@gsghCVs{* z_!Yn50)EFuT*75s!Bu484`ky{{Do_{jvKg%zwr-l;WqBzF7Dwz9^fJV#Unh%6FkL# zc!uYAftUCnuToOIO*5h7P;x4UVpc4Qmy%1#tyq;e*L*bPQSvJJ6kjF3;-~m41(X0K zPzh2BD#1!2rLa;&DXJ7xiYqoHL@A+^R7xqOl`=|MrJPb;si0I;Dk+tfDoRzQni8s1 zSL{ld60X!xYAUsq+DaXzu2N5lQ0gm@N&}^#(nx8nG*P0IXr-yrOo>sND=m~*rKQqJ ciBlX(Yb9Q3qqJ4pDeaXGrh|Mdrj?nm0mrXo&;S4c delta 1494 zcmW;MS6B`J6o>JrO{J2pw4_4jClyi3sFW0C?>(|d_7<}D-m>`+Av3b~-Ya{r?DhU# z{NCr>oSSoT9v;oM&dSWTHYORy7!Aorg9%JwmTokd=lG?+?qZ(UI6W=XJS!qO!`1?p zutF~6hBa(pi#)J{Jsgl1j>w1nD1d?}gu*C-qHuyUiopf0aDzKMP#m5p0WWyN2PIJo zrQwS*D2s9^j|!*=11iA}mEn&71fmM6A_&z`9l;1e4TK^LHBk$-Q3v7BO(IbjQHVx8 z#GpQ65r+n7h{x}qDpBN08&6TQ$I zeb5*E&>sUZ5J?z>!5D&}7>3~(fsq)6(HMiV7>Dsl#so~nB&1+6reG?jVLE1DCT3wa z=3p-7VLldMAr@gVmS8ECVL4V{C01cI)*uyYu@3980UNOio3RC3VZ=6U#}1@nCw3tn zyRip*k%4{Kj{`V}LpY2~9Klg!;TVqN1Ww`jprJ7P*306Xs8cL`VrqonwDYcb4O1KiCL@ITaC?#5{r^G1rl~^TC UX`nPz;*|uYk;y^cib?9#f0_84qN%2dHbUwjWywyqP!T0YjgTx^(ryw_)p{4HgR z?E4Zzh1}GQWLer|!Xc)2o1+bZwI5Q{u#^-odL$x0>u z#Hl@VwED(SPR(?vR+P+*s#7$MCC=)EIPQf{FKdL|jnr8VtdeF0m{Q{x zpb_6)UE2R^+TYE^K@_^VnCgpMUC_s8=VLuMw zAP(U$j^HSc;W&zL0w-|_r*Q_wIE!;A!FgQ3MaZ~>%eaE8C`B2r;W}>MCT^h|w{Zs* zxQlzZj|X^&M|g}Uc#3Cuju&`|S9py#sKi^m!+TWW13uyts!@Yle8v}iMV(ya`px~| EU;ODVzyJUM delta 1037 zcmW;G`CH2Y0LSr1s?EN0l`{9x9MRlYVG*k=np~j>IdUv#Dq%GYL#>r>8FM7}F@~@E zzH%QqB1a0((@*=sAK+a-yq@Rt!{_rnpQ1vitTNvzb7#s#Z?sYnv;GDp+l@|-Sss`! z7U+EiDJMat^|2ev9VuagCpTL!Zwxhg27l44R0*Qw4t6ze9VSz$-I(Su%N1fooZ??Y z&*HUNy|yOsK~wm`5B`urj%H9mfD+Bo0xi)Btr37gv_TMp(H1JSLwm_<>)_|HYIPma z37yeJ((2SzQBu4`?Mjpk)#{{JYrUo`x}iHlpg|AxL@)G4DEgo;!k|Sz^v3`U#2^gD z5DdjI495tBBLX8a3ZtPzB*tJYqA(8Qp+_`g(C~2r42Z)-OhP=2n2ZEW!BkAcbj&~^ zW?~j*BMHfvgSkk-Jj}-eq+%f!VKJ5<4NI{M%dr9`q+=ykVKvrZE!H6e>ye2pWMczz zuo0WE8D``n4;E~}R&0Y6+pz;XVZ$!$#vbIO0Cp6@fg%)RFGQ4}6#L-BejI=c2XP38 zaRg;JigFyoah$+OoWg0G!C9QcdAM-_7vaGrT*ehhxQc7IjvKg%TeyuoxQlzZj|X^& zM|g}Uc!~-@D { 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 () => { const mockValue = 'someBuildName'; jest.spyOn(Input, 'buildName', 'get').mockReturnValue(mockValue); diff --git a/src/model/build-parameters.ts b/src/model/build-parameters.ts index 8cf60d59..55bc29bd 100644 --- a/src/model/build-parameters.ts +++ b/src/model/build-parameters.ts @@ -26,6 +26,7 @@ class BuildParameters { public runnerTempPath!: string; public targetPlatform!: string; public projectPath!: string; + public buildProfile!: string; public buildName!: string; public buildPath!: string; public buildFile!: string; @@ -152,6 +153,7 @@ class BuildParameters { runnerTempPath: Input.runnerTempPath, targetPlatform: Input.targetPlatform, projectPath: Input.projectPath, + buildProfile: Input.buildProfile, buildName: Input.buildName, buildPath: `${Input.buildsPath}/${Input.targetPlatform}`, buildFile, diff --git a/src/model/image-environment-factory.ts b/src/model/image-environment-factory.ts index 347d6944..5f621924 100644 --- a/src/model/image-environment-factory.ts +++ b/src/model/image-environment-factory.ts @@ -36,6 +36,7 @@ class ImageEnvironmentFactory { value: process.env.USYM_UPLOAD_AUTH_TOKEN, }, { name: 'PROJECT_PATH', value: parameters.projectPath }, + { name: 'BUILD_PROFILE', value: parameters.buildProfile }, { name: 'BUILD_TARGET', value: parameters.targetPlatform }, { name: 'BUILD_NAME', value: parameters.buildName }, { name: 'BUILD_PATH', value: parameters.buildPath }, diff --git a/src/model/input.test.ts b/src/model/input.test.ts index ae284547..777d864b 100644 --- a/src/model/input.test.ts +++ b/src/model/input.test.ts @@ -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', () => { it('returns the default value', () => { expect(Input.buildName).toStrictEqual(Input.targetPlatform); diff --git a/src/model/input.ts b/src/model/input.ts index 0e988884..a0b79b94 100644 --- a/src/model/input.ts +++ b/src/model/input.ts @@ -107,6 +107,10 @@ class Input { return rawProjectPath.replace(/\/$/, ''); } + static get buildProfile(): string { + return Input.getInput('buildProfile') ?? ''; + } + static get runnerTempPath(): string { return Input.getInput('RUNNER_TEMP') ?? ''; } diff --git a/src/model/platform-setup/setup-mac.ts b/src/model/platform-setup/setup-mac.ts index 4566e163..17c0d413 100644 --- a/src/model/platform-setup/setup-mac.ts +++ b/src/model/platform-setup/setup-mac.ts @@ -170,6 +170,7 @@ class SetupMac { process.env.UNITY_LICENSING_SERVER = buildParameters.unityLicensingServer; process.env.SKIP_ACTIVATION = buildParameters.skipActivation; process.env.PROJECT_PATH = buildParameters.projectPath; + process.env.BUILD_PROFILE = buildParameters.buildProfile; process.env.BUILD_TARGET = buildParameters.targetPlatform; process.env.BUILD_NAME = buildParameters.buildName; process.env.BUILD_PATH = buildParameters.buildPath; diff --git a/test-project/Assets/Settings.meta b/test-project/Assets/Settings.meta new file mode 100644 index 00000000..469fdc4c --- /dev/null +++ b/test-project/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 28bfc999a135648538355bfcb6a23aee +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test-project/Assets/Settings/Build Profiles.meta b/test-project/Assets/Settings/Build Profiles.meta new file mode 100644 index 00000000..1bd4102a --- /dev/null +++ b/test-project/Assets/Settings/Build Profiles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cd91492ed9aca40c49d42156a4a8f387 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset b/test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset new file mode 100644 index 00000000..50ee7f2b --- /dev/null +++ b/test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset @@ -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 diff --git a/test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset.meta b/test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset.meta new file mode 100644 index 00000000..5370e7d2 --- /dev/null +++ b/test-project/Assets/Settings/Build Profiles/Sample WebGL Build Profile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b9aac23ad2add4b439decb0cf65b0d68 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: