You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by as...@apache.org on 2008/04/02 02:23:10 UTC
svn commit: r643662 - in /incubator/buildr/trunk:
doc/pages/packaging.textile doc/pages/whats_new.textile
lib/buildr/core/package.rb spec/packaging_spec.rb
Author: assaf
Date: Tue Apr 1 17:23:09 2008
New Revision: 643662
URL: http://svn.apache.org/viewvc?rev=643662&view=rev
Log:
Added: Ability to create a package that is not an artifact and specify the
target file using the :file argument.
Modified:
incubator/buildr/trunk/doc/pages/packaging.textile
incubator/buildr/trunk/doc/pages/whats_new.textile
incubator/buildr/trunk/lib/buildr/core/package.rb
incubator/buildr/trunk/spec/packaging_spec.rb
Modified: incubator/buildr/trunk/doc/pages/packaging.textile
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/doc/pages/packaging.textile?rev=643662&r1=643661&r2=643662&view=diff
==============================================================================
--- incubator/buildr/trunk/doc/pages/packaging.textile (original)
+++ incubator/buildr/trunk/doc/pages/packaging.textile Tue Apr 1 17:23:09 2008
@@ -35,121 +35,99 @@
Translation: you can create multiple packages from the same project.
-h2. Packaging JARs
+h2. Specifying And Referencing Packages
-There are several ways you can configure the JAR package. We'll start with the
-MANIFEST.MF file. You can include a specific file:
+Buildr supports several packaging types, and so when dealing with packages, you
+have to indicate the desired package type. The packaging type can be the first
+argument, or the value of the @:type@ argument. The following two are
+equivalent:
{{{!ruby
-package(:jar).with :manifest=>_('src/main/MANIFEST.MF')
+package :jar
+package :type=>:jar
}}}
-Or generate a manifest from a hash:
+If you do not specify a package type, Buildr will attempt to infer one.
-{{{!ruby
-package(:jar).with :manifest=>{ 'Copyright'=>'Acme Inc (C) 2007' }
-}}}
+In the documentation you will find a number of tasks dealing with specific
+packaging types (@ZipTask@, @JarTask@, etc). The @package@ method is a
+convenience mechanism that sets up the package for you associates it with
+various project life cycle tasks.
-You can also generate a JAR with no manifest with the value @false@, create a
-manifest with several sections using an array of hashes, or create it from a
-proc.
-
-In large projects, where all the packages use the same manifest, it's easier to
-set it once on the top project using the @manifest@ project property.
-Sub-projects inherit the property from their parents, and the @package@ method
-uses that property if you don't override it, as we do above.
-
-For example, we can get the same result by specifying this at the top project:
+To package a particular file, use the @:file@ argument, for example:
{{{!ruby
-manifest['Copyright'] = 'Acme Inc (C) 2007'
+package :zip, :file=>_('target/interesting.zip')
}}}
-If you need to mix-in the project's manifest with values that only one package
-uses, you can do so easily:
-
-{{{!ruby
-package(:jar).with :manifest=>manifest.merge('Main-Class'=>'com.acme.Main')
-}}}
-
-If you need to include more files in the @META-INF@ directory, you can use the
-@:meta_inf@ option. You can give it a file, or array of files. And yes, there
-is a @meta_inf@ project property you can set once to include the same set of
-file in all the JARs. It works like this:
+This returns a file task that will run as part of the project's @package@ task
+(generating all packages). It will invoke the @build@ task to generate any
+necessary prerequisites, before creating the specified file.
-{{{!ruby
-meta_inf << file('DISCLAIMER') << file('NOTICE')
-}}}
+The package type does not have to be the same as the file name extension, but
+if you don't specify the package type, it will be inferred from the extension.
-If you have a @LICENSE@ file, it's already included in the @meta_inf@ list of
-files.
+Most often you will want to use the second form to generate packages that are
+also artifacts. These packages have an artifact specification, which you can
+use to reference them from other projects (and buildfiles). They are also
+easier to share across projects: artifacts install themselves in the local
+repository when running the @install@ task, and upload to the remote repository
+when running the @upload@ task (see "Installing and
+Uploading":#installing_and_uploading).
-Other than that, @package :jar@ includes the contents of the compiler's target
-directory and resources, which most often is exactly what you intend it to do.
-If you want to include other files in the JAR, instead or in addition, you can
-do so using the @include@ and @exclude@ methods. If you do not want the target
-directory included in your JAR, simply call the @clean@ method on it.
+The artifact specification is based on the project name (using dashes instead
+of colons), group identifier and version number, all three obtained from the
+project definition. You can specify different values using the @:id@,
+@:group@, @:version@ and @:classifier@ arguments. For example:
{{{!ruby
-package(:jar).clean.include( only_these_files )
-}}}
-
+define 'killer-app', :version=>'1.0' do
+ # Generates silly-1.0.jar
+ package :jar, :id=>'silly'
-h2. Packaging WARs
-
-Pretty much everything you know about JARs works the same way for WARs, so
-let's just look at the differences.
-
-Without much prompting, @package :war@ picks the contents of the
-@src/main/webapp@ directory and places it at the root of the WAR, copies the
-compiler target directory into the @WEB-INF/classes@ path, and copies any
-compiled dependencies into the @WEB-INF/libs@ paths.
-
-Again, you can use the @include@ and @exclude@ methods to change the contents
-of the WAR. There are two convenience options you can use to make the more
-common changes. If you need to include a classes directory other than the
-default:
+ # Generates killer-app-la-web-1.x.war
+ project 'la-web' do
+ package :war, :version=>'1.x'
+ end
-{{{!ruby
-package(:war).with :classes=>_('target/additional')
+ # Generates killer-app-the-api-1.0-sources.zip
+ project 'teh-api' do
+ package :zip, :classifier=>'sources'
+ end
+end
}}}
-If you want to include a different set of libraries other than the default:
+The file name is determined from the identifier, version number, classifier and
+extension associated with that packaging type.
-{{{!ruby
-package(:war).with :libs=>MYSQL_JDBC
-}}}
+If you do not specify the packaging type, Buildr attempt to infer it from the
+project definition. In the general case it will use the default packaging
+type, ZIP. A project that compiles Java classes will default to JAR packaging;
+for other languages, consult the specific documentation.
+
+A single project can create multiple packages. For example, a Java project may
+generate a JAR package for the runtime library and another JAR containing just
+the API; a ZIP file for the source code and another ZIP for the documentation.
+Make sure to always call @package@ with enough information to identify the
+specific package you are referencing. Even if the project only defines a
+single package, calling the @package@ method with no arguments does not
+necessarily refer to that one.
-Both options accept a single value or an array. The @:classes@ option accepts
-the name of a directory containing class files, initially set to
-@compile.target@ and @resources.target@. The @:libs@ option accepts artifact
-specifications, file names and tasks, initially set to include everything in
-@compile.dependencies@.
-
-As you can guess, the package task has two attributes called @classes@ and
-@libs@; the @with@ method merely sets their value. If you need more precise
-control over these arrays, you can always work with them directly, for example:
+You can use the @packages@ method to obtain a list of all packages defined in
+the project, for example:
{{{!ruby
-# Add an artifact to the existing set:
-package(:war).libs += artifacts(MYSQL_JDBC)
-# Remove an artifact from the existing set:
-package(:war).libs -= artifacts(LOG4J)
-# List all the artifacts:
-puts 'Artifacts included in WAR package:'
-puts package(:war).libs.map(&:to_spec)
+project('killer-app:teh-impl').packages.first
+project('killer-app:teh-impl').packages.select { |pkg| pkg.type == :zip }
}}}
h2. Packaging ZIPs
-JARs and WARs are pretty much glorified ZIPs. The ZIP packaging is much
-simpler, it doesn't support the @:manifest@ and @:meta_inf@ options. Sounds
-simpler, and it is.
-
-Which is why we're going to use this section to tell you a bit more about ZIP
-file packaging. But anything you read here, you can also use with @package
-:jar@ and @package :war@. They're all ZIP files, afterall.
+ZIP is the most common form of packaging, used by default when no other
+packaging type applies. It also forms the basis for many other packaging types
+(e.g. JAR and WAR). Most of what you'll find here applies to other packaging
+types.
Let's start by including additional files in the ZIP package. We're going to
include the @target/docs@ directory and @README@ file:
@@ -247,6 +225,115 @@
}}}
+h2. Packaging JARs
+
+JAR packages extend ZIP packages with support for Manifest files and the
+META-INF directory. They also default to include the class files found in the
+@target/classes@ directory.
+
+You can tell the JAR package to include a particular Manifest file:
+
+{{{!ruby
+package(:jar).with :manifest=>_('src/main/MANIFEST.MF')
+}}}
+
+Or generate a manifest from a hash:
+
+{{{!ruby
+package(:jar).with :manifest=>{ 'Copyright'=>'Acme Inc (C) 2007' }
+}}}
+
+You can also generate a JAR with no manifest with the value @false@, create a
+manifest with several sections using an array of hashes, or create it from a
+proc.
+
+In large projects, where all the packages use the same manifest, it's easier to
+set it once on the top project using the @manifest@ project property.
+Sub-projects inherit the property from their parents, and the @package@ method
+uses that property if you don't override it, as we do above.
+
+For example, we can get the same result by specifying this at the top project:
+
+{{{!ruby
+manifest['Copyright'] = 'Acme Inc (C) 2007'
+}}}
+
+If you need to mix-in the project's manifest with values that only one package
+uses, you can do so easily:
+
+{{{!ruby
+package(:jar).with :manifest=>manifest.merge('Main-Class'=>'com.acme.Main')
+}}}
+
+If you need to include more files in the @META-INF@ directory, you can use the
+@:meta_inf@ option. You can give it a file, or array of files. And yes, there
+is a @meta_inf@ project property you can set once to include the same set of
+file in all the JARs. It works like this:
+
+{{{!ruby
+meta_inf << file('DISCLAIMER') << file('NOTICE')
+}}}
+
+If you have a @LICENSE@ file, it's already included in the @meta_inf@ list of
+files.
+
+Other than that, @package :jar@ includes the contents of the compiler's target
+directory and resources, which most often is exactly what you intend it to do.
+If you want to include other files in the JAR, instead or in addition, you can
+do so using the @include@ and @exclude@ methods. If you do not want the target
+directory included in your JAR, simply call the @clean@ method on it:
+
+{{{!ruby
+package(:jar).clean.include( only_these_files )
+}}}
+
+
+h2. Packaging WARs
+
+Pretty much everything you know about JARs works the same way for WARs, so
+let's just look at the differences.
+
+Without much prompting, @package :war@ picks the contents of the
+@src/main/webapp@ directory and places it at the root of the WAR, copies the
+compiler target directory into the @WEB-INF/classes@ path, and copies any
+compiled dependencies into the @WEB-INF/libs@ paths.
+
+Again, you can use the @include@ and @exclude@ methods to change the contents
+of the WAR. There are two convenience options you can use to make the more
+common changes. If you need to include a classes directory other than the
+default:
+
+{{{!ruby
+package(:war).with :classes=>_('target/additional')
+}}}
+
+If you want to include a different set of libraries other than the default:
+
+{{{!ruby
+package(:war).with :libs=>MYSQL_JDBC
+}}}
+
+Both options accept a single value or an array. The @:classes@ option accepts
+the name of a directory containing class files, initially set to
+@compile.target@ and @resources.target@. The @:libs@ option accepts artifact
+specifications, file names and tasks, initially set to include everything in
+@compile.dependencies@.
+
+As you can guess, the package task has two attributes called @classes@ and
+@libs@; the @with@ method merely sets their value. If you need more precise
+control over these arrays, you can always work with them directly, for example:
+
+{{{!ruby
+# Add an artifact to the existing set:
+package(:war).libs += artifacts(MYSQL_JDBC)
+# Remove an artifact from the existing set:
+package(:war).libs -= artifacts(LOG4J)
+# List all the artifacts:
+puts 'Artifacts included in WAR package:'
+puts package(:war).libs.map(&:to_spec)
+}}}
+
+
h2. Packaging AARs
Axis2 service archives are similar to JAR's (compiled classes go into the root
@@ -457,85 +544,6 @@
uri = URI("sftp://#{username}:#{password}@var/www/docs")
uri.upload file('docs')
end
-}}}
-
-
-h2. Specifying And Referencing Packages
-
-If it's a package to us and an artifact to someone else, then it must have an
-artifact specification. Right? And to have an artifact specification, it must
-have a group identifier, artifact identifier, version number and optional
-classifier. Where do they come from?
-
-These are all options you can pass to the @package@ method. Or, you can let it
-pick them from the project definition. In this case it pays to be lazy, and
-easier to just specify the group identifier and version number as project
-properties. The artifact identifier is just the project (full) name, with
-colons (@:@) replaced by dashes (@-@).
-
-Let's run an example and find out:
-
-{{{!ruby
-puts project('killer-app:teh-impl').packages.first.to_spec
-=> acme:killer-app-teh-impl:1.0:jar
-}}}
-
-What you don't like, you can always change. For example:
-
-{{{!ruby
-# Generates silly-1.0.jar
-package :jar, :id=>'silly'
-
-# Generates killer-app-la-web-1.x.war
-project 'la-web' do
- package :war, :version=>'1.x'
-end
-
-# Generates killer-app-the-api-1.0-sources.zip
-project 'teh-api' do
- package :zip, :classifier=>'sources'
-end
-}}}
-
-You can get all the packages created by a project and filter them yourself.
-Let's illustrate that with an example that prints artifact specifications for
-all the packages we generate through our projects:
-
-{{{!ruby
-puts projects.map(&:packages).flatten.map(&:to_spec).join(', ')
-=> acme:killer-app:zip:sources:1.0, acme:killer-app-teh-api:jar:1.0,
- acme:killer-app-teh-impl:jar:1.0, acme:killer-app-la-web:war:1.0
-}}}
-
-p(tip). The @map@ method takes an array, runs the block with each item, and
-returns a new array. The @&:@ shortcut is not a standard part of Ruby but a
-useful convenience that calls this method on each instance. So
-@map(&:packages)@ is just a shorter way of saying @map { |obj| obj.packages }@.
-
-Now reduce it to only use the JAR packages:
-
-{{{!ruby
-puts projects.map(&:packages).flatten.select { |pkg| pkg.type == :jar }.
- map(&:to_spec).join(', ')
-}}}
-
-But for a single package, we're just going to call @package@ with the right
-artifact specification. Each time you call @package@ on a project, it returns
-a file task for that package. It only creates the file task once, so call it
-twice, and you'll get the same file task. We can use that to our advantage:
-
-{{{!ruby
-compile.with project('teh-api').package(:jar)
-}}}
-
-But remember, @package@ decides which file task to return from the combination
-of file type, artifact and group identifiers, version number and classifier.
-So we need to pass the very same arguments to retrieve the very same package.
-For example:
-
-{{{!ruby
-projec(t'killer-app:teh-impl').package(:war)
-project('killer-app').package(:zip, :classifier=>'docs')
}}}
Modified: incubator/buildr/trunk/doc/pages/whats_new.textile
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/doc/pages/whats_new.textile?rev=643662&r1=643661&r2=643662&view=diff
==============================================================================
--- incubator/buildr/trunk/doc/pages/whats_new.textile (original)
+++ incubator/buildr/trunk/doc/pages/whats_new.textile Tue Apr 1 17:23:09 2008
@@ -56,6 +56,24 @@
Read more about "using Groovy":building.html#compiling_groovy.
+h4. Packaging Files
+
+The @package@ method is convenient enough that you can now use it to generate
+artifacts, an in addition to generate regular file tasks, specifying the file
+name using the @:file@ attribute. For example:
+
+{{{!ruby
+package :zip, :file=>_('target/interesting.zip')
+}}}
+
+Since this package is not an artifact and does not have a specification, it
+will not automatically install itself in the local repository or upload to a
+remote repository. For these, use the @package@ method as before.
+
+Read more about "the package
+method":packaging.html#specifying_and_referencing_packages.
+
+
h4. Packaging EARs
EAR packages support four component types:
Modified: incubator/buildr/trunk/lib/buildr/core/package.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/buildr/core/package.rb?rev=643662&r1=643661&r2=643662&view=diff
==============================================================================
--- incubator/buildr/trunk/lib/buildr/core/package.rb (original)
+++ incubator/buildr/trunk/lib/buildr/core/package.rb Tue Apr 1 17:23:09 2008
@@ -129,21 +129,28 @@
# end
def package(*args)
spec = Hash === args.last ? args.pop.dup : {}
- type = args.shift || compile.packaging || :zip
- rake_check_options spec, *ActsAsArtifact::ARTIFACT_ATTRIBUTES
- spec[:id] ||= self.id
- spec[:group] ||= self.group
- spec[:version] ||= self.version
- spec[:type] ||= type
+ if spec[:file]
+ rake_check_options spec, :file, :type
+ spec[:type] = args.shift || spec[:type] || spec[:file].split('.').last
+ file_name = spec[:file]
+ else
+ rake_check_options spec, *ActsAsArtifact::ARTIFACT_ATTRIBUTES
+ spec[:id] ||= self.id
+ spec[:group] ||= self.group
+ spec[:version] ||= self.version
+ spec[:type] = args.shift || spec[:type] || compile.packaging || :zip
+ end
- packager = method("package_as_#{type}") rescue fail("Don't know how to create a package of type #{type}")
+ packager = method("package_as_#{spec[:type]}") rescue fail("Don't know how to create a package of type #{spec[:type]}")
if packager.arity == 1
- spec = send("package_as_#{type}_spec", spec) if respond_to?("package_as_#{type}_spec")
- file_name = path_to(:target, Artifact.hash_to_file_name(spec))
+ unless file_name
+ spec = send("package_as_#{spec[:type]}_spec", spec) if respond_to?("package_as_#{spec[:type]}_spec")
+ file_name = path_to(:target, Artifact.hash_to_file_name(spec))
+ end
package = packages.find { |pkg| pkg.name == file_name } || packager.call(file_name)
else
warn_deprecated "We changed the way package_as methods are implemented. See the package method documentation for more details."
- file_name = path_to(:target, Artifact.hash_to_file_name(spec))
+ file_name ||= path_to(:target, Artifact.hash_to_file_name(spec))
package = packager.call(file_name, spec)
end
@@ -154,7 +161,9 @@
task 'package'=>package
package.enhance [task('build')]
- unless package.respond_to?(:install)
+ if spec[:file]
+ class << package ; self ; end.send(:define_method, :type) { spec[:type] }
+ elsif !package.respond_to?(:install)
# Make it an artifact using the specifications, and tell it how to create a POM.
package.extend ActsAsArtifact
package.send :apply_spec, spec.only(*Artifact::ARTIFACT_ATTRIBUTES)
Modified: incubator/buildr/trunk/spec/packaging_spec.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/packaging_spec.rb?rev=643662&r1=643661&r2=643662&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/packaging_spec.rb (original)
+++ incubator/buildr/trunk/spec/packaging_spec.rb Tue Apr 1 17:23:09 2008
@@ -302,6 +302,125 @@
end
+
+
+
+describe Project, '#package file' do
+ it 'should be a file task' do
+ define 'foo' do
+ package(:zip, :file=>'foo.zip').should be_kind_of(Rake::FileTask)
+ end
+ end
+
+ it 'should not require id, project or version' do
+ define 'foo', :group=>nil do
+ lambda { package(:zip, :file=>'foo.zip') }.should_not raise_error
+ lambda { package(:zip, :file=>'bar.zip', :id=>'error') }.should raise_error
+ lambda { package(:zip, :file=>'bar.zip', :group=>'error') }.should raise_error
+ lambda { package(:zip, :file=>'bar.zip', :version=>'error') }.should raise_error
+ end
+ end
+
+ it 'should not provide project or version' do
+ define 'foo' do
+ package(:zip, :file=>'foo.zip').tap do |pkg|
+ pkg.should_not respond_to(:group)
+ pkg.should_not respond_to(:version)
+ end
+ end
+ end
+
+ it 'should provide packaging type' do
+ define 'foo', :version=>'1.0' do
+ zip = package(:zip, :file=>'foo.zip')
+ jar = package(:jar, :file=>'bar.jar')
+ zip.type.should eql(:zip)
+ jar.type.should eql(:jar)
+ end
+ end
+
+ it 'should assume packaging type from extension if unspecified' do
+ define 'foo', :version=>'1.0' do
+ package(:file=>'foo.zip').class.should be(Buildr::ZipTask)
+ define 'bar' do
+ package(:file=>'bar.jar').class.should be(Buildr::Packaging::Java::JarTask)
+ end
+ end
+ end
+
+ it 'should support different packaging types' do
+ define 'foo', :version=>'1.0' do
+ package(:jar, :file=>'foo.jar').class.should be(Buildr::Packaging::Java::JarTask)
+ end
+ define 'bar' do
+ package(:type=>:war, :file=>'bar.war').class.should be(Buildr::Packaging::Java::WarTask)
+ end
+ end
+
+ it 'should fail if packaging not supported' do
+ lambda { define('foo') { package(:weirdo, :file=>'foo.zip') } }.should raise_error(RuntimeError, /Don't know how to create a package/)
+ end
+
+ it 'should create different tasks for each file' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip')
+ package(:jar, :file=>'foo.jar')
+ end
+ project('foo').packages.uniq.size.should be(2)
+ end
+
+ it 'should return the same task for subsequent calls' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip').should eql(package(:file=>'foo.zip'))
+ end
+ end
+
+ it 'should point to specified file' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip').should point_to_path('foo.zip')
+ package(:zip, :file=>'target/foo-1.0.zip').should point_to_path('target/foo-1.0.zip')
+ end
+ end
+
+ it 'should create prerequisite for package task' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip')
+ end
+ project('foo').task('package').prerequisites.should include(*project('foo').packages)
+ end
+
+ it 'should create task requiring a build' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip').prerequisites.should include(build)
+ end
+ end
+
+ it 'should create specified file during build' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip')
+ end
+ lambda { project('foo').task('package').invoke }.should change { File.exist?('foo.zip') }.to(true)
+ end
+
+ it 'should do nothing for installation/upload' do
+ define 'foo', :version=>'1.0' do
+ package(:zip, :file=>'foo.zip')
+ end
+ lambda do
+ task('install').invoke
+ task('upload').invoke
+ task('uninstall').invoke
+ end.should_not raise_error
+ end
+
+end
+
+
+
+
+
+
+
describe Rake::Task, ' package' do
it 'should be local task' do
define 'foo', :version=>'1.0' do