diff --git a/Documentation/guides/messages/README.md b/Documentation/guides/messages/README.md
index 6a4feb91b2e..ca0a392aa25 100644
--- a/Documentation/guides/messages/README.md
+++ b/Documentation/guides/messages/README.md
@@ -31,6 +31,7 @@ ms.date: 01/24/2020
+ APT0003: Invalid file name: It must contain only \[^a-zA-Z0-9_.\]+.
+ APT0004: Invalid file name: It must start with either A-Z or a-z or an underscore.
+ [APT2264](apt2264.md): The system cannot find the file specified. (2).
++ [APT2265](apt2265.md): The system cannot find the file specified. (2).
## JAVAxxxx: Java Tool
diff --git a/Documentation/guides/messages/apt2264.md b/Documentation/guides/messages/apt2264.md
index d058a29a74f..2ed5678f511 100644
--- a/Documentation/guides/messages/apt2264.md
+++ b/Documentation/guides/messages/apt2264.md
@@ -14,22 +14,21 @@ length allowed on windows.
## Solution
The best way to avoid this is to ensure that your project is not located
-deep in the folder structure. For example if you create all of your
-projects in folders such as
+deep in the folder structure.
+For example if you create all of your projects in folders such as
-`C:\Users\shelly\Visual Studio\Android\MyProjects\Com.SomeReallyLongCompanyName.MyBrillantApplication\MyBrilliantApplicaiton.Android\`
+`C:\Users\shelly\Visual Studio 2022\Android\MyProjects\Com.SomeReallyLongCompanyName.MyBrillantApplication\MyBrilliantApplicaiton.Android\`
you may well encounter problems with not only `aapt2` but also Ahead of Time
-compilation. Keeping your project names and folder structures short and
-concise will help work around these issues. For example instead of the above
-you could use
+compilation. Keeping your project names and folder structures short, concise
+will help work around these issues. For example instead of the above you could use
`C:\Work\Android\MyBrilliantApp`
Which is much shorter and much less likely to encounter path issues.
-However this is no always possible. Sometimes a project or a environment requires
-deep folder structures. In this case enabling long path support in Windows *might*
+However this is not always possible. Sometimes a project or a environment requires
+deep folder structures. Enabling long path support in Windows *might*
be enough to get your project working. Details on how to do this can be found
[here](https://learn.microsoft.com/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later).
diff --git a/Documentation/guides/messages/apt2265.md b/Documentation/guides/messages/apt2265.md
new file mode 100644
index 00000000000..1a404d40a09
--- /dev/null
+++ b/Documentation/guides/messages/apt2265.md
@@ -0,0 +1,29 @@
+---
+title: Xamarin.Android error APT2265
+description: APT2265 error code
+ms.date: 12/16/2022
+---
+# Xamarin.Android error APT2265
+
+## Issue
+
+The tool `aapt2` is unable to resolve one of the files it was passed.
+This is generally caused by non-ASCII characters in the project name
+or the path to the project.
+
+## Solution
+
+The best way to avoid this is to ensure that your project does not contain
+non-ASCII characters.
+For example if you create all of your projects in folders such as
+
+`C:\Users\shëlly\Visual Studio 2022\Android\MyProjects\Com.SomeReallyLongCompanyName.MyBrillantApplication\MyBrilliantApplicaiton.Android\`
+
+you may well encounter problems with not only `aapt2` but also Ahead of Time
+compilation. Keeping your project names and folder structures short, concise and
+ASCII will help work around these issues. For example instead of the above
+you could use
+
+`C:\Work\Android\MyBrilliantApp`
+
+Which is much shorter, contains no non-ASCII characters and much less likely to encounter issues.
diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs
index 7fe1bf1c1ae..4f8e7e69711 100644
--- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs
+++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs
@@ -818,6 +818,15 @@ public static string APT2264 {
}
}
+ ///
+ /// Looks up a localized string similar to This could be caused by the project having non-ASCII characters in it path.
+ ///
+ public static string APT2265 {
+ get {
+ return ResourceManager.GetString("APT2265", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Could not AOT the assembly: {0}.
///
diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
index dfb964b4851..c591e39618f 100644
--- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
+++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
@@ -542,7 +542,11 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
{0} - The assembly name
- This is probably caused by the project exceeding the Windows maximum path length limitation. See https://learn.microsoft.com/xamarin/android/errors-and-warnings/apt2264 for details.
+ This could be caused by the project exceeding the Windows maximum path length limitation. See https://learn.microsoft.com/xamarin/android/errors-and-warnings/apt2264 for details.
+ The following are literal names and should not be translated:
+
+
+ This could be caused by the project having non-ASCII characters in its filename or path. See https://learn.microsoft.com/xamarin/android/errors-and-warnings/apt2265 for details.
The following are literal names and should not be translated:
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs
index e411b66e61e..1c51cb5980e 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs
@@ -22,6 +22,8 @@ namespace Xamarin.Android.Tasks {
public abstract class Aapt2 : AndroidAsyncTask {
+ private const int MAX_PATH = 260;
+ private const int ASCII_MAX_CHAR = 127;
private static readonly int DefaultMaxAapt2Daemons = 6;
protected Dictionary _resource_name_case_map;
@@ -117,9 +119,10 @@ protected bool LogAapt2EventsFromOutput (string singleLine, MessageImportance me
return true;
var match = AndroidRunToolTask.AndroidErrorRegex.Match (singleLine.Trim ());
+ string file = string.Empty;
if (match.Success) {
- var file = match.Groups ["file"].Value;
+ file = match.Groups ["file"].Value;
int line = 0;
if (!string.IsNullOrEmpty (match.Groups ["line"]?.Value))
line = int.Parse (match.Groups ["line"].Value.Trim ()) + 1;
@@ -138,22 +141,8 @@ protected bool LogAapt2EventsFromOutput (string singleLine, MessageImportance me
LogCodedError ("APT0001", Properties.Resources.APT0001, message.Substring ("unknown option '".Length).TrimEnd ('.', '\''));
return false;
}
- if (message.Contains ("in APK") && message.Contains ("is compressed.")) {
- LogMessage (singleLine, messageImportance);
+ if (LogNotesOrWarnings (message, singleLine, messageImportance))
return true;
- }
- if (message.Contains ("fakeLogOpen")) {
- LogMessage (singleLine, messageImportance);
- return true;
- }
- if (message.Contains ("note:")) {
- LogMessage (singleLine, messageImportance);
- return true;
- }
- if (message.Contains ("warn:")) {
- LogCodedWarning (GetErrorCode (singleLine), singleLine);
- return true;
- }
if (level.Contains ("note")) {
LogMessage (message, messageImportance);
return true;
@@ -188,7 +177,7 @@ protected bool LogAapt2EventsFromOutput (string singleLine, MessageImportance me
message = message.Substring ("error: ".Length);
if (level.Contains ("error") || (line != 0 && !string.IsNullOrEmpty (file))) {
- var errorCode = GetErrorCode (message);
+ var errorCode = GetErrorCodeForFile (message, file);
if (manifestError)
LogCodedError (errorCode, string.Format (Xamarin.Android.Tasks.Properties.Resources.AAPTManifestError, message.TrimEnd('.')), AndroidManifestFile.ItemSpec, 0);
else
@@ -199,7 +188,9 @@ protected bool LogAapt2EventsFromOutput (string singleLine, MessageImportance me
if (!apptResult) {
var message = string.Format ("{0} \"{1}\".", singleLine.Trim (), singleLine.Substring (singleLine.LastIndexOfAny (new char [] { '\\', '/' }) + 1));
- var errorCode = GetErrorCode (message);
+ if (LogNotesOrWarnings (message, singleLine, messageImportance))
+ return true;
+ var errorCode = GetErrorCodeForFile (message, file);
LogCodedError (errorCode, AddAdditionalErrorText (errorCode, message), ToolName);
} else {
LogCodedWarning (GetErrorCode (singleLine), singleLine);
@@ -207,6 +198,46 @@ protected bool LogAapt2EventsFromOutput (string singleLine, MessageImportance me
return true;
}
+ bool LogNotesOrWarnings (string message, string singleLine, MessageImportance messageImportance)
+ {
+ if (message.Contains ("in APK") && message.Contains ("is compressed.")) {
+ LogMessage (singleLine, messageImportance);
+ return true;
+ }
+ else if (message.Contains ("fakeLogOpen")) {
+ LogMessage (singleLine, messageImportance);
+ return true;
+ }
+ else if (message.Contains ("note:")) {
+ LogMessage (singleLine, messageImportance);
+ return true;
+ }
+ else if (message.Contains ("warn:")) {
+ LogCodedWarning (GetErrorCode (singleLine), singleLine);
+ return true;
+ }
+ return false;
+ }
+
+ static bool IsFilePathToLong (string filePath)
+ {
+ if (OS.IsWindows && filePath.Length > MAX_PATH) {
+ return true;
+ }
+ return false;
+ }
+
+ static bool IsPathOnlyASCII (string filePath)
+ {
+ if (!OS.IsWindows)
+ return true;
+
+ foreach (var c in filePath)
+ if (c > ASCII_MAX_CHAR) // cannot use Char.IsAscii cos we are .netstandard2.0
+ return false;
+ return true;
+ }
+
static string AddAdditionalErrorText (string errorCode, string message)
{
var sb = new StringBuilder ();
@@ -216,10 +247,26 @@ static string AddAdditionalErrorText (string errorCode, string message)
case "APT2264":
sb.AppendLine (Xamarin.Android.Tasks.Properties.Resources.APT2264);
break;
+ case "APT2265":
+ sb.AppendLine (Xamarin.Android.Tasks.Properties.Resources.APT2265);
+ break;
}
return sb.ToString ();
}
+ static string GetErrorCodeForFile (string message, string filePath)
+ {
+ var errorCode = GetErrorCode (message);
+ switch (errorCode)
+ {
+ case "APT2265":
+ if (IsPathOnlyASCII (filePath) && IsFilePathToLong (filePath))
+ errorCode = "APT2264";
+ break;
+ }
+ return errorCode;
+ }
+
static string GetErrorCode (string message)
{
foreach (var tuple in error_codes)
@@ -493,7 +540,8 @@ static string GetErrorCode (string message)
Tuple.Create ("APT2261", "file failed to compile"),
Tuple.Create ("APT2262", "unexpected element found in "),
Tuple.Create ("APT2263", "found in "), // unexpected element found in
- Tuple.Create ("APT2264", "The system cannot find the file specified. (2)") // Windows Long Path error from aapt2
+ Tuple.Create ("APT2264", "The system cannot find the file specified. (2)"), // Windows Long Path error from aapt2
+ Tuple.Create ("APT2265", "The system cannot find the file specified. (2)") // Windows non-ASCII characters error from aapt2
};
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs
index 20d2a2282f5..c207d49e7e8 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs
@@ -112,6 +112,38 @@ public void BuildBasicApplicationReleaseProfiledAotWithoutDefaultProfile ()
StringAssertEx.DoesNotContainRegex (@$"Using profile data file.*{filename}\.aotprofile", b.LastBuildOutput, "Should not use default AOT profile", RegexOptions.IgnoreCase);
}
+ [Test]
+ [TestCase ("テスト", false, false, true)]
+ [TestCase ("テスト", true, true, false)]
+ [TestCase ("テスト", true, false, true)]
+ [TestCase ("随机生成器", false, false, true)]
+ [TestCase ("随机生成器", true, true, false)]
+ [TestCase ("随机生成器", true, false, true)]
+ [TestCase ("中国", false, false, true)]
+ [TestCase ("中国", true, true, false)]
+ [TestCase ("中国", true, false, true)]
+ public void BuildAotApplicationWithSpecialCharactersInProject (string testName, bool isRelease, bool aot, bool expectedResult)
+ {
+ if (!IsWindows)
+ expectedResult = true;
+ var rootPath = Path.Combine (Root, "temp", TestName);
+ var proj = new XamarinAndroidApplicationProject () {
+ ProjectName = testName,
+ IsRelease = isRelease,
+ AotAssemblies = aot,
+ };
+ proj.SetAndroidSupportedAbis ("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
+ using (var builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName))){
+ builder.ThrowOnBuildFailure = false;
+ Assert.AreEqual (expectedResult, builder.Build (proj), "Build should have succeeded.");
+ if (!expectedResult) {
+ var aotFailed = builder.LastBuildOutput.ContainsText ("Precompiling failed");
+ var aapt2Failed = builder.LastBuildOutput.ContainsText ("APT2265");
+ Assert.IsTrue (aotFailed || aapt2Failed, "Error APT2265 or an AOT error should have been raised.");
+ }
+ }
+ }
+
static object [] AotChecks () => new object [] {
new object[] {
/* supportedAbis */ "arm64-v8a",
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs
index 0184ef1d425..825e77f6735 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/DeviceTest.cs
@@ -26,6 +26,11 @@ public class DeviceTest: BaseTest
protected static bool IsDeviceAttached (bool refreshCachedValue = false)
{
if (string.IsNullOrEmpty (_shellEchoOutput) || refreshCachedValue) {
+ // run this twice as sometimes the first time returns the
+ // device as "offline".
+ RunAdbCommand ("devices");
+ var devices = RunAdbCommand ("devices");
+ TestContext.Out.WriteLine ($"LOG adb devices: {devices}");
_shellEchoOutput = RunAdbCommand ("shell echo OK", timeout: 15);
}
return _shellEchoOutput.Contains ("OK");
@@ -58,11 +63,14 @@ public void DeviceSetup ()
DeviceAbi = RunAdbCommand ("shell getprop ro.product.cpu.abilist64").Trim ();
if (string.IsNullOrEmpty (DeviceAbi))
- DeviceAbi = RunAdbCommand ("shell getprop ro.product.cpu.abi") ?? RunAdbCommand ("shell getprop ro.product.cpu.abi2");
+ DeviceAbi = (RunAdbCommand ("shell getprop ro.product.cpu.abi") ?? RunAdbCommand ("shell getprop ro.product.cpu.abi2")) ?? "x86_64";
if (DeviceAbi.Contains (",")) {
DeviceAbi = DeviceAbi.Split (',')[0];
}
+ } else {
+ TestContext.Out.WriteLine ($"LOG GetSdkVersion: {DeviceSdkVersion}");
+ DeviceAbi = "x86_64";
}
} catch (Exception ex) {
Console.Error.WriteLine ("Failed to determine whether there is Android target emulator or not: " + ex);
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index 1672b36b7cb..a6e903cc6dd 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -167,10 +167,11 @@ void Reset ()
[Test]
[Category ("UsesDevice")]
- public void SmokeTestBuildAndRunWithSpecialCharacters ()
+ [TestCase ("テスト")]
+ [TestCase ("随机生成器")]
+ [TestCase ("中国")]
+ public void SmokeTestBuildAndRunWithSpecialCharacters (string testName)
{
- var testName = "テスト";
-
var rootPath = Path.Combine (Root, "temp", TestName);
var proj = new XamarinFormsAndroidApplicationProject () {
ProjectName = testName,