Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAUI project with non-ASCII project name cannot release to my Android phone #7710

Merged
merged 8 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/guides/messages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 7 additions & 8 deletions Documentation/guides/messages/apt2264.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
29 changes: 29 additions & 0 deletions Documentation/guides/messages/apt2265.md
Original file line number Diff line number Diff line change
@@ -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.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 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,11 @@ 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 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.</value>
<comment>The following are literal names and should not be translated:</comment>
</data>
<data name="APT2265" xml:space="preserve">
<value>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.</value>
<comment>The following are literal names and should not be translated:</comment>
</data>
<data name="XA3001" xml:space="preserve">
Expand Down
86 changes: 67 additions & 19 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> _resource_name_case_map;

Expand Down Expand Up @@ -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;
Expand All @@ -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))
dellis1972 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -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
Expand All @@ -199,14 +188,56 @@ 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);
}
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 ();
Expand All @@ -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)
Expand Down Expand Up @@ -493,7 +540,8 @@ static string GetErrorCode (string message)
Tuple.Create ("APT2261", "file failed to compile"),
Tuple.Create ("APT2262", "unexpected element <activity> found in <manifest>"),
Tuple.Create ("APT2263", "found in <manifest>"), // unexpected element <xxxxx> found in <manifest>
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
};
}
}
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.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also verify the build error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
}

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
Loading