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 [5/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/artifact.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/artifact.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/artifact.rb (added)
+++ incubator/buildr/trunk/lib/java/artifact.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,738 @@
+require "core/project"
+require "core/transports"
+require "builder"
+
+module Buildr
+
+  desc "Download all artifacts"
+  task "artifacts"
+
+  # Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks.
+  #
+  # An artifact has an identifier, group identifier, type, version number and
+  # optional classifier. All can be used to locate it in the local repository,
+  # download from or upload to a remote repository.
+  #
+  # The #to_spec and #to_hash methods allow it to be used everywhere an artifact is
+  # accepted.
+  module ActsAsArtifact
+
+    ARTIFACT_ATTRIBUTES = [:group, :id, :type, :classifier, :version]
+
+    class << self
+    private
+      def included(mod)
+        mod.extend self
+      end
+    end
+
+    # The artifact identifier.
+    attr_reader :id
+    # The group identifier.
+    attr_reader :group
+    # The file type. (Symbol)
+    attr_reader :type
+    # The version number.
+    attr_reader :version
+    # Optional artifact classifier.
+    attr_reader :classifier
+
+    def snapshot?
+      version =~ /-SNAPSHOT$/
+    end
+
+    # :call-seq:
+    #   to_spec_hash() => Hash
+    #
+    # Returns the artifact specification as a hash. For example:
+    #   com.example:app:jar:1.2
+    # becomes:
+    #   { :group=>"com.example",
+    #     :id=>"app",
+    #     :type=>:jar,
+    #     :version=>"1.2" }
+    def to_spec_hash()
+      base = { :group=>group, :id=>id, :type=>type, :version=>version }
+      classifier.blank? ? base : base.merge(:classifier=>classifier)
+    end
+    alias_method :to_hash, :to_spec_hash
+
+    # :call-seq:
+    #   to_spec() => String
+    #
+    # Returns the artifact specification, in the structure:
+    #   <group>:<artifact>:<type>:<version>
+    # or
+    #   <group>:<artifact>:<type>:<classifier><:version>
+    def to_spec()
+      classifier.blank? ? "#{group}:#{id}:#{type}:#{version}" : "#{group}:#{id}:#{type}:#{classifier}:#{version}"
+    end
+
+    # :call-seq:
+    #   pom() => Artifact
+    # 
+    # Convenience method that returns a POM artifact.
+    def pom()
+      return self if type == :pom
+      Buildr.artifact(:group=>group, :id=>id, :version=>version, :type=>:pom, :classifier=>classifier)
+    end
+
+    # :call-seq:
+    #   pom_xml() => string
+    #
+    # Creates POM XML for this artifact.
+    def pom_xml()
+      xml = Builder::XmlMarkup.new(:indent=>2)
+      xml.instruct!
+      xml.project do
+        xml.modelVersion  "4.0.0"
+        xml.groupId       group
+        xml.artifactId    id
+        xml.version       version
+        xml.classifier    classifier if classifier
+      end
+    end
+
+    # :call-seq:
+    #   upload()
+    #   upload(url)
+    #   upload(options)
+    #
+    # Uploads the artifact, its POM and digital signatures to remote server.
+    #
+    # In the first form, uses the upload options specified by repositories.release_to.
+    # In the second form, uses a URL that includes all the relevant information.
+    # In the third form, uses a hash with the options :url, :username, :password,
+    # and :permissions. All but :url are optional.
+    def upload(upload_to = nil)
+      # Where do we release to?
+      upload_to ||= Buildr.repositories.release_to
+      upload_to = { :url=>upload_to } unless Hash === upload_to
+      raise ArgumentError, "Don't know where to upload, perhaps you forgot to set repositories.release_to" if upload_to[:url].blank?
+
+      # Set the upload URI, including mandatory slash (we expect it to be the base directory).
+      # Username/password may be part of URI, or separate entities.
+      uri = URI.parse(upload_to[:url].clone)
+      uri.path = uri.path + "/" unless uri.path[-1] == "/"
+      uri.user = upload_to[:username] if upload_to[:username]
+      uri.password = upload_to[:password] if upload_to[:password]
+
+      # Upload artifact relative to base URL, need to create path before uploading.
+      puts "Deploying #{to_spec}" if verbose
+      path = group.gsub(".", "/") + "/#{id}/#{version}/#{File.basename(name)}"
+      URI.upload uri + path, name, :permissions=>upload_to[:permissions]
+    end
+
+  protected
+
+    # Apply specification to this artifact.
+    def apply_spec(spec)
+      spec = Artifact.to_hash(spec)
+      ARTIFACT_ATTRIBUTES.each { |key| instance_variable_set("@#{key}", spec[key]) }
+      self
+    end
+    
+    def group_path
+      group.gsub(".", "/")
+    end
+
+  end
+
+ 
+  # A file task referencing an artifact in the local repository.
+  #
+  # This task includes all the artifact attributes (group, id, version, etc). It points
+  # to the artifact's path in the local repository. When invoked, it will download the
+  # artifact into the local repository if the artifact does not already exist.
+  #
+  # Note: You can enhance this task to create the artifact yourself, e.g. download it from
+  # a site that doesn't have a remote repository structure, copy it from a different disk, etc.
+  class Artifact < Rake::FileCreationTask
+
+    # The default artifact type.
+    DEFAULT_TYPE = :jar
+
+    include ActsAsArtifact
+
+    class << self
+
+      # :call-seq:
+      #   lookup(spec) => Artifact
+      #
+      # Lookup a previously registered artifact task based on its specification (String or Hash).
+      def lookup(spec)
+        @artifacts ||= {}
+        @artifacts[to_spec(spec)]
+      end
+
+      # :call-seq:
+      #   list() => specs
+      #
+      # Returns an array of specs for all the registered artifacts. (Anything created from artifact, or package).
+      def list()
+        @artifacts.keys
+      end
+
+      # :call-seq:
+      #   register(artifacts) => artifacts
+      #
+      # Register an artifact task(s) for later lookup (see #lookup).
+      def register(*tasks)
+        @artifacts ||= {}
+        fail "You can only register an artifact task, one of the arguments is not a Task that responds to to_spec()" unless
+          tasks.all? { |task| task.respond_to?(:to_spec) && task.respond_to?(:invoke) }
+        tasks.each { |task| @artifacts[task.to_spec] = task }
+        tasks
+      end
+
+      # :call-seq:
+      #   to_hash(spec_hash) => spec_hash
+      #   to_hash(spec_string) => spec_hash
+      #   to_hash(artifact) => spec_hash
+      #
+      # Turn a spec into a hash. This method accepts a String, Hash or any object that responds to
+      # the method to_spec. There are several reasons to use this method:
+      # * You can pass anything that could possibly be a spec, and get a hash.
+      # * It will check that the spec includes the group identifier, artifact
+      #   identifier and version number and set the file type, if missing.
+      # * It will always return a new specs hash.
+      def to_hash(spec)
+        if spec.respond_to?(:to_spec)
+          to_hash spec.to_spec
+        elsif Hash === spec
+          rake_check_options spec, :id, :group, :type, :classifier, :version
+          # Sanitize the hash and check it's valid.
+          spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k].to_s if spec[k] ; h }
+          fail "Missing group identifier for #{spec.inspect}" if spec[:group].blank?
+          fail "Missing artifact identifier for #{spec.inspect}" if spec[:id].blank?
+          fail "Missing version for #{spec.inspect}" if spec[:version].blank?
+          spec[:type] = spec[:type].blank? ? DEFAULT_TYPE : spec[:type].to_sym
+          spec
+        elsif String === spec
+          group, id, type, version, *rest = spec.split(":")
+          unless rest.empty?
+            # Optional classifier comes before version.
+            classifier, version = version, rest.shift
+            fail "Expecting <project:id:type:version> or <project:id:type:classifier:version>, found <#{spec}>" unless rest.empty?
+          end
+          to_hash :group=>group, :id=>id, :type=>type, :version=>version, :classifier=>classifier
+        else
+          fail "Expecting a String, Hash or object that responds to to_spec"
+        end
+      end
+
+      # :call-seq:
+      #   to_spec(spec_hash) => spec_string
+      #
+      # Convert a hash back to a spec string. This method accepts
+      # a string, hash or any object that responds to to_spec.
+      def to_spec(hash)
+        hash = to_hash(hash) unless Hash === hash
+        version = ":#{hash[:version]}" unless hash[:version].blank?
+        classifier = ":#{hash[:classifier]}" unless hash[:classifier].blank?
+        "#{hash[:group]}:#{hash[:id]}:#{hash[:type] || DEFAULT_TYPE}#{classifier}#{version}"
+      end
+
+      # :call-seq:
+      #   hash_to_file_name(spec_hash) => file_name
+      #
+      # Convert a hash spec to a file name.
+      def hash_to_file_name(hash)
+        version = "-#{hash[:version]}" unless hash[:version].blank?
+        classifier = "-#{hash[:classifier]}" unless hash[:classifier].blank?
+        "#{hash[:id]}#{version}#{classifier}.#{hash[:type] || DEFAULT_TYPE}"
+      end
+
+    end
+
+    def initialize(*args) #:nodoc:
+      super
+      enhance do |task|
+        # Default behavior: download the artifact from one of the remote repositories
+        # if the file does not exist. But this default behavior is counter productive
+        # if the artifact knows how to build itself (e.g. download from a different location),
+        # so don't perform it if the task found a different way to create the artifact.
+        task.enhance do
+          unless File.exist?(name)
+            puts "Downloading #{to_spec}" if verbose
+            download
+            pom.invoke rescue nil if pom && pom != self
+          end
+        end
+      end
+    end
+
+    # :call-seq:
+    #   from(path) => self
+    #
+    # Use this when you want to install or upload an artifact from a given file, for example:
+    #   test = artifact('group:id:jar:1.0').from('test.jar')
+    #   install test
+    # See also Buildr#install and Buildr#deploy.
+    def from(path)
+      path = File.expand_path(path.to_s)
+      enhance [path] do
+        verbose false do
+          mkpath File.dirname(name)
+          pom.invoke unless type == :pom
+          cp path, name
+          puts "Installed #{path} as #{to_spec}" if verbose
+        end
+      end
+      unless type == :pom
+        pom.enhance do
+          verbose false do
+            mkpath File.dirname(pom.name)
+            File.open(pom.name, 'w') { |file| file.write pom.pom_xml }
+          end
+        end
+      end
+      self
+    end
+
+  protected
+
+    # :call-seq:
+    #   download()
+    # 
+    # Downloads an artifact from one of the remote repositories, and stores it in the local
+    # repository. Accepts a String or Hash artifact specification, and returns a path to the
+    # artifact in the local repository. Raises an exception if the artifact is not found.
+    #
+    # This method attempts to download the artifact from each repository in the order in
+    # which they are returned from #remote, until successful. It always downloads the POM first.
+    def download()
+      puts "Downloading #{to_spec}" if Rake.application.options.trace
+      remote = Buildr.repositories.remote.map { |repo_url| URI === repo_url ? repo_url : URI.parse(repo_url) }
+      remote = remote.each { |repo_url| repo_url.path += "/" unless repo_url.path[-1] == "/" }
+      fail "No remote repositories defined!" if remote.empty?
+      exact_success = remote.find do |repo_url|
+        begin
+          path = "#{group_path}/#{id}/#{version}/#{File.basename(name)}"
+          URI.download repo_url + path, name
+          true
+        rescue URI::NotFoundError
+          false
+        rescue Exception=>error
+          puts error if verbose
+          puts error.backtrace.join("\n") if Rake.application.options.trace
+          false
+        end
+      end
+
+      if exact_success
+        return
+      elsif snapshot?
+        download_m2_snapshot(remote)
+      else
+        fail_download(remote)
+      end
+    end
+
+    def download_m2_snapshot(remote_uris)
+      remote_uris.find do |repo_url|
+        snapshot_url = current_snapshot_repo_url(repo_url)
+        if snapshot_url
+          begin
+            URI.download snapshot_url, name
+          rescue URI::NotFoundError
+            false
+          end
+        else
+          false
+        end
+      end or fail_download(remote_uris)
+    end
+
+    def current_snapshot_repo_url(repo_url)
+      begin
+        metadata_path = "#{group_path}/#{id}/#{version}/maven-metadata.xml"
+        metadata_xml = StringIO.new
+        URI.download repo_url + metadata_path, metadata_xml
+        metadata = REXML::Document.new(metadata_xml.string).root
+        timestamp = REXML::XPath.first(metadata, "//timestamp").text
+        build_number = REXML::XPath.first(metadata, "//buildNumber").text
+        snapshot_of = version[0, version.size - 9]
+        repo_url + "#{group_path}/#{id}/#{version}/#{id}-#{snapshot_of}-#{timestamp}-#{build_number}.#{type}"
+      rescue URI::NotFoundError
+        nil
+      end
+    end
+
+    def fail_download(remote_uris)
+      fail "Failed to download #{to_spec}, tried the following repositories:\n#{remote_uris.join("\n")}"
+    end
+  end
+
+
+  # Holds the path to the local repository, URLs for remote repositories, and settings for release server.
+  #
+  # You can access this object from the #repositories method. For example:
+  #   puts repositories.local
+  #   repositories.remote << "http://example.com/repo"
+  #   repositories.release_to = "sftp://example.com/var/www/public/repo"
+  class Repositories
+    include Singleton
+
+    # :call-seq:
+    #   local() => path
+    #
+    # Returns the path to the local repository.
+    #
+    # The default path is .m2/repository relative to the home directory.
+    def local()
+      @local ||= ENV["local_repo"] || File.join(Gem::user_home, ".m2/repository")
+    end
+
+    # :call-seq:
+    #   local = path
+    #
+    # Sets the path to the local repository.
+    #
+    # The best place to set the local repository path is from a buildr.rb file
+    # located in your home directory. That way all your projects will share the same
+    # path, without affecting other developers collaborating on these projects.
+    def local=(dir)
+      @local = dir ? File.expand_path(dir) : nil
+    end
+
+    # :call-seq:
+    #   locate(spec) => path
+    #
+    # Locates an artifact in the local repository based on its specification, and returns
+    # a file path.
+    #
+    # For example:
+    #   locate :group=>"log4j", :id=>"log4j", :version=>"1.1"
+    #     => ~/.m2/repository/log4j/log4j/1.1/log4j-1.1.jar
+    def locate(spec)
+      spec = Artifact.to_hash(spec)
+      File.join(local, spec[:group].split("."), spec[:id], spec[:version], Artifact.hash_to_file_name(spec))
+    end
+
+    # :call-seq:
+    #   remote() => Array
+    #
+    # Returns an array of all the remote repository URLs.
+    #
+    # When downloading artifacts, repositories are accessed in the order in which they appear here.
+    # The best way is to add repositories individually, for example:
+    #   repositories.remote << "http://example.com/repo"
+    def remote()
+      @remote ||= []
+    end
+
+    # :call-seq:
+    #   remote = Array
+    #   remote = url
+    #   remote = nil
+    #
+    # With a String argument, clears the array and set it to that single URL.
+    #
+    # With an Array argument, clears the array and set it to these specific URLs.
+    #
+    # With nil, clears the array.
+    def remote=(urls)
+      case urls
+      when nil
+        @remote = nil
+      when Array
+        @remote = urls.dup
+      else
+        @remote = [urls.to_s]
+      end
+    end
+
+    # *Deprecated* Please use options.proxy.http instead of repositories.proxy.
+    def proxy()
+      warn_deprecated "Please use options.proxy.http instead of repositories.proxy"
+      Buildr.options.proxy.http
+    end
+
+    # *Deprecated* Please use options.proxy.http = <url> instead of repositories.proxy.
+    def proxy=(proxy)
+      warn_deprecated "Please use options.proxy.http = <url> instead of repositories.proxy"
+      Buildr.options.proxy.http = proxy
+    end
+  
+    # *Deprecated* Just create an artifact and invoke it. 
+    def download(spec)
+      warn_deprecated "Just create and artifact and invoke it."
+      spec = Artifact.to_hash(spec) unless Hash === spec
+      filename = locate(spec)
+      
+      puts "Downloading #{Artifact.to_spec(spec)}" if Rake.application.options.trace
+      return filename if remote.any? do |repo_url|
+        repo_url = URI.parse(repo_url) unless URI === repo_url
+        repo_url.path += "/" unless repo_url.path[-1] == "/"
+        begin
+          path = spec[:group].gsub(".", "/") +
+            "/#{spec[:id]}/#{spec[:version]}/#{Artifact.hash_to_file_name(spec)}"
+          mkpath File.dirname(filename), :verbose=>false
+          # We absolutely need the POM, so make sure we download it before the artifact
+          # (unless the artifact is a POM).
+          URI.download repo_url + path.ext("pom"), filename.ext("pom")  unless type == :pom
+          URI.download repo_url + path, filename
+          true
+        rescue URI::NotFoundError
+          false
+        rescue Exception=>error
+          puts error if verbose
+          puts error.backtrace.join("\n") if Rake.application.options.trace
+          false
+        end
+      end
+      fail "Failed to download #{Artifact.to_spec(spec)}, tried the following repositories:\n#{remote.join("\n")}"
+    end
+
+    # :call-seq:
+    #   release_to = url
+    #   release_to = hash
+    #
+    # Specifies the release server. Accepts a Hash with different repository settings
+    # (e.g. url, username, password), or a String to only set the repository URL.
+    #
+    # Besides the URL, all other settings depend on the transport protocol in use.
+    #
+    # For example:
+    #   repositories.release_to = "sftp://john:secret@example.com/var/www/repo/"
+    #   repositories.release_to = { :url=>"sftp://example.com/var/www/repo/",
+    #                                :username="john", :password=>"secret" }
+    def release_to=(options)
+      options = { :url=>options } unless Hash === options
+      @release_to = options
+    end
+
+    # :call-seq:
+    #   release_to() => hash
+    #
+    # Returns the current release server setting as a Hash. This is a more convenient way to
+    # configure the settings, as it allows you to specify the settings progressively.
+    #
+    # For example, the Buildfile will contain the repository URL used by all developers:
+    #   repositories.release_to[:url] ||= "sftp://example.com/var/www/repo"
+    # Your private buildr.rb will contain your credentials:
+    #   repositories.release_to[:username] = "john"
+    #   repositories.release_to[:password] = "secret"
+    def release_to()
+      @release_to ||= {}
+    end
+
+    # *Deprecated* See release_to.
+    def deploy_to=(options)
+      warn_deprecated "Please use release_to instead."
+      self.release_to = options
+    end
+
+    # *Deprecated* See release_to.
+    def deploy_to()
+      warn_deprecated "Please use release_to instead."
+      self.release_to
+    end
+
+  end
+
+  # :call-seq:
+  #    repositories() => Repositories
+  #
+  # Returns an object you can use for setting the local repository path, remote repositories
+  # URL and release server settings.
+  #
+  # See Repositories.
+  def repositories()
+    Repositories.instance
+  end
+
+  # :call-seq:
+  #   artifact(spec) => Artifact
+  #   artifact(spec) { |task| ... } => Artifact
+  #
+  # Creates a file task to download and install the specified artifact in the local repository.
+  #
+  # You can use a String or a Hash for the artifact specification. The file task will point at
+  # the artifact's path inside the local repository. You can then use this tasks as a prerequisite
+  # for other tasks.
+  #
+  # This task will download and install the artifact only once. In fact, it will download and
+  # install the artifact if the artifact does not already exist. You can enhance it if you have
+  # a different way of creating the artifact in the local repository. See Artifact for more details.
+  #
+  # For example, to specify an artifact:
+  #   artifact("log4j:log4j:jar:1.1")
+  #
+  # To use the artifact in a task:
+  #   compile.with artifact("log4j:log4j:jar:1.1")
+  #
+  # To specify an artifact and the means for creating it:
+  #   download(artifact("dojo:dojo-widget:zip:2.0")=>
+  #     "http://download.dojotoolkit.org/release-2.0/dojo-2.0-widget.zip")
+  def artifact(spec, &block) #:yields:task
+    spec = Artifact.to_hash(spec)
+    unless task = Artifact.lookup(spec)
+      task = Artifact.define_task(repositories.locate(spec))
+      task.send :apply_spec, spec
+      Rake::Task["rake:artifacts"].enhance [task]
+      Artifact.register(task)
+    end
+    task.enhance &block
+  end
+
+  # :call-seq:
+  #   artifacts(*spec) => artifacts
+  #
+  # Handles multiple artifacts at a time. This method is the plural equivalent of
+  # #artifacts, but can do more things.
+  #
+  # You can pass any number of arguments, each of which can be:
+  # * An artifact specification (String or Hash). Returns the appropriate Artifact task.
+  # * An artifact of any other task. Returns the task as is.
+  # * A project. Returns all artifacts created (packaged) by that project.
+  # * A string. Returns that string, assumed to be a file name.
+  # * An array of artifacts or a Struct.
+  #
+  # For example, handling a collection of artifacts:
+  #   xml = [ xerces, xalan, jaxp ]
+  #   ws = [ axis, jax-ws, jaxb ]
+  #   db = [ jpa, mysql, sqltools ]
+  #   artifacts(xml, ws, db)
+  #
+  # Using artifacts created by a project:
+  #   artifact project("my-app")               # All packages
+  #   artifact project("mu-app").package(:war) # Only the WAR
+  def artifacts(*specs)
+    specs.flatten.inject([]) do |set, spec|
+      case spec
+      when Hash
+        set |= [artifact(spec)]
+      when /([^:]+:){2,4}/ # A spec as opposed to a file name.
+        set |= [artifact(spec)]
+      when String # Must always expand path.
+        set |= [File.expand_path(spec)]
+      when Project
+        set |= artifacts(spec.packages)
+      when Rake::Task
+        set |= [spec]
+      when Struct
+        set |= artifacts(spec.values)
+      else
+        fail "Invalid artifact specification in: #{specs.to_s}"
+      end
+    end
+  end
+
+  def transitive(*specs)
+    specs.flatten.inject([]) do |set, spec|
+      case spec
+      when /([^:]+:){2,4}/ # A spec as opposed to a file name.
+        artifact = artifact(spec)
+        set |= [artifact] unless artifact.type == :pom
+        set |= POM.load(artifact.pom).dependencies.map { |spec| artifact(spec) }
+      when Hash
+        set |= [transitive(spec)]
+      when String # Must always expand path.
+        set |= transitive(file(File.expand_path(spec)))
+      when Project
+        set |= transitive(spec.packages)
+      when Rake::Task
+        set |= spec.respond_to?(:to_spec) ? transitive(spec.to_spec) : [spec]
+      when Struct
+        set |= transitive(spec.values)
+      else
+        fail "Invalid artifact specification in: #{specs.to_s}"
+      end
+    end
+  end
+
+  # :call-seq:
+  #   groups(ids, :under=>group_name, :version=>number) => artifacts
+  #
+  # Convenience method for defining multiple artifacts that belong to the same group and version.
+  # Accepts multiple artifact identifiers follows by two hash values:
+  # * :under -- The group identifier
+  # * :version -- The version number
+  #
+  # For example:
+  #   group "xbean", "xbean_xpath", "xmlpublic", :under=>"xmlbeans", :version=>"2.1.0"
+  # Or:
+  #   group %w{xbean xbean_xpath xmlpublic}, :under=>"xmlbeans", :version=>"2.1.0"
+  def group(*args)
+    hash = args.pop
+    args.flatten.map { |id| artifact :group=>hash[:under], :version=>hash[:version], :id=>id }
+  end 
+
+  # :call-seq:
+  #   install(artifacts)
+  #
+  # Installs the specified artifacts in the local repository as part of the install task.
+  #
+  # You can use this to install various files in the local repository, for example:
+  #   install artifact('group:id:jar:1.0').from('some_jar.jar')
+  #   $ buildr install
+  def install(*args, &block)
+    artifacts = artifacts(args)
+    raise ArgumentError, 'This method can only install artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) }
+    all = (artifacts + artifacts.map { |artifact| artifact.pom }).uniq
+    task('install').tap do |task|
+      task.enhance all, &block
+      task 'uninstall' do
+        verbose false do
+          all.map(&:to_s ).each { |file| rm file if File.exist?(file) }
+        end
+      end
+    end
+  end
+
+  # :call-seq:
+  #   upload(artifacts)
+  #
+  # Uploads the specified artifacts to the release server as part of the upload task.
+  #
+  # You can use this to upload various files to the release server, for example:
+  #   upload artifact('group:id:jar:1.0').from('some_jar.jar')
+  #   $ buildr upload
+  def upload(*args, &block)
+    artifacts = artifacts(args)
+    raise ArgumentError, 'This method can only upload artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) }
+    all = (artifacts + artifacts.map { |artifact| artifact.pom }).uniq
+    task('upload').tap do |task|
+      task.enhance &block if block
+      task.enhance all do
+        all.each { |artifact| artifact.upload }
+      end
+    end
+  end
+
+  # *Deprecated* For artifact, call it's upload method; for anything else, use URI.upload.
+  def deploy(*args)
+    warn_deprecated "If it's an artifact, call it's upload method directly. Otherwise, use URI.upload."
+    # Where do we release to?
+    options = Hash === args.last ? args.pop : {}
+    deploy_to = options[:url] ? options : repositories.release_to
+    fail "Don't know where to deploy, perhaps you forgot to set repositories.deploy_to" if deploy_to[:url].blank?
+
+    args.flatten.each { |arg| arg.invoke if arg.respond_to?(:invoke) }
+    # Set the upload URI, including mandatory slash (we expect it to be the base directory).
+    # Username/password may be part of URI, or separate entities.
+    uri = URI.parse(deploy_to[:url].clone)
+    uri.path = uri.path + "/" unless uri.path[-1] == "/"
+    uri.user = deploy_to[:username] if deploy_to[:username]
+    uri.password = deploy_to[:password] if deploy_to[:password]
+
+    args.each do |arg|
+      if arg.respond_to?(:to_spec)
+        # Upload artifact relative to base URL, need to create path before uploading.
+        puts "Deploying #{arg.to_spec}" if verbose
+        spec = arg.to_spec_hash
+        path = spec[:group].gsub(".", "/") + "/#{spec[:id]}/#{spec[:version]}/" + Artifact.hash_to_file_name(spec)
+        URI.upload uri + path, arg.to_s, :permissions=>deploy_to[:permissions]
+      else
+        # Upload file to URL.
+        puts "Deploying #{arg}" if verbose
+        path = File.basename(args.to_s)
+        path = File.join(options[:path], path) if options[:path]
+        URI.upload uri + path, arg.to_s, :permissions=>deploy_to[:permissions]
+      end
+    end
+  end
+
+end

