Skip to content

Remove API adjuster #789

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

Open
jonpryor opened this issue Feb 3, 2021 · 4 comments
Open

Remove API adjuster #789

jonpryor opened this issue Feb 3, 2021 · 4 comments
Labels
generator Issues binding a Java library (generator, class-parse, etc.)

Comments

@jonpryor
Copy link
Member

jonpryor commented Feb 3, 2021

Context: dotnet/android#5580

The current binding workflow with class-parse results in three api.xml files:

  1. The class-parse output, in api.xml.class-parse
  2. The "adjusted" output, in api.xml (via generator --only-xml-adjuster --xml-adjuster-output=api.xml)
  3. The "fixed" output, after applying Metadata.xml to api.xml, in api.xml.fixed.

The problem is (2): it removes types present in (1).

Take for example dotnet/android#5580, in which the HttpRequest..serializer type is not bound.

The problem is that it's removed by (2):

  Error while processing type '[Class] com.vmware.chameleon.http.HttpRequest..serializer': Type 'kotlinx.serialization.internal.GeneratedSerializer' was not found. (TaskId:36)

and thus is not present in api.xml. Consequently, you can't write Metadata.xml transforms which attempt to reference the type, as the type doesn't exist when Metadata.xml is being processed.

The only workaround here is to use <add-node/> with Metadata.xml, copying the XML fragment from api.xml.class-parse, a'la

  <add-node path="//package[@name='com.vmware.chameleon.http']">
    <class
      abstract="false"
      deprecated="deprecated"
      jni-extends="Ljava/lang/Object;"
      extends="java.lang.Object"
      extends-generic-aware="java.lang.Object"
      final="true"
      name="HttpRequest..serializer"
      jni-signature="Lcom/vmware/chameleon/http/HttpRequest$$serializer;"
      source-file-name="HttpRequest.kt"
      static="true"
      visibility="public">
      <implements
        name="kotlinx.serialization.internal.GeneratedSerializer"
        name-generic-aware="kotlinx.serialization.internal.GeneratedSerializer&lt;com.vmware.chameleon.http.HttpRequest&gt;"
        jni-type="Lkotlinx/serialization/internal/GeneratedSerializer&lt;Lcom/vmware/chameleon/http/HttpRequest;&gt;;" />
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="childSerializers"
        native="false"
        return="kotlinx.serialization.KSerializer&lt;?&gt;[]"
        jni-return="[Lkotlinx/serialization/KSerializer&lt;*&gt;;"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="false"
        synthetic="false"
        jni-signature="()[Lkotlinx/serialization/KSerializer;"
        return-not-null="true" />
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="deserialize"
        native="false"
        return="com.vmware.chameleon.http.HttpRequest"
        jni-return="Lcom/vmware/chameleon/http/HttpRequest;"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="false"
        synthetic="false"
        jni-signature="(Lkotlinx/serialization/Decoder;)Lcom/vmware/chameleon/http/HttpRequest;"
        return-not-null="true">
        <parameter
          name="decoder"
          type="kotlinx.serialization.Decoder"
          jni-type="Lkotlinx/serialization/Decoder;"
          not-null="true" />
      </method>
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="deserialize"
        native="false"
        return="java.lang.Object"
        jni-return="Ljava/lang/Object;"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="true"
        synthetic="true"
        jni-signature="(Lkotlinx/serialization/Decoder;)Ljava/lang/Object;">
        <parameter
          name="p0"
          type="kotlinx.serialization.Decoder"
          jni-type="Lkotlinx/serialization/Decoder;" />
      </method>
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="getDescriptor"
        native="false"
        return="kotlinx.serialization.SerialDescriptor"
        jni-return="Lkotlinx/serialization/SerialDescriptor;"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="false"
        synthetic="false"
        jni-signature="()Lkotlinx/serialization/SerialDescriptor;"
        return-not-null="true" />
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="patch"
        native="false"
        return="com.vmware.chameleon.http.HttpRequest"
        jni-return="Lcom/vmware/chameleon/http/HttpRequest;"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="false"
        synthetic="false"
        jni-signature="(Lkotlinx/serialization/Decoder;Lcom/vmware/chameleon/http/HttpRequest;)Lcom/vmware/chameleon/http/HttpRequest;"
        return-not-null="true">
        <parameter
          name="this"
          type="kotlinx.serialization.Decoder"
          jni-type="Lkotlinx/serialization/Decoder;"
          not-null="true" />
        <parameter
          name="decoder"
          type="com.vmware.chameleon.http.HttpRequest"
          jni-type="Lcom/vmware/chameleon/http/HttpRequest;"
          not-null="true" />
      </method>
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="patch"
        native="false"
        return="java.lang.Object"
        jni-return="Ljava/lang/Object;"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="true"
        synthetic="true"
        jni-signature="(Lkotlinx/serialization/Decoder;Ljava/lang/Object;)Ljava/lang/Object;">
        <parameter
          name="p0"
          type="kotlinx.serialization.Decoder"
          jni-type="Lkotlinx/serialization/Decoder;" />
        <parameter
          name="p1"
          type="java.lang.Object"
          jni-type="Ljava/lang/Object;" />
      </method>
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="serialize"
        native="false"
        return="void"
        jni-return="V"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="false"
        synthetic="false"
        jni-signature="(Lkotlinx/serialization/Encoder;Lcom/vmware/chameleon/http/HttpRequest;)V">
        <parameter
          name="encoder"
          type="kotlinx.serialization.Encoder"
          jni-type="Lkotlinx/serialization/Encoder;"
          not-null="true" />
        <parameter
          name="value"
          type="com.vmware.chameleon.http.HttpRequest"
          jni-type="Lcom/vmware/chameleon/http/HttpRequest;"
          not-null="true" />
      </method>
      <method
        abstract="false"
        deprecated="not deprecated"
        final="false"
        name="serialize"
        native="false"
        return="void"
        jni-return="V"
        static="false"
        synchronized="false"
        visibility="public"
        bridge="true"
        synthetic="true"
        jni-signature="(Lkotlinx/serialization/Encoder;Ljava/lang/Object;)V">
        <parameter
          name="p0"
          type="kotlinx.serialization.Encoder"
          jni-type="Lkotlinx/serialization/Encoder;" />
        <parameter
          name="p1"
          type="java.lang.Object"
          jni-type="Ljava/lang/Object;" />
      </method>
      <field
        deprecated="not deprecated"
        final="true"
        name="INSTANCE"
        static="true"
        synthetic="false"
        transient="false"
        type="com.vmware.chameleon.http.HttpRequest..serializer"
        type-generic-aware="com.vmware.chameleon.http.HttpRequest..serializer"
        jni-signature="Lcom/vmware/chameleon/http/HttpRequest$$serializer;"
        visibility="public"
        volatile="false" />
    </class>
  </add-node>
