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