dotnet / android

.NET for Android provides open-source bindings of the Android SDK for use with .NET managed languages such as C#
MIT License
1.94k stars 533 forks source link

Assembly ending with "resources" causing mislocated assembly in apk #3745

Open erikpowa opened 5 years ago

erikpowa commented 5 years ago

short: "user" assembly -> foo-resources.dll assembly location in apk -> \assemblies\assets\foo-resources.dll

Steps to Reproduce

  1. Create empty app
  2. Create a library
  3. Add property to library <AssemblyName>foo-resources</AssemblyName>
  4. Reference the library
  5. BuildApk

Expected Behavior

\assemblies\foo-resources.dll

Actual Behavior

\assemblies\assets\foo-resources.dll

Version Information

master branch

Log File

erikpowa commented 5 years ago

Can't repro anymore. Tested at https://github.com/xamarin/xamarin-android/commit/da46500671b82fa7f15209ea2a091581cbf820f4

erikpowa commented 5 years ago

My test was faulty 🤦 which used foo-Resources, but still can repro with lower-case foo-resources.

brendanzagaeski commented 4 years ago

Notes for the Xamarin.Android team

This issue will by default result in a FileNotFoundException: Could not load file or assembly exception if an app attempts to load a non-satellite assembly that has a name ending with .resources.dll.

I encountered this behavior myself today in a test failure while working on and unrelated PR, so I started writing an automated build test for it before I found this existing issue. In case it might be handy for some point in the future, here's the test:

[Test]
public void AssembliesThatAreNamedLikeSatelliteAssembliesButAreNot ()
{
    var path = Path.Combine ("temp", TestName);
    var app = new XamarinAndroidApplicationProject {
        ProjectName = "App",
        Sources = {
            new BuildItem.Source ("Class1.cs") {
                TextContent = () => "public class Class1 : Library1.resources.Class1 { }"
            },
        },
        IsRelease = true
    };
    var lib = new DotNetStandard {
        ProjectName = "Library1.resources",
        Sdk = "Microsoft.NET.Sdk",
        TargetFramework = "netstandard2.0",
        Sources = {
            new BuildItem.Source ("Class1.cs") {
                TextContent = () => "namespace Library1.resources { public class Class1 { } }"
            }
        }
    };
    app.References.Add (new BuildItem.ProjectReference ($"..\\{lib.ProjectName}\\{lib.ProjectName}.csproj", lib.ProjectName, lib.ProjectGuid));

    using (var libBuilder = CreateDllBuilder (Path.Combine (path, lib.ProjectName), cleanupAfterSuccessfulBuild: false))
    using (var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName))) {
        Assert.IsTrue (libBuilder.Build (lib), "Library build should have succeeded.");
        Assert.IsTrue (appBuilder.Build (app), "App build should have succeeded.");
        var archive = Path.Combine (Root, appBuilder.ProjectDirectory, app.OutputPath, $"{app.PackageName}.apk");
        using (var zip = ZipHelper.OpenZip (archive)) {
            Assert.IsTrue (zip.ContainsEntry ("assemblies/Library1.resources.dll"), $"{archive} should contain an assemblies/Library1.resources.dll entry");
        }
    }
}

Based on a first look at the underlying problem, it seems the list of satellite assemblies is not preserved separately all the way into the BuildApk task, so the BuildApk task currently assumes that any assembly name that matches a certain regular expression should be treated as a satellite assembly:

https://github.com/xamarin/xamarin-android/blob/22bc14b1a89352824d2a54f6e12a4dbeaa6fe679/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs#L499-L501

https://github.com/xamarin/xamarin-android/blob/22bc14b1a89352824d2a54f6e12a4dbeaa6fe679/src/Xamarin.Android.Build.Tasks/Utilities/SatelliteAssembly.cs#L9-L13