h1. Artifacts

In Buildr, almost everything is a file or a file task. You compile source files
that come from the file system using dependencies found on the file system,
generating even more files. But how do you get these dependencies to start
with, and how do you share them with others?

Artifacts.  We designed Buildr to work as a drop-in replacement for Maven 2.0,
and share artifacts through the same local and remote repositories.  Artifact
tasks know how to download a file from one of the remote repositories, and
install it in the local repository, where Buildr can find it. Packages know how
to create files and upload them to remote repositories.

We'll get into all of that in a second, but first, let's introduce the artifact
specification. It's a simple string that takes one of two forms:

{{{
group:id:type:version
group:id:type:classifier:version
}}}

For example, @'org.apache.axis2:axis2:jar:1.2'@ refers to an artifact with
group identifier org.apache.axis2, artifact identifier axis2, a JAR file with
version 1.2.  Classifiers are typically used to distinguish between similar
file types, for example, a source distribution and a binary distribution that
otherwise have the same identifier and are both ZIP files.


h2. Specifying Artifacts

If your Buildfile spells out @'org.apache.axis2:axis2:jar:1.2'@ more than once,
you're doing something wrong.  Repeating the same string over and over will
make your code harder to maintain.  You'll know that when you upgrade to a new
version in one place, forget to do it in another, and end up with a mismatch.

You can use Ruby's syntax to do simple string substitution, for example:

{{{!ruby
AXIS_VERSION = '1.2'

compile.with "org.apache.axis2:axis2:jar:#{AXIS_VERSION}"
}}}

Better yet, you can define all your artifacts at the top of the Buildfile and
use constants to reference them in your project definition.  For example:

{{{!ruby
AXIS2 = 'org.apache.axis2:axis2:jar:1.2'

compile.with AXIS2
}}}

Note that we're not using a separate constant for the version number.  In our
experience, it's unnecessary.  The version number intentionally appears at the
end of the string, where it stands out easily.

If you have a set of artifacts that belong to the same group and version, and
that's quite common, you can use the @group@ shortcut:

{{{!ruby
AXIOM  = group('axiom-api', 'axiom-impl', 'axiom-dom',
  :under=>'org.apache.ws.commons.axiom', :version=>'1.2.4')
}}}

p(note). Buildr projects also define a @group@ attribute which can lead to
some confusion.  If you want to define an artifact group within a project
definition, you should use the explicit qualifier @Buildr::group@.

If you have several artifacts you always use together, consider placing them in
an array.  Methods that accept lists of artifacts also accept arrays.  For
example:

{{{!ruby
OPENJPA = ['org.apache.openjpa:openjpa-all:jar:0.9.7',
  'net.sourceforge.serp:serp:jar:1.12.0']
AXIS_OF_WS = [AXIS2, AXIOM]

compile.with OPENJPA, AXIS_OF_WS
}}}

Another way to group related artifacts together and access them individually is
using the @struct@ shortcut.  For example:

{{{!ruby
JAVAX = struct(
  :activation  =>'javax.activation:activation:jar:1.1',
  :persistence =>'javax.persistence:persistence-api:jar:1.0',
  :stream      =>'stax:stax-api:jar:1.0.1',
)

compile.with JAVAX.persistence, OPENJPA
}}}

In our experience, using constants in this manner makes your Buildfile much
easier to write and maintain.

And, of course, you can always place your artifact specifications in a separate
file and require it into your Buildfile.  For example, if you're working on
several different projects that all share the same artifacts:

{{{!ruby
require '../shared/artifacts'
}}}

When you use @require@, Ruby always looks for a filename with the @.rb@
extension, so in this case it expects to find @artifacts.rb@ in the @shared@
directory.

One last thing.  You can also treat artifact specifications as hashes.  For
example:

{{{!ruby
AXIS = { :group=>'org.apache.axis2', :id=>'axis2', :version=>'1.2' }
compile.with AXIS
puts compile.dependencies.first.to_hash
=> { :group=>'org.apache.axis2', :id=>'axis2',
     :version=>'1.2', :type=>:jar }
}}}


h2. Specifying Repositories

Buildr can download artifacts for you, but only if you tell it where to find
them.  You need to specify at least one remote repository, from which to
download these artifacts.

When you call @repositories.remote@, you get an array of URLs for the various
remote repositories.  Initially, it's an empty array, to which you can add new
repositories.  For example:

{{{!ruby
repositories.remote << 'http://www.ibiblio.org/maven2/'
}}}

If you need to use a proxy server to access remote repositories, you can set
the environment variable @HTTP_PROXY@ to the proxy server URL.  You can also
work without a proxy for certain hosts by specifying the @NO_PROXY@ environment
variable.  For example:

{{{!sh
$ export HTTP_PROXY = 'http://myproxy:8080'
$ export NO_PROXY = '*.mycompany.com,localhost,special:800'
}}}

Alternatively you can use the Buildr options @proxy.http@ and @proxy.exclude@:

{{{!ruby
options.proxy.http = 'http://myproxy:8080'
options.proxy.exclude << '*.mycompany.com'
options.proxy.exclude << 'localhost'
}}}

All the artifacts download into the local repository.  Since all your projects
share the same local repository, you only need to download each artifact once.
Buildr was designed to be used alongside Maven 2.0, for example, when migrating
projects from Maven 2.0 over to Buildr.  By default it will share the same
local repository, expecting the repository to be the @.m2/repository@ directory
inside your home directory.

You can choose to relocate the local repository by giving it a different path,
for example:

{{{!ruby
repositories.local = '/usr/local/maven/repository'
}}}

That's one change you don't want to commit into the Buildfile, so the best
place to do it is in your home directory's @buildr.rb@ file.

