Skip to content

Conversation

@dellis1972
Copy link
Contributor

@dellis1972 dellis1972 commented Sep 8, 2021

Binutils 2.36 introduced code to deal extra long path names on
Windows which makes compilation (in our case using as) and linking
to break if the source/object files reside in a directory which has
special characters (in our case they were テスト):

Xamarin.Android.Common.targets(1887,3): error XA3006: Could not compile native assembly file: typemaps.armeabi-v7a.s [SmokeTestBuildWithSpecialCharactersFalse\テスト\テスト.csproj]
  [aarch64-linux-android-as.EXE stderr] Assembler messages: (TaskId:187)
  [aarch64-linux-android-as.EXE stderr] Fatal error: can't create typemaps.arm64-v8a.o: Invalid argument (TaskId:187)

Downgrade binutils version to 2.35.2 which doesn't contain the offending
code block and will work for us, with relative paths. It will still
break with full paths if the path contains special characters, but since
our use case involves only relative paths, this is fine.

There is also a bug in aapt2 [1] which stops it processing a directory which has
certain special characters on windows. This will only appear on projects which
have library resources, such as Xamarin.Forms. The problem only appears when
processing a directory into a flata archive. Oddly it works file when processing a
file directly. In order to work around this we now generate a resource only res.zip
file when we extract the library resources. This res.zip is then used during the
generation of the flata archive. Weirdly, this works where directory transversal does
not.

Additionally it seems to be slightly quicker

Before
581 ms Aapt2Compile 1 calls

After
366 ms Aapt2Compile 1 calls

So the time taken to generate the res.zip is offset by the quicker execution of
aapt2 compile.

Finally, java tooling such as zipalign and apksigner also failed with the
special characters. This was because we were using full paths to the files
we were signing/aligning. Switching over to use relative paths fixes this particular
problem.

Add a set of Unit tests to make sure we can handle the special characters.

[1] https://issuetracker.google.com/issues/188679588

@dellis1972 dellis1972 requested a review from jonpryor as a code owner September 8, 2021 12:30
@dellis1972 dellis1972 marked this pull request as draft September 8, 2021 12:31
@jonpryor
Copy link
Contributor

jonpryor commented Sep 9, 2021

I was going to comment that the test works, so seemingly there isn't a problem, right?

…but then I thought about it: tests/MSBuildDeviceIntegration only appears to be run on macOS!

https://github.com/xamarin/xamarin-android/blob/fe8c8514fa59446e49eba6b52fd23747ca7c131f/build-tools/automation/azure-pipelines.yaml#L901-L906

https://github.com/xamarin/xamarin-android/blob/fe8c8514fa59446e49eba6b52fd23747ca7c131f/build-tools/automation/azure-pipelines.yaml#L1030-L1048

I don't think any of tests/MSBuildDeviceIntegration is run on Windows, is it?

Which means we have no idea if non-ASCII paths work on Windows at all, at least not via CI, right?

@dellis1972
Copy link
Contributor Author

I'll see about adding the SmokeTest run of MSBuildDeviceIntegration to the windows smoke test run

@dellis1972 dellis1972 force-pushed the specialcharacters branch 3 times, most recently from 29b21fb to 04ea048 Compare September 10, 2021 12:21
@dellis1972 dellis1972 force-pushed the specialcharacters branch 2 times, most recently from 53a1483 to 74ed2b3 Compare September 22, 2021 16:19
rawToDelete,
},
};
libProj.SetProperty ("Deterministic", "true");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jonathanpeppers I'm not sure why I've had to add this to this particular test. It was failing without it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Frankly, we should use $(Deterministic)=true everywhere

@dellis1972
Copy link
Contributor Author

Turns out @jonathanpeppers found AndroidStudio doesn't even let you create a project with these special characters. So I guess it doesn't work at all in Android Studio.

