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

Handle aapt2 error "The system cannot find the file specified." slightly better #7644

Merged
merged 5 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -30,6 +30,7 @@ ms.date: 01/24/2020
+ APT0002: Invalid file name: It must contain only \[^a-zA-Z0-9_.-\]+.
+ 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).

## JAVAxxxx: Java Tool

Expand Down
58 changes: 58 additions & 0 deletions Documentation/guides/messages/apt2264.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Xamarin.Android error APT2264
description: APT2264 error code
ms.date: 12/16/2022
---
# Xamarin.Android error APT2264

## Issue

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.

## Solution
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

As per the docs, two things must be done: the registry must be updated to set HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled -- and I wonder if that could/would break anything -- and secondly:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
        <ws2:longPathAware>true</ws2:longPathAware>
    </windowsSettings>
</application>

We do not currently ship an aapt2.exe.config manifest file.

Additionally, it may be moot; the next section in docs states:

These are the directory management functions that no longer have MAX_PATH restrictions if you opt-in to long path behavior: CreateDirectoryW, CreateDirectoryExW GetCurrentDirectoryW RemoveDirectoryW SetCurrentDirectoryW.

These are the file management functions that no longer have MAX_PATH restrictions if you opt-in to long path behavior: CopyFileW, CopyFile2, CopyFileExW, CreateFileW, CreateFile2, CreateHardLinkW, CreateSymbolicLinkW, DeleteFileW, FindFirstFileW, FindFirstFileExW, FindNextFileW, GetFileAttributesW, GetFileAttributesExW, SetFileAttributesW, GetFullPathNameW, GetLongPathNameW, MoveFileW, MoveFileExW, MoveFileWithProgressW, ReplaceFileW, SearchPathW, FindFirstFileNameW, FindNextFileNameW, FindFirstStreamW, FindNextStreamW, GetCompressedFileSizeW, GetFinalPathNameByHandleW.

Are these used?

C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Xamarin\Android> dumpbin /imports  aapt2.exe  | findstr File
File Type: EXECUTABLE IMAGE
                   8C CreateFileMappingA
                   90 CreateFileMappingW
                  1E9 GetFileAttributesA
                  1EE GetFileAttributesW
                  1F0 GetFileInformationByHandle
                  1F5 GetFileSizeEx
                  1F7 GetFileType
                  216 GetModuleFileNameA
                  27E GetSystemTimeAsFileTime
                  362 MapViewOfFile
                  3CF ReadFile
                  43B SetEndOfFile
                  44D SetFilePointer
                  4BD UnmapViewOfFile

Odd is GetFileAttributesA, which presumably has MAX_PATH limits, but GetFileAttributesW is also used, so when is GetFileAttributesA used vs. GetFileAttributesW? ¯\(ツ)/¯.

GetModuleFileNameA is also concerning, so it would not surprise me if aapt2.exe didn't work with long filenames even if we set the registry entry and provided an appropriate aapt2.exe.config manifest.

This requires testing, but I suspect that there's no point in suggesting the enabling of long paths, as aapt2 won't work with them.

Copy link
Contributor

Choose a reason for hiding this comment

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

aapt2 uses this function to convert UTF-8 file names to Windows Unicode

The android base library also has wrappers around the standard C I/O functions which add support for UTF-8 strings.

Copy link
Member

Choose a reason for hiding this comment

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

This strongly implies that if we provide an appropriate aapt2.exe.config, we can get long file name support if/when the registry setting is enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So shall I add the aapt2.exe.config to this PR or do a new one for that specific file?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should just check if you need it? My memory in the past, I just changed the registry key and everything worked.

But I don't remember if I was hitting an issue with aapt2 or a .NET process (which would probably opt into long paths).

Copy link
Member

Choose a reason for hiding this comment

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

@dellis1972: if this PR were "green" and otherwise ready to merge, I think aapt2.exe.config could be done as a separate PR. Given that this PR isn't green and ready to merge, might as well test locally and see if that works?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting, VS 2022 Preview wont even open a project/solution if the full path is greater than 260 characters even with LongPath enabled... you get a horrible dialog box.

Building with slightly fewer that 260 characters I get this build error