Buildr downloads artifacts when it needs to use them, for example, to compile a
project.  You don't need to download artifacts directly.  Except when you do,
for example, if you want to download all the latest artifacts and then go
off-line.  It's as simple as:

{{{!sh
$ buildr artifacts
}}}


h2. Downloading Artifacts

Within your buildfile you can download artifacts directly by invoking them, for
example:

{{{!ruby
artifact('org.apache.openjpa:openjpa-all:jar:0.9.7').invoke
artifacts(OPENJPA).each(&:invoke)
}}}

When you let Buildr download artifacts for you, or by invoking the artifact
task yourself, it scans through the remote repositories assuming each
repository follows the Maven 2 structure.  Starting from the root repository
URL, it will look for each artifact using the path
@group/id/version/id-version.type@ (or ...@/id-version-classifier.type@).  The
group identifier becomes a path by turning periods (@.@) into slashes (@/@).
So to find @org.apache.axis2:axis2:jar:1.2@, we're going to look for
@org/apache/axis2/axis2/1.2/axis2-1.2.jar@.

You'll find a lot of open source Java libraries in public repositories that
support this structure (for example, the "Ibiblio
Maven":http://www.ibiblio.org/maven2/ repository).  And, of course, every
remote repository you setup for your projects.

But there are exceptions to the rule.  Say we want to download the Dojo widget
library and use it in our project.  It's available from the Dojo Web site, but
that site doesn't follow the Maven repository conventions, so our feeble
attempt to use existing remote repositories will fail.

We can still treat Dojo as an artifact, by telling Buildr where to download it
from:

{{{!ruby
DOJO = '0.2.2'

url = "http://download.dojotoolkit.org/release-#{DOJO}/dojo-#{DOJO}-widget.zip"
download(artifact("dojo:dojo:zip:widget:#{DOJO}")=>url)
}}}

Explaining how it works is tricky, skip if you don't care for the details.  On
the other hand, it will give you a better understanding of Buildr/Rake, so if
not now, come back and read it later.

We use the @artifact@ method to create an @Artifact@ task that references the
Dojo widget in our local repository.  The @Artifact@ task is a file task with
some additional behavior added by Buildr.  When you call @compile.with@, that's
exactly what it does internally, turning each of your artifact specifications
into an @Artifact@ task.

But the @Artifact@ task doesn't know how to download the Dojo widget, only how
to handle conventional repositories.  So we're going to create a download task
as well.  We use the @download@ method to create a file task that downloads the
file from a remote URL.  (Of course, it will only download the file if it
doesn't already exist.)

But which task gets used when?  We could have defined these tasks separately
and used some glue code to make one use the other.  Instead, we call @download@
with the results of @artifact@.  Essentially, we're telling @download@ to use
the same file path as @artifact@.  So now we have two file tasks that point to
the very same file.  We wired them together.

You can't have more than one task pointing to the same file.  Rake's rule of
the road.  What Rake does is merge the tasks together, creating a single file
task for @artifact@, and then enhancing it with another action from @download@.
One task, two actions.  Statistically, we've doubled the odds that at least one
of these actions will manage to download the Dojo widget and install it in the
local repository.

Since we ordered the calls to @artifact@ first and @download@ second, we know
the actions will execute in that order.  But @artifact@ is slightly devilish:
when its action runs, it adds another action to the end of the list.  So the
@artifact@ action runs first, adds an action at the end, the @download@ action
runs second, and downloads the Dojo widget for us.  The second @artifact@
action runs last, but checks that the file already exist and doesn't try to
download it again.

Magic.


h2. Install and Upload

Generally you use artifacts that download from remote repositories into the
local repository, or artifacts packaged by the project itself (see
"Packaging":packaging.html), which are then installed into the local repository
and uploaded to the release server.

Some artifacts do not fall into either category.  In this example we're going
to download a ZIP file, extract a JAR file from it, and use that JAR file as an
artifact.  We would then expect to install this JAR in the local repository and
upload it to the release server, where it can be shared with other projects.

So let's start by creating a task that downloads the ZIP, and another one to
extract it and create the JAR file:

{{{!ruby
app_zip = download('target/app.zip'=>url)
bean_jar = file('target/app/bean.jar'=>unzip('target/app'=>app_zip))
}}}

When you call @artifact@, it returns an @Artifact@ task that points to the
artifact file in the local repository, downloading the file if it doesn't
already exist.  You can override this behavior by enhancing the task and
creating the file yourself (you may also want to create a POM file).  Or much
simpler, call the @from@ method on the artifact and tell it where to find the
source file.

So the next step is to specify the artifact and tell it to use the extracted
JAR file:

{{{!ruby
bean = artifact('example.com:beans:jar:1.0').from(bean_jar)
}}}

The artifact still points to the local repository, but when we invoke the task
it copies the source file over to the local repository, instead of attempting a
download.

Use the @install@ method if you want the artifact and its POM installed in the
local repository when you run the @install@ task.  Likewise, use the @upload@
method if you want the artifact uploaded to the release server when you run the
@upload@ task.  You do not need to do this on artifacts downloaded from a
remote server, or created with the @package@ method, the later are
automatically added to the list of installed/uploaded artifacts.

Our example ends by including the artifact in the @install@ and @upload@ tasks:

{{{!ruby
install bean
upload bean
}}}

p(tip).  Calling the @install@ (and likewise @upload@) method on an artifact
does not install the artifact, it simply adds a task that will execute when you
run @buildr install@.  If you need to download and install an artifact, invoke
the task directly with @install(<artifact>).invoke@.


Next we're going to "package some artifacts":packaging.html.
