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

[Xamarin.Android.Build.Tasks] correct paths in MarshalMethodsAssemblyRewriter #8151

Merged

Conversation

jonathanpeppers
Copy link
Member

Context: dotnet/maui#15399 (comment)

During a PR updating AndroidX dependencies in .NET MAUI, we got the error:

Task GenerateJavaStubs
...
System.IO.IOException: The process cannot access the file 'D:\a\_work\1\s\src\Compatibility\ControlGallery\src\Android\obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb' because it is being used by another process.
at System.IO.FileSystem.CopyFile(String sourceFullPath, String destFullPath, Boolean overwrite)
at Xamarin.Android.Tasks.MarshalMethodsAssemblyRewriter.Rewrite(DirectoryAssemblyResolver resolver, List`1 targetAssemblyPaths, Boolean brokenExceptionTransitions)
at Xamarin.Android.Tasks.GenerateJavaStubs.Run(DirectoryAssemblyResolver res, Boolean useMarshalMethods)
at Xamarin.Android.Tasks.GenerateJavaStubs.RunTask()
at Microsoft.Android.Build.Tasks.AndroidTask.Execute() in /Users/runner/work/1/s/xamarin-android/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs:line 25

Which has some very odd logging right before the failure:

Copying rewritten assembly: obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll.new -> obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll
Copying rewritten assembly: obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.pdb -> obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb

Copying Xamarin.AndroidX.CustomView.PoolingContainer.pdb to Xamarin.AndroidX.CustomView.pdb?

I could reproduce the issue in a test with PublishTrimmed=false:

[Test]
public void SimilarAndroidXAssemblyNames ([Values(true, false)] bool publishTrimmed)
{
    var proj = new XamarinAndroidApplicationProject {
        IsRelease = true,
        AotAssemblies = publishTrimmed,
        PackageReferences = {
            new Package { Id = "Xamarin.AndroidX.CustomView", Version = "1.1.0.17" },
            new Package { Id = "Xamarin.AndroidX.CustomView.PoolingContainer", Version = "1.0.0.4" },
        }
    };
    proj.SetProperty (KnownProperties.PublishTrimmed, publishTrimmed.ToString());
    proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", "AndroidX.CustomView.PoolingContainer.PoolingContainer.IsPoolingContainer (null);");
    using var builder = CreateApkBuilder ();
    Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
}

In MarshalMethodsAssemblyRewriter we write temporary assembly files to foo.new and copy to the original path at foo.dll. We next copy any symbol files if found.

I found two underlying issues here.

First, this Mono.Cecil API:

AssemblyDefinition.Write("foo.new", new WriterParameters {
    WriteSymbols = true
});

It would write a foo.pdb instead of foo.new.pdb, and so we have no way for this to write symbols to a temporary location.

I put the temporary location in a new subdirectory instead of appending .new to the path.

The second problem is this code:

target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (pdb)), ".pdb");
CopyFile (pdb, target);

It appears to lose .PoolingContainer from the path, and so it uses the destination of:

obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb

But using a new subdirectory instead, we bypass this issue.

After these changes, we instead get:

Copying rewritten assembly: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.dll -> obj\Release\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll
Copying rewritten assembly: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.pdb -> obj\Release\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.pdb
Deleting: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.dll
Deleting: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.pdb

Lastly, I removed .mdb file support -- but that is completely unrelated to the issue.

…Rewriter

Context: dotnet/maui#15399 (comment)

During a PR updating AndroidX dependencies in .NET MAUI, we got the
error:

    Task GenerateJavaStubs
    ...
    System.IO.IOException: The process cannot access the file 'D:\a\_work\1\s\src\Compatibility\ControlGallery\src\Android\obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb' because it is being used by another process.
    at System.IO.FileSystem.CopyFile(String sourceFullPath, String destFullPath, Boolean overwrite)
    at Xamarin.Android.Tasks.MarshalMethodsAssemblyRewriter.Rewrite(DirectoryAssemblyResolver resolver, List`1 targetAssemblyPaths, Boolean brokenExceptionTransitions)
    at Xamarin.Android.Tasks.GenerateJavaStubs.Run(DirectoryAssemblyResolver res, Boolean useMarshalMethods)
    at Xamarin.Android.Tasks.GenerateJavaStubs.RunTask()
    at Microsoft.Android.Build.Tasks.AndroidTask.Execute() in /Users/runner/work/1/s/xamarin-android/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs:line 25

