Skip to content

Commit d3c5b26

Browse files
author
Stewart Miles
committed
Added support for Unity mainTemplate.gradle files.
If Plugins/Android/mainTemplate.gradle is present in the project and Gradle is selected as the build system, the Android resolver will now patch the file with Android library the dependencies it is tracking. Bug: 131331407 Change-Id: Ib9e25bb8c6287a8412a73ab24b5a12a1d4b86d86
1 parent 04feff1 commit d3c5b26

File tree

11 files changed

+1022
-91
lines changed

11 files changed

+1022
-91
lines changed

source/PlayServicesResolver/PlayServicesResolver.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<Compile Include="src\CommandLine.cs" />
6161
<Compile Include="src\CommandLineDialog.cs" />
6262
<Compile Include="src\DefaultResolver.cs" />
63+
<Compile Include="src\GradleTemplateResolver.cs" />
6364
<Compile Include="src\IResolver.cs" />
6465
<Compile Include="src\JavaUtilities.cs" />
6566
<Compile Include="src\PlayServicesPreBuild.cs" />
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
// <copyright file="GradleTemplateResolver.cs" company="Google Inc.">
2+
// Copyright (C) 2019 Google Inc. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
namespace GooglePlayServices {
18+
using System;
19+
using System.Collections.Generic;
20+
using System.IO;
21+
22+
using Google;
23+
using Google.JarResolver;
24+
25+
using UnityEditor;
26+
27+
/// <summary>
28+
/// Resolver which simply injects dependencies into a gradle template file.
29+
/// </summary>
30+
internal class GradleTemplateResolver {
31+
32+
/// <summary>
33+
/// Path of the Gradle template file.
34+
/// </summary>
35+
public static string GradleTemplatePath =
36+
Path.Combine(SettingsDialog.AndroidPluginsDir, "mainTemplate.gradle");
37+
38+
/// <summary>
39+
/// Line that indicates the start of the injected repos block in the template.
40+
/// </summary>
41+
private const string ReposStartLine = "// Android Resolver Repos Start";
42+
43+
/// <summary>
44+
/// Line that indicates the end of the injected repos block in the template.
45+
/// </summary>
46+
private const string ReposEndLine = "// Android Resolver Repos End";
47+
48+
/// <summary>
49+
/// Line that indicates where to initially inject repos in the default template.
50+
/// </summary>
51+
private const string ReposInjectionLine = "apply plugin: 'com.android.application'";
52+
53+
/// <summary>
54+
/// Line that indicates the start of the injected dependencies block in the template.
55+
/// </summary>
56+
private const string DependenciesStartLine = "// Android Resolver Dependencies Start";
57+
58+
/// <summary>
59+
/// Line that indicates the end of the injected dependencies block in the template.
60+
/// </summary>
61+
private const string DependenciesEndLine = "// Android Resolver Dependencies End";
62+
63+
/// <summary>
64+
/// Token that indicates where dependencies should initially be injected.
65+
/// If this isn't present in the template dependencies will not be injected or they'll
66+
/// be removed.
67+
/// </summary>
68+
private const string DependenciesToken = "**DEPS**";
69+
70+
/// <summary>
71+
/// Find all srcaar files under a directory.
72+
/// </summary>
73+
private static List<string> FindSrcAars(string directory) {
74+
var foundFiles = new List<string>();
75+
foreach (string filename in Directory.GetFiles(directory)) {
76+
if (Path.GetExtension(filename).ToLower() == ".srcaar") {
77+
foundFiles.Add(filename);
78+
}
79+
}
80+
foreach (string currentDirectory in Directory.GetDirectories(directory)) {
81+
foundFiles.AddRange(FindSrcAars(currentDirectory));
82+
}
83+
return foundFiles;
84+
}
85+
86+
/// <summary>
87+
/// Copy srcaar files to aar files that are excluded from Unity's build process.
88+
/// </summary>
89+
private static bool CopySrcAars(ICollection<Dependency> dependencies) {
90+
// Find all repositories embedded in the project.
91+
var repos = new HashSet<string>();
92+
foreach (var dependency in dependencies) {
93+
foreach (var repo in dependency.Repositories) {
94+
if (repo.Replace("\\", "/").ToLower().StartsWith("assets/")) {
95+
repos.Add(repo);
96+
}
97+
}
98+
}
99+
bool succeeded = true;
100+
var aarFiles = new List<string>();
101+
// Copy each .srcaar file to .aar while configuring the plugin importer to ignore the
102+
// file.
103+
foreach (var repo in repos) {
104+
foreach (var srcaar in FindSrcAars(repo)) {
105+
var dir = Path.GetDirectoryName(srcaar);
106+
var filename = Path.GetFileNameWithoutExtension(srcaar);
107+
var targetFilename = Path.Combine(dir, filename + ".aar");
108+
aarFiles.Add(targetFilename);
109+
if (!File.Exists(targetFilename)) {
110+
bool configuredAar = false;
111+
if (AssetDatabase.CopyAsset(srcaar, targetFilename) &&
112+
PlayServicesResolver.LabelAssets(
113+
new [] { targetFilename }).Count == 0) {
114+
try {
115+
PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath(
116+
targetFilename);
117+
importer.SetCompatibleWithAnyPlatform(false);
118+
importer.SetCompatibleWithPlatform(BuildTarget.Android, false);
119+
configuredAar = true;
120+
} catch (Exception ex) {
121+
PlayServicesResolver.Log(String.Format(
122+
"Failed to disable {0} from being included by Unity's " +
123+
"internal build. {0} has been deleted and will not be " +
124+
"included in Gradle builds. ({1})", srcaar, ex),
125+
level: LogLevel.Error);
126+
}
127+
} else {
128+
PlayServicesResolver.Log(String.Format(
129+
"Unable to copy {0} to {1}. {1} will not be included in Gradle " +
130+
"builds.", srcaar, targetFilename), level: LogLevel.Error);
131+
}
132+
if (!configuredAar) {
133+
if (File.Exists(targetFilename)) {
134+
AssetDatabase.DeleteAsset(targetFilename);
135+
}
136+
succeeded = false;
137+
}
138+
}
139+
}
140+
}
141+
return succeeded;
142+
}
143+
144+
/// <summary>
145+
/// Finds an area in a set of lines to inject a block of text.
146+
/// </summary>
147+
private class TextFileLineInjector {
148+
// Token which, if found within a line, indicates where to inject the block of text if
149+
// the start / end block isn't found
150+
private string injectionToken;
151+
// Line that indicates the start of the block to replace.
152+
private string startBlockLine;
153+
// Line that indicates the end of the block to replace.
154+
private string endBlockLine;
155+
// Lines to inject.
156+
private List<string> replacementLines;
157+
// Shorthand name of the replacement block.
158+
private string replacementName;
159+
// Description of the file being modified.
160+
private string fileDescription;
161+
// Whether replacementLines has been injected.
162+
private bool injected = false;
163+
// Whether the injector is tracking a line between startBlockLine and endBlockLine.
164+
private bool inBlock = false;
165+
166+
/// <summary>
167+
/// Construct the injector.
168+
/// </summary>
169+
/// <param name="injectionToken">Token which, if found within a line, indicates where
170+
/// to inject the block of text if the start / end block isn't found.</param>
171+
/// <param name="startBlockLine">Line which indicates the start of the block to
172+
/// replace.</param>
173+
/// <param name="endBlockLine">Line which indicates the end of the block to replace.
174+
/// </param>
175+
/// <param name="replacementLines">Lines to inject.</param>
176+
/// <param name="replacementName">Shorthand name of the replacement block.</param>
177+
/// <param name="fileDescription">Description of the file being modified.</param>
178+
public TextFileLineInjector(string injectionToken,
179+
string startBlockLine,
180+
string endBlockLine,
181+
ICollection<string> replacementLines,
182+
string replacementName,
183+
string fileDescription) {
184+
this.injectionToken = injectionToken;
185+
this.startBlockLine = startBlockLine;
186+
this.endBlockLine = endBlockLine;
187+
this.replacementLines = new List<string>();
188+
if (replacementLines.Count > 0) {
189+
this.replacementLines.Add(startBlockLine);
190+
this.replacementLines.AddRange(replacementLines);
191+
this.replacementLines.Add(endBlockLine);
192+
}
193+
this.replacementName = replacementName;
194+
this.fileDescription = fileDescription;
195+
}
196+
197+
/// <summary>
198+
/// Process a line returning the set of lines to emit for this line.
199+
/// </summary>
200+
/// <param name="line">Line to process.</param>
201+
/// <param name="injectionApplied">Whether lines were injected.</param>
202+
/// <returns>List of lines to emit for the specified line.</returns>
203+
public List<string> ProcessLine(string line, out bool injectionApplied) {
204+
var trimmedLine = line.Trim();
205+
var outputLines = new List<string> { line };
206+
bool injectBlock = false;
207+
injectionApplied = false;
208+
if (injected) {
209+
return outputLines;
210+
}
211+
if (!inBlock) {
212+
if (trimmedLine.StartsWith(startBlockLine)) {
213+
inBlock = true;
214+
outputLines.Clear();
215+
} else if (trimmedLine.Contains(injectionToken)) {
216+
injectBlock = true;
217+
}
218+
} else {
219+
outputLines.Clear();
220+
if (trimmedLine.StartsWith(endBlockLine)) {
221+
inBlock = false;
222+
injectBlock = true;
223+
}
224+
}
225+
if (injectBlock) {
226+
injected = true;
227+
injectionApplied = true;
228+
if (replacementLines.Count > 0) {
229+
PlayServicesResolver.Log(String.Format("Adding {0} to {1}",
230+
replacementName, fileDescription),
231+
level: LogLevel.Verbose);
232+
outputLines.InsertRange(0, replacementLines);
233+
}
234+
}
235+
return outputLines;
236+
}
237+
}
238+
239+
/// <summary>
240+
/// Inject / update dependencies in the gradle template file.
241+
/// </summary>
242+
public static bool InjectDependencies(ICollection<Dependency> dependencies) {
243+
var fileDescription = String.Format("gradle template {0}", GradleTemplatePath);
244+
PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription),
245+
level: LogLevel.Verbose);
246+
IEnumerable<string> lines;
247+
try {
248+
lines = File.ReadAllLines(GradleTemplatePath);
249+
} catch (Exception ex) {
250+
PlayServicesResolver.Log(
251+
String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()),
252+
level: LogLevel.Error);
253+
return false;
254+
}
255+
256+
PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken,
257+
fileDescription),
258+
level: LogLevel.Verbose);
259+
// Determine whether dependencies should be injected.
260+
bool containsDeps = false;
261+
foreach (var line in lines) {
262+
if (line.Contains(DependenciesToken)) {
263+
containsDeps = true;
264+
break;
265+
}
266+
}
267+
268+
// If a dependencies token isn't present report a warning and abort.
269+
if (!containsDeps) {
270+
PlayServicesResolver.Log(
271+
String.Format("No {0} token found in {1}, Android Resolver libraries will " +
272+
"not be added to the file.", DependenciesToken, fileDescription),
273+
level: LogLevel.Warning);
274+
return true;
275+
}
276+
277+
// Copy all srcaar files in the project to aar filenames so that they'll be included in
278+
// the Gradle build.
279+
if (!CopySrcAars(dependencies)) return false;
280+
281+
TextFileLineInjector[] injectors = new [] {
282+
new TextFileLineInjector(ReposInjectionLine, ReposStartLine, ReposEndLine,
283+
PlayServicesResolver.GradleMavenReposLines(dependencies),
284+
"Repos", fileDescription),
285+
new TextFileLineInjector(DependenciesToken, DependenciesStartLine,
286+
DependenciesEndLine,
287+
PlayServicesResolver.GradleDependenciesLines(
288+
dependencies, includeDependenciesBlock: false),
289+
"Dependencies", fileDescription)
290+
};
291+
// Lines that will be written to the output file.
292+
var outputLines = new List<string>();
293+
foreach (var line in lines) {
294+
var currentOutputLines = new List<string>();
295+
foreach (var injector in injectors) {
296+
bool injectionApplied = false;
297+
currentOutputLines = injector.ProcessLine(line, out injectionApplied);
298+
if (injectionApplied || currentOutputLines.Count == 0) break;
299+
}
300+
outputLines.AddRange(currentOutputLines);
301+
}
302+
PlayServicesResolver.Log(
303+
String.Format("Writing updated {0}", fileDescription),
304+
level: LogLevel.Verbose);
305+
try {
306+
File.WriteAllText(GradleTemplatePath,
307+
String.Join("\n", outputLines.ToArray()) + "\n");
308+
} catch (Exception ex) {
309+
PlayServicesResolver.Log(
310+
String.Format("Unable to patch {0} ({1})", fileDescription,
311+
ex.ToString()), level: LogLevel.Error);
312+
return false;
313+
}
314+
return true;
315+
}
316+
}
317+
}

0 commit comments

Comments
 (0)