-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathiOSInfoPlistPostProcessor.cs
More file actions
167 lines (147 loc) · 6.5 KB
/
Copy pathiOSInfoPlistPostProcessor.cs
File metadata and controls
167 lines (147 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#nullable enable
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif
namespace Immutable.Audience.Editor
{
/// <summary>
/// Injects mobile-attribution keys into the generated iOS Xcode project's
/// <c>Info.plist</c>: <c>NSUserTrackingUsageDescription</c> (the ATT
/// prompt copy) and <c>SKAdNetworkItems</c>.
/// </summary>
/// <remarks>
/// Both keys are gated by the <c>AUDIENCE_MOBILE_ATTRIBUTION</c>
/// scripting define so a studio that hasn't opted into attribution
/// ships a clean <c>Info.plist</c> — Apple flags apps that include
/// either key without the corresponding code paths.
///
/// Values come from the <see cref="AudienceMobileBuildSettings"/>
/// asset. If the asset is missing, a default
/// <c>NSUserTrackingUsageDescription</c> is still written (Apple
/// rejects builds with the key missing) but no <c>SKAdNetworkItems</c>.
///
/// <c>callbackOrder = 9050</c> runs above Unity's own post-processors
/// (order 1) so studio post-processors with low orders run first,
/// while higher-order post-processors that extend
/// <c>SKAdNetworkItems</c> can still merge their entries on top.
/// </remarks>
internal static class iOSInfoPlistPostProcessor
{
internal const int CallbackOrder = 9050;
internal const string AttributionDefine = "AUDIENCE_MOBILE_ATTRIBUTION";
[PostProcessBuild(CallbackOrder)]
internal static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS) return;
#if UNITY_IOS
if (!AttributionDefineEnabled()) return;
var plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
if (!File.Exists(plistPath))
{
Debug.LogWarning(
$"[ImmutableAudience] iOS post-processor: Info.plist not found at {plistPath}. Skipping.");
return;
}
var settings = AudienceMobileBuildSettings.FindAsset();
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
ApplyTrackingUsageDescription(plist.root, settings);
ApplySKAdNetworkItems(plist.root, settings);
plist.WriteToFile(plistPath);
#endif
}
// Sanity-check the settings asset without running a full iOS build.
[MenuItem("Tools/Immutable/Audience/Validate iOS Build Settings")]
private static void ValidateBuildSettings()
{
if (!AttributionDefineEnabled())
{
Debug.LogWarning(
"[ImmutableAudience] AUDIENCE_MOBILE_ATTRIBUTION scripting define is not set " +
"for the iOS player target. The post-processor will not modify Info.plist. " +
"Add the define under Player Settings → Other Settings → Scripting Define Symbols.");
return;
}
var settings = AudienceMobileBuildSettings.FindAsset();
var description = settings != null
? settings.TrackingUsageDescription
: AudienceMobileBuildSettings.DefaultTrackingUsageDescription;
var ids = settings?.SKAdNetworkIds ?? new string[0];
Debug.Log(
"[ImmutableAudience] iOS Info.plist injection preview\n" +
$" NSUserTrackingUsageDescription: {description}\n" +
$" SKAdNetworkItems: {ids.Length} id(s)\n" +
(ids.Length == 0
? " (no SKAdNetwork ids configured — set them on the AudienceMobileBuildSettings asset)\n"
: string.Concat(System.Array.ConvertAll(ids, id => $" - {id}\n"))));
}
// Reads the iOS-target define list specifically — the post-processor
// mutates iOS build output regardless of which target the editor is
// currently focused on.
private static bool AttributionDefineEnabled()
{
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS) ?? string.Empty;
foreach (var define in defines.Split(';'))
{
if (define.Trim() == AttributionDefine) return true;
}
return false;
}
#if UNITY_IOS
internal static void ApplyTrackingUsageDescription(
PlistElementDict root,
AudienceMobileBuildSettings? settings)
{
var description = settings != null
? settings.TrackingUsageDescription
: AudienceMobileBuildSettings.DefaultTrackingUsageDescription;
// Always overwrite — the settings asset is the source of truth,
// beating any placeholder a lower-order post-processor wrote.
root.SetString("NSUserTrackingUsageDescription", description);
}
internal static void ApplySKAdNetworkItems(
PlistElementDict root,
AudienceMobileBuildSettings? settings)
{
var ids = settings?.SKAdNetworkIds ?? new string[0];
if (ids.Length == 0) return;
// Merge with any existing list so a lower-order post-processor's
// entries aren't clobbered. Dedup is case-insensitive per Apple's
// SKAdNetwork spec.
PlistElementArray array;
if (root.values.TryGetValue("SKAdNetworkItems", out var existing) &&
existing is PlistElementArray existingArray)
{
array = existingArray;
}
else
{
array = root.CreateArray("SKAdNetworkItems");
}
var existingIds = new System.Collections.Generic.HashSet<string>(
System.StringComparer.OrdinalIgnoreCase);
foreach (var item in array.values)
{
if (item is PlistElementDict dict &&
dict.values.TryGetValue("SKAdNetworkIdentifier", out var idValue) &&
idValue is PlistElementString idString)
{
existingIds.Add(idString.value);
}
}
foreach (var id in ids)
{
if (string.IsNullOrWhiteSpace(id)) continue;
if (existingIds.Contains(id)) continue;
var dict = array.AddDict();
dict.SetString("SKAdNetworkIdentifier", id);
existingIds.Add(id);
}
}
#endif
}
}