Added: incubator/buildr/trunk/lib/java/compile.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/compile.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/compile.rb (added)
+++ incubator/buildr/trunk/lib/java/compile.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,589 @@
+require "core/project"
+require "core/build"
+require "core/common"
+require "java/artifact"
+require "java/java"
+
+module Buildr
+  module Java
+
+    # Wraps Javac in a task that does all the heavy lifting.
+    #
+    # Accepts multiple source directories that are invoked as prerequisites before compilation.
+    # You can pass a task as a source directory, e.g. compile.from(apt).
+    #
+    # Likewise, classpath dependencies are invoked before compiling. All classpath dependencies
+    # are evaluated as #artifacts, so you can pass artifact specifications and even projects.
+    #
+    # Creates a file task for the target directory, so executing that task as a dependency will
+    # execute the compile task first.
+    #
+    # Compiler options are inherited form a parent task, e.g. the foo:bar:compile task inherits
+    # its options from the foo:compile task. Even if foo is an empty project that does not compile
+    # any classes itself, you can use it to set compile options for all its sub-projects.
+    #
+    # Normally, the project will take care of setting the source and target directory, and you
+    # only need to set options and classpath dependencies. See Project#compile.
+    class CompileTask < Rake::Task
+
+      # Compiler options, accessible from CompileTask#options.
+      #
+      # Supported options are:
+      # - warnings -- Generate warnings if true (opposite of -nowarn).
+      # - deprecation -- Output source locations where deprecated APIs are used.
+      # - source -- Source compatibility with specified release.
+      # - target -- Class file compatibility with specified release.
+      # - lint -- Value to pass to xlint argument. Use true to enable default lint
+      #   options, or pass a specific setting as string or array of strings.
+      # - debug -- Generate debugging info.
+      # - other -- Array of options to pass to the Java compiler as is.
+      #
+      # For example:
+      #   compile.options.warnings = true
+      #   compile.options.source = options.target = "1.6"
+      class Options
+
+        include InheritedAttributes
+
+        OPTIONS = [:warnings, :deprecation, :source, :target, :lint, :debug, :other]
+
+        # Generate warnings (opposite of -nowarn).
+        attr_accessor :warnings
+        inherited_attr(:warnings) { verbose }
+        # Output source locations where deprecated APIs are used.
+        attr_accessor :deprecation
+        inherited_attr :deprecation, false
+        # Provide source compatibility with specified release.
+        attr_accessor :source
+        inherited_attr :source
+        # Generate class files for specific VM version.
+        attr_accessor :target
+        inherited_attr :target
+        # Values to pass to Xlint: string or array. Use true to enable
+        # Xlint with no values.
+        attr_accessor :lint
+        inherited_attr :lint, false
+        # Generate all debugging info.
+        attr_accessor :debug
+        inherited_attr(:debug) { Buildr.options.debug }
+        # Array of arguments passed to the Java compiler as is.
+        attr_accessor :other
+        inherited_attr :other
+
+        def initialize(parent = nil) #:nodoc:
+          @parent = parent
+        end
+
+        attr_reader :parent # :nodoc:
+
+        # Resets all the options.
+        def clear()
+          OPTIONS.each { |name| send "#{name}=", nil }
+        end
+
+        def to_s() #:nodoc:
+          OPTIONS.inject({}){ |hash, name| hash[name] = send(name) ; hash }.reject{ |name,value| value.nil? }.inspect
+        end
+
+        # Returns Javac command line arguments from the set of options.
+        def javac_args()
+          args = []  
+          args << "-nowarn" unless warnings
+          args << "-verbose" if Rake.application.options.trace
+          args << "-g" if debug
+          args << "-deprecation" if deprecation
+          args << "-source" << source.to_s if source
+          args << "-target" << target.to_s if target
+          case lint
+          when Array
+            args << "-Xlint:#{lint.join(',')}"
+          when String
+            args << "-Xlint:#{lint}"
+          when true
+            args << "-Xlint"
+          end
+          args.concat(other.to_a) if other
+          args
+        end
+
+      end
+
+
+      def initialize(*args) #:nodoc:
+        super
+        parent = Project.task_in_parent_project(name)
+        if parent && parent.respond_to?(:options)
+          @options = Options.new(parent.options)
+        else
+          @options = Options.new
+        end
+        @sources = []
+        @classpath = []
+
+        enhance do |task|
+          mkpath target.to_s, :verbose=>false
+          Java.javac source_files.keys, :sourcepath=>sources.map(&:to_s).select { |source| File.directory?(source) }.uniq,
+            :classpath=>classpath, :output=>target, :javac_args=>options.javac_args, :name=>task.name
+          # By touching the target we let other tasks know we did something,
+          # and also prevent recompiling again for classpath dependencies.
+          touch target.to_s, :verbose=>false
+        end
+      end
+
+      # Source directories and files to compile.
+      attr_accessor :sources
+
+      # :call-seq:
+      #   from(*sources) => self
+      #
+      # Adds source directories and files to compile, and returns self.
+      #
+      # For example:
+      #   compile.from("src/java").into("classes").with("module1.jar")
+      def from(*sources)  
+        @sources |= sources.flatten
+        self
+      end
+
+      # Classpath dependencies.
+      attr_accessor :classpath
+
+      # :call-seq:
+      #   with(*artifacts) => self
+      #
+      # Adds files and artifacts as classpath dependencies, and returns self.
+      #
+      # Calls #artifacts on the arguments, so you can pass artifact specifications,
+      # tasks, projects, etc. Use this rather than setting the classpath directly.
+      #
+      # For example:
+      #   compile.with("module1.jar", "log4j:log4j:jar:1.0", project("foo"))
+      def with(*specs)
+        @classpath |= Buildr.artifacts(specs.flatten).uniq
+        self
+      end
+
+      # The target directory for the generated class files.
+      attr_reader :target
+
+      # :call-seq:
+      #   into(path) => self
+      #
+      # Sets the target directory and returns self. This will also set the compile task
+      # as a prerequisite to a file task on the target directory.
+      #
+      # For example:
+      #   compile(src_dir).into(target_dir).with(artifacts)
+      # Both compile.invoke and file(target_dir).invoke will compile the source files.
+      def into(path)
+        path = File.expand_path(path.to_s)
+        @target = file(path).enhance([self]) unless @target && @target.to_s == path
+        self
+      end
+
+      # Returns the compiler options.
+      attr_reader :options
+
+      # :call-seq:
+      #   using(options) => self
+      #
+      # Sets the compiler options from a hash and returns self.
+      #
+      # For example:
+      #   compile.using(:warnings=>true, :source=>"1.5")
+      def using(*args)
+        args.pop.each { |key, value| options.send "#{key}=", value } if Hash === args.last
+        args.each { |key| options.send "#{key}=", value = true }
+        self
+      end
+
+      def timestamp() #:nodoc:
+        # If we compiled successfully, then the target directory reflects that.
+        # If we didn't, see needed?
+        target ? target.timestamp : Rake::EARLY
+      end
+
+      def needed?() #:nodoc:
+        return false if source_files.empty?
+        return true unless File.exist?(target.to_s)
+        return true if source_files.any? { |j, c| !File.exist?(c) || File.stat(j).mtime > File.stat(c).mtime }
+        oldest = source_files.map { |j, c| File.stat(c).mtime }.min
+        return classpath.any? { |path| application[path].timestamp > oldest }
+      end
+
+      def prerequisites() #:nodoc:
+        super + classpath + sources
+      end
+
+      def invoke_prerequisites() #:nodoc:
+        prerequisites.each { |n| application[n, @scope].invoke }
+      end
+
+      # Returns the files to compile. This list is derived from the list of sources,
+      # expanding directories into files, and includes only source files that are
+      # newer than the corresponding class file. Includes all files if one or more
+      # classpath dependency has been updated.
+      def source_files()
+        @source_files ||= @sources.map(&:to_s).inject({}) do |map, source|
+          raise "Compile task #{name} has source files, but no target directory" unless target
+          target_dir = target.to_s
+          if File.directory?(source)
+            base = Pathname.new(source)
+            FileList["#{source}/**/*.java"].reject { |file| File.directory?(file) }.
+              each { |file| map[file] = File.join(target_dir, Pathname.new(file).relative_path_from(base).to_s.ext('.class')) }
+          else
+            map[source] = File.join(target_dir, File.basename(source).ext('.class'))
+          end
+          map
+        end
+      end
+
+    end
+
+ 
+    # The resources task is executed by the compile task to copy resource files over
+    # to the target directory. You can enhance this task in the normal way, but mostly
+    # you will use the task's filter.
+    #
+    # For example:
+    #   resources.filter.using "Copyright"=>"Acme Inc, 2007"
+    class ResourcesTask < Rake::Task
+
+      # Returns the filter used to copy resources over. See Buildr::Filter.
+      attr_reader :filter
+
+      def initialize(*args) #:nodoc:
+        super
+        @filter = Buildr::Filter.new
+        enhance { filter.run unless filter.sources.empty? }
+      end
+
+      # :call-seq:
+      #   include(*files) => self
+      #
+      # Includes the specified files in the filter and returns self.
+      def include(*files)
+        filter.include *files
+        self
+      end
+
+      # :call-seq:
+      #   exclude(*files) => self
+      #
+      # Excludes the specified files in the filter and returns self.
+      def exclude(*files)
+        filter.exclude *files
+        self
+      end
+
+      # :call-seq:
+      #   from(*sources) => self
+      #
+      # Adds additional directories from which to copy resources.
+      #
+      # For example:
+      #   resources.from _("src/etc")
+      def from(*sources)
+        filter.from *sources
+        self
+      end
+
+      # *Deprecated* Use #sources instead.
+      def source()
+        warn_deprecated "Please use sources instead."
+        filter.source
+      end
+
+      # Returns the list of source directories (each being a file task).
+      def sources()
+        filter.sources
+      end
+
+      # :call-seq:
+      #   target() => task
+      #
+      # Returns the filter's target directory as a file task.
+      def target()
+        filter.target
+      end
+
+      def prerequisites() #:nodoc:
+        super + filter.sources.flatten
+      end
+
+    end
+
+
+    # A convenient task for creating Javadocs from the project's compile task. Minimizes all
+    # the hard work to calling #from and #using.
+    #
+    # For example:
+    #   javadoc.from(projects("myapp:foo", "myapp:bar")).using(:windowtitle=>"My App")
+    # Or, short and sweet:
+    #   desc "My App"
+    #   define "myapp" do
+    #     . . .
+    #     javadoc projects("myapp:foo", "myapp:bar")
+    #   end
+    class JavadocTask < Rake::Task
+
+      def initialize(*args) #:nodoc:
+        super
+        @options = {}
+        @classpath = []
+        @sourcepath = []
+        @files = FileList[]
+        enhance do |task|
+          rm_rf target.to_s, :verbose=>false
+          Java.javadoc source_files, options.merge(:classpath=>classpath, :sourcepath=>sourcepath, :name=>name, :output=>target.to_s)
+          touch target.to_s, :verbose=>false
+        end
+      end
+
+      # The target directory for the generated Javadoc files.
+      attr_reader :target
+
+      # :call-seq:
+      #   into(path) => self
+      #
+      # Sets the target directory and returns self. This will also set the Javadoc task
+      # as a prerequisite to a file task on the target directory.
+      #
+      # For example:
+      #   package :zip, :classifier=>"docs", :include=>javadoc.target
+      def into(path)
+        path = File.expand_path(path.to_s)
+        @target = file(path).enhance([self]) unless @target && @target.to_s == path
+        self
+      end
+
+      # :call-seq:
+      #   include(*files) => self
+      #
+      # Includes additional source files and directories when generating the documentation
+      # and returns self. When specifying a directory, includes all .java files in that directory.
+      def include(*files)
+        @files.include *files
+        self
+      end
+
+      # :call-seq:
+      #   exclude(*files) => self
+      #
+      # Excludes source files and directories from generating the documentation.
+      def exclude(*files)
+        @files.exclude *files
+        self
+      end
+
+      # Classpath dependencies.
+      attr_accessor :classpath
+
+      # :call-seq:
+      #   with(*artifacts) => self
+      #
+      # Adds files and artifacts as classpath dependencies, and returns self.
+      def with(*specs)
+        @classpath |= Buildr.artifacts(specs.flatten).uniq
+        self
+      end
+
+      # Additional sourcepaths that are not part of the documented files.
+      attr_accessor :sourcepath
+        
+      # Returns the Javadoc options.
+      attr_reader :options
+
+      # :call-seq:
+      #   using(options) => self
+      #
+      # Sets the Javadoc options from a hash and returns self.
+      #
+      # For example:
+      #   javadoc.using :windowtitle=>"My application"
+      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:
+      #   from(*sources) => self
+      #
+      # Includes files, directories and projects in the Javadoc documentation and returns self.
+      #
+      # You can call this method with Java source files and directories containing Java source files
+      # to include these files in the Javadoc documentation, similar to #include. You can also call
+      # this method with projects. When called with a project, it includes all the source files compiled
+      # by that project and classpath dependencies used when compiling.
+      #
+      # For example:
+      #   javadoc.from projects("myapp:foo", "myapp:bar")
+      def from(*sources)
+        sources.flatten.each do |source|
+          case source
+          when Project
+            self.include source.compile.sources
+            self.with source.compile.classpath 
+          when Rake::Task, String
+            self.include source
+          else
+            fail "Don't know how to generate Javadocs from #{source || 'nil'}"
+          end
+        end
+        self
+      end
+
+      def prerequisites() #:nodoc:
+        super + @files + classpath + sourcepath
+      end
+
+      def source_files() #:nodoc:
+        @source_files ||= @files.map(&:to_s).
+          map { |file| File.directory?(file) ? FileList[File.join(file, "**/*.java")] : file }.
+          flatten.reject { |file| @files.exclude?(file) }
+      end
+
+      def needed?() #:nodoc:
+        return false if source_files.empty?
+        return true unless File.exist?(target.to_s)
+        source_files.map { |src| File.stat(src.to_s).mtime }.max > File.stat(target.to_s).mtime
+      end
+
+    end
+
+  end
+
+
+  # Local task to execute the compile task of the current project.
+  # This task is not itself a compile task.
+  desc "Compile all projects"
+  Project.local_task("compile") { |name| "Compiling #{name}" }
+
+  desc "Create the Javadocs for this project"
+  Project.local_task("javadoc")
+
+  class Project
+
+    # *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."
+      task("prepare").enhance prereqs, &block
+    end
+
+    # :call-seq:
+    #   compile(*sources) => CompileTask
+    #   compile(*sources) { |task| .. } => CompileTask
+    #
+    # The compile task does what its name suggests. This method returns the project's
+    # CompileTask. It also accepts a list of source directories and files to compile
+    # (equivalent to calling CompileTask#from on the task), and a block for any
+    # post-compilation work.
+    #
+    # The compile task will pick all the source files in the src/main/java directory,
+    # and unless specified, compile them into the target/classes directory. It will pick
+    # the default values for compiler options from the parent project's compile task.
+    #
+    # For example:
+    #   # Force target compatibility.
+    #   compile.options.source = "1.6"
+    #   # Include Apt-generated source files.
+    #   compile.from apt
+    #   # Include Log4J and the api sub-project artifacts.
+    #   compile.with "log4j:log4j:jar:1.2", project("api")
+    #   # Run the OpenJPA bytecode enhancer after compilation.
+    #   compile { open_jpa_enhance }
+    #
+    # For more information, see Java::CompileTask.
+    def compile(*sources, &block)
+      task("compile").from(sources).enhance &block
+    end
+
+    # :call-seq:
+    #   resources(*prereqs) => ResourcesTask
+    #   resources(*prereqs) { |task| .. } => ResourcesTask
+    #
+    # The resources task is executed by the compile task to copy resources files
+    # from the resource directory into the target directory.
+    #
+    # This method returns the project's resources task. It also accepts a list of
+    # prerequisites and a block, used to enhance the resources task.
+    #
+    # By default the resources task copies files from the src/main/resources into the
+    # same target directory as the #compile task. It does so using a filter that you
+    # can access by calling resources.filter (see Buildr::Filter).
+    #
+    # For example:
+    #   resources.from _("src/etc")
+    #   resources.filter.using "Copyright"=>"Acme Inc, 2007"
+    def resources(*prereqs, &block)
+      task("resources").enhance prereqs, &block
+    end
+
+    # :call-seq:
+    #   javadoc(*sources) => JavadocTask
+    #
+    # This method returns the project's Javadoc task. It also accepts a list of source files,
+    # directories and projects to include when generating the Javadocs.
+    #
+    # By default the Javadoc task uses all the source directories from compile.sources and generates
+    # Javadocs in the target/javadoc directory. This method accepts sources and adds them by calling
+    # JavadocsTask#from.
+    #
+    # For example, if you want to generate Javadocs for a given project that includes all source files
+    # in two of its sub-projects:
+    #   javadoc projects("myapp:foo", "myapp:bar").using(:windowtitle=>"Docs for foo and bar")
+    def javadoc(*sources, &block)
+      task("javadoc").from(*sources).enhance &block
+    end
+
+  end
+
+  Project.on_define do |project|
+    prepare = task("prepare")
+    # Resources task is a filter.
+    resources = Java::ResourcesTask.define_task("resources")
+    project.path_to("src/main/resources").tap { |dir| resources.from dir if File.exist?(dir) }
+    # Compile task requires prepare and performs resources, if anything compiled.
+    compile = Java::CompileTask.define_task("compile"=>[prepare, resources])
+    project.path_to("src/main/java").tap { |dir| compile.from dir if File.exist?(dir) }
+    compile.into project.path_to(:target, "classes")
+    resources.filter.into project.compile.target
+    Java::JavadocTask.define_task("javadoc"=>prepare).tap do |javadoc|
+      javadoc.into project.path_to(:target, "javadoc")
+      javadoc.using :windowtitle=>project.comment || project.name
+    end
+    project.recursive_task("compile")
+
+    project.enhance do |project|
+      # This comes last because the target path may change.
+      project.build project.compile.target
+      # This comes last so we can determine all the source paths and classpath dependencies.
+      project.javadoc.from project
+      project.clean { verbose(false) { rm_rf project.compile.target.to_s } }
+    end
+  end
+
+
+  class Options
+
+    # Returns the debug option (environment variable DEBUG).
+    def debug()
+      (ENV["DEBUG"] || ENV["debug"]) !~ /(no|off|false)/
+    end
+
+    # Sets the debug option (environment variable DEBUG).
+    #
+    # You can turn this option off directly, or by setting the environment variable
+    # DEBUG to "no". For example:
+    #   buildr build DEBUG=no
+    #
+    # The release tasks runs a build with <tt>DEBUG=no</tt>.
+    def debug=(flag)
+      ENV["debug"] = nil
+      ENV["DEBUG"] = flag.to_s
+    end
+
+  end
+
+end

