Package Import Export (Buckminster)

From Eclipsepedia

Jump to: navigation, search

Two things happened over the last couple of days.

1. Adrian Skehill at Iona was confused because he felt that Buckminster didn't resolve all dependencies correctly. He wrote "Now when I run Buckminster to resolve all our components, it fails to pull in a few of them that are identified as dependencies, e.g. org.apache.batik.bridge requires rg.apache.batik.css.engine but this plugin was not imported into the workspace and I get some failures."

As it turns out, there is no such bundle dependency. The org.apache.batik.bridge bundle does however import several packages from org.apache.batik.css. There' no way we can resolve that if we don't have access to information that tells us exaclty what packages that different plug-ins are exporting. This coincided with a discussion that I saw on the Equinox dev list and it gave me the idea to todays blogpost.

2. Next, Michal discovers that in order to write the OBR service at Cloudsmith, it would be nice if we knew more about the bundles that we have discovered. Import/Export package declarations in particular.

I don't believe in coincidence :-). This is a clear sign that the time has come to actually introduce those package declarations in the CSpec. Question is, what do they look like? Are they just some kind of attributes? Well, no not really, since a) the package can have a version and b) an imported package doesn't specify any particular component. So, is the package a component then? No, that would be very inconvenient view for us since a component has some location, it can be built, etc. It would make an OSGi bundle have several cspecs (shrug). So no, the packages are, in my view at least, best represented as attributes.

An OSGi Import-Package is declared as:

import ::= package-names ( ';' parameter )*

the parameters reflect conditions based on user-defined properties but the following are reserved:

resolution (mandatory | optional)
version (an OSGi version range)
bundle-symbolic-name (name of bundle from which the package should be imported
bundle-version (version range of the bundle

Key points:

  • An import from a specific bundle can be specificed but the import can also be anonyous
  • Both the specified bundle and the imported package can have version ranges
  • Parameters may specify user defined properties that the targeted component export must have defined.

Our current dependency clause has the following properties today:

  • Component name (required)
  • Component type (required)
  • Version Range
  • LDAP filter (i.e. (&(target.os=win32)(target.arch=x86)... etc.)

If we change our dependency so that the component name is no longer required, then we could do imports very easilly by just adding a sub-element to the dependency. It would look like this:

<dependencies>
    <!-- This is an example of imports from some anonymous bundle -->
    <dependency componentType="osgi.bundle" versionType="osgi">
       <import attribute="x.y.z" versionDesignator="1.2.3"/>
       <import attribute="a.b.c" versionDesignator="0.3.1"/>
       <import attribute="r.s.t" versionDesignator="3.3.1"/>
    </dependency>

    <!-- This is an example of imports from an appointed bundle -->
    <dependency component="foo.bar" versionDesignator="1.2.3" componentType="osgi.bundle" versionType="osgi">
       <import attribute="x.y.z" versionDesignator="1.2.3"/>
       <import attribute="a.b.c" versionDesignator="0.3.1"/>
       <import attribute="r.s.t" versionDesignator="3.3.1"/>
    </dependency>
</dependencies>

This means that we group imports together when they all:

  • either belong to the same component or are all anonymous
  • belong to the same version of that component (if component is specified)
  • belong to the same type of component
  • share the same property constraints

If imports are declared that do not share all of that, more then one dependency must be used.

The effect of declaring an import would be twofold.

  1. A resolution would not be complete unless it is self contained, i.e. all anonymous packages must be possible to resolve within the transitive closure of the resolution.
  2. The imported attributes would end up in the same namespace as the attributes that are local to the cspec. Normally, this would cause a collision but we have to ignore that. In the OSGi world, this is how "package substitution" is performed and it is generally recommended that you always import the packages that you export (even if you also define them) so that the class-loader is free to choose one implementation when several implementations exists. Not our problem perhaps but we need to be able to express this.

OK, så that concludes the package import support. Please note that this mechanism is not confined to Java, nor does it mention packages. It's a generic constrained 'attribute' import mechanism.

What do we need to do in order to export? Well, most of it is there already. An export in cspec terminology is a public attribute. We lack the power to express an attribute version and attribute properties. OSGi also stipulates that a property can be exported as 'mandatory' which means that a corresponding import must request that attribute in order to be fulfilled by the given export. Normally, attributes that are not requested in the import are ignored by the resolver.

We need a way to say that an artifact or product 'defines' other attributes (puts them into existence) so that they can be referenced as attributes in their own right. I.e.:

<public>
  <artifact name="x.y" path="jars/x.y.jar">
    <define name="x.y.x"/>
    <define name="x.y.y"/>
    <define name="x.y.z"/>
  </artifact>
</public>

We also need to be able to specify properties that are common to the things that are defined but not common to the actual definer.

<public>
  <artifact name="x.y" path="jars/x.y.jar">
    <property key="foo" value="an artifact property"/>
    <definitions>
      <property key="type" value="java.package" mandatory="true"/>
      <define name="x.y.x">
        <property key="foobar" value="true"/>
      </define>
      <define name="x.y.y"/>
      <define name="x.y.z"/>
    <definitions>
  </artifact>
</public>

The pre-defined version property would be inhertied from the cspec by default but we would need to be able to override it. This is because a package version denotes a specification version whereas a cspec version often is something very different, i.e.

<public>
  <artifact name="x.y" path="jars/x.y.jar">
    <definitions>
      <property key="type" value="java.package" mandatory="true"/>
      <define name="x.y.x" version="1.2.3">
        <property key="foobar" value="true"/>
      </define>
      <define name="x.y.y" version="1.2.4"/>
      <define name="x.y.z"/>
    <definitions>
  </artifact>
</public>

An alternative to allowing imports to collide with other attributes would be to specify that an artifact or a definition also can be imported. I.e.

<public>
  <artifact name="x.y" path="jars/x.y.jar">
    <definitions>
      <property key="type" value="java.package" mandatory="true"/>
      <define name="x.y.x" version="1.2.3" import="true">
        <property key="foobar" value="true"/>
      </define>
      <define name="x.y.y" version="1.2.4" import="true"/>
      <define name="x.y.z" import="true"/>
    <definitions>
  </artifact>
</public>