Repository navigation
chore(release): add one-command player builds #555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0649b13
36697b5
cfa4955
1794d03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| --- | ||
| name: asp-ft-player-builds | ||
| description: Build and run release players of Aspid.FastTools on macOS in one command - Mac Mono, Mac IL2CPP (Managed Stripping Level Minimal and High) and the iOS Simulator - and check that SerializableType resolves its stored names and that ENABLE_PROFILER is off. Covers the "player builds" items of the release checklist. - `/asp-ft-player-builds`, `/asp-ft-player-builds 6000.3.26f1`. | ||
| argument-hint: "[unity-version]" | ||
| disable-model-invocation: true | ||
| --- | ||
|
|
||
| # Player builds | ||
|
|
||
| The argument is the Unity version. No argument → the minimum: the first version of `UNITY_VERSIONS` in | ||
| `.github/workflows/ci.yml`. | ||
|
|
||
| ## When | ||
|
|
||
| - The manual QA of a **stable** release, before the merge of the release PR: the items "player builds on Mono and | ||
| IL2CPP", "non-development build", "IL2CPP stripping and `SerializableType`" and "one standalone and one AOT target" | ||
| of `.github/ISSUE_TEMPLATE/release_checklist.yml`. | ||
| - A change to `SerializableType`, the type lookup, the profiler marker generator or `ENABLE_PROFILER` code. | ||
|
|
||
| ## Run | ||
|
|
||
| Run it on the branch under test: for a release, a worktree of `chore/release-<version>`. The script builds the | ||
| package of its own checkout. | ||
|
|
||
| ```bash | ||
| scripts/player-builds.sh <scratch>/player-builds [unity-version] | ||
| ``` | ||
|
|
||
| - `<scratch>/player-builds` must not exist or be empty. It gets `Project/`, `Builds/` and `Logs/`. | ||
| - Run it in the background: all four targets take about 3–4 minutes. | ||
| - `--targets mac-mono,ios-sim` limits the run to the listed targets. | ||
| - It needs an Apple Silicon Mac (the players are arm64), the Unity Editor of the version, its modules `mac-il2cpp` | ||
| and `ios`, and Xcode for `ios-sim`. A missing module skips its target and prints the Unity Hub command that installs | ||
| it. Ask the user before you run an install: it downloads gigabytes. | ||
| - The simulator runs without its window. The script shuts it down after the run when it booted it. | ||
|
|
||
| ## Read the table | ||
|
|
||
| ``` | ||
| TARGET BUILD ERR/WARN AOTCHECK PROFILER REFERENCE MISSING | ||
| mac-mono Succeeded 0/0 3/3 off dev=off none | ||
| ``` | ||
|
|
||
| - `BUILD`: the `BuildReport` result. `xcodebuild-failed`: Unity made the Xcode project, Xcode did not build it. | ||
| - `ERR/WARN`: the errors and warnings of the `BuildReport`. | ||
| - `AOTCHECK`: the required fields that resolved in the player, out of all. Required are a `[Preserve]` class, | ||
| `Transform` and `List<int>`: each must resolve on every target. `no-run`: the player did not print `AOTCHECK DONE`. | ||
| - `PROFILER`: `ENABLE_PROFILER` and `DEVELOPMENT_BUILD` in the player. Both must be `off`. | ||
| - `REFERENCE MISSING`: reference fields that did not resolve. They are informational and do not fail the run: | ||
| - `plain`, a class referenced only by its stored name, and `samplePattern` with `samplePatternInstance`, | ||
| `LinePattern` of the Types sample: the linker removes them from Managed Stripping Level Medium up. The XML docs of | ||
| `SerializableTypeBase` say so. | ||
| - `strippedEngine`, `BoxCollider`: on every IL2CPP target Strip Engine Code removes the physics module, which the | ||
| check scene does not use. | ||
| - `plain` or `samplePattern` that turns `MISSING` on `mac-mono` or `mac-il2cpp-minimal` is a regression. | ||
|
|
||
| The exit code is 1 when a target is skipped or fails. The logs are in `Logs/`: `unity-*.log` for the builds, | ||
| `run-*.log` for the players, `xcodebuild-ios-sim.log`. The iOS Simulator player is always a development engine build, | ||
| so its log says "Build type 'Development'" and `debug=True`: the `PROFILER` column still checks the defines. | ||
|
|
||
| ## Report | ||
|
|
||
| Write the report in Russian. Keep it short: | ||
|
|
||
| 1. The first line: the verdict, the Unity version and the commit. | ||
| 2. The table as the script printed it. | ||
| 3. Each failure: the target and the first error lines from its log. | ||
| 4. The release checklist items that the run closes. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "name": "Aspid.FastTools.PlayerBuildCheck", | ||
| "rootNamespace": "Aspid.FastTools.PlayerBuildCheck", | ||
| "references": [ | ||
| "Aspid.FastTools", | ||
| "Aspid.FastTools.Samples.Types" | ||
| ], | ||
| "includePlatforms": [], | ||
| "excludePlatforms": [], | ||
| "allowUnsafeCode": false, | ||
| "overrideReferences": false, | ||
| "precompiledReferences": [], | ||
| "autoReferenced": false, | ||
| "defineConstraints": [], | ||
| "versionDefines": [], | ||
| "noEngineReferences": false | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "Aspid.FastTools.PlayerBuildCheck.Editor", | ||
| "rootNamespace": "Aspid.FastTools.PlayerBuildCheck", | ||
| "references": [ | ||
| "Aspid.FastTools", | ||
| "Aspid.FastTools.PlayerBuildCheck", | ||
| "Aspid.FastTools.Samples.Types" | ||
| ], | ||
| "includePlatforms": [ | ||
| "Editor" | ||
| ], | ||
| "excludePlatforms": [], | ||
| "allowUnsafeCode": false, | ||
| "overrideReferences": false, | ||
| "precompiledReferences": [], | ||
| "autoReferenced": false, | ||
| "defineConstraints": [], | ||
| "versionDefines": [], | ||
| "noEngineReferences": false | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| using System; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using UnityEditor; | ||
| using UnityEngine; | ||
| using UnityEditor.Build; | ||
| using UnityEditor.Build.Reporting; | ||
| using UnityEditor.SceneManagement; | ||
| using System.Collections.Generic; | ||
| using Aspid.FastTools.Samples.Types.Spawning; | ||
|
|
||
| // ReSharper disable once CheckNamespace | ||
| namespace Aspid.FastTools.PlayerBuildCheck | ||
| { | ||
| // Batch-mode entry point of scripts/player-builds.sh: | ||
| // -executeMethod Aspid.FastTools.PlayerBuildCheck.PlayerBuilds.Build | ||
| // -playerBuildTargets mac-mono,mac-il2cpp-minimal,mac-il2cpp-high,ios-sim -playerBuildOutput <dir> | ||
| // Builds every target without BuildOptions.Development, appends a line per target to <dir>/builds.tsv and exits | ||
| // with 1 when one fails. | ||
| public static class PlayerBuilds | ||
| { | ||
| private const string ProductName = "PlayerBuildCheck"; | ||
| private const string BundleId = "tech.aspid.fasttools.playerbuildcheck"; | ||
| private const string ScenePath = "Assets/PlayerBuildCheck/PlayerBuildCheck.unity"; | ||
|
|
||
| private static readonly Target[] _targets = | ||
| { | ||
| new("mac-mono", BuildTarget.StandaloneOSX, ScriptingImplementation.Mono2x, ManagedStrippingLevel.Disabled), | ||
| new("mac-il2cpp-minimal", BuildTarget.StandaloneOSX, ScriptingImplementation.IL2CPP, ManagedStrippingLevel.Minimal), | ||
| new("mac-il2cpp-high", BuildTarget.StandaloneOSX, ScriptingImplementation.IL2CPP, ManagedStrippingLevel.High), | ||
| new("ios-sim", BuildTarget.iOS, ScriptingImplementation.IL2CPP, ManagedStrippingLevel.High), | ||
| }; | ||
|
|
||
| public static void Build() | ||
| { | ||
| try | ||
| { | ||
| var ids = Argument("-playerBuildTargets").Split(',', StringSplitOptions.RemoveEmptyEntries); | ||
| var output = Argument("-playerBuildOutput"); | ||
| Directory.CreateDirectory(output); | ||
|
|
||
| CreateScene(); | ||
|
|
||
| var hasFailed = false; | ||
|
|
||
| foreach (var id in ids) | ||
| { | ||
| var target = _targets.FirstOrDefault(target => target.Id == id) | ||
| ?? throw new ArgumentException($"Unknown target {id}"); | ||
|
|
||
| var summary = BuildOne(target, output); | ||
| hasFailed |= summary.result != BuildResult.Succeeded; | ||
| // Appended: the script runs the Editor once per build target group into one output folder. | ||
| File.AppendAllLines(Path.Combine(output, "builds.tsv"), new[] | ||
| { | ||
| $"{id}\t{summary.result}\t{summary.totalErrors}\t{summary.totalWarnings}\t{summary.options}", | ||
| }); | ||
| } | ||
|
|
||
| EditorApplication.Exit(hasFailed ? 1 : 0); | ||
| } | ||
| catch (Exception exception) | ||
| { | ||
| Debug.LogException(exception); | ||
| EditorApplication.Exit(2); | ||
| } | ||
| } | ||
|
|
||
| private static BuildSummary BuildOne(Target target, string output) | ||
| { | ||
| var namedTarget = NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(target.BuildTarget)); | ||
| PlayerSettings.productName = ProductName; | ||
| PlayerSettings.SetApplicationIdentifier(namedTarget, BundleId); | ||
| PlayerSettings.SetScriptingBackend(namedTarget, target.Backend); | ||
| PlayerSettings.SetManagedStrippingLevel(namedTarget, target.Stripping); | ||
|
|
||
| if (target.BuildTarget == BuildTarget.StandaloneOSX) | ||
| UnityEditor.OSXStandalone.UserBuildSettings.architecture = OSArchitecture.ARM64; | ||
|
|
||
| if (target.BuildTarget == BuildTarget.iOS) | ||
| { | ||
| PlayerSettings.iOS.sdkVersion = iOSSdkVersion.SimulatorSDK; | ||
| PlayerSettings.iOS.simulatorSdkArchitecture = AppleMobileArchitectureSimulator.ARM64; | ||
| // Xcode 27 refuses the default deployment target of Unity 6000.0 (13.0) for the simulator. | ||
| PlayerSettings.iOS.targetOSVersionString = "15.0"; | ||
| } | ||
|
|
||
| var path = target.BuildTarget == BuildTarget.iOS | ||
| ? Path.Combine(output, target.Id) | ||
| : Path.Combine(output, target.Id, $"{ProductName}.app"); | ||
|
|
||
| var report = BuildPipeline.BuildPlayer(new BuildPlayerOptions | ||
| { | ||
| scenes = new[] { ScenePath }, | ||
| locationPathName = path, | ||
| target = target.BuildTarget, | ||
| options = BuildOptions.None, | ||
| }); | ||
|
|
||
| var summary = report.summary; | ||
| Debug.Log($"PLAYERBUILD {target.Id} result={summary.result} errors={summary.totalErrors} " + | ||
| $"warnings={summary.totalWarnings} options={summary.options} time={summary.totalTime} path={path}"); | ||
|
|
||
| foreach (var message in report.steps.SelectMany(step => step.messages)) | ||
| { | ||
| if (message.type is LogType.Error or LogType.Exception or LogType.Warning) | ||
| Debug.Log($"PLAYERBUILD {target.Id} {message.type}: {message.content}"); | ||
| } | ||
|
|
||
| return summary; | ||
| } | ||
|
|
||
| private static void CreateScene() | ||
| { | ||
| var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); | ||
| var probe = new GameObject(ProductName).AddComponent<PlayerBuildProbe>(); | ||
|
|
||
| // The names are written as strings: a typeof in the player code would keep the type from stripping. | ||
| var serialized = new SerializedObject(probe); | ||
| Set(serialized, field: "_preserved", name: typeof(PlayerBuildProbePreserved).AssemblyQualifiedName); | ||
| Set(serialized, field: "_engine", name: typeof(Transform).AssemblyQualifiedName); | ||
| Set(serialized, field: "_generic", name: typeof(List<int>).AssemblyQualifiedName); | ||
| Set(serialized, field: "_plain", name: typeof(PlayerBuildProbeTarget).AssemblyQualifiedName); | ||
| Set(serialized, field: "_strippedEngine", name: typeof(BoxCollider).AssemblyQualifiedName); | ||
| Set(serialized, field: "_samplePattern", name: typeof(LinePattern).AssemblyQualifiedName); | ||
| serialized.ApplyModifiedPropertiesWithoutUndo(); | ||
|
|
||
| Directory.CreateDirectory(Path.GetDirectoryName(ScenePath)!); | ||
| EditorSceneManager.SaveScene(scene, ScenePath); | ||
| AssetDatabase.SaveAssets(); | ||
| } | ||
|
|
||
| private static void Set(SerializedObject serialized, string field, string name) | ||
| { | ||
| var property = serialized.FindProperty($"{field}._assemblyQualifiedName") | ||
| ?? throw new InvalidOperationException($"{field}._assemblyQualifiedName not found"); | ||
|
|
||
| property.stringValue = name; | ||
| } | ||
|
|
||
| private static string Argument(string name) | ||
| { | ||
| var args = Environment.GetCommandLineArgs(); | ||
| var index = Array.IndexOf(args, name); | ||
|
|
||
| return index >= 0 && index + 1 < args.Length | ||
| ? args[index + 1] | ||
| : throw new ArgumentException($"{name} <value> is required"); | ||
| } | ||
|
|
||
| private sealed class Target | ||
| { | ||
| public Target(string id, BuildTarget buildTarget, ScriptingImplementation backend, ManagedStrippingLevel stripping) | ||
| { | ||
| Id = id; | ||
| BuildTarget = buildTarget; | ||
| Backend = backend; | ||
| Stripping = stripping; | ||
| } | ||
|
|
||
| public string Id { get; } | ||
|
|
||
| public BuildTarget BuildTarget { get; } | ||
|
|
||
| public ScriptingImplementation Backend { get; } | ||
|
|
||
| public ManagedStrippingLevel Stripping { get; } | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.