Added: incubator/buildr/trunk/lib/java/eclipse.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/eclipse.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/eclipse.rb (added)
+++ incubator/buildr/trunk/lib/java/eclipse.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,155 @@
+require "pathname"
+require "core/project"
+require "java/artifact"
+
+module Buildr
+
+  # Global task "eclipse" generates artifacts for all projects.
+  desc "Generate Eclipse artifacts for all projects"
+  Project.local_task "eclipse"=>"artifacts"
+
+  Project.on_define do |project|
+    eclipse = project.recursive_task("eclipse")
+
+    project.enhance do |project|
+
+      # We need paths relative to the top project's base directory.
+      root_path = lambda { |p| f = lambda { |p| p.parent ? f[p.parent] : p.base_dir } ; f[p] }[project]
+
+      # We want the Eclipse files changed every time the Buildfile changes, but also anything loaded by
+      # the Buildfile (buildr.rb, separate file listing dependencies, etc), so we add anything required
+      # after the Buildfile. So which don't know where Buildr shows up exactly, ignore files that show
+      # in $LOADED_FEATURES that we cannot resolve.
+      sources = Buildr.build_files.map { |file| File.expand_path(file) }.select { |file| File.exist?(file) }
+      sources << File.expand_path(Rake.application.rakefile, root_path) if Rake.application.rakefile
+
+      # Check if project has scala facet
+      scala = project.task("scalac") if Rake::Task.task_defined?(project.name+":"+"scalac")
+      
+      # Only for projects that are Eclipse packagable.
+      if project.packages.detect { |pkg| pkg.type.to_s =~ /(jar)|(war)|(rar)|(mar)|(aar)/ }
+        eclipse.enhance [ file(project.path_to(".classpath")), file(project.path_to(".project")) ]
+
+        # The only thing we need to look for is a change in the Buildfile.
+        file(project.path_to(".classpath")=>sources) do |task|
+          puts "Writing #{task.name}" if verbose
+
+          # Find a path relative to the project's root directory.
+          relative = lambda do |path|
+            msg = [:to_path, :to_str, :to_s].find { |msg| path.respond_to? msg }
+            path = path.__send__(msg)
+            Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(project.path_to)).to_s
+          end
+
+          m2repo = Buildr::Repositories.instance.local
+          excludes = [ '**/.svn/', '**/CVS/' ].join('|')
+
+          File.open(task.name, "w") do |file|
+            xml = Builder::XmlMarkup.new(:target=>file, :indent=>2)
+            xml.classpath do
+              # Note: Use the test classpath since Eclipse compiles both "main" and "test" classes using the same classpath
+              cp = project.test.compile.classpath.map(&:to_s) - [ project.compile.target.to_s ]
+              cp += scala.classpath.map(&:to_s) if scala
+              cp = cp.uniq
+
+              # Convert classpath elements into applicable Project objects
+              cp.collect! { |path| projects.detect { |prj| prj.packages.detect { |pkg| pkg.to_s == path } } || path }
+
+              # project_libs: artifacts created by other projects
+              project_libs, others = cp.partition { |path| path.is_a?(Project) }
+
+              # Separate artifacts from Maven2 repository
+              m2_libs, others = others.partition { |path| path.to_s.index(m2repo) == 0 }
+
+              # Generated: classpath elements in the project are assumed to be generated
+              generated, libs = others.partition { |path| path.to_s.index(project.path_to.to_s) == 0 }
+
+              xml.classpathentry :kind=>'con', :path=>'org.eclipse.jdt.launching.JRE_CONTAINER'
+              xml.classpathentry :kind=>'con', :path=>'ch.epfl.lamp.sdt.launching.SCALA_CONTAINER' if scala
+
+              srcs = project.compile.sources
+              srcs << scala.sources if scala
+              
+              # hack until we have sunit task
+              project.path_to("src/test/scala").tap do |dir|
+                srcs += dir if scala and File.exist?(dir)
+              end
+              
+              srcs = srcs.map { |src| relative[src] } + generated.map { |src| relative[src] }
+              srcs.sort.uniq.each do |path|
+                xml.classpathentry :kind=>'src', :path=>path, :excluding=>excludes
+              end
+
+              { :output => relative[project.compile.target],
+                :lib    => libs.map(&:to_s),
+                :var    => m2_libs.map { |path| path.to_s.sub(m2repo, 'M2_REPO') }
+              }.each do |kind, paths|
+                paths.sort.uniq.each do |path|
+                  xml.classpathentry :kind=>kind, :path=>path
+                end
+              end
+
+              # Classpath elements from other projects
+              project_libs.map(&:id).sort.uniq.each do |project_id|
+                xml.classpathentry :kind=>'src', :combineaccessrules=>"false", :path=>"/#{project_id}"
+              end
+
+              # Main resources implicitly copied into project.compile.target
+              # TODO: find solution that uses project.test.resources.filter.sources
+              [ "src/main/resources" ].each do |path|
+                if File.exist? project.path_to(path)
+                  xml.classpathentry :kind=>'src', :path=>path, :excluding=>excludes
+                end
+              end
+
+              # Test classes are generated in a separate output directory
+              test_sources = project.test.compile.sources.map { |src| relative[src] }
+              test_sources.each do |paths|
+                paths.sort.uniq.each do |path|
+                  xml.classpathentry :kind=>'src', :path=>path, :output => relative[project.test.compile.target], :excluding=>excludes
+                end
+              end
+
+              # Test resources go in separate output directory as well
+              # TODO: find solution that uses project.test.resources.filter.sources
+              [ "src/test/resources" ].each do |path|
+                if File.exist? project.path_to(path)
+                  xml.classpathentry :kind=>'src', :path=>path, :output => relative[project.test.compile.target], :excluding=>excludes
+                end
+              end
+            end
+          end
+        end
+
+        # The only thing we need to look for is a change in the Buildfile.
+        file(project.path_to(".project")=>sources) do |task|
+          puts "Writing #{task.name}" if verbose
+          File.open(task.name, "w") do |file|
+            xml = Builder::XmlMarkup.new(:target=>file, :indent=>2)
+            xml.projectDescription do
+              xml.name project.id
+              xml.projects
+              xml.buildSpec do
+                xml.buildCommand do
+                  xml.name "org.eclipse.jdt.core.javabuilder"
+                end
+                if scala
+                  xml.buildCommand do
+                    #xml.name "ch.epfl.lamp.sdt.core.scalabuilder"
+                    xml.name "scala.plugin.scalabuilder"
+                  end
+                end
+              end
+              xml.natures do
+                xml.nature "org.eclipse.jdt.core.javanature"
+                #xml.nature "ch.epfl.lamp.sdt.core.scalanature" if scala
+                xml.nature "scala.plugin.scalanature" if scala
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+
+end # module Buildr

Added: incubator/buildr/trunk/lib/java/idea.ipr.template
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/idea.ipr.template?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/idea.ipr.template (added)
+++ incubator/buildr/trunk/lib/java/idea.ipr.template Tue Nov 13 15:44:11 2007
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4" relativePaths="false">
+  <component name="AntConfiguration">
+    <defaultAnt bundledAnt="true" />
+  </component>
+  <component name="BuildJarProjectSettings">
+    <option name="BUILD_JARS_ON_MAKE" value="false" />
+  </component>
+  <component name="CodeStyleManager">
+    <option name="USE_DEFAULT_CODE_STYLE_SCHEME" value="true" />
+    <option name="CODE_STYLE_SCHEME" value="" />
+  </component>
+  <component name="CodeStyleProjectProfileManger">
+    <option name="PROJECT_PROFILE" />
+    <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+  </component>
+  <component name="CodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS" />
+    <option name="USE_PER_PROJECT_SETTINGS" value="false" />
+  </component>
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <option name="DEPLOY_AFTER_MAKE" value="0" />
+    <resourceExtensions />
+    <wildcardResourcePatterns>
+      <entry name="!?*.java" />
+    </wildcardResourcePatterns>
+  </component>
+  <component name="DataSourceManager" />
+  <component name="DataSourceManagerImpl" />
+  <component name="DependenciesAnalyzeManager">
+    <option name="myForwardDirection" value="false" />
+  </component>
+  <component name="DependencyValidationManager" />
+  <component name="EclipseCompilerSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="true" />
+    <option name="DEPRECATION" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="EclipseEmbeddedCompilerSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="true" />
+    <option name="DEPRECATION" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="EntryPointsManager">
+    <entry_points />
+  </component>
+  <component name="ExportToHTMLSettings">
+    <option name="PRINT_LINE_NUMBERS" value="false" />
+    <option name="OPEN_IN_BROWSER" value="false" />
+    <option name="OUTPUT_DIRECTORY" />
+  </component>
+  <component name="GUI Designer component loader factory" />
+  <component name="IdProvider" IDEtalkID="0246D33576B8D4BC3331F9A5BB848389" />
+  <component name="ImportConfiguration">
+    <option name="VENDOR" />
+    <option name="RELEASE_TAG" />
+    <option name="LOG_MESSAGE" />
+    <option name="CHECKOUT_AFTER_IMPORT" value="true" />
+  </component>
+  <component name="InspectionProjectProfileManager">
+    <option name="PROJECT_PROFILE" value="Project Default" />
+    <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+    <scopes />
+    <profiles>
+      <profile version="1.0" is_locked="false">
+        <option name="myName" value="Project Default" />
+        <option name="myLocal" value="false" />
+        <used_levels>
+          <error>
+            <option name="myName" value="ERROR" />
+            <option name="myVal" value="400" />
+          </error>
+          <warning>
+            <option name="myName" value="WARNING" />
+            <option name="myVal" value="300" />
+          </warning>
+          <information>
+            <option name="myName" value="INFO" />
+            <option name="myVal" value="200" />
+          </information>
+          <server>
+            <option name="myName" value="SERVER PROBLEM" />
+            <option name="myVal" value="100" />
+          </server>
+        </used_levels>
+      </profile>
+    </profiles>
+  </component>
+  <component name="JUnitProjectSettings">
+    <option name="TEST_RUNNER" value="UI" />
+  </component>
+  <component name="JavacSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="DEPRECATION" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="false" />
+    <option name="OPTION_NAVIGATOR" value="false" />
+    <option name="OPTION_INDEX" value="false" />
+    <option name="OPTION_SEPARATE_INDEX" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="false" />
+    <option name="OPTION_DEPRECATED_LIST" value="false" />
+    <option name="OTHER_OPTIONS" />
+    <option name="HEAP_SIZE" />
+    <option name="LOCALE" />
+    <option name="OPEN_IN_BROWSER" value="false" />
+  </component>
+  <component name="JikesSettings">
+    <option name="JIKES_PATH" value="" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="DEPRECATION" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="IS_EMACS_ERRORS_MODE" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="LogConsolePreferences">
+    <option name="FILTER_ERRORS" value="false" />
+    <option name="FILTER_WARNINGS" value="false" />
+    <option name="FILTER_INFO" value="true" />
+    <option name="CUSTOM_FILTER" />
+  </component>
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+  <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="true" project-jdk-name="1.5" />
+  <component name="ProjectRunConfigurationManager" />
+  <component name="RmicSettings">
+    <option name="IS_EANABLED" value="false" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="GENERATE_IIOP_STUBS" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="StarteamVcsAdapter" />
+  <component name="VssVcs" />
+  <component name="com.intellij.jsf.UserDefinedFacesConfigs">
+    <option name="USER_DEFINED_CONFIGS">
+      <value>
+        <list size="0" />
+      </value>
+    </option>
+  </component>
+  <component name="uidesigner-configuration">
+    <option name="INSTRUMENT_CLASSES" value="true" />
+    <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" />
+    <option name="DEFAULT_LAYOUT_MANAGER" value="GridLayoutManager" />
+  </component>
+  <UsedPathMacros>
+    <macro name="M2_REPO" />
+  </UsedPathMacros>
+</project>
+
+ 

Added: incubator/buildr/trunk/lib/java/idea.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/idea.rb?rev=594720&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/idea.rb (added)
+++ incubator/buildr/trunk/lib/java/idea.rb Tue Nov 13 15:44:11 2007
@@ -0,0 +1,159 @@
+require "pathname"
+require "core/project"
+require "java/artifact"
+require 'stringio'
+require 'rexml/document'
+
+module Buildr
+
+  # Global task "idea" generates artifacts for all projects.
+  desc "Generate Idea artifacts for all projects"
+  Project.local_task "idea"=>"artifacts"
+
+  Project.on_define do |project|
+    idea = project.recursive_task("idea")
+
+    project.enhance do |project|
+
+      # We need paths relative to the top project's base directory.
+      root_path = lambda { |p| f = lambda { |p| p.parent ? f[p.parent] : p.base_dir } ; f[p] }[project]
+      # We want the Eclipse files changed every time the Buildfile changes, but also anything loaded by
+      # the Buildfile (buildr.rb, separate file listing dependencies, etc), so we add anything required
+      # after the Buildfile. So which don't know where Buildr shows up exactly, ignore files that show
+      # in $LOADED_FEATURES that we cannot resolve.
+      sources = Buildr.build_files.map { |file| File.expand_path(file) }.select { |file| File.exist?(file) }
+      sources << File.expand_path(Rake.application.rakefile, root_path) if Rake.application.rakefile
+
+      # Find a path relative to the project's root directory.
+      relative = lambda do |path|
+        msg = [:to_path, :to_str, :to_s].find { |msg| path.respond_to? msg }
+        path = path.__send__(msg)
+        Pathname.new(path).relative_path_from(Pathname.new(project.path_to)).to_s
+      end
+
+      m2repo = Buildr::Repositories.instance.local
+      excludes = [ '**/.svn/', '**/CVS/' ].join('|')
+
+      # Only for projects that are packageable.
+      task_name = project.path_to("#{project.name.gsub(':', '-')}.iml")
+      idea.enhance [ file(task_name) ]
+
+      # The only thing we need to look for is a change in the Buildfile.
+      file(task_name=>sources) do |task|
+        puts "Writing #{task.name}" if verbose
+
+        # Idea handles modules slightly differently if they're WARs
+        idea_types = Hash.new("JAVA_MODULE")
+        idea_types["war"] = "J2EE_WEB_MODULE"
+
+        # Note: Use the test classpath since Eclipse compiles both "main" and "test" classes using the same classpath
+        cp = project.test.compile.classpath.map(&:to_s) - [ project.compile.target.to_s ]
+
+        # Convert classpath elements into applicable Project objects
+        cp.collect! { |path| projects.detect { |prj| prj.packages.detect { |pkg| pkg.to_s == path } } || path }
+
+        # project_libs: artifacts created by other projects
+        project_libs, others = cp.partition { |path| path.is_a?(Project) }
+
+        # Separate artifacts from Maven2 repository
+        m2_libs, others = others.partition { |path| path.to_s.index(m2repo) == 0 }
+
+        # Generated: classpath elements in the project are assumed to be generated
+        generated, libs = others.partition { |path| path.to_s.index(project.path_to.to_s) == 0 }
+
+        File.open(task.name, "w") do |file|
+          xml = Builder::XmlMarkup.new(:target=>file, :indent=>2)
+          # Project type is going to be the first package type
+          xml.module(:version=>"4", :relativePaths=>"false", :type=>idea_types[project.packages.first.type.to_s]) do
+
+            xml.component :name=>"ModuleRootManager"
+            xml.component "name"=>"NewModuleRootManager", "inherit-compiler-output"=>"false" do
+              xml.output :url=>"file://$MODULE_DIR$/#{relative[project.compile.target]}"
+              xml.tag! "exclude-output"
+
+              # TODO project.test.target isn't recognized, what's the proper way to get the test compile path?
+              xml.tag! "output-test", :url=>"file://$MODULE_DIR$/target/test-classes"
+
+              xml.content(:url=>"file://$MODULE_DIR$") do
+                srcs = project.compile.sources.map { |src| relative[src] } + generated.map { |src| relative[src] }
+                srcs.sort.uniq.each do |path|
+                  xml.sourceFolder :url=>"file://$MODULE_DIR$/#{path}", :isTestSource=>"false"
+                end
+                test_sources = project.test.compile.sources.map { |src| relative[src] }
+                test_sources.each do |paths|
+                  paths.sort.uniq.each do |path|
+                    xml.sourceFolder :url=>"file://$MODULE_DIR$/#{path}", :isTestSource=>"true"
+                  end
+                end
+                {"src/main/resources"=>false, "src/test/resources"=>true}.each do |key, value|
+                  xml.sourceFolder :url=>"file://$MODULE_DIR$/#{key}", :isTestSource=>"#{value}" if File.exist?(project.path_to(key))
+                end
+                xml.excludeFolder :url=>"file://$MODULE_DIR$/#{relative[project.compile.target]}"
+              end
+
+              xml.orderEntry :type=>"sourceFolder", :forTests=>"false"
+              xml.orderEntry :type=>"inheritedJdk"
+
+              # Classpath elements from other projects
+              project_libs.map(&:id).sort.uniq.each do |project_id|
+                xml.orderEntry :type=>'module', "module-name"=>project_id
+              end
+
+              # Libraries
+              ext_libs = libs.map {|path| "$MODULE_DIR$/#{path.to_s}" } + 
+              m2_libs.map { |path| path.to_s.sub(m2repo, "$M2_REPO$") }                  
+              ext_libs.each do |path|
+                xml.orderEntry :type=>"module-library" do
+                  xml.library do
+                    xml.CLASSES do
+                      xml.root :url=>"jar://#{path}!/"
+                    end
+                    xml.JAVADOC
+                    xml.SOURCES
+                  end
+                end
+              end
+
+              xml.orderEntryProperties
+            end
+          end
+        end
+      end
+
+      # Root project aggregates all the subprojects.
+      if project.parent == nil
+        task_name = project.path_to("#{project.name.gsub(':', '-')}.ipr")
+        idea.enhance [ file(task_name) ]
+
+        file(task_name=>sources) do |task|
+          puts "Writing #{task.name}" if verbose
+
+          # Generating just the little stanza that chanages from one project to another
+          partial = StringIO.new
+          xml = Builder::XmlMarkup.new(:target=>partial, :indent=>2)
+          xml.component(:name=>"ProjectModuleManager") do
+            xml.modules do
+              project.projects.each do |subp|
+                module_name = subp.name.gsub(":", "-")
+                module_path = subp.name.split(":"); module_path.shift
+                module_path = module_path.join("/")
+                path = "#{module_path}/#{module_name}.iml"
+                xml.module :fileurl=>"file://$PROJECT_DIR$/#{path}", :filepath=>"$PROJECT_DIR$/#{path}"
+              end
+              xml.module :fileurl=>"file://$PROJECT_DIR$/#{project.name}.iml", :filepath=>"$PROJECT_DIR$/#{project.name}.iml"
+            end
+          end
+
+          # Loading the whole fairly constant crap
+          template_xml = REXML::Document.new(File.open(File.dirname(__FILE__)+"/idea.ipr.template"))
+          include_xml = REXML::Document.new(partial.string)
+          template_xml.root.add_element(include_xml.root)
+          template_xml.write(File.new(task.name, "w"))
+
+        end
+      end
+
+    end
+  end
+
+end # module Buildr