Skip to content
New issue

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

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

Already on GitHub? # 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
# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants