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