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 2007/11/14 00:44:17 UTC
svn commit: r594720 [6/9] - in /incubator/buildr/trunk: ./ bin/ doc/
doc/css/ doc/images/ doc/pages/ lib/ lib/buildr/ lib/buildr/jetty/
lib/core/ lib/java/ lib/tasks/ test/
Added: incubator/buildr/trunk/lib/java/java.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/java.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/java.rb (added)
+++ incubator/buildr/trunk/lib/java/java.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,432 @@
+require "rjb" if RUBY_PLATFORM != 'java'
+require "java" if RUBY_PLATFORM == 'java'
+require "core/project"
+
+module Buildr
+
+ # Base module for all things Java.
+ module Java
+
+ # Options accepted by #java and other methods here.
+ JAVA_OPTIONS = [ :verbose, :classpath, :name, :java_args, :properties ]
+
+ # Returned by Java#wrapper, you can use this object to set the classpath, specify blocks to be invoked
+ # after loading RJB, and load RJB itself.
+ #
+ # RJB can be loaded exactly once, and once loaded, you cannot change its classpath. Of course you can
+ # call libraries that manage their own classpath, but the lazy way is to just tell RJB of all the
+ # classpath dependencies you need in advance, before loading it.
+ #
+ # For that reason, you should not load RJB until the moment you need it. You can call #load or call
+ # Java#wrapper with a block. For the same reason, you may need to specify code to execute when loading
+ # (see #setup).
+ #
+ # JRuby doesn't have the above limitation, but uses the same API regardless.
+ class JavaWrapper #:nodoc:
+
+ include Singleton
+
+ def initialize() #:nodoc:
+ @classpath = [Java.tools_jar].compact
+ if RUBY_PLATFORM == 'java'
+ # in order to get a complete picture, we need to add a few jars to the list.
+ @classpath += java.lang.System.getProperty('java.class.path').split(':').compact
+ end
+ @setup = []
+
+ setup do
+ setup do
+ cp = Buildr.artifacts(@classpath).map(&:to_s)
+ cp.each { |path| file(path).invoke }
+
+ if RUBY_PLATFORM == 'java'
+ cp.each do |jlib|
+ require jlib
+ end
+ else
+ ::Rjb.load cp.join(File::PATH_SEPARATOR), Buildr.options.java_args.flatten
+ end
+ end
+ end
+ end
+
+ attr_accessor :classpath
+
+ def setup(&block)
+ @setup << block
+ self
+ end
+
+ def onload(&block)
+ warn_deprecated "use setup { |wrapper| ... } instead"
+ setup &block
+ end
+
+ def load(&block)
+ @setup.each { |block| block.call self }
+ @setup.clear
+ end
+
+ def import(jlib)
+ if RUBY_PLATFORM == 'java'
+ ::Java.send(jlib)
+ else
+ ::Rjb.import jlib
+ end
+ end
+
+ def method_missing(sym, *args, &block) #:nodoc:
+ # these aren't the same, but depending on method_missing while
+ # supporting two unrelated systems is asking for trouble anyways.
+ if RUBY_PLATFORM == 'java'
+ ::Java.send sym, *args, &block
+ else
+ ::Rjb.send sym, *args, &block
+ end
+ end
+ end
+
+ class << self
+
+ # :call-seq:
+ # version() => string
+ #
+ # Returns the version number of the JVM.
+ #
+ # For example:
+ # puts Java.version
+ # => 1.5.0_10
+ def version()
+ @version ||= Java.wrapper { |jw| jw.import("java.lang.System").getProperty("java.version") }
+ end
+
+ # :call-seq:
+ # tools_jar() => path
+ #
+ # Returns a path to tools.jar. On OS/X which has not tools.jar, returns an empty array,
+ # on all other platforms, fails if it doesn't find tools.jar.
+ def tools_jar()
+ return [] if darwin?
+ @tools ||= [File.join(home, "lib/tools.jar")] or raise "I need tools.jar to compile, can't find it in #{home}/lib"
+ end
+
+ # :call-seq:
+ # home() => path
+ #
+ # Returns JAVA_HOME, fails if JAVA_HOME not set.
+ def home()
+ @home ||= ENV["JAVA_HOME"] or fail "Are we forgetting something? JAVA_HOME not set."
+ end
+
+ # :call-seq:
+ # java(class, *args, options?)
+ #
+ # Runs Java with the specified arguments.
+ #
+ # The last argument may be a Hash with additional options:
+ # * :classpath -- One or more file names, tasks or artifact specifications.
+ # These are all expanded into artifacts, and all tasks are invoked.
+ # * :java_args -- Any additional arguments to pass (e.g. -hotspot, -xms)
+ # * :properties -- Hash of system properties (e.g. "path"=>base_dir).
+ # * :name -- Shows this name, otherwise shows the first argument (the class name).
+ # * :verbose -- If true, prints the command and all its argument.
+ def java(*args, &block)
+ options = Hash === args.last ? args.pop : {}
+ options[:verbose] ||= Rake.application.options.trace || false
+ rake_check_options options, *JAVA_OPTIONS
+
+ name = options[:name] || "java #{args.first}"
+ cmd_args = [path_to_bin("java")]
+ classpath = classpath_from(options)
+ cmd_args << "-cp" << classpath.join(File::PATH_SEPARATOR) unless classpath.empty?
+ options[:properties].each { |k, v| cmd_args << "-D#{k}=#{v}" } if options[:properties]
+ cmd_args += (options[:java_args] || Buildr.options.java_args).flatten
+ cmd_args += args.flatten.compact
+ unless Rake.application.options.dryrun
+ puts "Running #{name}" if verbose
+ block = lambda { |ok, res| fail "Failed to execute #{name}, see errors above" unless ok } unless block
+ puts cmd_args.join(" ") if Rake.application.options.trace
+ system(cmd_args.map { |arg| %Q{"#{arg}"} }.join(" ")).tap do |ok|
+ block.call ok, $?
+ end
+ end
+ end
+
+ # :call-seq:
+ # apt(*files, options)
+ #
+ # Runs Apt with the specified arguments.
+ #
+ # The last argument may be a Hash with additional options:
+ # * :compile -- If true, compile source files to class files.
+ # * :source -- Specifies source compatibility with a given JVM release.
+ # * :output -- Directory where to place the generated source files, or the
+ # generated class files when compiling.
+ # * :classpath -- One or more file names, tasks or artifact specifications.
+ # These are all expanded into artifacts, and all tasks are invoked.
+ def apt(*args)
+ options = Hash === args.last ? args.pop : {}
+ rake_check_options options, :compile, :source, :output, :classpath
+
+ files = args.flatten.map(&:to_s).
+ collect { |arg| File.directory?(arg) ? FileList["#{arg}/**/*.java"] : arg }.flatten
+ cmd_args = [ Rake.application.options.trace ? "-verbose" : "-nowarn" ]
+ if options[:compile]
+ cmd_args << "-d" << options[:output].to_s
+ else
+ cmd_args << "-nocompile" << "-s" << options[:output].to_s
+ end
+ cmd_args << "-source" << options[:source] if options[:source]
+ classpath = classpath_from(options)
+ cmd_args << "-cp" << classpath.join(File::PATH_SEPARATOR) unless classpath.empty?
+ cmd_args += files
+ unless Rake.application.options.dryrun
+ puts "Running apt" if verbose
+ puts (["apt"] + cmd_args).join(" ") if Rake.application.options.trace
+ Java.wrapper do |jw|
+ jw.import("com.sun.tools.apt.Main").process(cmd_args) == 0 or
+ fail "Failed to process annotations, see errors above"
+ end
+ end
+ end
+
+ # :call-seq:
+ # javac(*files, options)
+ #
+ # Runs Javac with the specified arguments.
+ #
+ # The last argument may be a Hash with additional options:
+ # * :output -- Target directory for all compiled class files.
+ # * :classpath -- One or more file names, tasks or artifact specifications.
+ # These are all expanded into artifacts, and all tasks are invoked.
+ # * :sourcepath -- Additional source paths to use.
+ # * :javac_args -- Any additional arguments to pass (e.g. -extdirs, -encoding)
+ # * :name -- Shows this name, otherwise shows the working directory.
+ def javac(*args)
+ options = Hash === args.last ? args.pop : {}
+ rake_check_options options, :classpath, :sourcepath, :output, :javac_args, :name
+
+ files = args.flatten.each { |f| f.invoke if f.respond_to?(:invoke) }.map(&:to_s).
+ collect { |arg| File.directory?(arg) ? FileList["#{arg}/**/*.java"] : arg }.flatten
+ name = options[:name] || Dir.pwd
+
+ cmd_args = []
+ classpath = classpath_from(options)
+ cmd_args << "-cp" << classpath.join(File::PATH_SEPARATOR) unless classpath.empty?
+ cmd_args << "-sourcepath" << options[:sourcepath].join(File::PATH_SEPARATOR) if options[:sourcepath]
+ cmd_args << "-d" << options[:output].to_s if options[:output]
+ cmd_args += options[:javac_args].flatten if options[:javac_args]
+ cmd_args += files
+ unless Rake.application.options.dryrun
+ puts "Compiling #{files.size} source files in #{name}" if verbose
+ puts (["javac"] + cmd_args).join(" ") if Rake.application.options.trace
+ Java.wrapper do |jw|
+ jw.import("com.sun.tools.javac.Main").compile(cmd_args) == 0 or
+ fail "Failed to compile, see errors above"
+ end
+ end
+ end
+
+ # :call-seq:
+ # javadoc(*files, options)
+ #
+ # Runs Javadocs with the specified files and options.
+ #
+ # This method accepts the following special options:
+ # * :output -- The output directory
+ # * :classpath -- Array of classpath dependencies.
+ # * :sourcepath -- Array of sourcepaths (paths or tasks).
+ # * :name -- Shows this name, otherwise shows the working directory.
+ #
+ # All other options are passed to Javadoc as following:
+ # * true -- As is, for example, :author=>true becomes -author
+ # * false -- Prefixed, for example, :index=>false becomes -noindex
+ # * string -- Option with value, for example, :windowtitle=>"My project" becomes -windowtitle "My project"
+ # * array -- Option with set of values separated by spaces.
+ def javadoc(*args)
+ options = Hash === args.last ? args.pop : {}
+
+ cmd_args = [ "-d", options[:output], Rake.application.options.trace ? "-verbose" : "-quiet" ]
+ options.reject { |key, value| [:output, :name, :sourcepath, :classpath].include?(key) }.
+ each { |key, value| value.invoke if value.respond_to?(:invoke) }.
+ each do |key, value|
+ case value
+ when true, nil
+ cmd_args << "-#{key}"
+ when false
+ cmd_args << "-no#{key}"
+ when Hash
+ value.each { |k,v| cmd_args << "-#{key}" << k.to_s << v.to_s }
+ else
+ cmd_args += Array(value).map { |item| ["-#{key}", item.to_s] }.flatten
+ end
+ end
+ [:sourcepath, :classpath].each do |option|
+ options[option].to_a.flatten.tap do |paths|
+ cmd_args << "-#{option}" << paths.flatten.map(&:to_s).join(File::PATH_SEPARATOR) unless paths.empty?
+ end
+ end
+ cmd_args += args.flatten.uniq
+ name = options[:name] || Dir.pwd
+ unless Rake.application.options.dryrun
+ puts "Generating Javadoc for #{name}" if verbose
+ puts (["javadoc"] + cmd_args).join(" ") if Rake.application.options.trace
+ Java.wrapper do |jw|
+ jw.import("com.sun.tools.javadoc.Main").execute(cmd_args) == 0 or
+ fail "Failed to generate Javadocs, see errors above"
+ end
+ end
+ end
+
+ # :call-seq:
+ # junit(*classes, options) => [ passed, failed ]
+ #
+ # Runs JUnit test cases from the specified classes. Returns an array with two lists,
+ # one containing the names of all classes that passes, the other containing the names
+ # of all classes that failed.
+ #
+ # The last argument may be a Hash with additional options:
+ # * :classpath -- One or more file names, tasks or artifact specifications.
+ # These are all expanded into artifacts, and all tasks are invoked.
+ # * :properties -- Hash of system properties (e.g. "path"=>base_dir).
+ # * :java_args -- Any additional arguments to pass (e.g. -hotspot, -xms)
+ # * :verbose -- If true, prints the command and all its argument.
+ #
+ # *Deprecated:* Please use JUnitTask instead.Use the test task to run JUnit and other test frameworks.
+ def junit(*args)
+ warn_deprecated "Use the test task to run JUnit and other test frameworks"
+ options = Hash === args.last ? args.pop : {}
+ options[:verbose] ||= Rake.application.options.trace || false
+ rake_check_options options, :verbose, :classpath, :properties, :java_args
+
+ classpath = classpath_from(options) + JUnitTask::requires
+ tests = args.flatten
+ failed = tests.inject([]) do |failed, test|
+ begin
+ java "junit.textui.TestRunner", test, :classpath=>classpath, :properties=>options[:properties],
+ :name=>"#{test}", :verbose=>options[:verbose], :java_args=>options[:java_args]
+ failed
+ rescue
+ failed << test
+ end
+ end
+ [ tests - failed, failed ]
+ end
+
+
+ # :call-seq:
+ # wrapper() => JavaWrapper
+ # wrapper() { ... }
+ #
+ # This method can be used in two ways. Without a block, returns the
+ # JavaWrapper object which you can use to configure the classpath or call
+ # other methods. With a block, loads RJB or sets up JRuby and yields to
+ # the block, returning its result.
+ #
+ # For example:
+ # # Add class path dependency.
+ # Java.wrapper.classpath << REQUIRES
+ # # Require AntWrap when loading RJB/JRuby.
+ # Java.wrapper.setup { require "antwrap" }
+ #
+ # def execute(name, options)
+ # options = options.merge(:name=>name, :base_dir=>Dir.pwd, :declarative=>true)
+ # # Load RJB/JRuby and run AntWrap.
+ # Java.wrapper { AntProject.new(options) }
+ # end
+ def wrapper()
+ if block_given?
+ JavaWrapper.instance.load
+ yield JavaWrapper.instance
+ else
+ JavaWrapper.instance
+ end
+ end
+
+ def rjb(&block)
+ warn_deprecated "please use Java.wrapper() instead"
+ wrapper &block
+ end
+
+ # :call-seq:
+ # path_to_bin(cmd?) => path
+ #
+ # Returns the path to the specified Java command (with no argument to java itself).
+ def path_to_bin(name = "java")
+ File.join(home, "bin", name)
+ end
+
+ protected
+
+ # :call-seq:
+ # classpath_from(options) => files
+ #
+ # Extracts the classpath from the options, expands it by calling artifacts, invokes
+ # each of the artifacts and returns an array of paths.
+ def classpath_from(options)
+ classpath = (options[:classpath] || []).collect
+ Buildr.artifacts(classpath).each { |t| t.invoke if t.respond_to?(:invoke) }.map(&:to_s)
+ end
+
+ def darwin?() #:nodoc:
+ RUBY_PLATFORM =~ /darwin/i
+ end
+
+ end
+
+ # See Java#java.
+ def java(*args)
+ Java.java(*args)
+ end
+
+ # :call-seq:
+ # apt(*sources) => task
+ #
+ # Returns a task that will use Java#apt to generate source files in target/generated/apt,
+ # from all the source directories passed as arguments. Uses the compile.sources list if
+ # on arguments supplied.
+ #
+ # For example:
+ #
+ def apt(*sources)
+ sources = compile.sources if sources.empty?
+ file(path_to(:target, "generated/apt")=>sources) do |task|
+ Java.apt(sources.map(&:to_s) - [task.name], :output=>task.name,
+ :classpath=>compile.classpath, :source=>compile.options.source)
+ end
+ end
+
+ end
+
+ include Java
+
+ class Options
+
+ # :call-seq:
+ # java_args => array
+ #
+ # Returns the Java arguments.
+ def java_args()
+ @java_args ||= (ENV["JAVA_OPTS"] || ENV["JAVA_OPTIONS"] || "").split(" ")
+ end
+
+ # :call-seq:
+ # java_args = array|string|nil
+ #
+ # Sets the Java arguments. These arguments are used when creating a JVM, including for use with RJB
+ # for most tasks (e.g. Ant, compile) and when forking a separate JVM (e.g. JUnit tests). You can also
+ # use the JAVA_OPTS environment variable.
+ #
+ # For example:
+ # options.java_args = "-verbose"
+ # Or:
+ # $ set JAVA_OPTS = "-Xms1g"
+ # $ buildr
+ def java_args=(args)
+ args = args.split if String === args
+ @java_args = args.to_a
+ end
+
+ end
+
+end
Added: incubator/buildr/trunk/lib/java/packaging.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/packaging.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/packaging.rb (added)
+++ incubator/buildr/trunk/lib/java/packaging.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,581 @@
+require "core/project"
+require "java/artifact"
+require "java/java"
+require "java/compile"
+require "java/test"
+require "tasks/zip"
+require "tasks/tar"
+
+
+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", "Created-By: Buildr"]
+
+ # Adds support for MANIFEST.MF and other META-INF files.
+ module WithManifest
+
+ class << self
+ # jruby issue
+ #protected
+ def included(mod)
+ mod.alias_method_chain :initialize, :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_with_manifest(*args) #:nodoc:
+ initialize_without_manifest *args
+ @manifest = false
+ @meta_inf = []
+
+ prepare do
+ @prerequisites << manifest if String === manifest || Rake::Task === manifest
+ [meta_inf].flatten.map { |file| file.to_s }.uniq.each { |file| path("META-INF").include file }
+ end
+
+ enhance do
+ if manifest
+ # Tempfiles gets deleted on garbage collection, so we're going to hold on to it
+ # through instance variable not closure variable.
+ Tempfile.open "MANIFEST.MF" do |@manifest_tmp|
+ lines = String === manifest || Rake::Task === manifest ? manifest_lines_from(File.read(manifest.to_s)) :
+ manifest_lines_from(manifest)
+ @manifest_tmp.write((MANIFEST_HEADER + lines).join("\n"))
+ @manifest_tmp.write "\n"
+ path("META-INF").include @manifest_tmp.path, :as=>"MANIFEST.MF"
+ end
+ end
+ end
+ end
+
+ private
+
+ def manifest_lines_from(arg)
+ case arg
+ when Hash
+ arg.map { |name, value| "#{name}: #{value}" }.sort.
+ map { |line| manifest_wrap_at_72(line) }.flatten
+ when Array
+ arg.map { |section|
+ name = section.has_key?("Name") ? ["Name: #{section["Name"]}"] : []
+ name + section.except("Name").map { |name, value| "#{name}: #{value}" }.sort + [""]
+ }.flatten.map { |line| manifest_wrap_at_72(line) }.flatten
+ when Proc, Method
+ manifest_lines_from(arg.call)
+ when String
+ arg.split("\n").map { |line| manifest_wrap_at_72(line) }.flatten
+ else
+ fail "Invalid manifest, expecting Hash, Array, file name/task or proc/method."
+ end
+ end
+
+ def manifest_wrap_at_72(arg)
+ #return arg.map { |line| manifest_wrap_at_72(line) }.flatten.join("\n") if Array === arg
+ return arg if arg.size < 72
+ [ arg[0..70], manifest_wrap_at_72(" " + arg[71..-1]) ]
+ 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).with(:libs=>"log4j:log4j:jar:1.1")
+ class WarTask < JarTask
+
+ # 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 = []
+ prepare do
+ @classes.to_a.flatten.each { |classes| path("WEB-INF/classes").include classes, :as=>"." }
+ path("WEB-INF/lib").include Buildr.artifacts(@libs) unless @libs.nil? || @libs.empty?
+ end
+ 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
+
+ # Extends the JarTask to create an AAR file (Axis2 service archive).
+ #
+ # Supports all the same options as JarTask, with the addition of :wsdls, :services_xml and :libs.
+ #
+ # * :wsdls -- WSDL files to include (under META-INF). By default packaging will include all WSDL
+ # files found under src/main/axis2.
+ # * :services_xml -- Location of services.xml file (included under META-INF). By default packaging
+ # takes this from src/main/axis2/services.xml. Use a different path if you genereate the services.xml
+ # file as part of the build.
+ # * :libs -- Array of files, tasks, artifact specifications, etc that will be added to the /lib directory.
+ #
+ # For example:
+ # package(:aar).with(:libs=>"log4j:log4j:jar:1.1")
+ #
+ # filter.from("src/main/axis2").into("target").include("services.xml", "*.wsdl").using("http_port"=>"8080")
+ # package(:aar).wsdls.clear
+ # package(:aar).with(:services_xml=>_("target/services.xml"), :wsdls=>_("target/*.wsdl"))
+ class AarTask < JarTask
+ # Artifacts to include under /lib.
+ attr_accessor :libs
+ # WSDLs to include under META-INF (defaults to all WSDLs under src/main/axis2).
+ attr_accessor :wsdls
+ # Location of services.xml file (defaults to src/main/axis2/services.xml).
+ attr_accessor :services_xml
+
+ def initialize(*args) #:nodoc:
+ super
+ @libs = []
+ @wsdls = []
+ prepare do
+ path("META-INF").include @wsdls
+ path("META-INF").include @services_xml, :as=>["services.xml"] if @services_xml
+ path("lib").include Buildr.artifacts(@libs) unless @libs.nil? || @libs.empty?
+ end
+ end
+
+ def libs=(value) #:nodoc:
+ @libs = Buildr.artifacts(value)
+ end
+
+ def wsdls=(value) #:nodoc:
+ @wsdls |= Array(value)
+ 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]
+
+ # 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, 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 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")
+ #
+ # 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.
+ #
+ # 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.
+ #
+ # 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.
+ #
+ # For example:
+ # define "project" do
+ # define "beans" do
+ # package :jar
+ # end
+ # define "webapp" do
+ # compile.with project("beans")
+ # package(:war).with :libs=>MYSQL_JDBC
+ # end
+ # package(:zip, :classifier=>"sources").include path_to(".")
+ # end
+ #
+ # 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.
+ #
+ # A package is also an artifact. The following tasks operate on packages created by the project:
+ # 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. 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}")
+ 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") { |file| file.write pom.pom_xml }
+ end
+
+ # 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
+ # 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(Buildr.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, pom]
+ 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("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
+ #
+ # 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)
+ rake_check_options options, *PACKAGE_OPTIONS + [:manifest, :meta_inf, :include]
+ Java::Packaging::JarTask.define_task(file_name).tap do |jar|
+ 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
+ jar.with compile.target unless compile.sources.empty?
+ jar.with resources.target unless resources.sources.empty?
+ end
+ end
+ else
+ rake_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)
+ rake_check_options options, *PACKAGE_OPTIONS + [:manifest, :meta_inf, :classes, :libs, :include]
+ Java::Packaging::WarTask.define_task(file_name).tap do |war|
+ 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.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
+ war.with :classes=>compile.target unless compile.sources.empty?
+ war.with :classes=>resources.target unless resources.sources.empty?
+ end
+ 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
+ war.with :libs=>compile.classpath
+ end
+ # Add included files, or the webapp directory.
+ 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]
+ else
+ path_to("src/main/webapp").tap { |path| war.with path if File.exist?(path) }
+ end
+ end
+ else
+ rake_check_options options, *PACKAGE_OPTIONS
+ end
+ file(file_name)
+ end
+
+ def package_as_aar(file_name, options) #:nodoc:
+ rake_check_options options, *PACKAGE_OPTIONS
+ unless Rake::Task.task_defined?(file_name)
+ Java::Packaging::AarTask.define_task(file_name).tap do |aar|
+ aar.with :manifest=>manifest, :meta_inf=>meta_inf
+ aar.with :wsdls=>path_to("src/main/axis2/*.wsdl")
+ aar.with :services_xml=>path_to("src/main/axis2/services.xml")
+ aar.with compile.target unless compile.sources.empty?
+ aar.with resources.target unless resources.sources.empty?
+ aar.with :libs=>compile.classpath
+ end
+ end
+ file(file_name)
+ end
+
+ 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
+ end
+ else
+ rake_check_options options, *PACKAGE_OPTIONS
+ end
+ file(file_name)
+ end
+
+ def package_as_tar(file_name, options) #:nodoc:
+ rake_check_options options, *PACKAGE_OPTIONS
+ unless Rake::Task.task_defined?(file_name)
+ TarTask.define_task(file_name)
+ end
+ file(file_name)
+ end
+ alias :package_as_tgz :package_as_tar
+
+ 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
Added: incubator/buildr/trunk/lib/java/pom.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/pom.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/pom.rb (added)
+++ incubator/buildr/trunk/lib/java/pom.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,162 @@
+require "xmlsimple"
+require "java/artifact"
+
+
+module Buildr
+ class POM
+
+ POM_TO_SPEC_MAP = { :group=>"groupId", :id=>"artifactId", :type=>"type",
+ :version=>"version", :classifier=>"classifier", :scope=>"scope" }
+ SCOPES_TRANSITIVE = [nil, "compile", "runtime"]
+ SCOPES_WE_USE = SCOPES_TRANSITIVE + ["provided"]
+
+ # POM project as Hash (using XmlSimple).
+ attr_reader :project
+ # Parent POM if referenced by this POM.
+ attr_reader :parent
+
+ class << self
+
+ # :call-seq:
+ # POM.load(arg)
+ #
+ # Load new POM object form various kind of sources such as artifact, hash representing spec, filename, XML.
+ def load(source)
+ case source
+ when Hash
+ load(Buildr.artifact(source).pom)
+ when Artifact
+ pom = source.pom
+ pom.invoke
+ load(pom.to_s)
+ when Rake::FileTask
+ source.invoke
+ load(source.to_s)
+ when String
+ filename = File.expand_path(source)
+ unless pom = cache[filename]
+ puts "Loading m2 pom file from #{filename}" if Rake.application.options.trace
+ pom = POM.new(IO.read(filename))
+ cache[filename] = pom
+ end
+ pom
+ else
+ raise ArgumentError, "Expecting Hash spec, Artifact, file name or file task"
+ end
+ end
+
+ private
+
+ def cache()
+ @cache ||= {}
+ end
+
+ end
+
+ def initialize(xml) #:nodoc:
+ @project = XmlSimple.xml_in(xml)
+ @parent = POM.load(pom_to_hash(project["parent"].first)) if project["parent"]
+ end
+
+ # :call-seq:
+ # dependencies(scopes?) => artifacts
+ #
+ # Returns list of required dependencies as specified by the POM. You can specify which scopes
+ # to use (e.g. "compile", "runtime"); use +nil+ for dependencies with unspecified scope.
+ # The default scopes are +nil+, "compile" and "runtime" (aka SCOPES_WE_USE).
+ def dependencies(scopes = SCOPES_WE_USE)
+ #try to cache dependencies also
+ @depends_for_scopes ||= {}
+ unless depends = @depends_for_scopes[scopes]
+ declared = project["dependencies"].first["dependency"] rescue nil
+ depends = (declared || []).reject { |dep| value_of(dep["optional"]) =~ /true/ }.
+ map { |dep|
+ spec = pom_to_hash(dep, properties)
+ apply = managed(spec)
+ spec = apply.merge(spec) if apply
+
+ #calculate transitive dependencies
+ if scopes.include?(spec[:scope])
+ spec.delete(:scope)
+
+ exclusions = dep["exclusions"]["exclusion"] rescue nil
+ transitive_deps = POM.load(spec).dependencies(SCOPES_TRANSITIVE)
+ transitive_deps = transitive_deps.reject{|dep|
+ exclusions.find {|ex| dep.index("#{dep['groupdId'].first}:#{dep['artifactId'].first}:") == 0}
+ } if exclusions
+
+ [Artifact.to_spec(spec)] + transitive_deps
+ end
+ }.flatten.compact.uniq_by{|spec| art = spec.split(':'); "#{art[0]}:#{art[1]}"}
+
+ @depends_for_scopes[scopes] = depends
+ end
+ depends
+ end
+
+ # :call-seq:
+ # properties() => hash
+ #
+ # Returns properties available to this POM as hash. Includes explicit properties and pom.xxx/project.xxx
+ # properties for groupId, artifactId, version and packaging.
+ def properties()
+ @properties ||= begin
+ pom = ["groupId", "artifactId", "version", "packaging"].inject({}) { |hash, key|
+ value = project[key] || (parent ? parent.project[key] : nil)
+ hash["pom.#{key}"] = hash["project.#{key}"] = value_of(value) if value
+ hash
+ }
+ props = project["properties"].first rescue {}
+ props = props.inject({}) { |mapped, pair| mapped[pair.first] = value_of(pair.last, pom) ; mapped }
+ (parent ? parent.properties.merge(props) : props).merge(pom)
+ end
+ end
+
+ # :call-seq:
+ # managed() => hash
+ # managed(hash) => hash
+ #
+ # The first form returns all the managed dependencies specified by this POM in dependencyManagement.
+ # The second form uses a single spec hash and expands it from the current/parent POM. Used to determine
+ # the version number if specified in dependencyManagement instead of dependencies.
+ def managed(spec = nil)
+ if spec
+ managed.detect { |dep| [:group, :id, :type, :classifier].all? { |key| spec[key] == dep[key] } } ||
+ (parent ? parent.managed(spec) : nil)
+ else
+ @managed ||= begin
+ managed = project["dependencyManagement"].first["dependencies"].first["dependency"] rescue nil
+ managed ? managed.map { |dep| pom_to_hash(dep, properties) } : []
+ end
+ end
+ end
+
+ private
+
+ # :call-seq:
+ # value_of(element) => string
+ # value_of(element, true) => string
+ #
+ # Returns the normalized text value of an element from its XmlSimple value. The second form performs
+ # property substitution.
+ def value_of(element, substitute = nil)
+ value = element.to_a.join.strip
+ substitute ? value.gsub(/\$\{([^}]+)\}/) { |key| substitute[$1] } : value
+ end
+
+ # :call-seq:
+ # pom_to_hash(element) => hash
+ # pom_to_hash(element, true) => hash
+ #
+ # Return the spec hash from an XmlSimple POM referencing element (e.g. project, parent, dependency).
+ # The second form performs property substitution.
+ def pom_to_hash(element, substitute = nil)
+ hash = POM_TO_SPEC_MAP.inject({}) { |spec, pair|
+ spec[pair.first] = value_of(element[pair.last], substitute) if element[pair.last]
+ spec
+ }
+ { :type=>"jar" }.merge(hash)
+ end
+
+ end
+end
Added: incubator/buildr/trunk/lib/java/test.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/test.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/test.rb (added)
+++ incubator/buildr/trunk/lib/java/test.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,795 @@
+require "core/build"
+require "java/compile"
+require "java/ant"
+require "core/help"
+
+
+module Buildr
+ module Java
+
+ # *Deprecated:* Use the test task directly instead of calling test.junit.
+ class JUnitTask < Rake::Task #:nodoc:
+
+ # The classpath used for running the tests. Includes the compile classpath,
+ # compiled classes (target). For everything else, add by calling #with.
+ attr_accessor :classpath
+
+ def initialize(*args) #:nodoc:
+ super
+ @parent = Rake::Task["#{name.split(":")[0...-1].join(":")}"]
+ end
+
+ # :call-seq:
+ # include(*classes) => self
+ #
+ # Include only the specified test cases. Unless specified, the default is to include
+ # all test cases. This method accepts multiple arguments and returns self.
+ #
+ # Test cases are specified using the fully qualified class name. You can also use file-like
+ # patterns (glob) to specify collection of classes. For example:
+ # test.include "com.example.FirstTest"
+ # test.include "com.example.*"
+ # test.include "com.example.Module*"
+ # test.include "*.{First,Second}Test"
+ #
+ # By default, all classes that have a name ending with Test or Suite are included.
+ # Use these suffixes for your test and test suite classes respectively, to distinguish them
+ # from stubs, helper classes, etc.
+ def include(*classes)
+ @parent.include *classes
+ self
+ end
+
+ # :call-seq:
+ # exclude(*classes) => self
+ #
+ # Exclude the specified test cases. This method accepts multiple arguments and returns self.
+ # See #include for the type of arguments you can use.
+ def exclude(*classes)
+ @parent.exclude *classes
+ self
+ end
+
+ # :call-seq:
+ # from(*paths) => self
+ #
+ # Specify one or more directories that include test cases.
+ def from(*files)
+ self
+ end
+
+ # :call-seq:
+ # with(*specs) => self
+ #
+ # Specify artifacts (specs, tasks, files, etc) to include in the classpath when running
+ # the test cases.
+ def with(*files)
+ (@parent.options[:classpath] ||= []).concat files.flatten
+ self
+ end
+
+ # Returns the JUnit options.
+ def options()
+ @parent.options
+ end
+
+ # :call-seq:
+ # using(options) => self
+ #
+ # Sets the JUnit options from a hash and returns self. Right now supports passing :properties to JUnit,
+ # and :java_args to the JVM.
+ #
+ # For example:
+ # test.junit.using :properties=>{ "root"=>base_dir }
+ def using(options)
+ @parent.using options
+ self
+ end
+
+ end
+
+
+ # The test task controls the entire test lifecycle.
+ #
+ # You can use the test task in three ways. You can access and configure specific test tasks,
+ # e.g. enhance the #compile task, or run code during #setup/#teardown.
+ #
+ # You can use convenient methods that handle the most common settings. For example, add classpath
+ # dependencies using #with, or include only specific test cases using #include.
+ #
+ # You can also enhance this task directly. This task will first execute the #compile task, followed
+ # by the #setup task, run the unit tests, any other enhancements, and end by executing #teardown.
+ #
+ # Unit tests are fun from classed compiled by the test.compile class that match the TEST_FILE_PATTERN
+ # (i.e. MyClassTest, MyClassTestSuite, etc). The test framework is determined by setting one of the
+ # test framework options to true, for example:
+ # test.unsing :testng
+ class TestTask < Rake::Task
+
+ class << self
+
+ # Used by the local test and integration tasks to
+ # a) Find the local project(s),
+ # b) Find all its sub-projects and narrow down to those that have either unit or integration tests,
+ # c) Run all the (either unit or integration) tests, and
+ # d) Ignore failure if necessary.
+ def run_local_tests(integration) #:nodoc:
+ Project.local_projects do |project|
+ # !(foo ^ bar) tests for equality and accepts nil as false (and select is less obfuscated than reject on ^).
+ projects = ([project] + project.projects).select { |project| !(project.test.options[:integration] ^ integration) }
+ projects.each do |project|
+ puts "Testing #{project.name}" if verbose
+ begin
+ project.test.invoke
+ rescue
+ raise unless Buildr.options.test == :all
+ end
+ end
+ end
+ end
+
+ # Used by the test/integration rule to only run tests that match the specified names.
+ def only_run(tests) #:nodoc:
+ tests = tests.map { |name| name =~ /\*/ ? name : "*#{name}*" }
+ # Since the test case may reside in a sub-project, we need to set the include/exclude pattern on
+ # all sub-projects, but only invoke test on the local project.
+ Project.projects.each { |project| project.test.instance_eval { @include = tests ; @exclude.clear } }
+ end
+ end
+
+ # List of supported test framework, first one being a default. Test frameworks are added by
+ # including them in TestTask (e.g. JUnit, TestNG).
+ TEST_FRAMEWORKS = []
+
+ # Default options already set on each test task.
+ DEFAULT_OPTIONS = { :fail_on_failure=>true, :fork=>:once, :properties=>{}, :environment=>{} }
+
+ # JMock version..
+ JMOCK_VERSION = "1.2.0"
+ # JMock specification.
+ JMOCK_REQUIRES = "jmock:jmock:jar:#{JMOCK_VERSION}"
+
+ # The classpath used for running the tests. Includes the compiled classes (compile.target) and
+ # their classpath dependencies. Will also include anything you pass to #with, shared between the
+ # testing compile and run classpath dependencies.
+ attr_reader :classpath
+
+ def initialize(*args) #:nodoc:
+ super
+ @classpath = []
+ @include = []
+ @exclude = []
+ parent = Project.task_in_parent_project(name)
+ @options = parent && parent.respond_to?(:options) ? parent.options.clone : DEFAULT_OPTIONS.clone
+ enhance { run_tests }
+ end
+
+ def execute() #:nodoc:
+ setup.invoke
+ begin
+ super
+ @project.task("test:junit").invoke # In case someone enhanced it
+ rescue RuntimeError
+ raise if options[:fail_on_failure]
+ ensure
+ teardown.invoke
+ end
+ end
+
+ # *Deprecated* Add a prerequisite to the compile task instead.
+ def prepare(*prereqs, &block)
+ warn_deprecated "Add a prerequisite to the compile task instead of using the prepare task."
+ @project.task("test:prepare").enhance prereqs, &block
+ end
+
+ # :call-seq:
+ # compile(*sources) => CompileTask
+ # compile(*sources) { |task| .. } => CompileTask
+ #
+ # The compile task is similar to the Project's compile task. However, it compiles all
+ # files found in the src/java/test directory into the target/test-classes directory.
+ # This task is executed by the test task before running any test cases.
+ #
+ # Once the project definition is complete, all classpath dependencies from the regular
+ # compile task are copied over, so you only need to specify classpath dependencies
+ # specific to your test cases. You can do so by calling #with on the test task.
+ # The classpath dependencies used here are also copied over to the junit task.
+ def compile(*sources, &block)
+ @project.task("test:compile").from(sources).enhance &block
+ end
+
+ # :call-seq:
+ # resources(*prereqs) => ResourcesTask
+ # resources(*prereqs) { |task| .. } => ResourcesTask
+ #
+ # Executes by the #compile task to copy resource files over. See Project#resources.
+ def resources(*prereqs, &block)
+ @project.task("test:resources").enhance prereqs, &block
+ end
+
+ # *Deprecated* Use the test task directly instead of calling test.junit.
+ def junit()
+ warn_deprecated "Use the test task directly instead of calling test.junit."
+ @project.task("test:junit")
+ end
+
+ # :call-seq:
+ # setup(*prereqs) => task
+ # setup(*prereqs) { |task| .. } => task
+ #
+ # Returns the setup task. The setup task is executed at the beginning of the test task,
+ # after compiling the test files.
+ def setup(*prereqs, &block)
+ @project.task("test:setup").enhance prereqs, &block
+ end
+
+ # :call-seq:
+ # teardown(*prereqs) => task
+ # teardown(*prereqs) { |task| .. } => task
+ #
+ # Returns the teardown task. The teardown task is executed at the end of the test task.
+ def teardown(*prereqs, &block)
+ @project.task("test:teardown").enhance prereqs, &block
+ end
+
+ # :call-seq:
+ # with(*specs) => self
+ #
+ # Specify artifacts (specs, tasks, files, etc) to include in the classpath when compiling
+ # and running test cases.
+ def with(*artifacts)
+ @classpath |= Buildr.artifacts(artifacts.flatten).uniq
+ compile.with artifacts
+ self
+ end
+
+ # Returns various test options.
+ attr_reader :options
+
+ # :call-seq:
+ # using(options) => self
+ #
+ # Sets various test options and returns self. Accepts a hash of options, or symbols (a symbol sets that
+ # option to true). For example:
+ # test.using :testng, :fork=>:each, :properties=>{ "url"=>"http://localhost:8080" }
+ #
+ # Currently supports the following options:
+ # * :fail_on_failure -- True to fail on test failure (default is true).
+ # * :fork -- Fork test cases (JUnit only).
+ # * :java_args -- Java arguments when forking a new JVM.
+ # * :properties -- System properties.
+ # * :environment -- Environment variables.
+ #
+ # The :fork option takes the following values:
+ # * :once -- Fork one JVM for each project (default).
+ # * :each -- Fork one JVM for each test case.
+ # * false -- Do not fork, running all test cases in the same JVM.
+ def using(*args)
+ args.pop.each { |key, value| options[key.to_sym] = value } if Hash === args.last
+ args.each { |key| options[key.to_sym] = true }
+ self
+ end
+
+ # :call-seq:
+ # include(*classes) => self
+ #
+ # Include only the specified test cases. Unless specified, the default is to include
+ # all test cases. This method accepts multiple arguments and returns self.
+ #
+ # Test cases are specified using the fully qualified class name. You can also use file-like
+ # patterns (glob) to specify collection of classes. For example:
+ # test.include "com.example.FirstTest"
+ # test.include "com.example.*"
+ # test.include "com.example.Module*"
+ # test.include "*.{First,Second}Test"
+ #
+ # By default, all classes that have a name ending with Test or Suite are included.
+ # Use these suffixes for your test and test suite classes respectively, to distinguish them
+ # from stubs, helper classes, etc.
+ def include(*classes)
+ @include += classes
+ self
+ end
+
+ # :call-seq:
+ # exclude(*classes) => self
+ #
+ # Exclude the specified test cases. This method accepts multiple arguments and returns self.
+ # See #include for the type of arguments you can use.
+ def exclude(*classes)
+ @exclude += classes
+ self
+ end
+
+ # :call-seq:
+ # classes() => strings
+ #
+ # List of test classes to run. Determined by finding all the test classes in the target directory,
+ # and reducing based on the include/exclude patterns.
+ def classes()
+ base = Pathname.new(compile.target.to_s)
+ patterns = self.class.const_get("#{framework.to_s.upcase}_TESTS_PATTERN").to_a
+ FileList[patterns.map { |pattern| "#{base}/**/#{pattern}.class" }].
+ map { |file| Pathname.new(file).relative_path_from(base).to_s.ext("").gsub(File::SEPARATOR, ".") }.
+ select { |name| include?(name) }.reject { |name| name =~ /\$/ }.sort
+ end
+
+ # List of failed test classes. Set after running the tests.
+ attr_reader :failed_tests
+
+ # :call-seq:
+ # include?(name) => boolean
+ #
+ # Returns true if the specified class name matches the inclusion/exclusion pattern. Used to determine
+ # which tests to execute.
+ def include?(name)
+ (@include.empty? || @include.any? { |pattern| File.fnmatch(pattern, name) }) &&
+ !@exclude.any? { |pattern| File.fnmatch(pattern, name) }
+ end
+
+ # :call-seq:
+ # requires() => classpath
+ #
+ # Returns the classpath for the selected test frameworks. Necessary for compiling and running test cases.
+ def requires()
+ self.class.const_get("#{framework.to_s.upcase}_REQUIRES").to_a + [JMOCK_REQUIRES]
+ end
+
+ # :call-seq:
+ # framework() => symbol
+ #
+ # Returns the test framework, e.g. :junit, :testng.
+ def framework()
+ @framework ||= TEST_FRAMEWORKS.detect { |name| options[name] } || TEST_FRAMEWORKS.first
+ end
+
+ # :call-seq:
+ # report_to() => file
+ #
+ # Test frameworks that can produce reports, will write them to this directory.
+ #
+ # This is framework dependent, so unless you use the default test framework, call this method
+ # after setting the test framework.
+ def report_to()
+ @report_to ||= file(@project.path_to(:reports, "#{framework}")=>self)
+ end
+
+ protected
+
+ # :call-seq:
+ # run_tests()
+ #
+ # Runs the test cases using the selected test framework. Executes as part of the task.
+ def run_tests()
+ classes = self.classes
+ if classes.empty?
+ @failed_tests = []
+ else
+ puts "Running tests in #{@project.name}" if verbose
+ @failed_tests = send("#{framework}_run",
+ :classes => classes,
+ :classpath => @classpath + [compile.target],
+ :properties => { 'baseDir' => compile.target.to_s }.merge(options[:properties] || {}),
+ :environment=> options[:environment] || {},
+ :java_args => options[:java_args] || Buildr.options.java_args)
+ unless @failed_tests.empty?
+ warn "The following tests failed:\n#{@failed_tests.join("\n")}" if verbose
+ fail "Tests failed!"
+ end
+ end
+ end
+
+ end
+
+
+ # The JUnit test framework. This is the default test framework, but you can force it by
+ # adding the following to your project:
+ # test.using :testng
+ #
+ # You can use the report method to control the junit:report task.
+ module JUnit
+
+ # Used by the junit:report task. Access through JUnit#report if you want to set various
+ # options for that task, for example:
+ # JUnit.report.frames = false
+ class Report
+
+ # Ant-Trax required for running the JUnitReport task.
+ Java.wrapper.setup { |jw| jw.classpath << "org.apache.ant:ant-trax:jar:#{Ant::VERSION}" }
+
+ # Parameters passed to the Ant JUnitReport task.
+ attr_reader :params
+ # True (default) to produce a report using frames, false to produce a single-page report.
+ attr_accessor :frames
+ # Directory for the report style (defaults to using the internal style).
+ attr_accessor :style_dir
+ # Target directory for generated report.
+ attr_accessor :target
+
+ def initialize()
+ @params = {}
+ @frames = true
+ @target = "reports/junit"
+ end
+
+ # :call-seq:
+ # generate(projects, target?)
+ #
+ # Generates a JUnit report for these projects (must run JUnit tests first) into the
+ # target directory. You can specify a target, or let it pick the default one from the
+ # target attribute.
+ def generate(projects, target = @target.to_s)
+ html_in = File.join(target, "html")
+ rm_rf html_in ; mkpath html_in
+
+ Buildr.ant("junit-report") do |ant|
+ ant.junitreport :todir=>target do
+ projects.select { |project| project.test.framework == :junit }.
+ map { |project| project.test.report_to.to_s }.select { |path| File.exist?(path) }.
+ each { |path| ant.fileset(:dir=>path) { ant.include :name=>"TEST-*.xml" } }
+ options = { :format=>frames ? "frames" : "noframes" }
+ options[:styledir] = style_dir if style_dir
+ ant.report options.merge(:todir=>html_in) do
+ params.each { |key, value| ant.param :name=>key, :expression=>value }
+ end
+ end
+ end
+ end
+
+ end
+
+ # JUnit version number.
+ JUNIT_VERSION = "4.3.1"
+ # JUnit specification.
+ JUNIT_REQUIRES = "junit:junit:jar:#{JUNIT_VERSION}"
+ # Pattern for selecting JUnit test classes. Regardless of include/exclude patterns, only classes
+ # that match this pattern are used.
+ JUNIT_TESTS_PATTERN = [ "Test*", "*Test" ]
+
+ # Ant-JUnit requires for JUnit and JUnit reports tasks.
+ Java.wrapper.setup { |jw| jw.classpath << "org.apache.ant:ant-junit:jar:#{Ant::VERSION}" }
+
+ class << self
+
+ # :call-seq:
+ # report()
+ #
+ # Returns the Report object used by the junit:report task. You can use this object to set
+ # various options that affect your report, for example:
+ # JUnit.report.frames = false
+ # JUnit.report.params["title"] = "My App"
+ def report()
+ @report ||= Report.new
+ end
+
+ def included(mod)
+ mod::TEST_FRAMEWORKS << :junit
+ end
+ private :included
+
+ end
+
+ private
+
+ def junit_run(args)
+ rm_rf report_to.to_s ; mkpath report_to.to_s
+ # Use Ant to execute the Junit tasks, gives us performance and reporting.
+ Buildr.ant("junit") do |ant|
+ case options[:fork]
+ when false
+ forking = {}
+ when :each
+ forking = { :fork=>true, :forkmode=>"perTest" }
+ when true, :once
+ forking = { :fork=>true, :forkmode=>"once" }
+ else
+ fail "Option fork must be :once, :each or false."
+ end
+ ant.junit forking.merge(:clonevm=>options[:clonevm] || false, :dir=>@project.path_to) do
+ ant.classpath :path=>args[:classpath].map(&:to_s).each { |path| file(path).invoke }.join(File::PATH_SEPARATOR)
+ args[:properties].each { |key, value| ant.sysproperty :key=>key, :value=>value }
+ args[:environment].each { |key, value| ant.env :key=>key, :value=>value }
+ java_args = args[:java_args]
+ java_args = java_args.split(" ") if String === java_args
+ java_args.each { |value| ant.jvmarg :value=>value } if java_args
+ ant.formatter :type=>"plain"
+ ant.formatter :type=>"xml"
+ ant.formatter :type=>"plain", :usefile=>false # log test
+ ant.formatter :type=>"xml"
+ ant.batchtest :todir=>report_to.to_s, :failureproperty=>"failed" do
+ ant.fileset :dir=>compile.target.to_s do
+ args[:classes].each { |cls| ant.include :name=>cls.gsub(".", "/").ext("class") }
+ end
+ end
+ end
+ return [] unless ant.project.getProperty("failed")
+ end
+ # But Ant doesn't tell us what went kaput, so we'll have to parse the test files.
+ args[:classes].inject([]) do |failed, name|
+ if report = File.read(File.join(report_to.to_s, "TEST-#{name}.txt")) rescue nil
+ # The second line (if exists) is the status line and we scan it for its values.
+ status = (report.split("\n")[1] || "").scan(/(run|failures|errors):\s*(\d+)/i).
+ inject(Hash.new(0)) { |hash, pair| hash[pair[0].downcase.to_sym] = pair[1].to_i ; hash }
+ failed << name if status[:failures] > 0 || status[:errors] > 0
+ end
+ failed
+ end
+ end
+
+ namespace "junit" do
+ desc "Generate JUnit tests report in #{report.target}"
+ task("report") do |task|
+ report.generate Project.projects
+ puts "Generated JUnit tests report in #{report.target}"
+ end
+ end
+
+ task("clean") { rm_rf report.target.to_s }
+
+ end
+
+
+ # The TestNG test framework. Use by adding the following to your project:
+ # test.using :testng
+ module TestNG
+
+ # TestNG version number.
+ TESTNG_VERSION = "5.5"
+ # TestNG specification.
+ TESTNG_REQUIRES = "org.testng:testng:jar:jdk15:#{TESTNG_VERSION}"
+ # Pattern for selecting TestNG test classes. Regardless of include/exclude patterns, only classes
+ # that match this pattern are used.
+ TESTNG_TESTS_PATTERN = [ "Test*", "*Test", "*TestCase" ]
+
+ class << self
+
+ def included(mod)
+ mod::TEST_FRAMEWORKS << :testng
+ end
+ private :included
+
+ end
+
+ private
+
+ def testng_run(args)
+ cmd_args = [ "org.testng.TestNG", "-sourcedir", compile.sources.join(";"), "-suitename", @project.name ]
+ cmd_args << "-d" << report_to.to_s
+ cmd_options = args.only(:classpath, :properties, :java_args)
+ args[:classes].inject([]) do |failed, test|
+ begin
+ Buildr.java cmd_args, "-testclass", test, cmd_options.merge(:name=>test)
+ failed
+ rescue
+ failed << test
+ end
+ end
+ end
+
+ end
+
+ class TestTask ; include JUnit ; include TestNG ; end
+
+ end
+
+
+ class Project
+
+ # :call-seq:
+ # test(*prereqs) => TestTask
+ # test(*prereqs) { |task| .. } => TestTask
+ #
+ # Returns the test task. The test task controls the entire test lifecycle.
+ #
+ # You can use the test task in three ways. You can access and configure specific
+ # test tasks, e.g. enhance the compile task by calling test.compile, setup for
+ # the test cases by enhancing test.setup and so forth.
+ #
+ # You can use convenient methods that handle the most common settings. For example,
+ # add classpath dependencies using test.with, or include only specific test cases
+ # using test.include.
+ #
+ # You can also enhance this task directly. This method accepts a list of arguments
+ # that are used as prerequisites and an optional block that will be executed by the
+ # test task.
+ #
+ # This task compiles the project and the test cases (in that order) before running any tests.
+ # It execute the setup task, runs all the test cases, any enhancements, and ends with the
+ # teardown tasks.
+ def test(*prereqs, &block)
+ task("test").enhance prereqs, &block
+ end
+
+ end
+
+
+ Project.on_define do |project|
+ # Define a recursive test task, and pass it a reference to the project so it can discover all other tasks.
+ Java::TestTask.define_task("test")
+ project.test.instance_eval { instance_variable_set :@project, project }
+ #project.recursive_task("test")
+ # Similar to the regular resources task but using different paths.
+ resources = Java::ResourcesTask.define_task("test:resources")
+ project.path_to("src/test/resources").tap { |dir| resources.from dir if File.exist?(dir) }
+ # Similar to the regular compile task but using different paths.
+ compile = Java::CompileTask.define_task("test:compile"=>[project.compile, task("test:prepare"), project.test.resources])
+ project.path_to("src/test/java").tap { |dir| compile.from dir if File.exist?(dir) }
+ compile.into project.path_to(:target, "test-classes")
+ resources.filter.into compile.target
+ project.test.enhance [compile]
+ # Define the JUnit task here, otherwise we get a normal task.
+ Java::JUnitTask.define_task("test:junit")
+ # Define these tasks once, otherwise we may get a namespace error.
+ project.test.setup ; project.test.teardown
+
+ project.enhance do |project|
+ # Copy the regular compile classpath over, and also include the generated classes, both of which
+ # can be used in the test cases. And don't forget the classpath required by the test framework (e.g. JUnit).
+ project.test.with project.compile.classpath, project.compile.target, project.test.requires
+ project.clean do
+ verbose(false) do
+ rm_rf project.test.compile.target.to_s
+ rm_rf project.test.report_to.to_s
+ end
+ end
+ end
+ end
+
+
+ class Options
+
+ # Runs test cases after the build when true (default). This forces test cases to execute
+ # after the build, including when running build related tasks like install, deploy and release.
+ #
+ # Set to false to not run any test cases. Set to :all to run all test cases, ignoring failures.
+ #
+ # This option is set from the environment variable "test", so you can also do:
+
+ # Returns the test option (environment variable TEST). Possible values are:
+ # * :false -- Do not run any test cases (also accepts "no" and "skip").
+ # * :true -- Run all test cases, stop on failure (default if not set).
+ # * :all -- Run all test cases, ignore failures.
+ def test()
+ case value = ENV["TEST"] || ENV["test"]
+ when /^(no|off|false|skip)$/i
+ false
+ when /^all$/i
+ :all
+ when /^(yes|on|true)$/i, nil
+ true
+ else
+ warn "Expecting the environment variable test to be 'no' or 'all', not sure what to do with #{value}, so I'm just going to run all the test cases and stop at failure."
+ true
+ end
+ end
+
+ # Sets the test option (environment variable TEST). Possible values are true, false or :all.
+ #
+ # You can also set this from the environment variable, e.g.:
+ #
+ # buildr # With tests
+ # buildr test=no # Without tests
+ # buildr test=all # Ignore failures
+ # set TEST=no
+ # buildr # Without tests
+ def test=(flag)
+ ENV["test"] = nil
+ ENV["TEST"] = flag.to_s
+ end
+
+ end
+
+
+ desc "Run all test cases"
+ task("test") { TestTask.run_local_tests false }
+
+ # This rule takes a suffix and runs that test case in the current project. For example;
+ # buildr test:MyTest
+ # will run the test case class com.example.MyTest, if found in the current project.
+ #
+ # If you want to run multiple test cases, separate tham with a comma. You can also use glob
+ # (* and ?) patterns to match multiple tests, e.g. com.example.* to run all test cases in
+ # a given package. If you don't specify a glob pattern, asterisks are added for you.
+ rule /^test:.*$/ do |task|
+ TestTask.only_run task.name.scan(/test:(.*)/)[0][0].split(",")
+ task("test").invoke
+ end
+
+ task "build" do |task|
+ # Make sure this happens as the last action on the build, so all other enhancements
+ # are made to run before starting the test cases.
+ task.enhance do
+ task("test").invoke unless Buildr.options.test == false
+ end
+ end
+
+
+ # The integration tests task. Buildr has one such task (see Buildr#integration) that runs
+ # all tests marked with :integration=>true, and has a setup/teardown tasks separate from
+ # the unit tests.
+ class IntegrationTestsTask < Rake::Task
+
+ def initialize(*args) #:nodoc:
+ super
+ task "#{name}-setup"
+ task "#{name}-teardown"
+ enhance { puts "Running integration tests..." if verbose }
+ end
+
+ def execute() #:nodoc:
+ setup.invoke
+ begin
+ super
+ ensure
+ teardown.invoke
+ end
+ end
+
+ # :call-seq:
+ # setup(*prereqs) => task
+ # setup(*prereqs) { |task| .. } => task
+ #
+ # Returns the setup task. The setup task is executed before running the integration tests.
+ def setup(*prereqs, &block)
+ Rake::Task["rake:integration-setup"].enhance prereqs, &block
+ end
+
+ # :call-seq:
+ # teardown(*prereqs) => task
+ # teardown(*prereqs) { |task| .. } => task
+ #
+ # Returns the teardown task. The teardown task is executed after running the integration tests.
+ def teardown(*prereqs, &block)
+ Rake::Task["rake:integration-teardown"].enhance prereqs, &block
+ end
+
+ end
+
+ # :call-seq:
+ # integration() { |task| .... }
+ # integration() => IntegrationTestTask
+ #
+ # Use this method to return the integration tests task, or enhance it with a block to execute.
+ #
+ # There is one integration tests task you can execute directly, or as a result of running the package
+ # task (or tasks that depend on it, like install and deploy). It contains all the tests marked with
+ # :integration=>true, all other tests are considered unit tests and run by the test task before packaging.
+ # So essentially: build=>test=>packaging=>integration=>install/deploy.
+ #
+ # You add new test cases from projects that define integration tests using the regular test task,
+ # but with the following addition:
+ # test.using :integration
+ #
+ # Use this method to enhance the setup and teardown tasks that are executed before (and after) all
+ # integration tests are run, for example, to start a Web server or create a database.
+ def integration(*deps, &block)
+ Rake::Task["rake:integration"].enhance deps, &block
+ end
+
+ IntegrationTestsTask.define_task("integration") { TestTask.run_local_tests true }
+
+ # Similar to test:[pattern] but for integration tests.
+ rule /^integration:.*$/ do |task|
+ TestTask.only_run task.name.scan(/integration:(.*)/)[0][0].split(",")
+ task("integration").invoke
+ end
+
+ # Anything that comes after local packaging (install, deploy) executes the integration tests,
+ # which do not conflict with integration invoking the project's own packaging (package=>
+ # integration=>foo:package is not circular, just confusing to debug.)
+ task "package" do |task|
+ integration.invoke if Buildr.options.test && Rake.application.original_dir == Dir.pwd
+ end
+
+
+ task("help") do
+ puts
+ puts "To run a full build without running any test cases:"
+ puts " buildr test=no"
+ puts "To run specific test case:"
+ puts " buildr test:MyTest"
+ puts "To run integration tests:"
+ puts " buildr integration"
+ end
+
+end
Added: incubator/buildr/trunk/lib/tasks/concat.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/tasks/concat.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/tasks/concat.rb (added)
+++ incubator/buildr/trunk/lib/tasks/concat.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,35 @@
+module Buildr
+
+ # A file task that concatenates all its prerequisites to create a new file.
+ #
+ # For example:
+ # concat("master.sql"=>["users.sql", "orders.sql", reports.sql"]
+ #
+ # See also Buildr#concat.
+ class ConcatTask < Rake::FileTask
+ def initialize(*args) #:nodoc:
+ super
+ enhance do |task|
+ content = prerequisites.inject("") do |content, prereq|
+ content << File.read(prereq.to_s) if File.exists?(prereq) && !File.directory?(prereq)
+ content
+ end
+ File.open(task.name, "wb") { |file| file.write content }
+ end
+ end
+ end
+
+ # :call-seq:
+ # concat(target=>files) => task
+ #
+ # Creates and returns a file task that concatenates all its prerequisites to create
+ # a new file. See #ConcatTask.
+ #
+ # For example:
+ # concat("master.sql"=>["users.sql", "orders.sql", reports.sql"]
+ def concat(args)
+ file, deps = Rake.application.resolve_args(args)
+ ConcatTask.define_task File.expand_path(file)=>deps
+ end
+
+end
Added: incubator/buildr/trunk/lib/tasks/tar.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/tasks/tar.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/tasks/tar.rb (added)
+++ incubator/buildr/trunk/lib/tasks/tar.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,87 @@
+require 'tasks/zip'
+require 'archive/tar/minitar'
+
+module Buildr
+
+ # The TarTask creates a new Tar file. You can include any number of files and and directories,
+ # use exclusion patterns, and include files into specific directories.
+ #
+ # To create a GZipped Tar, either set the gzip option to true, or use the .tgz or .gz suffix.
+ #
+ # For example:
+ # tar("test.tgz").tap do |task|
+ # task.include "srcs"
+ # task.include "README", "LICENSE"
+ # end
+ #
+ # See Buildr#tar and ArchiveTask.
+ class TarTask < ArchiveTask
+
+ # To create a GZipped Tar, either set this option to true, or use the .tgz/.gz suffix.
+ attr_accessor :gzip
+ # Permission mode for files contained in the Tar. Defaults to 0755.
+ attr_accessor :mode
+
+ def initialize(*args, &block) #:nodoc:
+ super
+ self.gzip = name =~ /\.[t?]gz$/
+ self.mode = '0755'
+ end
+
+ private
+
+ def create_from(file_map)
+ if gzip
+ StringIO.new.tap do |io|
+ create_tar io, file_map
+ io.seek 0
+ Zlib::GzipWriter.open(name) { |gzip| gzip.write io.read }
+ end
+ else
+ File.open(name, 'wb') { |file| create_tar file, file_map }
+ end
+ end
+
+ def create_tar(out, file_map)
+ Archive::Tar::Minitar::Writer.open(out) do |tar|
+ options = { :mode=>mode || '0755', :mtime=>Time.now }
+
+ file_map.each do |path, content|
+ if content.respond_to?(:call)
+ tar.add_file(path, options) { |os, opts| content.call os }
+ elsif content.nil? || File.directory?(content.to_s)
+ else
+ File.open content.to_s, 'rb' do |is|
+ tar.add_file path, options.merge(:mode=>is.stat.mode, :mtime=>is.stat.mtime, :uid=>is.stat.uid, :gid=>is.stat.gid) do |os, opts|
+ while data = is.read(4096)
+ os.write(data)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ end
+
+end
+
+
+# :call-seq:
+# tar(file) => TarTask
+#
+# The TarTask creates a new Tar file. You can include any number of files and
+# and directories, use exclusion patterns, and include files into specific
+# directories.
+#
+# To create a GZipped Tar, either set the gzip option to true, or use the .tgz or .gz suffix.
+#
+# For example:
+# tar("test.tgz").tap do |tgz|
+# tgz.include "srcs"
+# tgz.include "README", "LICENSE"
+# end
+def tar(file)
+ TarTask.define_task(file)
+end