lib/java/packaging.rb in buildr-1.1.3 vs lib/java/packaging.rb in buildr-1.2.0

- old
+ new

@@ -1,9 +1,10 @@ require "core/project" require "java/artifact" require "java/java" require "java/compile" +require "java/test" require "tasks/zip" module Buildr module Java @@ -13,61 +14,43 @@ # the project definition, since it does all the heavy lifting. module Packaging MANIFEST_HEADER = "Manifest-Version: 1.0\nCreated-By: Buildr\n" - # Extends the ZipTask to create a JAR file. - # - # This task supports two additional attributes: manifest and meta-inf. - # - # The manifest attribute specifies how to create the MANIFEST.MF file. - # * A hash of manifest properties (name/value pairs). - # * An array of hashes, one for each section of the manifest. - # * A string providing the name of an existing manifest file. - # * A file task can be used the same way. - # * Proc or method called to return the contents of the manifest file. - # * False to not generate a manifest file. - # - # The meta-inf attribute lists one or more files that should be copied into - # the META-INF directory. - # - # For example: - # package(:jar).using(:manifest=>"src/MANIFEST.MF") - # package(:jar).meta_inf << file("README") - class JarTask < ZipTask + # Adds support for MANIFEST.MF and other META-INF files. + module WithManifest + class << self + protected + def included(mod) + mod.alias_method_chain :initialize, :manifest + mod.alias_method_chain :invoke_prerequisites, :manifest + mod.alias_method_chain :create, :manifest + end + end + # Specifies how to create the manifest file. attr_accessor :manifest # Specifies files to include in the META-INF directory. attr_accessor :meta_inf - def initialize(*args) #:nodoc: - super - @manifest = true - @meta_inf = [] - end + private - def []=(key, value) #:nodoc: - if key.to_sym == :manifest - self.manifest = value - elsif key.to_sym == :meta_inf - self.meta_inf = [value].flatten - else - super key, value - end - value + def invoke_prerequisites_with_manifest() + prerequisites << file(manifest.to_s) if String === manifest || Rake::Task === manifest + [meta_inf].flatten.each { |file| prerequisites << file(file.to_s) } + invoke_prerequisites_without_manifest end - def prerequisites() #:nodoc: - super + [ String === manifest ? file(manifest) : nil ].compact + - [meta_inf].flatten.map { |file| String === file ? file(file) : file } + def initialize_with_manifest(*args) + @manifest = false + @meta_inf = [] + initialize_without_manifest *args end - protected - - def create(zip) #:nodoc: + def create_with_manifest(zip) #:nodoc: [meta_inf].flatten.map(&:to_s).uniq.each { |file| zip.add "META-INF/#{File.basename(file)}", file } unless manifest == false zip.file.open("META-INF/MANIFEST.MF", "w") do |output| output << MANIFEST_HEADER if manifest @@ -78,51 +61,118 @@ output << manifest.reject { |section| section.empty? }.map { |section| section.map { |pair| pair.join(": ") }.sort.join("\n").concat("\n") }.join("\n") << "\n" when Proc, Method output << manifest.call - when String, Task + when String, Rake::Task output << File.read(manifest.to_s) end end end end - super zip + create_without_manifest zip end end + class ::Buildr::ZipTask + include WithManifest + end + + + # Extends the ZipTask to create a JAR file. + # + # This task supports two additional attributes: manifest and meta-inf. + # + # The manifest attribute specifies how to create the MANIFEST.MF file. + # * A hash of manifest properties (name/value pairs). + # * An array of hashes, one for each section of the manifest. + # * A string providing the name of an existing manifest file. + # * A file task can be used the same way. + # * Proc or method called to return the contents of the manifest file. + # * False to not generate a manifest file. + # + # The meta-inf attribute lists one or more files that should be copied into + # the META-INF directory. + # + # For example: + # package(:jar).with(:manifest=>"src/MANIFEST.MF") + # package(:jar).meta_inf << file("README") + class JarTask < ZipTask + + def initialize(*args) #:nodoc: + super + end + + # :call-seq: + # with(options) => self + # + # Additional + # Pass options to the task. Returns self. ZipTask itself does not support any options, + # but other tasks (e.g. JarTask, WarTask) do. + # + # For example: + # package(:jar).with(:manifest=>"MANIFEST_MF") + def with(*args) + super args.pop if Hash === args.last + include :from=>args + self + end + + end + # Extends the JarTask to create a WAR file. # # Supports all the same options as JarTask, in additon to these two options: # * :libs -- An array of files, tasks, artifact specifications, etc that will be added # to the WEB-INF/lib directory. # * :classes -- A directory containing class files for inclusion in the WEB-INF/classes # directory. # # For example: - # package(:war).using(:libs=>"log4j:log4j:jar:1.1") + # package(:war).with(:libs=>"log4j:log4j:jar:1.1") class WarTask < JarTask - def []=(key, value) #:nodoc: - case key.to_sym - when :libs - self.include Buildr.artifacts(value), :path=>"WEB-INF/lib" - when :classes - self.include value, :path=>"WEB-INF/classes", :as=>"." - else - super key, value - end - value + # Directories with class files to include under WEB-INF/classes. + attr_accessor :classes + + # Artifacts to include under WEB-INF/libs. + attr_accessor :libs + + def initialize(*args) #:nodoc: + super + @classes = [] + @libs = [] end + def invoke_prerequisites() #:nodoc: + @classes.to_a.flatten.each { |classes| path("WEB-INF/classes").include classes, :as=>"." } + path("WEB-INF/lib").include Buildr.artifacts(@libs.to_a.flatten) + super + end + + def libs=(value) #:nodoc: + @libs |= Buildr.artifacts(value) + end + + def classes=(value) #:nodoc: + @classes |= [value].flatten.map { |dir| file(dir.to_s) } + end + end end end + Project.on_define do |project| + # Need to run buildr before package, since package is often used as a dependency by tasks that + # expect build to happen. + task "package"=>task("build") + end + + class Project # Options accepted by #package method for all package types. PACKAGE_OPTIONS = [:group, :id, :version, :type, :classifier] @@ -159,111 +209,90 @@ license = project.file("LICENSE") File.exist?(license.to_s) ? [license] : [] end # :call-seq: - # package(type, options?) => task + # package(type, spec?) => task # # Defines and returns a package created by this project. # # The first argument declares the package type. For example, :jar to create a JAR file. - # The second argument provides additional options used when defining the package. + # The package is an artifact that takes its artifact specification from the project. + # You can override the artifact specification by passing various options in the second + # argument, for example: + # package(:zip, :classifier=>"sources") # - # The following options are supported by all package types: - # * :id -- The artifact identifier. By default, uses the project's #id property. - # * :group -- The group identifier. By default, uses the project's #group property. - # * :version -- The version number. By default, uses the project's #version property. - # * :classifier -- Artifact classifier. By default, the artifact has no classifier. + # Packages that are ZIP files provides various ways to include additional files, directories, + # and even merge ZIPs together. Have a look at ZipTask for more information. In case you're + # wondering, JAR and WAR packages are ZIP files. # - # The JAR packager adds the following options: + # You can also enhance a JAR package using the ZipTask#with method that accepts the following options: # * :manifest -- Specifies how to create the MANIFEST.MF. By default, uses the project's # #manifest property. # * :meta_inf -- Specifies files to be included in the META-INF directory. By default, # uses the project's #meta-inf property. - # * :include -- List of files and directories to include in the JAR. By default, - # includes the contents of the target/classes directory. # - # The WAR packager adds the following options: - # * :manifest -- See JAR. - # * :meta_inf -- See JAR. - # * :classes -- Directories of class files to include in WEB-INF/classes. By default, - # includes the contents of the target/classes directory. - # * :libs -- Artifacts and files to include in WEB-INF/libs. By default, includes the - # compile classpath. - # * :include -- List of files and directories to include in the JAR. By default, - # includes the contents of the src/main/webapp directory. + # The WAR package supports the same options and adds a few more: + # * :classes -- Directories of class files to include in WEB-INF/classes. Includes the compile + # target directory by default. + # * :libs -- Artifacts and files to include in WEB-INF/libs. Includes the compile classpath + # dependencies by default. # - # The ZIP packager adds the following options: - # * :include -- List of file and directories to include in the ZIP. - # - # In addition, you can always enhance the package task directly, e.g. by calling the - # ZipTask#include and ZipTask#with methods. - # # For example: # define "project" do # define "beans" do # package :jar # end # define "webapp" do # compile.with project("beans") - # package :war + # package(:war).with :libs=>MYSQL_JDBC # end - # package :zip, :classifier=>"sources", :include=>"." + # package(:zip, :classifier=>"sources").include path_to(".") # end # - # The first time you call package, it defines the new task. Afterwards, it only uses some of the - # options (file_name, id, etc) to find an existing package task and return it. + # Two other packaging types are: + # * package :sources -- Creates a ZIP file with the source code and classifier "sources", for use by IDEs. + # * package :javadoc -- Creates a ZIP file with the Javadocs and classifier "javadoc". You can use the + # javadoc method to further customize it. # - # For example: - # # Create a new package that includes an existing package. - # package(:war).include project("foo:bar").package(:jar) - # # A package is also an artifact. The following tasks operate on packages created by the project: - # rake deploy # Deploy packages created by the project - # rake install # Install packages created by the project - # rake package # Create packages - # rake uninstall # Remove previously installed packages + # buildr upload # Upload packages created by the project + # buildr install # Install packages created by the project + # buildr package # Create packages + # buildr uninstall # Remove previously installed packages # # If you want to add additional packaging types, implement a method with the name package_as_[type] - # that accepts two arguments, the file name and a hash of options. The method must yield to the - # block with the package only when first called to define the package, and must return the package - # from each call. + # that accepts two arguments, the file name and a hash of options. You can change the options and + # file name, e.g. to add a classifier or change the file type. Your method may be called multiple times, + # and must return the same file task on each call. def package(type = :jar, options = nil) options = options.nil? ? {} : options.dup options[:id] ||= self.id options[:group] ||= self.group options[:version] ||= self.version options[:type] = type file_name = path_to(:target, Artifact.hash_to_file_name(options)) packager = method("package_as_#{type}") rescue fail("Don't know how to create a package of type #{type}") - packager.call(file_name, options) do |package| + package = packager.call(file_name, options) { warn_deprecated "Yielding from package_as_ no longer necessary." } + unless packages.include?(package) # Make it an artifact using the specifications, and tell it how to create a POM. package.extend ActsAsArtifact package.send :apply_spec, Hash[*Artifact::ARTIFACT_ATTRIBUTES.map { |k| [ k,options[k]] }.flatten] # Another task to create the POM file. pom_spec = package.to_spec_hash.merge(:type=>:pom) pom = file(Buildr.repositories.locate(pom_spec)) pom.extend ActsAsArtifact pom.send :apply_spec, pom_spec pom.enhance do mkpath File.dirname(pom.name), :verbose=>false - File.open(pom.name, "w") do |file| - xml = Builder::XmlMarkup.new(:target=>file, :indent=>2) - xml.instruct! - xml.project do - xml.modelVersion "4.0.0" - xml.groupId pom.group - xml.artifactId pom.id - xml.version pom.version - xml.classifier pom.classifier if pom.classifier - end - end + File.open(pom.name, "w") { |file| file.write pom.pom_xml } end - # Make sure the package task creates it, and it invokes the build task first. + # We already run build before package, but we also need to do so if the package itself is + # used as a dependency, before we get to run the package task. task "package"=>package package.enhance [task("build")] # Install the artifact along with its POM. Since the artifact (package task) is created # in the target directory, we need to copy it into the local repository. However, the @@ -280,19 +309,20 @@ task "uninstall" do |task| verbose(Rake.application.options.trace || false) do [ installed, pom ].map(&:to_s).each { |file| rm file if File.exist?(file) } end end - task("deploy") { deploy(package, pom) } + task("upload") { package.pom.invoke ; package.pom.upload ; package.upload } # Add the package to the list of packages created by this project, and # register it as an artifact. The later is required so if we look up the spec # we find the package in the project's target directory, instead of finding it # in the local repository and attempting to install it. packages << package Artifact.register package, pom end + package end # :call-seq: # packages() => tasks # @@ -310,19 +340,23 @@ def package_as_jar(file_name, options) #:nodoc: unless Rake::Task.task_defined?(file_name) rake_check_options options, *PACKAGE_OPTIONS + [:manifest, :meta_inf, :include] Java::Packaging::JarTask.define_task(file_name).tap do |jar| - jar.manifest = options.has_key?(:manifest) ? options[:manifest] : manifest - jar.meta_inf = options[:meta_inf] || meta_inf + jar.with :manifest=>manifest, :meta_inf=>meta_inf + [:manifest, :meta_inf].each do |option| + if options.has_key?(option) + warn_deprecated "The :#{option} option in package(:jar) is deprecated, please use package(:jar).with(:#{option}=>) instead." + jar.with option=>options[option] + end + end if options[:include] + warn_deprecated "The :include option in package(:jar) is deprecated, please use package(:jar).include(files) instead." jar.include options[:include] else - # Can only decide on this once we're done configuring the compile task. - enhance { jar.include compile.target, :as=>"." } + jar.with compile.target unless compile.sources.empty? end - yield jar end else rake_check_options options, *PACKAGE_OPTIONS end file(file_name) @@ -330,32 +364,37 @@ def package_as_war(file_name, options) #:nodoc: unless Rake::Task.task_defined?(file_name) rake_check_options options, *PACKAGE_OPTIONS + [:manifest, :meta_inf, :classes, :libs, :include] Java::Packaging::WarTask.define_task(file_name).tap do |war| - war.manifest = options.has_key?(:manifest) ? options[:manifest] : manifest - war.meta_inf = options[:meta_inf] || meta_inf + war.with :manifest=>manifest, :meta_inf=>meta_inf + [:manifest, :meta_inf].each do |option| + if options.has_key?(option) + warn_deprecated "The :#{option} option in package :war is deprecated, please use package(:war).with(:#{option}=>) instead." + war.with option=>options[option] + end + end # Add libraries in WEB-INF lib, and classes in WEB-INF classes - if options[:classes] + if options.has_key?(:classes) + warn_deprecated "The :classes option in package(:war) is deprecated, please use package(:war).with(:classes=>) instead." war.with :classes=>options[:classes] else - # Can only decide on this once we're done configuring the compile task. - enhance { war.with :classes=>compile.target unless compile.sources.empty? } + war.with :classes=>compile.target unless compile.sources.empty? end - if options[:libs] + if options.has_key?(:libs) + warn_deprecated "The :libs option in package(:war) is deprecated, please use package(:war).with(:libs=>) instead." war.with :libs=>options[:libs].collect else - # Can only decide on this once we're done configuring the compile task. - enhance { war.with :libs=>compile.classpath } + war.with :libs=>compile.classpath end # Add included files, or the webapp directory. - if options[:include] + if options.has_key?(:include) + warn_deprecated "The :include option in package(:war) is deprecated, please use package(:war).include(files) instead." war.include options[:include] - elsif File.exist?(path_to("src/main/webapp")) - war.include path_to("src/main/webapp"), :as=>"." + else + path_to("src/main/webapp").tap { |path| war.with path if File.exist?(path) } end - yield war end else rake_check_options options, *PACKAGE_OPTIONS end file(file_name) @@ -364,17 +403,96 @@ def package_as_zip(file_name, options) #:nodoc: unless Rake::Task.task_defined?(file_name) rake_check_options options, *PACKAGE_OPTIONS + [:include] ZipTask.define_task(file_name).tap do |zip| if options[:include] + warn_deprecated "The :include option in package(:zip) is deprecated, please use package(:zip).include(files) instead." zip.include options[:include] end - yield zip end else rake_check_options options, *PACKAGE_OPTIONS end file(file_name) + end + + def package_as_sources(file_name, options) #:nodoc: + rake_check_options options, *PACKAGE_OPTIONS + options.merge!(:type=>:zip, :classifier=>"sources") + file_name = path_to(:target, Artifact.hash_to_file_name(options)) + ZipTask.define_task(file_name).tap { |zip| zip.include :from=>compile.sources } unless Rake::Task.task_defined?(file_name) + file(file_name) + end + + def package_as_javadoc(file_name, options) #:nodoc: + rake_check_options options, *PACKAGE_OPTIONS + options.merge!(:type=>:zip, :classifier=>"javadoc") + file_name = path_to(:target, Artifact.hash_to_file_name(options)) + unless Rake::Task.task_defined?(file_name) + ZipTask.define_task(file_name).tap { |zip| zip.include :from=>javadoc.target } + javadoc.options[:windowtitle] ||= project.comment || project.name + end + file(file_name) + end + + end + + class Project + + # :call-seq: + # package_with_sources(options?) + # + # Call this when you want the project (and all its sub-projects) to create a source distribution. + # You can use the source distribution in an IDE when debugging. + # + # A source distribution is a ZIP package with the classifier "sources", which includes all the + # sources used by the compile task. + # + # Packages use the project's manifest and meta_inf properties, which you can override by passing + # different values (e.g. false to exclude the manifest) in the options. + # + # To create source distributions only for specific projects, use the :only and :except options, + # for example: + # package_with_sources :only=>["foo:bar", "foo:baz"] + # + # (Same as calling package :sources on each project/sub-project that has source directories.) + def package_with_sources(options = nil) + options ||= {} + enhance do + selected = options[:only] ? projects(options[:only]) : + options[:except] ? ([self] + projects - projects(options[:except])) : + [self] + projects + selected.reject { |project| project.compile.sources.empty? }. + each { |project| project.package(:sources) } + end + end + + # :call-seq: + # package_with_javadoc(options?) + # + # Call this when you want the project (and all its sub-projects) to create a JavaDoc distribution. + # You can use the JavaDoc distribution in an IDE when coding against the API. + # + # A JavaDoc distribution is a ZIP package with the classifier "javadoc", which includes all the + # sources used by the compile task. + # + # Packages use the project's manifest and meta_inf properties, which you can override by passing + # different values (e.g. false to exclude the manifest) in the options. + # + # To create JavaDoc distributions only for specific projects, use the :only and :except options, + # for example: + # package_with_javadoc :only=>["foo:bar", "foo:baz"] + # + # (Same as calling package :javadoc on each project/sub-project that has source directories.) + def package_with_javadoc(options = nil) + options ||= {} + enhance do + selected = options[:only] ? projects(options[:only]) : + options[:except] ? ([self] + projects - projects(options[:except])) : + [self] + projects + selected.reject { |project| project.compile.sources.empty? }. + each { |project| project.package(:javadoc) } + end end end end