lib/core/project.rb in buildr-1.0.0 vs lib/core/project.rb in buildr-1.1.0

- old
+ new

@@ -88,16 +88,15 @@ # | |__resources <-- Resources to copy (tests) # |__target <-- Packages created here # | |__classes <-- Generated when compiling # | |__test-classes <-- Generated when compiling tests # - # You can only define a project once using #define. Afterwards, you - # can obtain the project definition using #project. However, when - # working with sub-projects, one project may reference another ahead - # of its definition: the sub-project definitions are then evaluated - # based on their dependencies with each other. Circular dependencies - # are not allowed. + # You can only define a project once using #define. Afterwards, you can obtain the project + # definition using #project. The order in which you define projects is not important, + # project definitions are evaluated when you ask for them. Circular dependencies will not + # work. Rake tasks are only created after the project is evaluated, so if you need to access + # a task (e.g. compile) use <code>project("foo").compile</code> instead of <code>task("foo:compile")</code>. # # For example: # define "myapp", :version=>"1.1" do # # define "wepapp" do @@ -119,10 +118,13 @@ # => "myapp:myapp-beans:jar:1.1" class Project < Rake::Task class << self + # :call-seq: + # define(name, properties?) { |project| ... } => project + # # See Buildr#define. def define(name, properties, &block) #:nodoc: # Make sure a sub-project is only defined within the parent project, # to prevent silly mistakes that lead to inconsistencies (e.g. # namespaces will be all out of whack). @@ -143,38 +145,66 @@ project.enhance { project.instance_eval &block } if block # Top-level project? Invoke the project definition. Sub-project? We don't invoke # the project definiton yet (allow project() calls to establish order of evaluation), # but must do so before the parent project's definition is done. - if project.parent - project.parent.enhance { project.invoke } - else - project.invoke - end + project.parent.enhance { project.invoke } if project.parent end end + # :call-seq: + # project(name) => project + # # See Buildr#project. - def project(name) #:nodoc: - @projects && @projects[name] or raise "No such project #{name}" - @projects[name].tap { |project| project.invoke } + def project(*args) #:nodoc: + options = args.pop if Hash === args.last + rake_check_options options, :scope if options + raise ArgumentError, "Only one project name at a time" unless args.size == 1 + @projects ||= {} + name = args.first + if options && options[:scope] + # We assume parent project is evaluated. + project = options[:scope].split(":").inject([[]]) { |scopes, scope| scopes << (scopes.last + [scope]) }. + map { |scope| @projects[(scope + [name]).join(":")] }. + select { |project| project }.last + end + unless project + # Parent project not evaluated. + name.split(":").tap { |parts| @projects[parts.first].invoke if parts.size > 1 } + project = @projects[name] + end + raise "No such project #{name}" unless project + project.invoke + project end + # :call-seq: + # projects(*names) => projects + # # See Buildr#projects. def projects(*names) #:nodoc: options = names.pop if Hash === names.last - rake_check_options options, :in if options + rake_check_options options, :scope if options @projects ||= {} - names = @projects.keys if names.empty? - if options && options[:in] - parent = @projects[options[:in].to_s] or raise "No such project #{options[:in].to_s}" - names.uniq.map { |name| @projects[name] or raise "No such project #{name}" }. - select { |project| project.parent == parent }. - each { |project| project.invoke }.sort_by(&:name) + if options && options[:scope] + # We assume parent project is evaluated. + if names.empty? + parent = @projects[options[:scope]] or raise "No such project #{options[:scope]}" + @projects.values.select { |project| project.parent == parent }. + each { |project| project.invoke }.sort_by(&:name) + else + names.uniq.map { |name| project(name, :scope=>options[:scope]) } + end + elsif names.empty? + # Parent project(s) not evaluated so we don't know all the projects yet. + @projects.values.each(&:invoke) + @projects.keys.map { |name| project(name) or raise "No such project #{name}" }.sort_by(&:name) else + # Parent project(s) not evaluated, for the sub-projects we may need to find. + names.map { |name| name.split(":") }.select { |name| name.size > 1 }.map(&:first).uniq.each { |name| project(name) } names.uniq.map { |name| project(name) or raise "No such project #{name}" }.sort_by(&:name) - end + end end # :call-seq: # clear() # @@ -205,11 +235,11 @@ # # The optional block is called with the project name when the task executes # and returns a message that, for example "Building project #{name}". def local_task(args, &block) task args do |task| - projects = Project.projects.select { |project| project.base_dir == Rake.application.original_dir } + projects = local_projects if projects.empty? warn "No projects defined for directory #{Rake.application.original_dir}" if verbose else projects.each do |project| puts block.call(project.name) if block && verbose @@ -235,26 +265,24 @@ # enhance it from within #on_define. def on_define(&block) (@on_define ||= []) << block if block end - def warnings() #:nodoc: - [].tap do |msgs| - msgs << "There are no project definitions in your Rakefile" if @projects.nil? || @projects.empty? - # Find all projects that: - # * Are referenced but never defined. This is probably a typo. - # * Do not have a base directory. - (@projects || {}).each do |name, project| - msgs << "Project #{name} refers to the directory #{project.base_dir}, which does not exist" unless File.exist?(project.base_dir) - end - end - end - def scope_name(scope, task_name) #:nodoc: task_name end + def local_projects(dir = Rake.application.original_dir) #:nodoc: + dir = File.expand_path(dir) + projects = Project.projects.select { |project| project.base_dir == dir } + if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir + local_projects(File.dirname(dir)) + else + projects + end + end + end include InheritedAttributes # The project name. For example, "foo" for the top-level project, and "foo:bar" @@ -295,13 +323,11 @@ def base_dir() if @base_dir.nil? if @parent # For sub-project, a good default is a directory in the parent's base_dir, # using the same name as the project. - sub_dir = File.join(@parent.base_dir, name.split(":").last) - @base_dir = File.exist?(sub_dir) ? sub_dir : @parent.base_dir - @base_dir = sub_dir + @base_dir = File.join(@parent.base_dir, name.split(":").last) else # For top-level project, a good default is the directory where we found the Rakefile. @base_dir = Dir.pwd end end @@ -354,22 +380,45 @@ Project.define "#{self.name}:#{name}", properties, &block end # :call-seq: # project(name) => project + # project => self # - # Same as Buildr#project. - def project(name) - Project.project(name) + # Same as Buildr#project. This method is called on a project, so a relative name is + # sufficient to find a sub-project. + # + # When called on a project without a name, returns the project itself. You can use that when + # setting project properties, for example: + # define "foo" do + # project.version = "1.0" + # end + def project(*args) + if Hash === args.last + options = args.pop + else + options = {} + end + if args.empty? + self + else + Project.project *(args + [{ :scope=>self.name }.merge(options)]) + end end # :call-seq: # projects(*names) => projects # - # Same as Buildr#projects. - def projects(*names) - Project.projects(*names) + # Same as Buildr#projects. This method is called on a project, so relative names are + # sufficient to find sub-projects. + def projects(*args) + if Hash === args.last + options = args.pop + else + options = {} + end + Project.projects *(args + [{ :scope=>self.name }.merge(options)]) end # :call-seq: # file(path) => Task # file(path=>prereqs) => Task @@ -457,10 +506,14 @@ def execute() #:nodoc: # Reset the namespace, so all tasks are automatically defined in the project's namespace. Rake.application.in_namespace(":#{name}") { super } end + def inspect() #:nodoc: + %Q{project(#{name.inspect})} + end + end # :call-seq: # define(name, properties?) { |project| ... } => project # @@ -502,10 +555,15 @@ # :call-seq: # project(name) => project # # Returns a project definition. # + # When called from outside a project definition, must reference the project by its + # full name, e.g. "foo:bar" to access the sub-project "bar" in "foo". When called + # from inside a project, relative names are sufficient, e.g. <code>project("foo").project("bar")</code> + # will find the sub-project "bar" in "foo". + # # You cannot reference a project before the project is defined. When working with # sub-projects, the project definition is stored by calling #define, and evaluated # before a call to the parent project's #define method returns. # # However, if you call #project with the name of another sub-project, its definition @@ -517,44 +575,56 @@ # define "myapp" do # self.version = "1.1" # # define "webapp" do # # webapp is defined first, but beans is evaluated first - # compile.with project("myapp:beans") + # compile.with project("beans") # package :war # end # # define "beans" do # package :jar # end # end - def project(name) - Project.project(name) + # + # puts project("myapp:beans").version + def project(*args) + Project.project *args end # :call-seq: # projects(*names) => projects - # projects(:in=>parent) => projects # - # With no arguments, returns a list of all projects defined so far. With arguments, - # returns a list of these projects, fails on undefined projects. + # With no arguments, returns a list of all projects defined so far. When called on a project, + # returns all its sub-projects (direct descendants). # + # With arguments, returns a list of named projects, fails on any name that does not exist. + # As with #project, you can use relative names when calling this method on a project. + # # Like #project, this method evaluates the definition of each project before returning it. # Be advised of circular dependencies. # - # Use the :in option if you only want the sub-projects of a given parent project. - # # For example: # files = projects.map { |prj| FileList[prj.path_to("src/**/*.java") }.flatten # puts "There are #{files.size} source files in #{projects.size} projects" # # puts projects("myapp:beans", "myapp:webapp").map(&:name) # Same as: - # puts projects(:in=>"mayapp").map(&:name) - def projects(*names) - Project.projects *names + # puts project("myapp").projects.map(&:name) + def projects(*args) + Project.projects *args end - # Add project definition tests. - task("check") { |task| task.note *Project.warnings } + desc "List all projects defined by this Rakefile" + task "projects" do + wide = projects.map(&:name).map(&:size).max + projects.each do |project| + puts project.comment.blank? ? project.name : ("%-#{wide}s #%s" % [project.name, project.comment]) + end + end + + # Forces all the projects to be evaluated before executing any other task. + # If we don't do that, we don't get to have tasks available when running Rake. + task("buildr:projects") { projects } + Rake.application.top_level_tasks.unshift "buildr:projects" end