Now that 1.3 is out, it's time to start talking about future releases.
 We've got the 1.3.x line of releases, for dealing with outstanding issues,
new ones we'll find along the way, and building on new features that showed
up in 1.3.

Separately, there's 1.4, the next major release and a place to make more
significant changes.  We just have to decide what those would be.

I propose that we focus 1.4 on improving the way we handle dependencies.

Specifically what I have in mind are transitive dependencies, version
matching, and OSGi support.  So I'll start by talking about some things I've
been looking into recently.


For starters, adding a dependency method to artifact (which also includes
packages) that returns an array of all the known dependencies for that
artifacts.  Ruby arrays are pretty powerful, you can do things like:

# Does it have a particular dependency?
foo.dependencies.any? { |dep| ... }
# Add new dependencies
bar.dependencies << ....
# Merge foo's dependencies into bar
bar.dependencies |= foo.dependencies

One problem that comes up frequently are broken dependency lists, they may
be missing a dependency, or reference the wrong version, or include one you
don't want to use.  Mutable arrays would make it easy to fix these broken
dependency lists, for example:

# Change the artifact in memory
artifact( spec ).exclude( dont-want-this ).include( but want this )
# And use it
compile.with spec

It will be even better to introduce a Dependencies class that extends array
and adds all sorts of convenience methods and lazy expansion, similar to
Rake's FileList.  Then you could do things like:

# Find dependency by its specification
foo.dependencies.has?( spec )
# Exclude all dependencies that match a pattern
foo.dependencies.exclude!( pattern )
# Expand all transitive dependencies and return flat list
foo.dependencies.expand
# Replace one with another
foo.dependencies.replace!( dont-want, want )

The API for methods like compile.with and compile.dependencies remain the
same, just changing the underlying implementation.

Packages would acquire dependencies from the project, for example,
package(:jar) would add all the dependencies from compile.dependencies,
which of course you can fix to your heart's content.


Version matching makes it possible to point at the most recent version that
satisfies a particular version requirement, so >=1.2 to get 1.2.0 or later,
or ~>1.2.3 to get any version between 1.2.3 and 1.3 (excluding).


OSGi is a set of big specs that boil a lot of oceans, the part I'm
particularly interested in, is managing dependencies.  When we started
working on Buildr, OSGi was used almost exclusively by Eclipse.  That
changed.  We're seeing more and more servers and frameworks adopting OSGi.

Imagine that we had OSGi support.  You will specify all the dependencies
once in the buildfile.  Those will be used to build the project, and also
incorporated into the package manifest.  Other projects could use these
packages, reading the dependency list from the package manifest.  More
important, when you get to deploy those packages, the runtime can use that
information to bring in the right dependencies.

That's one of those things that, once you use it, you won't want to go back.


The problem is, OSGi and Maven are incompatible with each other.  I'll try
to summarize the differences:

1. The OSGi package identifier is typically the same as the file name.  The
Maven package identifier combines group and artifact identifiers, only the
later is used as the file name.

2.  Both use four-part version numbers.  The first three are numerical (e.g.
1.2.3) and managed the same way, the fourth one is the qualifier.  OSGi
qualifiers are alphanumeric and compared lexically, so R1 comes before R2;
using number is not advised, 10 comes before 2.  Maven qualifiers are
numeric for releases, and alphanumeric for pre-releases; the qualifier 10
comes after 2, and 2 comes after R2.

3.  The rules for version matching are therefore not the same.  The same
rule could match different versions, depending on whethr you use OSGi or
Maven.

There's also the issue that OSGi keeps the meta-data inside the package,
while Maven requires the additional POM file, and if both are present we
have to decide which one to rely on.

It's possible to some extent to support both at the same time, but there
will always be that impedence mismatch.  If we do a good enough job, it will
probably only affect edge cases, but we need to be aware.

It will definitely be easier to pick one of the two, either OSGi or Maven,
and add transitive support and version matching based on that model, then do
translation when necessary (e.g. when reading POM file, generating OSGi
manifest).

If I had to start all over again, I would go for OSGi with Maven
compatibility layer for three simple reasons:

1. Runtime deployment
2. Simpler to implement, and if your build doesn't quite work, troubleshoot
and fix.
3. Works the same way as most other packaging schemes (e.g. Gems, Debian,
RPM).


Comments, ideas?

Assaf

Reply via email to