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] <FilterAssemblies/> support missing TFI #2958

Merged

Conversation

jonathanpeppers
Copy link
Member

@jonathanpeppers jonathanpeppers commented Apr 11, 2019

Context: https://www.nuget.org/packages/Refractored.Controls.CircleImageView/
Context: https://github.com/Azure-Samples/MyDriving
Context: #2934

The MyDriving sample app currently fails to build on master with:

Resources/layout/fragment_profile.axml(2): error APT0000: attribute civ_border_width (aka com.microsoft.mydriving:civ_border_width) not found.

The failure happens with both aapt and aapt2.

This layout is using a custom view such as:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto">
    ...
    <refractored.controls.CircleImageView local:civ_border_width="0dp" />
    ...
</ScrollView>

This comes from the Refractored.Controls.CircleImageView NuGet
package.

In 5ec3e3a, I added a <FilterAssemblies/> MSBuild task that appears
to be to blame. It is not returning
Refractored.Controls.CircleImageView.dll, but it needs to!

Refractored.Controls.CircleImageView.dll has no [assembly: System.Runtime.Versioning.TargetFrameworkAttribute]...

// C:\src\des\MyDriving\packages\Refractored.Controls.CircleImageView.1.0.1\lib\MonoAndroid10\Refractored.Controls.CircleImageView.dll
// Refractored.Controls.CircleImageView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Global type: <Module>
// Architecture: x86
// Runtime: v4.0.30319
// Hash algorithm: SHA1

using Android.Runtime;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

