Skip to content

Commit 77a6606

Browse files
authored
[Xamarin.Android.Build.Tasks] Error APT2265 for invalid Asset Path (#9809)
Context: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/2356711 Users regularly run into the following error. error APT2000: The system cannot find the specified file. (2): C:\Users\xxx\source\repos\AdventureWorks.MAUI\AdventureWorks.MAUI\obj\Debug\net9.0-android\assets One of the reasons behind this is [an `aapt2` bug][0] in which it cannot traverse directories containing non-ASCII characters. This has been a long standing bug that does not seem to be getting fixed any time soon. As we know why this issue occurs, lets at least raise the specific error code with instructions on how to fix it. [0]: https://issuetracker.google.com/issues/188679588
1 parent a4919b0 commit 77a6606

File tree

5 files changed

+55
-6
lines changed

5 files changed

+55
-6
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ namespace Xamarin.Android.Tasks {
2222
public abstract class Aapt2 : AsyncTask {
2323

2424
private const int MAX_PATH = 260;
25-
private const int ASCII_MAX_CHAR = 127;
2625
private static readonly int DefaultMaxAapt2Daemons = 6;
2726
protected Dictionary<string, string> _resource_name_case_map;
2827

@@ -52,6 +51,17 @@ public abstract class Aapt2 : AsyncTask {
5251
public static bool IsInvalidFilename (string path) =>
5352
Path.GetFileName (path).StartsWith (".", StringComparison.Ordinal);
5453

54+
/// <summary>
55+
/// Returns <see langword="true"/> if <paramref name="c"/> is an ASCII
56+
/// character ([ U+0000..U+007F ]).
57+
/// </summary>
58+
/// <remarks>
59+
/// Per http://www.unicode.org/glossary/#ASCII, ASCII is only U+0000..U+007F.
60+
/// We cannot use Char.IsAscii cos we are .netstandard2.0
61+
/// Source https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Private.CoreLib/src/System/Char.cs#L91
62+
/// </remarks>
63+
public static bool IsAscii(char c) => (uint)c <= '\x007f';
64+
5565
protected string ResourceDirectoryFullPath (string resourceDirectory)
5666
{
5767
return (Path.IsPathRooted (resourceDirectory) ? resourceDirectory : Path.Combine (WorkingDirectory, resourceDirectory)).TrimEnd ('\\');
@@ -226,13 +236,13 @@ static bool IsFilePathToLong (string filePath)
226236
return false;
227237
}
228238

229-
static bool IsPathOnlyASCII (string filePath)
239+
static protected bool IsPathOnlyASCII (string filePath)
230240
{
231241
if (!OS.IsWindows)
232242
return true;
233243

234244
foreach (var c in filePath)
235-
if (c > ASCII_MAX_CHAR) // cannot use Char.IsAscii cos we are .netstandard2.0
245+
if (!IsAscii (c))
236246
return false;
237247
return true;
238248
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,16 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s
272272
}
273273
}
274274

275+
var hasAssetsErrors = false;
275276
// When adding Assets the first item found takes precedence.
276-
// So we need to add the applicaiton Assets first.
277+
// So we need to add the application Assets first.
277278
if (!string.IsNullOrEmpty (AssetsDirectory)) {
278279
var assetDir = GetFullPath (AssetsDirectory.TrimEnd ('\\'));
279280
if (Directory.Exists (assetDir)) {
281+
if (OS.IsWindows && !IsPathOnlyASCII (assetDir)) {
282+
hasAssetsErrors = true;
283+
LogCodedError ("APT2265", Properties.Resources.APT2265, assetDir);
284+
}
280285
cmd.Add ("-A");
281286
cmd.Add (assetDir);
282287
} else {
@@ -289,6 +294,11 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s
289294
var assetDir = GetFullPath (AdditionalAndroidAssetPaths [i].ItemSpec.TrimEnd ('\\'));
290295
if (!string.IsNullOrWhiteSpace (assetDir)) {
291296
if (Directory.Exists (assetDir)) {
297+
if (OS.IsWindows && !IsPathOnlyASCII (assetDir)) {
298+
hasAssetsErrors = true;
299+
LogCodedError ("APT2265", Properties.Resources.APT2265, assetDir);
300+
continue;
301+
}
292302
cmd.Add ("-A");
293303
cmd.Add (GetFullPath (assetDir));
294304
} else {
@@ -298,6 +308,10 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s
298308
}
299309
}
300310

311+
if (hasAssetsErrors) {
312+
return Array.Empty<string> ();
313+
}
314+
301315
if (!string.IsNullOrEmpty (ProguardRuleOutput)) {
302316
cmd.Add ("--proguard");
303317
cmd.Add (GetFullPath (GetManifestRulesFile (manifestDir)));

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,13 @@ protected string[] GenerateCommandLineCommands (ITaskItem manifest, ITaskItem ou
6363
cmd.Add ("--custom-package");
6464
cmd.Add (PackageName);
6565
foreach (var assetDirectory in AssetDirectories) {
66+
var fullPath = GetFullPath (assetDirectory.ItemSpec);
67+
if (OS.IsWindows && !IsPathOnlyASCII (fullPath)) {
68+
LogCodedError ("APT2265", Properties.Resources.APT2265, fullPath);
69+
continue;
70+
}
6671
cmd.Add ("-A");
67-
cmd.Add (GetFullPath (assetDirectory.ItemSpec));
72+
cmd.Add (fullPath);
6873
}
6974
cmd.Add ($"-o");
7075
cmd.Add (GetFullPath (output.ItemSpec));

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,26 @@ public void CheckAssetsAreIncludedInAPK ()
133133
}
134134
}
135135

136+
[Test]
137+
public void InvalidAssetDirectoryWithNonASCIIChars ()
138+
{
139+
var proj = new XamarinAndroidApplicationProject () {
140+
ProjectName = "App1",
141+
IsRelease = true,
142+
OtherBuildItems = {
143+
new AndroidItem.AndroidAsset ("Assets\\asset1.txt") {
144+
TextContent = () => "Asset1",
145+
Encoding = Encoding.ASCII,
146+
},
147+
},
148+
};
149+
using (var b = CreateApkBuilder (Path.Combine ("temp", "InvalidAssetDirectoryWithNonASCIIChars_Ümläüt", proj.ProjectName))) {
150+
b.ThrowOnBuildFailure = false;
151+
Assert.AreEqual (!IsWindows, b.Build (proj), $"{proj.ProjectName} should {(IsWindows ? "not " : "")}have built successfully.");
152+
Assert.AreEqual (IsWindows, b.LastBuildOutput.ContainsText ("APT2265"), $"APT2265 should {(IsWindows ? "" : "not ")}have been raised.");
153+
}
154+
}
155+
136156
[Test]
137157
public void FullPath ()
138158
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ public void BuildApplicationWithSpacesInPath ([Values (true, false)] bool enable
659659
IsRelease = true,
660660
AotAssemblies = true,
661661
LinkTool = linkTool,
662-
References = { new BuildItem ("ProjectReference", $"..\\{folderName}Library1\\Library1.csproj") }
662+
References = { new BuildItem ("ProjectReference", $"..\\{folderName}Library1\\Library1.csproj") },
663663
};
664664
proj.OtherBuildItems.Add (new BuildItem ("AndroidJavaLibrary", "Hello (World).jar") { BinaryContent = () => Convert.FromBase64String (@"
665665
UEsDBBQACAgIAMl8lUsAAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAA

0 commit comments

Comments
 (0)