Skip to content

Commit

Permalink
MAUI project with non-ASCII project name cannot release to my Android…
Browse files Browse the repository at this point in the history
… phone

Context https://i.azdo.io/1714603
Context https://issuetracker.google.com/issues/188679588

There is a known issue with `aapt2`, `AndroidAsset` and non-ASCII paths/project names.
`aapt2` has a bug where it cannot correctly traverse a directory which contains non-ASCII
characters. We curently have no way to work around this issue.

So the PR adds some more detail to the specific error message which `aapt2` raises in
these cases. It suggests the user check their paths to make sure it does not contain
non-ASCII characters.

We also add some unit tests to cover this senario.
  • Loading branch information
dellis1972 committed Sep 6, 2023
1 parent dda99cd commit 06b658f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 28 deletions.
18 changes: 10 additions & 8 deletions Documentation/guides/messages/apt2264.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,29 @@ ms.date: 12/16/2022

The tool `aapt2` is unable to resolve one of the files it was passed.
This is generally caused by the path being longer than the Maximum Path
length allowed on windows.
length allowed on windows. Alternately it might be due to 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 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 and does not contain non-ASCII characters.
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\shëlly\Visual Studio\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
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 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. For non-ASCII paths there is no work around.
In the long path case, 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).

Expand Down
2 changes: 1 addition & 1 deletion src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
{0} - The assembly name</comment>
</data>
<data name="APT2264" xml:space="preserve">
<value>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.</value>
<value>This is probably caused by the project having non-ASCII characters in it path or exceeding the Windows maximum path length limitation. See https://learn.microsoft.com/xamarin/android/errors-and-warnings/apt2264 for details.</value>
<comment>The following are literal names and should not be translated:</comment>
</data>
<data name="XA3001" xml:space="preserve">
Expand Down
39 changes: 24 additions & 15 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,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;
Expand Down Expand Up @@ -199,6 +185,8 @@ protected bool LogAapt2EventsFromOutput (string singleLine, MessageImportance me

if (!apptResult) {
var message = string.Format ("{0} \"{1}\".", singleLine.Trim (), singleLine.Substring (singleLine.LastIndexOfAny (new char [] { '\\', '/' }) + 1));
if (LogNotesOrWarnings (message, singleLine, messageImportance))
return true;
var errorCode = GetErrorCode (message);
LogCodedError (errorCode, AddAdditionalErrorText (errorCode, message), ToolName);
} else {
Expand All @@ -207,6 +195,27 @@ 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;
}
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;
}
return false;
}

static string AddAdditionalErrorText (string errorCode, string message)
{
var sb = new StringBuilder ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,33 @@ 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.");
}
}

static object [] AotChecks () => new object [] {
new object[] {
/* supportedAbis */ "arm64-v8a",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 06b658f

Please sign in to comment.