Error | MSB3491 | Could not write lines to file "obj\Debug\net7.0-android\android-arm64\AndroidApp1.GlobalUsings.g.cs". Could not find a part of the path 'C:\Users\dean\Documents\Visual Studio 2022\Some Projects which will have a very very very long file path because we need it to\some other folder which will put this over the limit becuase\App1\AndroidApp1\AndroidApp1\obj\Debug\net7.0-android\android-arm64\AndroidApp1.GlobalUsings.g.cs'. | AndroidApp1 | C:\Program Files\dotnet\sdk\7.0.101\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.GenerateGlobalUsings.targets

Copy link
Member

Choose a reason for hiding this comment

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

I think you might have to test an "almost long path", where only aapt2 would get long path errors?

Copy link
Contributor Author

@dellis1972 dellis1972 Jan 19, 2023

Choose a reason for hiding this comment

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

So I did some more testing and it seems that aapt2 was able to switch to log paths with only the registry setting change (not aapt2.exe.config file).

I know this because the diagnostic output was putting the \\?\ stuff at the start of the filenames. So I don't think we need the config file. I will update the docs to mention the registry change though.


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

`C:\Users\shelly\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
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*
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).


If long path support does not work changing the location of the
`$(BaseIntermediateOutputPath)` can help solve these problems. In order for this
to work the setting MUST be changed before ANY build or restore occurs. To do this
you can make use of the MSBuild `Directory.Build.props` support.

Creating a `Directory.Build.props` file in your solution or project directory which
redefines the `$(BaseIntermediateOutputPath)` to somewhere nearer the root of the drive
with solve these issues. Adding a file with the following contents will create the `obj`
directory in a different location of your choosing.

```
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath Condition=" '$(OS)' == 'Windows_NT' ">C:\Intermediate\$(ProjectName)</BaseIntermediateOutputPath>
<BaseIntermediateOutputPath Condition=" '$(OS)' != 'Windows_NT' ">/tmp/Intermediate/$(ProjectName)</BaseIntermediateOutputPath>
</PropertyGroup>
</Project
```

Using this technique will reduce the lengths of the paths sent to the various tools like `aapt2`.
Note this is generally only a Windows issue. So there is no need to override the `$(BaseIntermediateOutputPath)`
on Mac or Linux based environments. However you might want to override everywhere to be consistent.

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

4 changes: 4 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,10 @@ Please change the value to an assembly-qualifed type name which inherits from '{
<comment>The following are literal names and should not be translated: Java.Interop.DoNotPackageAttribute
{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>
<comment>The following are literal names and should not be translated:</comment>
</data>
<data name="XA3001" xml:space="preserve">
<value>Could not AOT the assembly: {0}</value>
<comment>The abbreviation "AOT" should not be translated.</comment>
Expand Down
22 changes: 19 additions & 3 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aapt2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,23 +188,38 @@ 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);
if (manifestError)
LogCodedError (GetErrorCode (message), string.Format (Xamarin.Android.Tasks.Properties.Resources.AAPTManifestError, message.TrimEnd('.')), AndroidManifestFile.ItemSpec, 0);
LogCodedError (errorCode, string.Format (Xamarin.Android.Tasks.Properties.Resources.AAPTManifestError, message.TrimEnd('.')), AndroidManifestFile.ItemSpec, 0);
else
LogCodedError (GetErrorCode (message), message, file, line);
LogCodedError (errorCode, AddAdditionalErrorText (errorCode, message), file, line);
return true;
}
}

if (!apptResult) {
var message = string.Format ("{0} \"{1}\".", singleLine.Trim (), singleLine.Substring (singleLine.LastIndexOfAny (new char [] { '\\', '/' }) + 1));
LogCodedError (GetErrorCode (message), message, ToolName);
var errorCode = GetErrorCode (message);
LogCodedError (errorCode, AddAdditionalErrorText (errorCode, message), ToolName);
} else {
LogCodedWarning (GetErrorCode (singleLine), singleLine);
}
return true;
}

static string AddAdditionalErrorText (string errorCode, string message)
{
var sb = new StringBuilder ();
sb.AppendLine (message);
switch (errorCode)
{
case "APT2264":
sb.AppendLine (Xamarin.Android.Tasks.Properties.Resources.APT2264);
break;
}
return sb.ToString ();
}

static string GetErrorCode (string message)
{
foreach (var tuple in error_codes)
Expand Down Expand Up @@ -478,6 +493,7 @@ 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
};
}
}