Which has some very odd logging right before the failure:

    Copying rewritten assembly: obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll.new -> obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll
    Copying rewritten assembly: obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.pdb -> obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb

Copying `Xamarin.AndroidX.CustomView.PoolingContainer.pdb` to
`Xamarin.AndroidX.CustomView.pdb`?

I could reproduce the issue in a test with `PublishTrimmed=false`:

    [Test]
    public void SimilarAndroidXAssemblyNames ([Values(true, false)] bool publishTrimmed)
    {
        var proj = new XamarinAndroidApplicationProject {
            IsRelease = true,
            AotAssemblies = publishTrimmed,
            PackageReferences = {
                new Package { Id = "Xamarin.AndroidX.CustomView", Version = "1.1.0.17" },
                new Package { Id = "Xamarin.AndroidX.CustomView.PoolingContainer", Version = "1.0.0.4" },
            }
        };
        proj.SetProperty (KnownProperties.PublishTrimmed, publishTrimmed.ToString());
        proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", "AndroidX.CustomView.PoolingContainer.PoolingContainer.IsPoolingContainer (null);");
        using var builder = CreateApkBuilder ();
        Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
    }

In `MarshalMethodsAssemblyRewriter` we write temporary assembly files to
`foo.new` and copy to the original path at `foo.dll`. We next copy any
symbol files if found.

I found two underlying issues here.

First, this `Mono.Cecil` API:

    AssemblyDefinition.Write("foo.new", new WriterParameters {
        WriteSymbols = true
    });

It would write a `foo.pdb` instead of `foo.new.pdb`, and so we have no
way for this to write symbols to a temporary location.

I put the temporary location in a `new` subdirectory instead of
appending `.new` to the path.

The second problem is this code:

    target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (pdb)), ".pdb");
    CopyFile (pdb, target);

It appears to lose `.PoolingContainer` from the path, and so it uses the
destination of:

    obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb

But using a `new` subdirectory instead, we bypass this issue.

After these changes, we instead get:

    Copying rewritten assembly: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.dll -> obj\Release\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll
    Copying rewritten assembly: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.pdb -> obj\Release\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.pdb
    Deleting: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.dll
    Deleting: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.pdb