[assembly: AssemblyTitle("Refractored.Controls.CircleImageView")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("2015 Refractored LLC/James Montemagno")]
[assembly: AssemblyTrademark("")]
[assembly: NamespaceMapping(Java = "de.hdodenhof.circleimageview", Managed = "Refractored.Controls")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: SecurityPermission(8, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]

It is indeed a MonoAndroid assembly, since it references
Mono.Android.dll. It is weird, though...

What should we do?

One idea is to look for Mono.Android.dll references in each
assembly instead of TargetFrameworkIdentifier.

Looking at the assemblies in Xamarin.Forms, there is a complication to
this:

  • Xamarin.Forms.Core.dll (a NetStandard library) references
    • Xamarin.Forms.Platform.dll (a MonoAndroid library?) references
      • Xamarin.Forms.Platform.Android.dll

But Xamarin.Forms.Platform.dll does not reference
Mono.Android.dll?

So then should we "recursively" look for any reference to
Mono.Android.dll in a given dependency tree?

I don't think so? This would include more assemblies than we want...
In the above example Xamarin.Forms.Core.dll would get counted as a
"MonoAndroid" assembly.

Conclusion

I think we should stick with TargetFrameworkIdentifier, and in the
rare case it is missing look for a Mono.Android.dll reference for
the single assembly. This seems like it is going to cover all cases to
me, and we still will get good performance.

So we will cover:

  • Xamarin.Forms.Platform.dll counted as a "MonoAndroid" assembly
    since it has a TargetFrameworkIdentifier (but no reference).
  • Refractored.Controls.CircleImageView.dll counted as a
    "MonoAndroid" assembly. It has no TargetFrameworkIdentifier, but has a reference to Mono.Android.dll`.

Changes include:

  • <FilterAssemblies/> needs to also check for an assembly reference
    to Mono.Android as a fallback.
  • <ResolveAssemblies/> now adds a %(MonoAndroidReference)=True
    item metadata.
  • When creating the @(_ResolvedUserMonoAndroidAssemblies) item
    group, we also check for %(MonoAndroidReference)=True.

I added a few tests to verify these scenarios.

@jonathanpeppers jonathanpeppers added the do-not-merge PR should not be merged. label Apr 11, 2019
@jonathanpeppers
Copy link
Member Author

I want to look at <ResolveAssemblies/> some more on this one.

@jonathanpeppers jonathanpeppers force-pushed the missing-targetframeworkidentifier branch from 6613f3f to 5ca58d1 Compare April 11, 2019 16:07
@jonathanpeppers jonathanpeppers removed the do-not-merge PR should not be merged. label Apr 11, 2019
// Fallback to looking at references
if (string.IsNullOrEmpty (targetFrameworkIdentifier) && !string.IsNullOrEmpty (FallbackReference)) {
Log.LogDebugMessage ($"Checking references for: {assemblyItem.ItemSpec}");
foreach (var handle in reader.AssemblyReferences) {
Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be recursive? As-is, this will only handle direct assembly references.

For example, Xamarin.Forms.Core.dll references Xamarin.Forms.Platform.dll -- which exists for multiple platforms -- and the MonoAndroid10/Xamarin.Forms.Platform.dll references Xamarin.Forms.Platform.Android.dll which references Mono.Android.dll. (Deep breath.)

Thus, should we effectively emit this, i.e. "recursively" resolve references?

<ItemGroup>
  <_ResolvedUserAssemblies Include="Xamarin.Forms">
    <TargetFrameworkIdentifier>MonoAndroid</TargetFrameworkIdentifier>
</ItemGroup>

...though then everything could then be treated as MonoAndroid profile assemblies, which feels like it would defeat the purpose.

Thus, how thorough do we need to be?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have only found this one case where this [assembly:TargetFramework] was missing... It was shipped to NuGet in 2015.

I built it from source: https://github.com/jamesmontemagno/CircleImageView-Xamarin.Android

And the assembly produced with VS 2019 has:

[assembly: TargetFramework("MonoAndroid,Version=v5.0", FrameworkDisplayName = "Xamarin.Android v5.0 Support")]

Copy link
Member

Choose a reason for hiding this comment

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

I’m overthinking this; Xamarin.Forms does not need to have %(TargetFrameworkIdentifier)=MonoAndroid.

Reason being that while, indirectly, mythical type A using Xamarin.Forms has a reference graph which eventually includes Mono.Android.dll (via Xamarin.Forms.Platform.Android.dll), A cannot use any types from Mono.Android.dll — even indirect inheritance! — without referencing it explicitly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated the commit message around this conversation 👍

@jonathanpeppers jonathanpeppers force-pushed the missing-targetframeworkidentifier branch from 5ca58d1 to 3ae293f Compare April 12, 2019 03:48
Context: https://www.nuget.org/packages/Refractored.Controls.CircleImageView/
Context: https://github.com/Azure-Samples/MyDriving

The MyDriving sample app currently fails to build on master with:

    Resources/layout/fragment_profile.axml(2): error APT0000: attribute civ_border_width (aka com.microsoft.mydriving:civ_border_width) not found.

The failure happens with both `aapt` and `aapt2`.

This layout is using a custom view such as:

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:local="http://schemas.android.com/apk/res-auto">
        ...
        <refractored.controls.CircleImageView local:civ_border_width="0dp" />
        ...
    </ScrollView>

This comes from the `Refractored.Controls.CircleImageView` NuGet
package.

In 5ec3e3a, I added a `<FilterAssemblies/>` MSBuild task that appears
to be to blame. It is not returning
`Refractored.Controls.CircleImageView.dll`, but it needs to!

`Refractored.Controls.CircleImageView.dll` has no `[assembly:
System.Runtime.Versioning.TargetFrameworkAttribute]`...

    // C:\src\des\MyDriving\packages\Refractored.Controls.CircleImageView.1.0.1\lib\MonoAndroid10\Refractored.Controls.CircleImageView.dll
    // Refractored.Controls.CircleImageView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    // Global type: <Module>
    // Architecture: x86
    // Runtime: v4.0.30319
    // Hash algorithm: SHA1

    using Android.Runtime;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Security;
    using System.Security.Permissions;

    [assembly: AssemblyTitle("Refractored.Controls.CircleImageView")]
    [assembly: AssemblyDescription("")]
    [assembly: AssemblyConfiguration("")]
    [assembly: AssemblyCompany("")]
    [assembly: AssemblyProduct("")]
    [assembly: AssemblyCopyright("2015 Refractored LLC/James Montemagno")]
    [assembly: AssemblyTrademark("")]
    [assembly: NamespaceMapping(Java = "de.hdodenhof.circleimageview", Managed = "Refractored.Controls")]
    [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
    [assembly: SecurityPermission(8, SkipVerification = true)]
    [assembly: AssemblyVersion("1.0.0.0")]
    [module: UnverifiableCode]

It is indeed a `MonoAndroid` assembly, since it references
`Mono.Android.dll`. It is weird, though...

~~ What should we do? ~~

One idea is to look for `Mono.Android.dll` references in each
assembly *instead* of `TargetFrameworkIdentifier`.

Looking at the assemblies in Xamarin.Forms, there is a complication to
this:

* `Xamarin.Forms.Core.dll` (a NetStandard library) references
  * `Xamarin.Forms.Platform.dll` (a MonoAndroid library?) references
    * `Xamarin.Forms.Platform.Android.dll`

But `Xamarin.Forms.Platform.dll` does not reference
`Mono.Android.dll`?

So then should we "recursively" look for any reference to
`Mono.Android.dll` in a given dependency tree?

I don't think so? This would include more assemblies than we want...
In the above example `Xamarin.Forms.Core.dll` would get counted as a
"MonoAndroid" assembly.

~~ Conclusion ~~

I think we should stick with `TargetFrameworkIdentifier`, and in the
rare case it is missing look for a `Mono.Android.dll` reference for
the single assembly. This seems like it is going to cover all cases to
me, and we still will get good performance.

So we will cover:

* `Xamarin.Forms.Platform.dll` counted as a "MonoAndroid" assembly
  since it has a `TargetFrameworkIdentifier` (but no reference).
* `Refractored.Controls.CircleImageView.dll` counted as a
  "MonoAndroid" assembly. It has no `TargetFrameworkIdentifier, but
  has a reference to `Mono.Android.dll`.

Changes include:

* `<FilterAssemblies/>` needs to also check for an assembly reference
  to `Mono.Android` as a fallback.
* `<ResolveAssemblies/>` now adds a `%(MonoAndroidReference)=True`
  item metadata.
* When creating the `@(_ResolvedUserMonoAndroidAssemblies)` item
  group, we also check for `%(MonoAndroidReference)=True`.

I added a few tests to verify these scenarios.
@jonathanpeppers jonathanpeppers force-pushed the missing-targetframeworkidentifier branch from 3ae293f to 71623cc Compare April 12, 2019 03:51
@jonpryor jonpryor merged commit c8125b4 into dotnet:master Apr 12, 2019
@github-actions github-actions bot locked and limited conversation to collaborators Jan 31, 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.

3 participants