require "core/project" require "java/artifact" require "java/java" require "java/compile" require "tasks/zip" module Buildr module Java # This module includes comming packaging classes. Each one is also reflected by # a packaging method. Generally, you would want to use the packaging method from # 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 # 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 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 end def prerequisites() #:nodoc: super + [ String === manifest ? file(manifest) : nil ].compact + [meta_inf].flatten.map { |file| String === file ? file(file) : file } end protected def create(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 case manifest when Hash output << manifest.map { |pair| pair.map(&:to_s).join(": ") }.sort.join("\n") when Array output << manifest.reject { |section| section.empty? }.map { |section| section.map { |pair| pair.join(": ") }.sort.join("\n").concat("\n") }.join("\n") when Proc, Method output << manifest.call when String, Task output << File.read(manifest.to_s) end end end end super zip 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") class WarTask < JarTask def []=(key, value) #:nodoc: case key.to_sym when :libs self.include artifacts(value), :path=>"WEB-INF/lib" when :classes self.include value, :path=>"WEB-INF/classes", :as=>"." else super key, value end value end end end end class Project # Options accepted by #package method for all package types. PACKAGE_OPTIONS = [:group, :id, :version, :type, :classifier] # The project's identifier. Same as the project name, with colons replaced by dashes. # The ID for project foo:bar is foo-bar. attr_reader :id def id() name.gsub(":", "-") end # Group used for packaging. Inherited from parent project. Defaults to the top-level project name. attr_accessor :group inherited_attr(:group) { |project| project.name } # Version used for packaging. Inherited from parent project. attr_accessor :version inherited_attr :version # Manifest used for packaging. Inherited from parent project. The default value is a hash that includes # the Build-By, Build-Jdk, Implementation-Title and Implementation-Version values. # The later are taken from the project's comment (or name) and version number. attr_accessor :manifest inherited_attr :manifest do |project| manifest = { "Build-By"=>ENV['USER'], "Build-Jdk"=>Java.version } manifest["Implementation-Title"] = self.comment || self.name manifest["Implementation-Version"] = project.version if project.version manifest end # Files to always include in the package META-INF directory. The default value include # the LICENSE file if one exists in the project's base directory. attr_accessor :meta_inf inherited_attr(:meta_inf) do |project| license = project.file("LICENSE") File.exist?(license.to_s) ? [license] : [] end # :call-seq: # package(type, options?) => 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 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. # # The JAR packager adds 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 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 # end # package :zip, :classifier=>"sources", :include=>"." # 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. # # 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 # # 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. 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| # Make it an artifact using the specifications, and tell it how to create a POM. package.extend ActsAsArtifact package.send :apply_spec, options package.pom.enhance do |pom| 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 options[:group] xml.artifactId options[:id] xml.version options[:version] xml.classifier options[:classifier] if options[:classifier] end end end # Make sure the package task creates it, and it invokes the build task first. 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 # POM artifact (created by calling artifact on its spec) is already mapped to its right # place in the local repository, so we only need to invoke it. installed = file(repositories.locate(package)=>package) { |task| verbose(Rake.application.options.trace || false) do mkpath File.dirname(task.name), :verbose=>false cp package.name, task.name end puts "Installed #{task.name}" if verbose } task "install"=>[installed, package.pom] task "uninstall" do |task| verbose(Rake.application.options.trace || false) do [ installed, package.pom ].map(&:to_s).each { |file| rm file if File.exist?(file) } end end task("deploy") { deploy(package, package.pom) } # 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, package.pom end end # :call-seq: # packages() => tasks # # Returns all packages created by this project. A project may create any number of packages. # # This method is used whenever you pass a project to Buildr#artifact or any other method # that accepts artifact specifications and projects. You can use it to list all packages # created by the project. If you want to return a specific package, it is often more # convenient to call #package with the type. def packages() @packages ||= [] end protected def package_as_jar(file_name, options) #:nodoc: unless Rake::Task.task_defined?(file_name) fu_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 if options[:include] jar.include options[:include] else # Can only decide on this once we're done configuring the compile task. enhance { jar.include compile.target, :as=>"." } end yield jar end else fu_check_options options, *PACKAGE_OPTIONS end file(file_name) end def package_as_war(file_name, options) #:nodoc: unless Rake::Task.task_defined?(file_name) fu_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 # Add libraries in WEB-INF lib, and classes in WEB-INF classes if options[:classes] 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? } end if options[:libs] 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 } end # Add included files, or the webapp directory. if options[:include] war.include options[:include] elsif File.exist?(path_to("src/main/webapp")) war.include path_to("src/main/webapp"), :as=>"." end yield war end else fu_check_options options, *PACKAGE_OPTIONS end file(file_name) end def package_as_zip(file_name, options) #:nodoc: unless Rake::Task.task_defined?(file_name) fu_check_options options, *PACKAGE_OPTIONS + [:include] ZipTask.define_task(file_name).tap do |zip| if options[:include] zip.include options[:include] end yield zip end else fu_check_options options, *PACKAGE_OPTIONS end file(file_name) end end end