Lastly, I removed `.mdb` file support -- but that is completely
unrelated to the issue.
foreach (string targetPath in targetAssemblyPaths) {
string target = Path.Combine (targetPath, Path.GetFileNameWithoutExtension (path));
string target = Path.Combine (targetPath, Path.GetFileName (path));
CopyFile (path, target);
Copy link
Member

Choose a reason for hiding this comment

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

I realize that the "original" version in 8bc7a3e didn't do this, but should we verify that target exists before this CopyFile()?

@grendello?

targetPaths could contain multiple directories, such as arm64-v8a/linked and x86_64/linked, and it feels like this method could possibly copy/overwrite the "wrong" ABI?

Does MarshalMethodAssemblyRewriter properly deal with per-ABI assemblies? (See also 929e701?)

Copy link
Contributor

Choose a reason for hiding this comment

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

@jonpryor all the known target-specific assemblies are ones which we won't rewrite, since they don't have any Java.Object-derived classes. But yes, in general, the code would fail if it encountered Java.Object-derived types in a target-specific assembly, since the task (and thus MarshalMethodsAssemblyRewriter) would be passed several copies of those assemblies.

Copy link
Contributor

Choose a reason for hiding this comment

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

IOW, we're safe now with the code as-is, but it wouldn't hurt to amend it to make target-specific assemblies are handled properly. I guess the DestinationSubPath metadata item could be used to build the output path, by appending it to the new/ path?

Copy link
Member

Choose a reason for hiding this comment

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

@grendello: as per 929e701, any assembly can now become a target-specific assembly if (when) it uses IntPtr.Size. We likely shouldn't address this here, but we likely will need to address it.

Copy link
Member

Choose a reason for hiding this comment

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

We should merge this PR, and followup later with a more complete fix for per-ABI assemblies.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, as it requires a review of all our tasks

@jonpryor jonpryor merged commit efa14e2 into dotnet:main Jun 28, 2023
jonathanpeppers added a commit that referenced this pull request Jun 28, 2023
…#8151)

Context: dotnet/maui#15399 (comment)

During a PR updating AndroidX dependencies in .NET MAUI, we saw:

	Task GenerateJavaStubs
	  …
	  System.IO.IOException: The process cannot access the file 'D:\a\_work\1\s\src\Compatibility\ControlGallery\src\Android\obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb' because it is being used by another process.
	     at System.IO.FileSystem.CopyFile(String sourceFullPath, String destFullPath, Boolean overwrite)
	     at Xamarin.Android.Tasks.MarshalMethodsAssemblyRewriter.Rewrite(DirectoryAssemblyResolver resolver, List`1 targetAssemblyPaths, Boolean brokenExceptionTransitions)
	     at Xamarin.Android.Tasks.GenerateJavaStubs.Run(DirectoryAssemblyResolver res, Boolean useMarshalMethods)
	     at Xamarin.Android.Tasks.GenerateJavaStubs.RunTask()
	     at Microsoft.Android.Build.Tasks.AndroidTask.Execute()

Which has some very odd logging right before the failure:

	Copying rewritten assembly: obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll.new -> obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll
	Copying rewritten assembly: obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.pdb -> obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb

Copying `Xamarin.AndroidX.CustomView.PoolingContainer.pdb` to
`Xamarin.AndroidX.CustomView.pdb`?  Where is  `.PoolingContainer`?

I could reproduce the issue in a test with `$(PublishTrimmed)`=False:

	[Test]
	public void SimilarAndroidXAssemblyNames ([Values(true, false)] bool publishTrimmed)
	{
	    var proj = new XamarinAndroidApplicationProject {
	        IsRelease = true,
	        AotAssemblies = publishTrimmed,
	        PackageReferences = {
	            new Package { Id = "Xamarin.AndroidX.CustomView", Version = "1.1.0.17" },
	            new Package { Id = "Xamarin.AndroidX.CustomView.PoolingContainer", Version = "1.0.0.4" },
	        }
	    };
	    proj.SetProperty (KnownProperties.PublishTrimmed, publishTrimmed.ToString());
	    proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", "AndroidX.CustomView.PoolingContainer.PoolingContainer.IsPoolingContainer (null);");
	    using var builder = CreateApkBuilder ();
	    Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
	}

In `MarshalMethodsAssemblyRewriter` we write temporary assembly files
to `foo.new` and copy to the original path at `foo.dll`.  We next
copy  any symbol files if found.

I found two underlying issues here:

First, this `Mono.Cecil` API:

	AssemblyDefinition.Write("foo.new", new WriterParameters {
	    WriteSymbols = true,
	});

would write a `foo.pdb` instead of `foo.new.pdb`, and so we have no
way for this to write symbols to a temporary location.

I put the temporary location in a `new` subdirectory instead of
appending `.new` to the path.

The second problem is this code:

	target = Path.ChangeExtension (Path.Combine (targetPath, Path.GetFileNameWithoutExtension (pdb)), ".pdb");
	CopyFile (pdb, target);

It appears to lose `.PoolingContainer` from the path, and so it uses
the destination of:

	obj\Release\net8.0-android\android\assets\Xamarin.AndroidX.CustomView.pdb

By using a `new` subdirectory instead, we bypass this issue.

After these changes, we instead get:

	Copying rewritten assembly: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.dll -> obj\Release\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.dll
	Copying rewritten assembly: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.pdb -> obj\Release\android\assets\Xamarin.AndroidX.CustomView.PoolingContainer.pdb
	Deleting: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.dll
	Deleting: obj\Release\android\assets\new\Xamarin.AndroidX.CustomView.PoolingContainer.pdb

Lastly, I removed `.mdb` file support -- but that is completely
unrelated to the issue.
grendello added a commit to grendello/xamarin-android that referenced this pull request Jul 6, 2023
* main:
  Bump to dotnet/installer@28d4a6b4be 8.0.100-preview.7.23330.16 (dotnet#8162)
  [Xamarin.Android.Build.Tasks] Move MonoAndroidAssetsDirIntermediate (dotnet#8166)
  [xaprepare] update Debian dependencies for current unstable (trixie) (dotnet#8169)
  [CI] Use dotnet test slicer in nightly tests (dotnet#8154)
  [Xamarin.Android.Build.Tasks] MarshalMethodsAssemblyRewriter+new file (dotnet#8151)
  Bump to dotnet/installer@d2a244f560 8.0.100-preview.7.23325.5 (dotnet#8142)
@github-actions github-actions bot locked and limited conversation to collaborators Jan 22, 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