|
2 | 2 | using UnityEditor; |
3 | 3 | using UnityEditor.Build; |
4 | 4 | using UnityEditor.Build.Reporting; |
| 5 | +using UnityEngine; |
5 | 6 | using System.IO; |
6 | 7 | using System.Text.RegularExpressions; |
7 | 8 |
|
8 | 9 | /// <summary> |
9 | 10 | /// Keeps Unity's version fields in sync with files generated by CI: |
10 | 11 | /// - version.txt -> PlayerSettings.bundleVersion (maps to Android versionName) |
11 | | -/// - versionCode.txt (optional) -> PlayerSettings.Android.bundleVersionCode |
| 12 | +/// - versionCode.txt -> PlayerSettings.Android.bundleVersionCode |
12 | 13 | /// |
13 | 14 | /// If versionCode.txt is missing, it will derive a versionCode from version.txt using: |
14 | 15 | /// code = MAJOR*10000 + MINOR*100 + PATCH |
15 | 16 | /// This ensures a monotonic integer for Google Play uploads. |
16 | 17 | /// </summary> |
17 | | -public class SyncBundleVersionPreprocess : IPreprocessBuildWithReport |
| 18 | +public class SyncBundleVersion : IPreprocessBuildWithReport |
18 | 19 | { |
19 | 20 | /// <summary> |
20 | 21 | /// Determines the order in which build preprocessors are called. 0 means default/early execution. |
21 | 22 | /// </summary> |
22 | 23 | public int callbackOrder => 0; |
23 | 24 |
|
24 | | - /// <summary> |
25 | | - /// Keeps Unity's internal application version (Application.version) in sync with CI/CD-managed versions. |
26 | | - /// </summary> |
27 | | - public void OnPreprocessBuild(BuildReport report) |
| 25 | + // Auto-run whenever scripts reload (after pulling a Release PR, switching branches, etc.) |
| 26 | + [InitializeOnLoadMethod] |
| 27 | + private static void AutoSyncOnReload() |
28 | 28 | { |
29 | | - var root = Directory.GetCurrentDirectory(); |
| 29 | + // Delay a tick so Unity finishes the domain reload before touching PlayerSettings |
| 30 | + EditorApplication.delayCall += SyncNow; |
| 31 | + } |
30 | 32 |
|
31 | | - // 1) Sync human-readable version |
32 | | - var versionPath = Path.Combine(root, "version.txt"); |
33 | | - if (File.Exists(versionPath)) |
34 | | - { |
35 | | - var v = File.ReadAllText(versionPath).Trim(); |
| 33 | + // Manual command: Tools > Versioning > Sync from Files |
| 34 | + [MenuItem("Tools/Versioning/Sync from Files")] |
| 35 | + public static void SyncNow() |
| 36 | + { |
| 37 | + string root = Directory.GetCurrentDirectory(); |
| 38 | + string assets = Application.dataPath; |
36 | 39 |
|
37 | | - // Update if the file is not empty and differs from current bundleVersion |
38 | | - if (!string.IsNullOrEmpty(v) && PlayerSettings.bundleVersion != v) |
39 | | - { |
40 | | - PlayerSettings.bundleVersion = v; // Update Unity's app version |
41 | | - AssetDatabase.SaveAssets(); |
42 | | - UnityEngine.Debug.Log($"[Build] Synced bundleVersion -> {v}"); |
43 | | - } |
| 40 | + // Prefer Assets/version.txt; fallback to root/version.txt |
| 41 | + string versionPath = File.Exists(Path.Combine(assets, "version.txt")) |
| 42 | + ? Path.Combine(assets, "version.txt") |
| 43 | + : Path.Combine(root, "version.txt"); |
44 | 44 |
|
45 | | - // 2) Android versionCode: prefer explicit file; else derive from semver |
| 45 | + if (!File.Exists(versionPath)) |
| 46 | + { |
| 47 | + Debug.LogWarning("[Versioning] version.txt not found in Assets/ or project root; bundleVersion not changed."); |
| 48 | + return; |
| 49 | + } |
46 | 50 |
|
| 51 | + // Extract only the semver token (optionally prefixed with 'v'), ignore any trailing markers |
| 52 | + // Example line: "1.2.1 x-release-please-version" |
| 53 | + string raw = File.ReadAllText(versionPath); |
| 54 | + var mVer = Regex.Match(raw, @"^\s*v?(?<ver>\d+\.\d+\.\d+)", RegexOptions.Multiline); |
| 55 | + if (!mVer.Success) |
| 56 | + { |
| 57 | + Debug.LogWarning($"[Versioning] Could not find a semantic version in '{versionPath}'."); |
| 58 | + return; |
| 59 | + } |
47 | 60 |
|
48 | | -#if UNITY_ANDROID |
| 61 | + string v = mVer.Groups["ver"].Value; // e.g., "1.2.1" |
49 | 62 |
|
50 | | - // Local holder for the versionCode we want to apply to the Android build. |
51 | | - int code; // Google Play requires this to be a monotonically increasing integer. |
52 | | - var codePath = Path.Combine(root, "versionCode.txt"); |
| 63 | + if (PlayerSettings.bundleVersion != v) |
| 64 | + { |
| 65 | + PlayerSettings.bundleVersion = v; |
| 66 | + Debug.Log($"[Versioning] Synced PlayerSettings.bundleVersion -> {v}"); |
| 67 | + } |
53 | 68 |
|
54 | | - // Preferred path: if versionCode.txt exists and contains a valid integer, use it as-is. |
55 | | - if (File.Exists(codePath) && int.TryParse(File.ReadAllText(codePath).Trim(), out code)) |
56 | | - { |
57 | | - SetAndroidVersionCodeIfDifferent(code); |
58 | | - } |
59 | | - else |
60 | | - { |
61 | | - // Fallback path: derive an integer versionCode from the semver in version.txt (string 'v'). |
62 | | - var m = Regex.Match(v, @"^(?<maj>\d+)\.(?<min>\d+)\.(?<pat>\d+)"); |
63 | | - if (m.Success) |
64 | | - { |
65 | | - // This scheme reserves two digits for MINOR and two for PATCH (00–99 each). |
66 | | - // If you expect MINOR or PATCH to exceed 99, switch to a wider encoding |
67 | | - // (e.g., MAJOR*1_000_000 + MINOR*1_000 + PATCH). |
68 | | - int maj = int.Parse(m.Groups["maj"].Value); |
69 | | - int min = int.Parse(m.Groups["min"].Value); |
70 | | - int pat = int.Parse(m.Groups["pat"].Value); |
71 | | - code = maj * 10000 + min * 100 + pat; |
72 | | - SetAndroidVersionCodeIfDifferent(code); |
73 | | - } |
74 | | - else |
75 | | - { |
76 | | - // If version.txt doesn't match MAJOR.MINOR.PATCH, we can't derive a versionCode. |
77 | | - // Build will still proceed with the current PlayerSettings.Android.bundleVersionCode. |
78 | | - UnityEngine.Debug.LogWarning($"[Build] Could not parse semver from '{v}' to derive Android versionCode."); |
79 | | - } |
80 | | - } |
81 | | -#endif |
| 69 | + // Android versionCode: prefer explicit file; else derive from semver |
| 70 | + int code; |
| 71 | + string codePath = File.Exists(Path.Combine(assets, "versionCode.txt")) |
| 72 | + ? Path.Combine(assets, "versionCode.txt") |
| 73 | + : Path.Combine(root, "versionCode.txt"); |
| 74 | + |
| 75 | + if (File.Exists(codePath) && int.TryParse(File.ReadAllText(codePath).Trim(), out code)) |
| 76 | + { |
| 77 | + SetAndroidVersionCodeIfDifferent(code); |
82 | 78 | } |
83 | 79 | else |
84 | 80 | { |
85 | | - UnityEngine.Debug.LogWarning("[Build] version.txt not found; bundleVersion not changed."); |
| 81 | + // Derive from MAJOR.MINOR.PATCH (v already parsed as pure semver) |
| 82 | + var m = Regex.Match(v, @"^(?<maj>\d+)\.(?<min>\d+)\.(?<pat>\d+)$"); |
| 83 | + if (m.Success) |
| 84 | + { |
| 85 | + int maj = int.Parse(m.Groups["maj"].Value); |
| 86 | + int min = int.Parse(m.Groups["min"].Value); |
| 87 | + int pat = int.Parse(m.Groups["pat"].Value); |
| 88 | + code = maj * 10000 + min * 100 + pat; // e.g., 2.3.4 -> 20304 |
| 89 | + SetAndroidVersionCodeIfDifferent(code); |
| 90 | + } |
86 | 91 | } |
87 | | - } |
88 | 92 |
|
| 93 | + AssetDatabase.SaveAssets(); |
| 94 | + } |
89 | 95 |
|
90 | | -#if UNITY_ANDROID && UNITY_EDITOR |
| 96 | + // Safety net: also run right before every build |
| 97 | + public void OnPreprocessBuild(BuildReport report) => SyncNow(); |
91 | 98 |
|
92 | | - /// <summary> |
93 | | - /// Writes <see cref="PlayerSettings.Android.bundleVersionCode"/> if the value differ. |
94 | | - /// </summary> |
95 | 99 | private static void SetAndroidVersionCodeIfDifferent(int code) |
| 100 | + { |
| 101 | + if (PlayerSettings.Android.bundleVersionCode != code) |
96 | 102 | { |
97 | | - if (PlayerSettings.Android.bundleVersionCode != code) |
98 | | - { |
99 | | - PlayerSettings.Android.bundleVersionCode = code; // Updates ProjectSettings/ProjectSettings.asset |
100 | | - AssetDatabase.SaveAssets(); // Keeps the change |
101 | | - UnityEngine.Debug.Log($"[Build] Synced Android bundleVersionCode -> {code}"); |
102 | | - } |
| 103 | + PlayerSettings.Android.bundleVersionCode = code; |
| 104 | + Debug.Log($"[Versioning] Synced Android bundleVersionCode -> {code}"); |
103 | 105 | } |
104 | | -#endif |
| 106 | + } |
105 | 107 | } |
106 | 108 | #endif |
0 commit comments