@jonpryor
Copy link
Member Author

jonpryor commented Feb 3, 2021

Note: in the case of dotnet/android#5580, the above <add-node/> does not work, because generator doesn't like .. in the name.

If I manually replace all instances of HttpRequest..serializer with HttpRequest._serializer, a HttpRequest._serializer binding is created. Unfortunately it's not valid, because the register attribute is wrong.

Instead, I can add managedName into the <add-node/> text, and then it works!

    <class
      abstract="false"
      managedName="HttpRequest._Serializer"

Something I hadn't considered.

Regardless, generator should do more than "silently fail" when a type name has .. in it.

@jpobst jpobst added enhancement Proposed change to current functionality generator Issues binding a Java library (generator, class-parse, etc.) labels Feb 3, 2021
@jonpryor jonpryor added this to the Under consideration milestone Feb 11, 2021
@jpobst jpobst removed their assignment Feb 16, 2021
@jpobst
Copy link
Contributor

jpobst commented Mar 11, 2021

I just happened to notice the .. issue in generator. Leaving a link here for future me:

https://github.com/xamarin/java.interop/blob/main/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBaseSupport.cs#L24-L27

@akalghatgi
Copy link

@jpobst when is this planned... we are stuck with are release...

jonpryor pushed a commit that referenced this issue Apr 22, 2021
Context: #789

One issue with the proposal to flip the order of `ApiXmlAdjuster` and
`metadata` in Issue #789 is that the "apply metadata" step is
intertwined with the "parse API xml document into POCO's" step.
This means that the adjuster step cannot be inserted between them,
as the code is currently written.

Separate out the two steps so that they can be reordered in the future.

Additionally, refactor the metadata fixup step so that it can be unit
tested more easily by moving it to `Java.Interop.Tools.Generator.dll`.
Unit tests for applying metadata have been added.
jonpryor pushed a commit that referenced this issue Oct 26, 2021
Fixes: #739
Fixes: #852
Fixes: #853

Context: #789
Context: #814
Context: #815

Today, we use a process called `ApiXmlAdjuster` to convert our "new"
`class-parse` format to the old `jar2xml` format.  This process
attempts to resolve every Java type referenced by the library we
intend to bind.  However, performing this as a separate step has
some disadvantages:

  - It duplicates work by reading in `api.xml.class-parse`
    and Cecil'ing all reference assemblies, then writing it back out
    as `api.xml`.
    `generator` then reads in `api.xml` and Cecils all the reference
    assemblies again.

  - This work is performed as a separate step before `generator` and
    thus cannot be influenced by `metadata`.

  - This work is cached, which is "good", but it creates issues for
    users because they only see warnings from it for full Rebuilds.

"Replace" `ApiXmlAdjuster` with `Java.Interop.Tools.JavaTypeSystem`,
which is a much more robust Java model to perform this work, and
contains enough information to be reused by `generator`.
This prevents us from needing to have a second Java and Cecil import
step, and we can run `metadata` directly on `api.xml.class-parse`
before the import.

NOTE: This commit sets the groundwork for the above benefits, but
does not achieve them yet.  Initially we are only focused on
achieving parity with `ApiXmlAdjuster` to make correctness
comparisons possible.

The new `JavaTypeSystem` infrastructure is used by default; the
previous `ApiXmlAdjuster` system can be used instead by using:

	generator --use-legacy-java-resolver=true …

