# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with this # work for additional information regarding copyright ownership. The ASF # licenses this file to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. module Buildr #:nodoc: # Symbolic mapping for directory layout. Used for both the default and custom layouts. # # For example, the default layout maps [:source, :main, :java] to 'src/main/java', and # [:target, :main, :classes] to 'target/classes'. You can use this to change the layout # of your projects. # # To map [:source, :main] into the 'sources' directory: # my_layout = Layout.new # my_layout[:source, :main] = 'sources' # # define 'foo', :layout=>my_layout do # ... # end # # To map [:source, :main, :java] to 'java/main': # class MainLast < Layout # def expand(*args) # if args[0..1] == [:source, :main] # super args[2], :main, *args[3,] # else # super # end # end # end # # define 'foo', :layout=>MainLast do # ... # end class Layout class << self # Default layout used by new projects. attr_accessor :default end def initialize #:nodoc: @mapping = {} end # Expands list of symbols and path names into a full path, for example: # puts default.expand(:source, :main, :java) # => "src/main/java" def expand(*args) args = args.compact.reject { |s| s.to_s.empty? }.map(&:to_sym) return '' if args.empty? @mapping[args] ||= File.join(*[expand(*args[0..-2]), args.last.to_s].reject(&:empty?)) if args.size > 1 return @mapping[args] || args.first.to_s end # Resolves a list of symbols into a path. def [](*args) @mapping[args.map(&:to_sym)] end # Specifies the path resolved from a list of symbols. def []=(*args) @mapping[args[0...-1].map(&:to_sym)] = args.last end def initialize_copy(copy) copy.instance_variable_set :@mapping, @mapping.clone end # Default layout has the following properties: # * :source maps to the 'src' directory. # * Anything under :source maps verbatim (e.g. :source, :main becomes 'src/main') # * :target maps to the 'target' directory. # * :target, :main maps to the 'target' directory as well. # * Anything under :target, :main maps verbatim (e.g. :target, :main, :classes becomes 'target/classes') # * Anything else under :target also maps verbatim (e.g. :target, :test becomes 'target/test') class Default < Layout def initialize super self[:source] = 'src' self[:target, :main] = 'target' end end self.default = Default.new end # A project definition is where you define all the tasks associated with # the project you're building. # # The project itself will define several life cycle tasks for you. For example, # it automatically creates a compile task that will compile all the source files # found in src/main/java into target/classes, a test task that will compile source # files from src/test/java and run all the JUnit tests found there, and a build # task to compile and then run the tests. # # You use the project definition to enhance these tasks, for example, telling the # compile task which class path dependencies to use. Or telling the project how # to package an artifact, e.g. creating a JAR using package :jar. # # You can also define additional tasks that are executed by project tasks, # or invoked from rake. # # Tasks created by the project are all prefixed with the project name, e.g. # the project foo creates the task foo:compile. If foo contains a sub-project bar, # the later will define the task foo:bar:compile. Since the compile task is # recursive, compiling foo will also compile foo:bar. # # If you run: # buildr compile # from the command line, it will execute the compile task of the current project. # # Projects and sub-projects follow a directory hierarchy. The Buildfile is assumed to # reside in the same directory as the top-level project, and each sub-project is # contained in a sub-directory in the same name. For example: # /home/foo # |__ Buildfile # |__ src/main/java # |__ foo # |__ src/main/java # # The default structure of each project is assumed to be: # src # |__main # | |__java <-- Source files to compile # | |__resources <-- Resources to copy # | |__webapp <-- For WARs # |__test # | |__java <-- Source files to compile (tests) # | |__resources <-- Resources to copy (tests) # |__target <-- Packages created here # | |__classes <-- Generated when compiling # | |__resources <-- Copied (and filtered) from resources # | |__test/classes <-- Generated when compiling tests # | |__test/resources <-- Copied (and filtered) from resources # |__reports <-- Test, coverage and other reports # # You can change the project layout by passing a new Layout to the project definition. # # 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 project('foo').compile instead of task('foo:compile'). # # For example: # define 'myapp', :version=>'1.1' do # # define 'wepapp' do # compile.with project('myapp:beans') # package :war # end # # define 'beans' do # compile.with DEPENDS # package :jar # end # end # # puts projects.map(&:name) # => [ 'myapp', 'myapp:beans', 'myapp:webapp' ] # puts project('myapp:webapp').parent.name # => 'myapp' # puts project('myapp:webapp').compile.classpath.map(&:to_spec) # => '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). Buildr.application.current_scope == name.split(':')[0...-1] or raise "You can only define a sub project (#{name}) within the definition of its parent project" @projects ||= {} raise "You cannot define the same project (#{name}) more than once" if @projects[name] # Projects with names like: compile, test, build are invalid, so we have # to make sure the project has not the name of an already defined task raise "Invalid project name: #{name.inspect} is already used for a task" if Buildr.application.lookup(name) Project.define_task(name).tap do |project| # Define the project to prevent duplicate definition. @projects[name] = project # Set the project properties first, actions may use them. properties.each { |name, value| project.send "#{name}=", value } if properties # Setup to call before/after define extension callbacks # Don't cache list of extensions, since project may add new extensions. project.enhance do |project| project.send :call_callbacks, :before_define project.enhance do |project| project.send :call_callbacks, :after_define end end # Enhance the project using the definition block. project.enhance { project.instance_exec project, &block } if block # Mark the project as defined project.enhance do |project| project.send :define! end # Top-level project? Invoke the project definition. Sub-project? We don't invoke # the project definition yet (allow project calls to establish order of evaluation), # but must do so before the parent project's definition is done. project.parent.enhance { project.invoke } if project.parent end end # :call-seq: # project(name) => project # # See Buildr#project. def project(*args, &block) #:nodoc: options = args.pop if Hash === args.last return define(args.first, options, &block) if block rake_check_options options, :scope, :no_invoke if options no_invoke = options && options[:no_invoke] raise ArgumentError, 'Only one project name at a time' unless args.size == 1 @projects ||= {} name = args.first.to_s # Make sure parent project is evaluated (e.g. if looking for foo:bar, find foo first) unless @projects[name] parts = name.split(':') project(parts.first, options || {}) if parts.size > 1 end 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 project ||= @projects[name] # Not found in scope. raise "No such project #{name}" unless project project.invoke unless project.defined? || no_invoke || Buildr.application.current_scope.join(":").to_s == project.name.to_s 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, :scope, :no_invoke if options no_invoke = options && options[:no_invoke] @projects ||= {} names = names.flatten if options && options[:scope] # We assume parent project is evaluated. if names.empty? parent = @projects[options[:scope].to_s] or raise "No such project #{options[:scope]}" @projects.values.select { |project| project.parent == parent }.each { |project| project.invoke unless no_invoke }. map { |project| [project] + projects(:scope => project, :no_invoke => no_invoke) }.flatten.sort_by(&:name) else names.uniq.map { |name| project(name, :scope => options[:scope], :no_invoke => no_invoke) } end elsif names.empty? # Parent project(s) not evaluated so we don't know all the projects yet. @projects.values.each { |project| project.invoke unless no_invoke } @projects.keys.map { |name| project(name, :no_invoke => no_invoke) 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, :no_invoke => no_invoke) } names.uniq.map { |name| project(name, :no_invoke => no_invoke) or raise "No such project #{name}" }.sort_by(&:name) end end # :call-seq: # clear # # Discard all project definitions. def clear @projects.clear if @projects end # :call-seq: # local_task(name) # local_task(name) { |name| ... } # # Defines a local task with an optional execution message. # # A local task is a task that executes a task with the same name, defined in the # current project, the project's with a base directory that is the same as the # current directory. # # Complicated? Try this: # buildr build # is the same as: # buildr foo:build # But: # cd bar # buildr build # is the same as: # buildr foo:bar:build # # 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, args| args = task.arg_names.map {|n| args[n]} local_projects do |project| info block.call(project.name) if block task("#{project.name}:#{task.name}").invoke *args end end end # *Deprecated* Check the Extension module to see how extensions are handled. def on_define(&block) Buildr.application.deprecated 'This method is deprecated, see Extension' (@on_define ||= []) << block if block end def scope_name(scope, task_name) #:nodoc: task_name end def local_projects(dir = nil, &block) #:nodoc: dir = File.expand_path(dir || Buildr.application.original_dir) projects = @projects ? @projects.values : [] projects = projects.select { |project| project.base_dir == dir } if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir local_projects(File.dirname(dir), &block) elsif block if projects.empty? warn "No projects defined for directory #{Buildr.application.original_dir}" else projects.each { |project| block[project] } end else projects end end # :call-seq: # parent_task(task_name) => task_name or nil # # Returns a parent task, basically a task in a higher namespace. For example, the parent # of 'foo:test:compile' is 'foo:compile' and the parent of 'foo:compile' is 'compile'. def parent_task(task_name) #:nodoc: namespace = task_name.split(':') last_name = namespace.pop namespace.pop Buildr.application.lookup((namespace + [last_name]).join(':'), []) unless namespace.empty? end # :call-seq: # project_from_task(task) => project # # Figure out project associated to this task and return it. def project_from_task(task) #:nodoc: project = Buildr.application.lookup('rake:' + task.to_s.gsub(/:[^:]*$/, '')) project if Project === project end # Loaded extension modules. def extension_modules #:nodoc: @extension_modules ||= [] end # Extension callbacks that apply to all projects def global_callbacks #:nodoc: @global_callbacks ||= [] end end # Project has visibility to everything in the Buildr namespace. include Buildr # The project name. For example, 'foo' for the top-level project, and 'foo:bar' # for its sub-project. attr_reader :name # The parent project if this is a sub-project. attr_reader :parent def initialize(*args) #:nodoc: super split = name.split(':') if split.size > 1 # Get parent project, but do not invoke it's definition to prevent circular # dependencies (it's being invoked right now, so calling project will fail). @parent = task(split[0...-1].join(':')) raise "No parent project #{split[0...-1].join(':')}" unless @parent && Project === parent end # Inherit all global callbacks @callbacks = Project.global_callbacks.dup end # # Returns the root project for this project. # # If this project is a subproject it will find the top # level project and return it, else it will return itself. # def root_project p = project while p.parent p = p.parent end p end # :call-seq: # base_dir => path # # Returns the project's base directory. # # The Buildfile defines top-level project, so it's logical that the top-level project's # base directory is the one in which we find the Buildfile. And each sub-project has # a base directory that is one level down, with the same name as the sub-project. # # For example: # /home/foo/ <-- base_directory of project 'foo' # /home/foo/Buildfile <-- builds 'foo' # /home/foo/bar <-- sub-project 'foo:bar' 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. @base_dir = File.expand_path(name.split(':').last, parent.base_dir) else # For top-level project, a good default is the directory where we found the Buildfile. @base_dir = Dir.pwd end end @base_dir end # Returns the layout associated with this project. def layout @layout ||= (parent ? parent.layout : Layout.default).clone end # :call-seq: # path_to(*names) => path # # Returns a path from a combination of name, relative to the project's base directory. # Essentially, joins all the supplied names and expands the path relative to #base_dir. # Symbol arguments are converted to paths based on the layout, so whenever possible stick # to these. For example: # path_to(:source, :main, :java) # => 'src/main/java' # # Keep in mind that all tasks are defined and executed relative to the Buildfile directory, # so you want to use #path_to to get the actual path within the project as a matter of practice. # # For example: # path_to('foo', 'bar') # => foo/bar # path_to('/tmp') # => /tmp # path_to(:base_dir, 'foo') # same as path_to('foo") # => /home/project1/foo def path_to(*names) File.expand_path(layout.expand(*names), base_dir) end alias :_ :path_to # :call-seq: # file(path) => Task # file(path=>prereqs) => Task # file(path) { |task| ... } => Task # # Creates and returns a new file task in the project. Similar to calling Rake's # file method, but the path is expanded relative to the project's base directory, # and the task executes in the project's base directory. # # For example: # define 'foo' do # define 'bar' do # file('src') { ... } # end # end # # puts project('foo:bar').file('src').to_s # => '/home/foo/bar/src' def file(*args, &block) task_name, arg_names, deps = Buildr.application.resolve_args(args) task = Rake::FileTask.define_task(path_to(task_name)) task.set_arg_names(arg_names) unless arg_names.empty? task.enhance Array(deps), &block end # :call-seq: # task(name) => Task # task(name=>prereqs) => Task # task(name) { |task| ... } => Task # # Creates and returns a new task in the project. Similar to calling Rake's task # method, but prefixes the task name with the project name and executes the task # in the project's base directory. # # For example: # define 'foo' do # task 'doda' # end # # puts project('foo').task('doda').name # => 'foo:doda' # # When called from within the project definition, creates a new task if the task # does not already exist. If called from outside the project definition, returns # the named task and raises an exception if the task is not defined. # # As with Rake's task method, calling this method enhances the task with the # prerequisites and optional block. def task(*args, &block) task_name, arg_names, deps = Buildr.application.resolve_args(args) if task_name =~ /^:/ task = Buildr.application.switch_to_namespace [] do Rake::Task.define_task(task_name[1..-1]) end elsif Buildr.application.current_scope == name.split(':') task = Rake::Task.define_task(task_name) else unless task = Buildr.application.lookup(task_name, name.split(':')) raise "You cannot define a project task outside the project definition, and no task #{name}:#{task_name} defined in the project" end end task.set_arg_names(arg_names) unless arg_names.empty? task.enhance Array(deps), &block end # :call-seq: # recursive_task(name=>prereqs) { |task| ... } # # Define a recursive task. A recursive task executes itself and the same task # in all the sub-projects. def recursive_task(*args, &block) task_name, arg_names, deps = Buildr.application.resolve_args(args) task = Buildr.options.parallel ? multitask(task_name) : task(task_name) parent.task(task_name).enhance [task] if parent task.set_arg_names(arg_names) unless arg_names.empty? task.enhance Array(deps), &block end # :call-seq: # project(name) => project # project => self # # 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, &block) if Hash === args.last options = args.pop else options = {} end if args.empty? self else Project.project *(args + [{ :scope=>self.name }.merge(options)]), &block end end # :call-seq: # projects(*names) => projects # # 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 def inspect #:nodoc: %Q{project(#{name.inspect})} end def callbacks #:nodoc: # global + project_local callbacks for this project @callbacks ||= [] end def calledback #:nodoc: # project-local callbacks that have been called @calledback ||= {} end def defined? @defined end protected def define! @defined = true end # :call-seq: # base_dir = dir # # Sets the project's base directory. Allows you to specify a base directory by calling # this accessor, or with the :base_dir property when calling #define. # # You can only set the base directory once for a given project, and only before accessing # the base directory (for example, by calling #file or #path_to). # Set the base directory. Note: you can only do this once for a project, # and only before accessing the base directory. If you try reading the # value with #base_dir, the base directory cannot be set again. def base_dir=(dir) raise 'Cannot set base directory twice, or after reading its value' if @base_dir @base_dir = File.expand_path(dir) end # Sets the project layout. Accepts Layout object or class (or for that matter, anything # that can expand). def layout=(layout) raise 'Cannot set directory layout twice, or after reading its value' if @layout @layout = layout.is_a?(Class) ? layout.new : layout end # :call-seq: # define(name, properties?) { |project| ... } => project # # Define a new sub-project within this project. See Buildr#define. def define(name, properties = nil, &block) Project.define "#{self.name}:#{name}", properties, &block end def execute(args) #:nodoc: Buildr.application.switch_to_namespace name.split(':') do super end end # Call all extension callbacks for a particular phase, e.g. :before_define, :after_define. def call_callbacks(phase) #:nodoc: remaining = @callbacks.select { |cb| cb.phase == phase } known_callbacks = remaining.map { |cb| cb.name } # call each extension in order until remaining.empty? callback = first_satisfied(remaining, known_callbacks) if callback.nil? hash = remaining.map { |cb| { cb.name => cb.dependencies} } fail "Unsatisfied dependencies in extensions for #{phase}: #{hash.inspect}" end callback.blocks.each { |b| b.call(self) } end end private # find first callback with satisfied dependencies def first_satisfied(r, known_callbacks) remaining_names = r.map { |cb| cb.name } res = r.find do |cb| cb.dependencies.each do |dep| fail "Unknown #{phase.inspect} extension dependency: #{dep.inspect}" unless known_callbacks.index(dep) end satisfied = cb.dependencies.find { |dep| remaining_names.index(dep) } == nil cb if satisfied end r.delete res end end # The basic mechanism for extending projects in Buildr are Ruby modules. In fact, # base features like compiling and testing are all developed in the form of modules, # and then added to the core Project class. # # A module defines instance methods that are then mixed into the project and become # instance methods of the project. There are two general ways for extending projects. # You can extend all projects by including the module in Project: # class Project # include MyExtension # end # You can also extend a given project instance and only that instance by extending # it with the module: # define 'foo' do # extend MyExtension # end # # Some extensions require tighter integration with the project, specifically for # setting up tasks and properties, or for configuring tasks based on the project # definition. You can do that by adding callbacks to the process. # # The easiest way to add callbacks is by incorporating the Extension module in your # own extension, and using the various class methods to define callback behavior: # * first_time -- This block will be called once for any particular extension. # You can use this to setup top-level and local tasks. # * before_define -- This block is called once for the project with the project # instance, right before running the project definition. You can use this # to add tasks and set properties that will be used in the project definition. # * after_define -- This block is called once for the project with the project # instance, right after running the project definition. You can use this to # do any post-processing that depends on the project definition. # # This example illustrates how to write a simple extension: # module LinesOfCode # include Extension # # first_time do # # Define task not specific to any projet. # desc 'Count lines of code in current project' # Project.local_task('loc') # end # # before_define do |project| # # Define the loc task for this particular project. # Rake::Task.define_task 'loc' do |task| # lines = task.prerequisites.map { |path| Dir['#{path}/**/*'] }.flatten.uniq. # inject(0) { |total, file| total + File.readlines(file).count } # puts "Project #{project.name} has #{lines} lines of code" # end # end # # after_define do |project| # # Now that we know all the source directories, add them. # task('loc'=>compile.sources + compile.test.sources) # end # # # To use this method in your project: # # loc path_1, path_2 # def loc(*paths) # task('loc'=>paths) # end # # end # # class Buildr::Project # include LinesOfCode # end module Extension # Extension callback details class Callback #:nodoc: attr_accessor :phase, :name, :dependencies, :blocks def initialize(phase, name, dependencies, blocks) @phase = phase @name = name @dependencies = dependencies @blocks = (blocks ? (Array === blocks ? blocks : [blocks]) : []) end def merge(callback) Callback.new(phase, name, @dependencies + callback.dependencies, @blocks + callback.blocks) end end def self.included(base) #:nodoc: base.extend ClassMethods end # Methods added to the extension module when including Extension. module ClassMethods def included(base) #:nodoc: # When included in Project, add module instance, merge callbacks and call first_time. if Project == base && !base.extension_modules.include?(module_callbacks) base.extension_modules << module_callbacks merge_callbacks(base.global_callbacks, module_callbacks) first_time = module_callbacks.select { |c| c.phase == :first_time } first_time.each do |c| c.blocks.each { |b| b.call } end end end def extended(base) #:nodoc: # When extending project, merge after_define callbacks and call before_define callback(s) # immediately if Project === base merge_callbacks(base.callbacks, module_callbacks.select { |cb| cb.phase == :after_define }) calls = module_callbacks.select { |cb| cb.phase == :before_define } calls.each do |cb| cb.blocks.each { |b| b.call(base) } unless base.calledback[cb] base.calledback[cb] = cb end end end # This block will be called once for any particular extension included in Project. # You can use this to setup top-level and local tasks. def first_time(&block) module_callbacks << Callback.new(:first_time, self.name, [], block) end # This block is called once for the project with the project instance, # right before running the project definition. You can use this to add # tasks and set properties that will be used in the project definition. # # The block may be named and dependencies may be declared similar to Rake # task dependencies: # # before_define(:my_setup) do |project| # # do stuff on project # end # # # my_setup code must run before :compile # before_define(:compile => :my_setup) # def before_define(*args, &block) if args.empty? name = self.name deps = [] else name, args, deps = Buildr.application.resolve_args(args) end module_callbacks << Callback.new(:before_define, name, deps, block) end # This block is called once for the project with the project instance, # right after running the project definition. You can use this to do # any post-processing that depends on the project definition. # # The block may be named and dependencies may be declared similar to Rake # task dependencies: # # after_define(:my_setup) do |project| # # do stuff on project # end # # # my_setup code must run before :compile (but only after project is defined) # after_define(:compile => :my_setup) # def after_define(*args, &block) if args.empty? name = self.name deps = [] else name, args, deps = Buildr.application.resolve_args(args) end module_callbacks << Callback.new(:after_define, name, deps, block) end private def module_callbacks begin const_get('Callbacks') rescue callbacks = [] const_set('Callbacks', callbacks) end end def merge_callbacks(base, merge) # index by phase and name index = base.inject({}) { |hash,cb| { [cb.phase, cb.name] => cb } } merge.each do |cb| existing = index[[cb.phase, cb.name]] if existing base[base.index(existing)] = existing.merge(cb) else base << cb end index[[cb.phase, cb.name]] = cb end base end end end # :call-seq: # define(name, properties?) { |project| ... } => project # # Defines a new project. # # The first argument is the project name. Each project must have a unique name. # For a sub-project, the actual project name is created by prefixing the parent # project's name. # # The second argument is optional and contains a hash or properties that are set # on the project. You can only use properties that are supported by the project # definition, e.g. :group and :version. You can also set these properties from the # project definition. # # You pass a block that is executed in the context of the project definition. # This block is used to define the project and tasks that are part of the project. # Do not perform any work inside the project itself, as it will execute each time # the Buildfile is loaded. Instead, use it to create and extend tasks that are # related to the project. # # For example: # define 'foo', :version=>'1.0' do # # define 'bar' do # compile.with 'org.apache.axis2:axis2:jar:1.1' # end # end # # puts project('foo').version # => '1.0' # puts project('foo:bar').compile.classpath.map(&:to_spec) # => 'org.apache.axis2:axis2:jar:1.1' # % buildr build # => Compiling 14 source files in foo:bar def define(name, properties = nil, &block) #:yields:project Project.define(name, properties, &block) end # :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. project('foo').project('bar') # 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 # is evaluated immediately. So the returned project definition is always complete, # and you can access its definition (e.g. to find files relative to the base directory, # or packages created by that project). # # For example: # define 'myapp' do # self.version = '1.1' # # define 'webapp' do # # webapp is defined first, but beans is evaluated first # compile.with project('beans') # package :war # end # # define 'beans' do # package :jar # end # end # # puts project('myapp:beans').version def project(*args, &block) Project.project *args, &block end # :call-seq: # projects(*names) => 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. # # 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 project('myapp').projects.map(&:name) def projects(*args) Project.projects *args end end