diff --git a/CHANGELOG.md b/CHANGELOG.md index 1041b62..ed32206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Android builds with Minify (R8) enabled failed to launch the SDK with `ClassNotFoundException: com.bugsee.unity.UnityWrapper`: Unity does not read the package's `Plugins/Android/bugsee-unity-wrapper.proguard`, so R8 stripped the wrapper classes C# reaches over JNI. A new post-generate step (`BugseeAndroidProguardRules`) appends the rules, between markers and idempotently, to the generated `unityLibrary/proguard-unity.txt` (the library's consumer rules), so no Player Settings change is needed. The rules now also keep the `UnityManagedException` constructor (built by name from C#) and the SDK interfaces implemented with `AndroidJavaProxy`; tests fail when a class C# names over JNI, a wrapper method it calls, or a proxied interface has no rule. +- IL2CPP Android, iOS and tvOS builds now write `LineNumberMappings.json` and `MethodMap.tsv`: the package adds `--emit-source-mapping --emit-method-map` to the Additional IL2CPP Arguments for the build (restored afterward, also after a failed or cancelled build: on the editor's next update, or when the editor quits, which covers a batchmode run that exits right after a failed build; only a killed editor process leaves them in `ProjectSettings.asset` until the next editor session restores them) when the il2cpp-linemap upload is on (`BUGSEE_APP_TOKEN` set in the Unity Editor's environment, `BUGSEE_NO_IL2CPP_MAPPING` unset, and no explicit mapping file via `BUGSEE_IL2CPP_MAPPING`). IL2CPP writes neither file unless asked (with Cloud Diagnostics crash reporting off, or on Unity 2021.3, where Unity does not add `--emit-source-mapping` itself), so the uploaded linemap had no `MethodMap.tsv` and mangled managed names could not be demangled (without the source-mapping argument there was no linemap at all). Verified with real Android IL2CPP builds on Unity 2022.3.62f3 and 2021.3.45f1 (both arguments exist in 6000.0.84f1 too; not built there, and iOS/tvOS were not built). - Android builds on Unity 6 failed in Gradle (`DependencyHandler.module(java.lang.Object)` while evaluating `:launcher`): the package wrote its own `Assets/Plugins/Android/baseProjectTemplate.gradle` and `launcherTemplate.gradle` pinning Android Gradle plugin 7.4.2, which cannot run on Unity 6's Gradle 9. The package no longer creates Gradle templates. It patches the Gradle project Unity generates instead (so nothing in Assets goes stale on Unity upgrades): the Bugsee Gradle plugin is added when the generated project pins AGP 8.6+ (previously: any Unity 6), and on 2021.3 the release build type gets `debugSymbolLevel 'FULL'`. The exact templates earlier package versions wrote are removed on an Android build when Unity's default AGP is another major; any other template pinning a different AGP major is left alone with a warning naming the file and the fix. The sample no longer ships custom Gradle templates. Verified with real IL2CPP arm64 builds on 2021.3.45f1 and 6000.0.84f1; Bugsee Gradle plugin 4.0.8 works with Unity 6's AGP 9.0.0 / Gradle 9.3.1. - Android native symbols on Unity 6: release builds no longer force `debugSymbolLevel 'FULL'` in the launcher; the level follows Player Settings (Create symbols.zip), which Unity 6 defaults to Disabled. Public gives symbol tables only (no file:line); Debugging is about ten times larger. An Android build with a Bugsee app token set now warns when the level is not Debugging, naming what is lost and the size trade-off. On Unity 6 the setting is `UnityEditor.Android.UserBuildSettings.DebugSymbols` (level None/SymbolTable/Full plus a Zip format; the 2021.3 `androidCreateSymbols` is obsolete there), and the opt-in also enables the Zip format. Opt in with `Bugsee > Android > Use Full Native Symbols (Debugging) For Symbol Upload` (or `BUGSEE_ANDROID_FULL_SYMBOLS=1` in CI): that build uses Debugging and the setting is restored afterwards (the saved state lives in `Library/Bugsee`, survives an editor exit after an interrupted build, and is restored only while the settings still hold what the opt-in set; a non-Android build never touches it). - Android Gradle cleanup never deletes a template you edited: a file is removed only when it is exactly what an earlier package version generated (or an unedited Unity 2021.3 default base copy), after copying it (and its `.meta`) to `Library/Bugsee/legacy-templates//`. A marker-bearing launcher with your own changes is kept with a warning. A launcher that is left alone and still uses `lintOptions`/`aaptOptions` is reported, and an unreadable AGP version (version catalog, Kotlin DSL) now warns that the Bugsee Gradle plugin was skipped. diff --git a/Documentation~/index.md b/Documentation~/index.md index 314939d..4d7367a 100644 --- a/Documentation~/index.md +++ b/Documentation~/index.md @@ -138,7 +138,7 @@ The iOS Run Script uploads for **every** Xcode configuration (Debug included, so | Artifact | When | CLI type | |---|---|---| -| `LineNumberMappings.json` (+ MethodMap) | Android post-build; iOS at **Xcode build** (every configuration) | `il2cpp-linemap` | +| `LineNumberMappings.json` + `MethodMap.tsv` (the package passes `--emit-source-mapping --emit-method-map` to IL2CPP for the build) | Android post-build; iOS at **Xcode build** (every configuration) | `il2cpp-linemap` | | Android `symbols.zip` / ELF | Android post-build | `elf` | | ProGuard / R8 `mapping.txt` | Android post-build when present | `proguard` | | iOS `.dSYM` | Xcode Run Script (`Bugsee Upload Symbols`), every configuration | `dsym` | @@ -196,14 +196,14 @@ Releases built without R8 (Minify off, the Unity default) have no mapping and no **iOS** 1. Scripting Backend: IL2CPP -2. Enable source mapping / Script Debugging as needed for LineNumberMappings generation +2. Nothing to enable for LineNumberMappings: when `BUGSEE_APP_TOKEN` is set in the environment of the Unity Editor process during the build, the package adds `--emit-source-mapping --emit-method-map` to the IL2CPP arguments for the build (restored afterward, also after a failed build). A token set only for the Xcode Run Script (step 4) is too late: Unity then adds no arguments and the export may lack `MethodMap.tsv` 3. Build or archive in Xcode: the Run Script uploads the dSYMs of the configuration being built. Debug has a dSYM only with `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym` (Unity exports Debug as `dwarf`); without one the script prints a warning and uploads no dSYM 4. Ensure `BUGSEE_APP_TOKEN` is visible to the Xcode Run Script phase 5. The iOS export sets `DEBUG_INFORMATION_FORMAT=dwarf-with-dsym` on every configuration of Unity-iPhone and UnityFramework, so Debug builds have a dSYM too. `dsymutil` on UnityFramework adds time to each Debug build; to keep Unity's own setting, tick **Bugsee → iOS → Keep Xcode Debug Info Format (skip dSYM force)** or set `BUGSEE_NO_DSYM_FORCE` in the Editor's environment (CI) ### Script Debugging vs dSYM -- **Script Debugging** / IL2CPP stacktrace information controls managed stack quality and LineNumberMappings generation. +- **Script Debugging** / IL2CPP stacktrace information controls managed stack quality. LineNumberMappings.json and MethodMap.tsv come from the IL2CPP arguments the package adds for the build when the upload is on (not from Script Debugging); set `BUGSEE_NO_IL2CPP_MAPPING` to turn that and the upload off, or `BUGSEE_IL2CPP_MAPPING` to upload a file you made yourself (Player Settings are then left alone). Adding or removing the token changes the arguments, which makes IL2CPP regenerate its cache once. - **dSYM / ELF** carry native line programs used for the primary LNM remap (native PC → C++ file/line → C#). - You need **both** the linemap upload and native symbols with line info for viewer C# file/line on native fatals. diff --git a/Editor/BugseeIl2CppLinemapUpload.cs b/Editor/BugseeIl2CppLinemapUpload.cs index f0a783b..eaa4c2d 100644 --- a/Editor/BugseeIl2CppLinemapUpload.cs +++ b/Editor/BugseeIl2CppLinemapUpload.cs @@ -59,7 +59,8 @@ public void OnPostprocessBuild(BuildReport report) { Debug.LogWarning( "Bugsee: LineNumberMappings.json not found — skipping il2cpp-linemap upload. " + - "Set BUGSEE_IL2CPP_MAPPING or enable IL2CPP source mapping in Player Settings."); + "The package adds --emit-source-mapping --emit-method-map to the IL2CPP arguments of an IL2CPP build when this upload is on; " + + "check that the scripting backend is IL2CPP, or set BUGSEE_IL2CPP_MAPPING to a LineNumberMappings.json."); return; } } diff --git a/Editor/BugseeIl2CppMappingArgs.cs b/Editor/BugseeIl2CppMappingArgs.cs new file mode 100644 index 0000000..fcedc5c --- /dev/null +++ b/Editor/BugseeIl2CppMappingArgs.cs @@ -0,0 +1,221 @@ +#if UNITY_EDITOR +using System; +using System.IO; +using Bugsee.WrapperPolicy; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace Bugsee.Editor +{ + /// + /// Makes an IL2CPP Android/iOS build write LineNumberMappings.json and MethodMap.tsv + /// (see ): IL2CPP writes neither unless asked, so the il2cpp-linemap upload had no + /// mapping, or one without MethodMap (mangled names could not be demangled). Runs only when the il2cpp-linemap upload + /// is on (a Bugsee app token, and BUGSEE_NO_IL2CPP_MAPPING unset). The arguments are added for this build and the + /// Player Settings string is restored afterwards (the string is saved in the VCS-tracked ProjectSettings.asset while + /// the build runs). The post-build callback is skipped when the pipeline aborts, so a one-shot editor update handler + /// restores once no build is running (success, failure or cancel); the saved state in Library/Bugsee also covers an + /// editor exit (an editor quit restores too, so a batchmode run that exits right after a failed build is clean; only a + /// killed editor process leaves the arguments for the next session). A string the user changed meanwhile is kept (see ). + /// Unity itself adds --emit-source-mapping on 2022.3 and 6000 when Cloud Diagnostics crash reporting is on; the + /// duplicate argument is harmless. Toggling the token changes the arguments and so invalidates the IL2CPP cache. + /// + [InitializeOnLoad] + sealed class BugseeIl2CppMappingArgs : IPreprocessBuildWithReport, IPostprocessBuildWithReport + { + public int callbackOrder => 20; + + const string InProgressKey = "Bugsee.Il2CppArgsBuildInProgress"; + + [Serializable] + sealed class PendingRestore + { + public string previous; + public string applied; + } + + static string PendingPath => + Path.Combine(Path.GetDirectoryName(Path.GetFullPath(Application.dataPath)), "Library", "Bugsee", "il2cpp-args-restore.json"); + + static BugseeIl2CppMappingArgs() + { + EditorApplication.quitting += RestoreOnQuit; + EditorApplication.delayCall += () => + { + if (!File.Exists(PendingPath)) + { + return; + } + + // A domain reload during a build loses the update handler: set it again until the build is over. + if (BuildPipeline.isBuildingPlayer || SessionState.GetBool(InProgressKey, false)) + { + EditorApplication.update += RestoreWhenBuildEnds; + } + else + { + ResolvePending(force: false); + } + }; + } + + static void RestoreOnQuit() + { + try + { + // A batchmode run that exits straight after a failed build never reaches an update: restore here (the + // change is written to ProjectSettings.asset before the editor exits, checked on 2022.3.62f3). + // Never while a build is still running. + if (!BuildPipeline.isBuildingPlayer) + { + ResolvePending(force: true); + } + } + catch (Exception) + { + } + } + + static void RestoreWhenBuildEnds() + { + if (BuildPipeline.isBuildingPlayer) + { + return; + } + + EditorApplication.update -= RestoreWhenBuildEnds; + ResolvePending(force: false); + } + + static bool Applies(BuildReport report) + { + BuildTarget platform = report.summary.platform; + if (platform != BuildTarget.Android && platform != BuildTarget.iOS && platform != BuildTarget.tvOS) + { + return false; + } + + // iOS has no Mono backend; Android builds Mono too, which writes no IL2CPP output. + bool il2cpp = platform != BuildTarget.Android || + PlayerSettings.GetScriptingBackend(NamedBuildTarget.Android) == ScriptingImplementation.IL2CPP; + return il2cpp && + BugseeCliRunner.HasAppToken && + Environment.GetEnvironmentVariable("BUGSEE_NO_IL2CPP_MAPPING") == null && + string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BUGSEE_IL2CPP_MAPPING")); + } + + public void OnPreprocessBuild(BuildReport report) + { + // A restore left by an earlier build is settled first (no build of this kind is running yet). + ResolvePending(force: true); + if (!Applies(report)) + { + return; + } + + string previous = PlayerSettings.GetAdditionalIl2CppArgs() ?? string.Empty; + string applied = Il2CppMappingArgs.Ensure(previous); + if (string.Equals(previous, applied, StringComparison.Ordinal)) + { + return; + } + + // The record is the only way back to the user's arguments: without it nothing is applied, and an + // unwritable Library/Bugsee must not fail the build over this optional feature. + try + { + Directory.CreateDirectory(Path.GetDirectoryName(PendingPath)); + File.WriteAllText(PendingPath, JsonUtility.ToJson(new PendingRestore { previous = previous, applied = applied })); + } + catch (Exception ex) + { + Debug.LogWarning("[Bugsee] Could not save the IL2CPP arguments restore record (" + ex.Message + + "); not adding the mapping arguments, so the line-number map and MethodMap are not written for this build."); + return; + } + + SessionState.SetBool(InProgressKey, true); + try + { + PlayerSettings.SetAdditionalIl2CppArgs(applied); + } + catch (Exception ex) + { + Debug.LogWarning("[Bugsee] Could not set the additional IL2CPP arguments: " + ex.Message); + SessionState.EraseBool(InProgressKey); + try { File.Delete(PendingPath); } catch (Exception) { } + return; + } + + EditorApplication.update -= RestoreWhenBuildEnds; + EditorApplication.update += RestoreWhenBuildEnds; + Debug.Log("[Bugsee] Building with IL2CPP arguments \"" + applied + "\" so the line-number map and MethodMap are written (restored afterwards)."); + } + + public void OnPostprocessBuild(BuildReport report) + { + // Fast path: BuildPipeline.isBuildingPlayer is still true inside this callback. + ResolvePending(force: true); + } + + /// The caller knows no build of ours is running (post-build callback, or before a new build). + static void ResolvePending(bool force) + { + if (!File.Exists(PendingPath)) + { + return; + } + + PendingRestore pending = null; + try + { + pending = JsonUtility.FromJson(File.ReadAllText(PendingPath)); + } + catch (Exception ex) when (Il2CppArgsRestorePolicy.IsTransientReadError(ex)) + { + // Unreadable right now, not unparseable: keep the record for the next attempt. + Debug.LogWarning("[Bugsee] Could not read the IL2CPP arguments restore record: " + ex.Message); + return; + } + catch (Exception) + { + // Unparseable: nothing to restore from. + } + + bool restoreThrew = false; + if (pending != null) + { + try + { + bool unchanged = string.Equals(PlayerSettings.GetAdditionalIl2CppArgs(), pending.applied, StringComparison.Ordinal); + switch (Il2CppArgsRestorePolicy.Decide(!force && BuildPipeline.isBuildingPlayer, unchanged)) + { + case SymbolRestoreAction.Skip: + return; + case SymbolRestoreAction.Restore: + PlayerSettings.SetAdditionalIl2CppArgs(pending.previous ?? string.Empty); + Debug.Log("[Bugsee] Restored the additional IL2CPP arguments."); + break; + default: + Debug.Log("[Bugsee] The additional IL2CPP arguments were changed since the Bugsee build added the mapping arguments; keeping your setting."); + break; + } + } + catch (Exception ex) + { + restoreThrew = true; + Debug.LogWarning("[Bugsee] Could not restore the additional IL2CPP arguments (the record is kept for the next editor session): " + ex.Message); + } + } + + if (Il2CppArgsRestorePolicy.DeleteRecord(pending != null, restoreThrew)) + { + SessionState.EraseBool(InProgressKey); + try { File.Delete(PendingPath); } catch (Exception) { } + } + } + } +} +#endif diff --git a/Editor/BugseeIl2CppMappingArgs.cs.meta b/Editor/BugseeIl2CppMappingArgs.cs.meta new file mode 100644 index 0000000..025bbfd --- /dev/null +++ b/Editor/BugseeIl2CppMappingArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e033bec9615a4527846417246edf718c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/WrapperPolicy/Il2CppArgsRestorePolicy.cs b/Runtime/WrapperPolicy/Il2CppArgsRestorePolicy.cs new file mode 100644 index 0000000..13b9d66 --- /dev/null +++ b/Runtime/WrapperPolicy/Il2CppArgsRestorePolicy.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; + +namespace Bugsee.WrapperPolicy +{ + /// + /// When to put Player Settings > Additional IL2CPP Arguments back after the build added the mapping arguments + /// (see ). The post-build callback is not called when the pipeline aborts, so a failed or + /// cancelled build is settled later: while a build is still running nothing is touched; afterwards the arguments are + /// restored only if they still hold what the build applied, so a string the user edited meanwhile is kept. + /// + public static class Il2CppArgsRestorePolicy + { + public static SymbolRestoreAction Decide(bool buildInProgress, bool currentEqualsApplied) + { + if (buildInProgress) + { + return SymbolRestoreAction.Skip; + } + + return currentEqualsApplied ? SymbolRestoreAction.Restore : SymbolRestoreAction.Discard; + } + + /// + /// Whether the saved record may be deleted once has been acted on. It is the only way back to the + /// user's arguments in a later editor session, so it is kept when the record parsed but the restore threw (the flags + /// are still in Player Settings); it goes after a successful restore, a deliberate discard, or when it cannot be parsed. + /// + public static bool DeleteRecord(bool recordParsed, bool restoreThrew) + { + return !recordParsed || !restoreThrew; + } + + /// + /// A failure to read the record that says nothing about its content (locked, unreadable, no permission): the record is + /// kept for the next attempt. Anything else (a parse failure) means it holds nothing to restore from. + /// + public static bool IsTransientReadError(Exception ex) + { + return ex is IOException || ex is UnauthorizedAccessException; + } + } +} diff --git a/Runtime/WrapperPolicy/Il2CppArgsRestorePolicy.cs.meta b/Runtime/WrapperPolicy/Il2CppArgsRestorePolicy.cs.meta new file mode 100644 index 0000000..c33dd21 --- /dev/null +++ b/Runtime/WrapperPolicy/Il2CppArgsRestorePolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84bfe6757b8e74a4abef6cdae97e3057 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/WrapperPolicy/Il2CppMappingArgs.cs b/Runtime/WrapperPolicy/Il2CppMappingArgs.cs new file mode 100644 index 0000000..9b89104 --- /dev/null +++ b/Runtime/WrapperPolicy/Il2CppMappingArgs.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; + +namespace Bugsee.WrapperPolicy +{ + /// + /// The IL2CPP arguments that make a build write Symbols/LineNumberMappings.json (C# file:line) and + /// Symbols/MethodMap.tsv (mangled C++ name to C# signature) next to the generated C++. Neither file exists + /// unless the arguments are passed, and without them the il2cpp-linemap upload has nothing (or no MethodMap) to send. + /// + public static class Il2CppMappingArgs + { + public const string SourceMapping = "--emit-source-mapping"; + public const string MethodMap = "--emit-method-map"; + + /// + /// (Player Settings > Additional IL2CPP Arguments) with the mapping arguments it is + /// missing appended; the same string when it already has both. Existing arguments are never reordered or removed. + /// + public static string Ensure(string existing) + { + string result = existing ?? string.Empty; + foreach (string flag in new[] { SourceMapping, MethodMap }) + { + if (!Has(result, flag)) + { + result = result.Length == 0 || char.IsWhiteSpace(result[result.Length - 1]) ? result + flag : result + " " + flag; + } + } + + return result; + } + + static bool Has(string args, string flag) + { + var separators = new[] { ' ', '\t', '\r', '\n' }; + foreach (string token in args.Split(separators, StringSplitOptions.RemoveEmptyEntries)) + { + if (token == flag || token.StartsWith(flag + "=", StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + } +} diff --git a/Runtime/WrapperPolicy/Il2CppMappingArgs.cs.meta b/Runtime/WrapperPolicy/Il2CppMappingArgs.cs.meta new file mode 100644 index 0000000..f93684b --- /dev/null +++ b/Runtime/WrapperPolicy/Il2CppMappingArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c51f5af9dcb445c9f0333848d83ccf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/BugseeMiniGame/Assets/Editor/E2EBuild.cs b/Samples~/BugseeMiniGame/Assets/Editor/E2EBuild.cs index 7a11b1e..715a94d 100644 --- a/Samples~/BugseeMiniGame/Assets/Editor/E2EBuild.cs +++ b/Samples~/BugseeMiniGame/Assets/Editor/E2EBuild.cs @@ -100,12 +100,6 @@ static void Build(BuildTarget target) #endif } - if (il2cpp) - { - try { PlayerSettings.SetAdditionalIl2CppArgs("--emit-source-mapping"); } - catch { /* older editors */ } - } - // A copy left by a build that was killed would otherwise take priority over everything else. RemoveOverride(); var endpoint = Environment.GetEnvironmentVariable("BUGSEE_ENDPOINT"); diff --git a/Tests~/WrapperPolicy/Il2CppMappingArgsTests.cs b/Tests~/WrapperPolicy/Il2CppMappingArgsTests.cs new file mode 100644 index 0000000..d0da4d7 --- /dev/null +++ b/Tests~/WrapperPolicy/Il2CppMappingArgsTests.cs @@ -0,0 +1,68 @@ +using NUnit.Framework; + +namespace Bugsee.WrapperPolicy.Tests +{ + public class Il2CppMappingArgsTests + { + [TestCase(null, "--emit-source-mapping --emit-method-map")] + [TestCase("", "--emit-source-mapping --emit-method-map")] + [TestCase("--emit-source-mapping", "--emit-source-mapping --emit-method-map")] + [TestCase("--emit-method-map", "--emit-method-map --emit-source-mapping")] + [TestCase("--emit-null-checks", "--emit-null-checks --emit-source-mapping --emit-method-map")] + [TestCase("--emit-null-checks ", "--emit-null-checks --emit-source-mapping --emit-method-map")] + [TestCase("--emit-method-map --emit-source-mapping", "--emit-method-map --emit-source-mapping")] + [TestCase("--emit-source-mapping=1 --emit-method-map", "--emit-source-mapping=1 --emit-method-map")] + public void Missing_mapping_arguments_are_appended_and_nothing_else_changes(string existing, string expected) + { + Assert.That(Il2CppMappingArgs.Ensure(existing), Is.EqualTo(expected)); + } + + [Test] + public void A_flag_that_only_shares_a_prefix_does_not_count() + { + Assert.That(Il2CppMappingArgs.Ensure("--emit-method-map-extra"), Is.EqualTo("--emit-method-map-extra --emit-source-mapping --emit-method-map")); + } + + [Test] + public void Ensure_is_idempotent() + { + string once = Il2CppMappingArgs.Ensure("--emit-null-checks"); + + Assert.That(Il2CppMappingArgs.Ensure(once), Is.EqualTo(once)); + } + + [Test] + public void Restore_waits_while_a_build_runs_and_after_a_failed_build_restores_what_it_applied() + { + Assert.That(Il2CppArgsRestorePolicy.Decide(buildInProgress: true, currentEqualsApplied: true), Is.EqualTo(SymbolRestoreAction.Skip)); + Assert.That(Il2CppArgsRestorePolicy.Decide(buildInProgress: true, currentEqualsApplied: false), Is.EqualTo(SymbolRestoreAction.Skip)); + // The failure window: no post-build callback ran, the build is over, the settings still hold the applied string. + Assert.That(Il2CppArgsRestorePolicy.Decide(buildInProgress: false, currentEqualsApplied: true), Is.EqualTo(SymbolRestoreAction.Restore)); + } + + [Test] + public void An_args_string_the_user_edited_since_is_kept() + { + Assert.That(Il2CppArgsRestorePolicy.Decide(buildInProgress: false, currentEqualsApplied: false), Is.EqualTo(SymbolRestoreAction.Discard)); + } + + [Test] + public void The_record_is_kept_when_the_restore_threw_and_deleted_in_every_other_case() + { + // Parsed record, restore threw (caught exception path): the flags are still in Player Settings, keep the way back. + Assert.That(Il2CppArgsRestorePolicy.DeleteRecord(recordParsed: true, restoreThrew: true), Is.False); + // Restore or a deliberate discard completed. + Assert.That(Il2CppArgsRestorePolicy.DeleteRecord(recordParsed: true, restoreThrew: false), Is.True); + // Nothing to restore from. + Assert.That(Il2CppArgsRestorePolicy.DeleteRecord(recordParsed: false, restoreThrew: false), Is.True); + } + + [Test] + public void An_unreadable_record_is_kept_but_a_parse_failure_is_not() + { + Assert.That(Il2CppArgsRestorePolicy.IsTransientReadError(new System.IO.IOException()), Is.True); + Assert.That(Il2CppArgsRestorePolicy.IsTransientReadError(new System.UnauthorizedAccessException()), Is.True); + Assert.That(Il2CppArgsRestorePolicy.IsTransientReadError(new System.ArgumentException("bad json")), Is.False); + } + } +} diff --git a/Tools~/CompileCheck/Editor/UnityEditorStubs.cs b/Tools~/CompileCheck/Editor/UnityEditorStubs.cs index 07b5f4b..dd453f1 100644 --- a/Tools~/CompileCheck/Editor/UnityEditorStubs.cs +++ b/Tools~/CompileCheck/Editor/UnityEditorStubs.cs @@ -27,6 +27,8 @@ public static class PlayerSettings { public static string bundleVersion; public static ScriptingImplementation GetScriptingBackend(UnityEditor.Build.NamedBuildTarget buildTarget) => default; + public static string GetAdditionalIl2CppArgs() => null; + public static void SetAdditionalIl2CppArgs(string args) { } public static class Android {