We will provide an MSBuild property to control this when we integrate
with xamarin-android in the future.


~~ Warning Messages ~~

In order to better surface potentially real errors to users, we have
tweaked the warnings displayed.  Errors we encounter during the
initial phase of Java type resolution are emitted as warnings.
These are missing external Java types and generally indicate the user
is missing a reference `.jar` or binding NuGet:

	warning BG8605: The Java type 'androidx.appcompat.widget.AppCompatTextView' could not be found (are you missing a Java reference jar/aar or a Java binding library NuGet?)

The remaining resolution errors are generally the fallout from those
missing external types.  For example, if the above missing type
caused us to remove `com.example.MyTextView`, then that may result in
us removing the method `GetMyTextView()` due to a missing return type.

Today these messages are only displayed in a Diagnostic log and are
often missed.  Instead, we are going to write them to a log file
called `java-resolution-report.log`, and then display a warning
notifying the user that some types could not be bound, which can be
double clicked to view the log file.

	warning BG8606: Some types or members could not be bound because referenced Java types could not be found. See the 'java-resolution-report.log' file for details.

Example `java-resolution-report.log`:

	==== Cycle 1 ====

	'[Class] com.example.MyTextView' was removed because the Java type 'androidx.appcompat.widget.AppCompatTextView' could not be found.

	==== Cycle 2 ====

	'[Method] com.example.MyActivity.getMyTextView' was removed because the Java type 'com.example.MyTextView' could not be found.


"Cycles" represent the passes through the type collection when
resolving the collection.  For example:

  - Cycle 1 removed type `com.example.MyType` because its external
    base type `android.util.List` was missing

  - Cycle 2 removed type `com.example.MyDerivedType` because its base
    type `com.example.MyType` is now missing

  - Cycle 3 removed method `com.example.Foo.getBar()` because it
    returns `com.example.MyDerivedType`, which is now missing

This distinction can be interesting, because Cycle 1 removals are
generally due to missing `.jar` dependencies, whereas the remaining
cycles are just the internal fallout from previous cycles, which will
largely be fixed by fixing the first cycle issues.


~~ Additional Fixes ~~

There are several issues with the `ApiXmlAdjuster` process that *can*
be fixed by this change.  For the initial purpose of being able to
verify that we produce the same output, these have been disabled in
the new `JavaTypeSystem` code.

  * [ApiXmlAdjuster removes required methods from interfaces][0] -
    Currently disabled via flag
    `TypeResolutionOptions.RemoveInterfacesWithUnresolvableMembers`.

  * [ApiXmlAdjuster fails to resolve parent type parameters for nested types][1] -
    Currently commented out in `JavaTypeModel.GetApplicableTypeParameters()`.

  * [ApiXmlAdjuster doesn't remove nested types][2] -
    Currently enabled, compares have been done with a custom
    `ApiXmlAdjuster` where this has been fixed.

  * [ApiXmlAdjuster prefers JavaList over ArrayList][3] -
    Currently enabled, compares have been done with a custom
    `ApiXmlAdjuster` where this has been fixed.


~~ Performance ~~

Although performance isn't really a focus, there are wins here as well,
largely due to only resolving reference types that are actually
referenced.  Example: `ApiXmlAdjuster` fully resolves all 5K+ types in
`Mono.Android.dll`, while `JavaTypeSystem` only resolves the ones
needed by the library being bound.

Time to resolve `androidx.appcompat.appcompat`:

  * ApiXmlAdjuster: 2849ms
  * JavaTypeSystem: 1514ms


~~ Testing ~~

  - Unit tests have been ported from `ApiXmlAdjuster-Tests`
  - Tested for identical output for 136 current AndroidX packages
  - Tested for identical output for 140 current GPS/FB packages

Note `Mono.Android.dll` does not use `ApiXmlAdjuster`, so no testing
was performed with it.

[0]: #814
[1]: #815
[2]: #852
[3]: #853
@jonpryor jonpryor modified the milestones: Under consideration, NET7 Mar 8, 2022
@jpobst
Copy link
Contributor

jpobst commented May 23, 2022

I'm not sure this really explains what the issue is, or what the desired fix is. Resolving all Java types and removing types and members that depend on types we cannot resolve is a needed step in the binding process, lest we create broken bindings.

I think we would need to explore:

  • Cases where something is getting removed that shouldn't get removed
  • What is the appropriate fix(es) to solve those cases

For example, in this reported case:

Error while processing type '[Class] com.vmware.chameleon.http.HttpRequest..serializer': Type 'kotlinx.serialization.internal.GeneratedSerializer' was not found.

Why is GeneratedSerializer not found? Why do we still want to bind HttpRequest..serializer if it requires the missing GeneratedSerializer?

@jpobst jpobst removed this from the 7.0.0 milestone Jul 5, 2022
@jpobst jpobst removed the enhancement Proposed change to current functionality label Apr 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generator Issues binding a Java library (generator, class-parse, etc.)
Projects
None yet
Development

No branches or pull requests

3 participants