@dellis1972 dellis1972 force-pushed the specialcharacters branch 2 times, most recently from 0850448 to 80a4cdc Compare October 4, 2021 13:47
@dellis1972 dellis1972 changed the title Special Char Unit Test [WIP] Special Char Unit Test Oct 5, 2021
@dellis1972 dellis1972 changed the title Special Char Unit Test Special Char Support Oct 5, 2021
@dellis1972 dellis1972 marked this pull request as ready for review October 5, 2021 10:29
if (isDirectory) {
cmd.Add ("--dir");
cmd.Add (isArchive ? "--zip" : "--dir");
cmd.Add (GetFullPath (fileOrDirectory).TrimEnd ('\\'));
Copy link
Contributor

Choose a reason for hiding this comment

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

Considering that this PR is in the context of "Chinese character set support", and that this is a command-line argument to aapt2, should this really use GetFullPath(fileOrDirectory) at all anymore?

I'm kinda surprised that this works at all, in that the PRs are green…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It needs to use get full path because aapt2 does not work in the project directory, but in a temp directory. This is to get around problems where the aapt2 daemons would lock the project directory while VS was running. So ALL the paths we pass to aapt2 need to be full paths.

The reason this works is because passing a zip file means aapt2 does not try to traverse the directory, which is where the buggy code is in aapt2.

cmd.AppendSwitch (AdditionalArguments);

cmd.AppendSwitchIfNotNull (" ", Path.GetFullPath (ApkToSign));
cmd.AppendSwitchIfNotNull (" ", ApkToSign);
Copy link
Contributor

Choose a reason for hiding this comment

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

…especially considering that we're removing Path.GetFullPath() here

</PropertyGroup>
<AndroidSignPackage Condition=" '$(AndroidUseApkSigner)' != 'true' "
UnsignedApk="%(ApkAbiFilesIntermediate.FullPath)"
UnsignedApk="%(ApkAbiFilesIntermediate.Identity)"
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're using %(Identity), why not instead use @(ApkAbiFilesIntermediate)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto subsequent changes as well. Why prefer %(Foo.Identity) over @(Foo)?

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 AndroidSignPackage and ApkSigner need to use %(Foo.Identity) so that MSbuild will batch the calls. These tools can only sign one package at a time. If we provided @(Foo) and we had multiple apps (like we do when we split on abi) we would send the following to the signing tool

fooo.apk;foo2.apk

which is not what we want. What we want is for MSbuild to call AndroidSignPackage for each apk, and we do that via batching.

@dellis1972
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Binutils 2.36 introduced [code][0] to deal extra long path names on
Windows which makes compilation (in our case using `as`) and linking
to break if the source/object files reside in a directory which has
special characters (in our case they were `テスト`):

    Xamarin.Android.Common.targets(1887,3): error XA3006: Could not compile native assembly file: typemaps.armeabi-v7a.s [SmokeTestBuildWithSpecialCharactersFalse\テスト\テスト.csproj]
      [aarch64-linux-android-as.EXE stderr] Assembler messages: (TaskId:187)
      [aarch64-linux-android-as.EXE stderr] Fatal error: can't create typemaps.arm64-v8a.o: Invalid argument (TaskId:187)

Downgrade binutils version to 2.35.2 which doesn't contain the offending
code block and will work for us, with relative paths. It will **still**
break with full paths if the path contains special characters, but since
our use case involves only relative paths, this is fine.

There is also a bug in aapt2 [1] which stops it processing a directory which has
certain special characters on windows. This will only appear on projects which
have library resources, such as Xamarin.Forms. The problem only appears when
processing a directory into a flata archive. Oddly it works file when processing a
file directly. In order to work around this we now generate a resource only `res.zip`
file when we extract the library resources. This `res.zip` is then used during the
generation of the `flata` archive. Weirdly, this works where directory transversal does
not.

Additionally it seems to be slightly quicker

Before
`581 ms  Aapt2Compile                               1 calls`

After
`366 ms  Aapt2Compile                               1 calls`

So the time taken to generate the `res.zip` is offset by the quicker execution of
`aapt2 compile`.

Finally, `java` tooling such as `zipalign` and `apksigner` also failed with the
special characters. This was because we were using full paths to the files
we were signing/aligning. Switching over to use relative paths fixes this particular
problem.

Add a set of Unit tests to make sure we can handle the special characters.

[0]: https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=bfd/bfdio.c;h=463b3879c52ba6beac47190f8eb0810b0c330e65;hb=HEAD#l124
[1] https://issuetracker.google.com/issues/188679588
Comment on lines +419 to +427
CreateResourceArchive (resDir, resDirArchive);
var skipProcessing = aarFile.GetMetadata (AndroidSkipResourceProcessing);
if (string.IsNullOrEmpty (skipProcessing)) {
skipProcessing = "True";
}
resolvedResourceDirectories.Add (new TaskItem (Path.GetFullPath (resDir), new Dictionary<string, string> {
{ OriginalFile, aarFullPath },
{ AndroidSkipResourceProcessing, skipProcessing },
{ ResourceDirectoryArchive, Path.GetFullPath (resDirArchive)},
Copy link
Member

Choose a reason for hiding this comment

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

For .aar files, can we simply set their existing path to %(ResourceDirectoryArchive) and not create a new .zip file?

I think this will help performance quite a bit for AndroidX, which is mostly .aar files now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that. aapt2 errors because there are files like classes.jar etc in there. It only seems to like files which ONLY contain res files.

Comment on lines +26 to +35
[Test]
[Category ("SmokeTests")]
public void SmokeTestBuildWithSpecialCharacters ([Values (false, true)] bool forms)
{
var testName = "テスト";

var rootPath = Path.Combine (Root, "temp", TestName);
var proj = forms ?
new XamarinFormsAndroidApplicationProject () :
new XamarinAndroidApplicationProject ();
Copy link
Member

Choose a reason for hiding this comment

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

Should we put a test like this in MSBuildDeviceIntegration, to make sure it deploys and the app launches?

I'm wondering if something Fast Deployment might break, and we didn't catch it yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem, as outlined earlier, is that the MSBuildDeviceIntegration tests only run on macOS, which doesn't have a "non-ASCII characters" problem in the first place.

What we want/need is for MSBuildDevice integration to run on Windows. The problem is we tried that in an earlier iteration of #6297, and hit #6297 (comment)

Instead, it (later) insta-fails, because of virtualization:

 [emulator stdout] emulator: ERROR: x86_64 emulation currently requires hardware acceleration! (TaskId:5)
 C:\a\_work\1\s\build-tools\scripts\TestApks.targets(65,5): error : Emulator failed to start: emulator: ERROR: x86_64 emulation currently requires hardware acceleration! [C:\a\_work\1\s\tests\Mono.Android-Tests\Mono.Android-Tests.csproj]
 [emulator stdout] CPU acceleration status: Hyper-V detected and Windows Hypervisor Platform is available. Please ensure both the "Hyper-V" and "Windows Hypervisor Platform" features enabled in "Turn Windows features on or off". (TaskId:5)
 [emulator stdout] More info on configuring VM acceleration on Windows: (TaskId:5)
 [emulator stdout] https://developer.android.com/studio/run/emulator-acceleration#vm-windows (TaskId:5)
 [emulator stdout] General information on acceleration: https://developer.android.com/studio/run/emulator-acceleration. (TaskId:5)

@pjcollins had additional followup: #6297 (comment)

  • Investigate Helix machine pools for Android emulator testing.
  • Revisit physical Windows machine usage. One possible target would be the shared pool we used to run QA tests on. Some of these machines have physical Android devices attached, but could still potentially be used: …
  • See if we can somehow enable virtualization on our 1ES Windows pool.

In the meantime, we're going to need to rely on manual testing for Windows (ugh).

Copy link
Contributor

Choose a reason for hiding this comment

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

This suggests an "alternate half step": instead of requiring "full" MSBuildDeviceIntegration tests, would building the project with /t:SignAndroidPackage /p:Configuration=Release be a meaningfully useful intermediate step? This wouldn't test fast deployment -- or any deployment, for that matter -- but would hit the "binutils doesn't like non-ASCII" codepath, plus aapt, plus lots of other parts of the build system.

Should we add a "build (no device attached!) test" around the SignAndroidPackage target and non-ASCII directories?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test already does that. A basic release build produces the apk etc, so it will go through all the bin utils stuff, and aapt2. It runs on windows and Mac.

We do have a version of the same test which runs on Device but that is only gonna run on Mac because the emulator cannot run on the build machines we use for windows.

@jonpryor
Copy link
Contributor

[Xamarin.Android.Build.Tasks] Special Char Support (#6273)

What would we like?  The ability for Xamarin.Android projects to
reside in directories containing non-ASCII characters.

The problem(s)?  For starters, many Android SDK utilities don't
support non-ASCII characters when running on Windows, e.g.:

  * https://issuetracker.google.com/issues/188679588

Android Studio (kinda/sorta) "enforces" this, showing a warning
message when **Save location** contains non-ASCII characters:

![Android Studio New Project dialog with non-ASCII characters](https://user-images.githubusercontent.com/184788/134550831-b5c5335a-11ca-4296-b30c-cb54e3302917.png)

> Save location: /tmp/テスト
> 
> ⚠️ Your project location contains non-ASCII characters.

These are not particularly encouraging foundations.

Improve support for non-ASCII characters by updating various parts
of our build system to:

 1. Prefer *relatives* paths when possible, as we can ensure
    ASCII-only paths this way, and

 2. "Otherwise" change *how* we invoke Android tools to avoid their
    use of full paths and directory traversal.

Additionally, we've long used GNU Binutils to build `libxamarin-app.so`
(commit decfbccf, fc3f0282, others), and we discovered that
Binutils 2.36 introduced [code][0] to deal extra long path names on
Windows which makes compilation (in our case using `as`) and linking
to break if the source/object files reside in a directory which has
non-ASCII characters (in our case they were `テスト`):

	Xamarin.Android.Common.targets(1887,3): error XA3006: Could not compile native assembly file: typemaps.armeabi-v7a.s [SmokeTestBuildWithSpecialCharactersFalse\テスト\テスト.csproj]
	  [aarch64-linux-android-as.EXE stderr] Assembler messages: (TaskId:187)
	  [aarch64-linux-android-as.EXE stderr] Fatal error: can't create typemaps.arm64-v8a.o: Invalid argument (TaskId:187)

Downgrade to Binutils 2.35.2, which doesn't contain the offending
code block and will work for us, so long as we use relative paths.
It will **still** break with full paths if the path contains special
characters, but since our use case involves only relative paths,
this is fine.

There is also a bug in [`aapt2`][1] which stops it processing a
directory which has certain special characters on Windows.  This will
only appear on projects which have library resources, such as
Xamarin.Forms.  The problem only appears when processing a directory
into a `.flata` archive.  Oddly it works fine when processing a file
directly.  In order to work around this we now generate a resource
only `res.zip` file when we extract the library resources.
`res.zip` is then used during the generation of the `.flata` archive.
Weirdly, this works where directory transversal does not.

Additionally it seems to be slightly quicker during incremental builds:

  * Before

        581 ms  Aapt2Compile                               1 calls

  * After

        366 ms  Aapt2Compile                               1 calls

So the time taken to generate the `res.zip` is offset by the quicker
execution of `aapt2 compile`.

Finally, Java tooling such as `zipalign` and `apksigner` also failed
with the special characters.  This was because we were using full
paths to the files we were signing/aligning. Switching over to use
relative paths fixes this particular problem.

Add a set of Unit tests to make sure we can handle the special characters.

[0]: https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=bfd/bfdio.c;h=463b3879c52ba6beac47190f8eb0810b0c330e65;hb=HEAD#l124
[1]: https://issuetracker.google.com/issues/188679588

@jonpryor jonpryor merged commit fd5f31c into dotnet:main Oct 18, 2021
@dellis1972 dellis1972 deleted the specialcharacters branch October 19, 2021 10:16
@github-actions github-actions bot locked and limited conversation to collaborators Jan 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants