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