Skip to content

Commit 51f56aa

Browse files
[Xamarin.Android.Build.Tasks] audit WorkingDirectory usage in <Aapt2Link/> (dotnet#5107)
@mattleibow was hitting an error building a `.sln` file using `msbuild /m` on Windows: > msbuild /m … obj\Release\100\android\manifest\AndroidManifest.xml(7,0): error APT2260: resource mipmap/ic_launcher (aka com.companyname.skiasharpsample:mipmap/ic_launcher) not found. obj\Release\100\android\manifest\AndroidManifest.xml(7,0): error APT2260: resource string/app_name (aka com.companyname.skiasharpsample:string/app_name) not found. obj\Release\100\android\manifest\AndroidManifest.xml(7,0): error APT2260: resource mipmap/ic_launcher_round (aka com.companyname.skiasharpsample:mipmap/ic_launcher_round) not found. obj\Release\100\android\manifest\AndroidManifest.xml(9,0): error APT2260: resource style/MainTheme.Splash (aka com.companyname.skiasharpsample:style/MainTheme.Splash) not found. Xamarin.Android.Aapt2.targets(226,3): error APT2067: failed processing manifest. Reviewing the code and the `.binlog`, it seemed like the `File.Exists()` check within `<Aapt2Link/>` was returning `false`. This failure only happened on Windows, and it only failed *sometimes*. This is the classic problem when: 1. MSBuild is building a solution on multiple nodes. 2. A background thread uses a relative path. 3. The value of the current working directory sometimes changes. I went through and audited all the paths in the `<Aapt2Link/>` MSBuild task. I made sure they all explicitly use full paths and log if any `File.Exist()` or `Directory.Exists()` checks return `false`. I added a new test for this scenario. This test won't fully reproduce the problem when using `xabuild`, as it crashes with: MSBUILD : error MSB1025: An internal failure occurred while running MSBuild. Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: Node 6 does not have a provider. at Microsoft.Build.Shared.ErrorUtilities.ThrowInternalError(String message, Object[] args) at Microsoft.Build.BackEnd.NodeManager.SendData(Int32 node, INodePacket packet) at Microsoft.Build.Execution.BuildManager.PerformSchedulingActions(IEnumerable`1 responses) at Microsoft.Build.Execution.BuildManager.PerformSchedulingActions(IEnumerable`1 responses) at Microsoft.Build.Execution.BuildManager.HandleNewRequest(Int32 node, BuildRequestBlocker blocker) at Microsoft.Build.Execution.BuildManager.ProcessPacket(Int32 node, INodePacket packet) at Microsoft.Build.Execution.BuildManager.<>c__DisplayClass70_0.<Microsoft.Build.BackEnd.INodePacketHandler.PacketReceived>b__0() at Microsoft.Build.Execution.BuildManager.ProcessWorkQueue(Action action) --- End of stack trace from previous location where exception was thrown --- at Microsoft.Build.Execution.BuildManager.EndBuild() at Microsoft.Build.CommandLine.MSBuildApp.BuildProject(String projectFile, String[] targets, String toolsVersion, Dictionary`2 globalProperties, Dictionary`2 restoreProperties, ILogger[] loggers, LoggerVerbosity verbosity, DistributedLoggerRecord[] distributedLoggerRecords, Boolean needToValidateProject, String schemaFile, Int32 cpuCount, Boolean enableNodeReuse, TextWriter preprocessWriter, TextWriter targetsWriter, Boolean detailedSummary, ISet`1 warningsAsErrors, ISet`1 warningsAsMessages, Boolean enableRestore, ProfilerLogger profilerLogger, Boolean enableProfiler, Boolean interactive, Boolean isolateProjects, Boolean graphBuild, Boolean lowPriority, String[] inputResultsCaches, String outputResultsCache) On our CI the test should be valid, however; it uses a system install of Xamarin.Android. [0]: https://github.com/xamarin/xamarin-android/blob/3a067cc4b1706c8b6413e96598ae317c8f73c2e5/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs#L223
1 parent 39526e3 commit 51f56aa

File tree

5 files changed

+119
-22
lines changed

5 files changed

+119
-22
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#### Application and library build and deployment
2+
3+
* [Github PR 5107](https://github.com/xamarin/xamarin-android/pull/5107):
4+
Starting in Xamarin.Android 11.0, building
5+
solutions in parallel with `msbuild YourSolution.sln -m` could
6+
fail with errors such as:
7+
8+
```
9+
obj\Release\100\android\manifest\AndroidManifest.xml(7,0): error APT2260: resource mipmap/ic_launcher (aka com.companyname.skiasharpsample:mipmap/ic_launcher) not found.
10+
obj\Release\100\android\manifest\AndroidManifest.xml(7,0): error APT2260: resource string/app_name (aka com.companyname.skiasharpsample:string/app_name) not found.
11+
obj\Release\100\android\manifest\AndroidManifest.xml(7,0): error APT2260: resource mipmap/ic_launcher_round (aka com.companyname.skiasharpsample:mipmap/ic_launcher_round) not found.
12+
obj\Release\100\android\manifest\AndroidManifest.xml(9,0): error APT2260: resource style/MainTheme.Splash (aka com.companyname.skiasharpsample:style/MainTheme.Splash) not found.
13+
Xamarin.Android.Aapt2.targets(226,3): error APT2067: failed processing manifest.
14+
```

src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -180,52 +180,53 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s
180180

181181
if (AdditionalResourceArchives != null) {
182182
for (int i = AdditionalResourceArchives.Length - 1; i >= 0; i--) {
183-
var flata = Path.Combine (WorkingDirectory, AdditionalResourceArchives [i].ItemSpec);
183+
var flata = GetFullPath (AdditionalResourceArchives [i].ItemSpec);
184184
if (Directory.Exists (flata)) {
185185
foreach (var line in Directory.EnumerateFiles (flata, "*.flat", SearchOption.TopDirectoryOnly)) {
186186
cmd.Add ("-R");
187187
cmd.Add (GetFullPath (line));
188188
}
189189
} else if (File.Exists (flata)) {
190190
cmd.Add ("-R");
191-
cmd.Add (GetFullPath (flata));
191+
cmd.Add (flata);
192192
} else {
193-
LogDebugMessage ("Archive does not exist: " + flata);
193+
LogDebugMessage ($"Archive does not exist: {flata}");
194194
}
195195
}
196196
}
197197

198198
if (CompiledResourceFlatArchive != null) {
199-
var flata = Path.Combine (WorkingDirectory, CompiledResourceFlatArchive.ItemSpec);
199+
var flata = GetFullPath (CompiledResourceFlatArchive.ItemSpec);
200200
if (Directory.Exists (flata)) {
201201
foreach (var line in Directory.EnumerateFiles (flata, "*.flat", SearchOption.TopDirectoryOnly)) {
202202
cmd.Add ("-R");
203203
cmd.Add (GetFullPath (line));
204204
}
205205
} else if (File.Exists (flata)) {
206-
cmd.Add ("-R");
207-
cmd.Add (GetFullPath (flata));
206+
cmd.Add ("-R");
207+
cmd.Add (flata);
208208
} else {
209-
LogDebugMessage ("Archive does not exist: " + flata);
209+
LogDebugMessage ($"Archive does not exist: {flata}");
210210
}
211211
}
212212

213213
if (CompiledResourceFlatFiles != null) {
214-
List<ITaskItem> appFiles = new List<ITaskItem> ();
214+
var appFiles = new List<string> ();
215215
for (int i = CompiledResourceFlatFiles.Length - 1; i >= 0; i--) {
216216
var file = CompiledResourceFlatFiles [i];
217-
if (!string.IsNullOrEmpty (file.GetMetadata ("ResourceDirectory")) && File.Exists (file.ItemSpec)) {
217+
var fullPath = GetFullPath (file.ItemSpec);
218+
if (!File.Exists (fullPath)) {
219+
LogDebugMessage ($"File does not exist: {fullPath}");
220+
} else if (!string.IsNullOrEmpty (file.GetMetadata ("ResourceDirectory"))) {
218221
cmd.Add ("-R");
219-
cmd.Add (GetFullPath (file.ItemSpec));
222+
cmd.Add (fullPath);
220223
} else {
221-
appFiles.Add(file);
224+
appFiles.Add (fullPath);
222225
}
223226
}
224-
foreach (var file in appFiles) {
225-
if (File.Exists (file.ItemSpec)) {
226-
cmd.Add ("-R");
227-
cmd.Add (GetFullPath (file.ItemSpec));
228-
}
227+
foreach (var fullPath in appFiles) {
228+
cmd.Add ("-R");
229+
cmd.Add (fullPath);
229230
}
230231
}
231232

@@ -267,13 +268,13 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s
267268

268269
// When adding Assets the first item found takes precedence.
269270
// So we need to add the applicaiton Assets first.
270-
if (!string.IsNullOrWhiteSpace (AssetsDirectory)) {
271-
var assetDir = AssetsDirectory.TrimEnd ('\\');
272-
if (!Path.IsPathRooted (assetDir))
273-
assetDir = Path.Combine (WorkingDirectory, assetDir);
274-
if (!string.IsNullOrWhiteSpace (assetDir) && Directory.Exists (assetDir)) {
271+
if (!string.IsNullOrEmpty (AssetsDirectory)) {
272+
var assetDir = GetFullPath (AssetsDirectory.TrimEnd ('\\'));
273+
if (Directory.Exists (assetDir)) {
275274
cmd.Add ("-A");
276-
cmd.Add (GetFullPath (assetDir));
275+
cmd.Add (assetDir);
276+
} else {
277+
LogDebugMessage ($"asset directory did not exist: {assetDir}");
277278
}
278279
}
279280

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,5 +1374,64 @@ BuildItem CreateItem (string include) =>
13741374
b.Output.AssertTargetIsSkipped ("_CompileResources");
13751375
}
13761376
}
1377+
1378+
[Test]
1379+
public void SolutionBuildSeveralProjects ()
1380+
{
1381+
const int libraryCount = 10;
1382+
var path = Path.Combine ("temp", TestName);
1383+
TestOutputDirectories [TestContext.CurrentContext.Test.ID] = Path.Combine (Root, path);
1384+
using (var sb = new SolutionBuilder ($"{TestName}.sln") {
1385+
SolutionPath = Path.Combine (Root, path),
1386+
MaxCpuCount = 4,
1387+
BuildingInsideVisualStudio = false, // allow projects dependencies to build
1388+
}) {
1389+
var apps = new List<XamarinAndroidApplicationProject> ();
1390+
var app1 = new XamarinAndroidApplicationProject {
1391+
ProjectName = "App1"
1392+
};
1393+
apps.Add (app1);
1394+
sb.Projects.Add (app1);
1395+
1396+
var app2 = new XamarinAndroidApplicationProject {
1397+
ProjectName = "App2"
1398+
};
1399+
apps.Add (app2);
1400+
sb.Projects.Add (app2);
1401+
1402+
for (var i = 0; i < libraryCount; i++) {
1403+
var index = i;
1404+
var lib = new XamarinAndroidLibraryProject {
1405+
ProjectName = $"Lib{i}",
1406+
AndroidResources = {
1407+
new AndroidItem.AndroidResource ($"Resources\\values\\library_name{index}.xml") {
1408+
TextContent = () =>
1409+
$@"<?xml version=""1.0"" encoding=""utf-8""?>
1410+
<resources>
1411+
<string name=""library_name{index}"">Lib{index}</string>
1412+
</resources>"
1413+
}
1414+
}
1415+
};
1416+
foreach (var app in apps) {
1417+
app.AddReference (lib);
1418+
}
1419+
sb.Projects.Add (lib);
1420+
}
1421+
1422+
// Add usage of Lib0.Resource.String.library_name0
1423+
var builder = new StringBuilder ();
1424+
for (int i = 0; i < libraryCount; i++) {
1425+
builder.AppendLine ($"int library_name{i} = Lib{i}.Resource.String.library_name{i};");
1426+
}
1427+
foreach (var app in apps) {
1428+
app.Sources.Add (new BuildItem.Source ("Foo.cs") {
1429+
TextContent = () => $"class Foo {{ void Bar () {{ {builder} }} }}",
1430+
});
1431+
}
1432+
1433+
Assert.IsTrue (sb.Build (), "Solution should have built.");
1434+
}
1435+
}
13771436
}
13781437
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public class Builder : IDisposable
2929
/// This passes /p:BuildingInsideVisualStudio=True, command-line to MSBuild
3030
/// </summary>
3131
public bool BuildingInsideVisualStudio { get; set; } = true;
32+
/// <summary>
33+
/// Passes /m:N to MSBuild, defaults to null to omit the /m parameter completely.
34+
/// </summary>
35+
public int? MaxCpuCount { get; set; }
3236
public LoggerVerbosity Verbosity { get; set; }
3337
public IEnumerable<string> LastBuildOutput {
3438
get {
@@ -281,6 +285,14 @@ protected bool BuildInternal (string projectOrSolution, string target, string []
281285
if (AutomaticNuGetRestore && restore && !UseDotNet) {
282286
args.Append (" /restore");
283287
}
288+
if (MaxCpuCount != null) {
289+
if (!string.Equals (Path.GetFileNameWithoutExtension (psi.FileName), "xabuild", StringComparison.OrdinalIgnoreCase)) {
290+
args.Append ($" /maxCpuCount:{MaxCpuCount}");
291+
args.Append (" /nodeReuse:false"); // Disable the MSBuild daemon
292+
} else {
293+
Console.WriteLine ($"Ignoring MaxCpuCount={MaxCpuCount}, running with xabuild.");
294+
}
295+
}
284296
args.Append ($" @\"{responseFile}\"");
285297
using (var sw = new StreamWriter (responseFile, append: false, encoding: Encoding.UTF8)) {
286298
sw.WriteLine ($" /p:BuildingInsideVisualStudio={BuildingInsideVisualStudio}");

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/XamarinProject.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ public XamarinProject (string debugConfigurationName = "Debug", string releaseCo
101101
}
102102
}
103103

104+
/// <summary>
105+
/// Adds a reference to another project. The optional include path uses a relative path and ProjectName if omitted.
106+
/// </summary>
107+
public void AddReference (XamarinProject other, string include = null)
108+
{
109+
if (string.IsNullOrEmpty (include)) {
110+
include = $"..\\{other.ProjectName}\\{other.ProjectName}.csproj";
111+
}
112+
References.Add (new BuildItem.ProjectReference (include, other.ProjectName, other.ProjectGuid));
113+
}
114+
104115
protected virtual bool SetExtraNuGetConfigSources => Builder.UseDotNet;
105116

106117
public string GetProperty (string name)

0 commit comments

Comments
 (0)