Class: Buildr::Project

Inherits:
Rake::Task show all
Includes:
Buildr, Ant, Apt, Build, Checks, Compile, Eclipse, Idea, Idea7x, Javadoc, Package, PackageAsGem, Buildr::Packaging::Java, Test, CoberturaExtension, EmmaExtension
Defined in:
lib/buildr/ide/idea7x.rb,
lib/buildr/ide/idea.rb,
lib/buildr/java/ant.rb,
lib/buildr/core/test.rb,
lib/buildr/java/emma.rb,
lib/buildr/core/build.rb,
lib/buildr/core/checks.rb,
lib/buildr/ide/eclipse.rb,
lib/buildr/core/compile.rb,
lib/buildr/core/project.rb,
lib/buildr/java/compiler.rb,
lib/buildr/java/cobertura.rb,
lib/buildr/java/packaging.rb,
lib/buildr/packaging/gems.rb,
lib/buildr/packaging/package.rb

Overview

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 heirarchy. 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'

Defined Under Namespace

Modules: RecursiveTask Classes: NoSuchProject

Constant Summary

Constants included from Buildr

ScalaCheck, ScalaSpecs, ScalaTest, VERSION

Constants included from Ant

Ant::VERSION

Constants included from Idea7x

Idea7x::CLASSIFIER, Idea7x::FILE_PATH_PREFIX, Idea7x::IML_SUFFIX, Idea7x::IPR_TEMPLATE, Idea7x::MODULE_DIR, Idea7x::MODULE_DIR_URL, Idea7x::PROJECT_DIR, Idea7x::PROJECT_DIR_URL

Instance Attribute Summary collapse

Attributes included from Package

#group, #version

Attributes included from Buildr::Packaging::Java

#manifest, #meta_inf

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Package

#id, #package, #packages

Methods included from Extension

included

Methods included from PackageAsGem

#package_as_gem

Methods included from Buildr::Packaging::Java

#package_with_javadoc, #package_with_sources

Methods included from Apt

#apt

Methods included from Javadoc

#javadoc

Methods included from Buildr

application, application=, #artifact, #artifact_ns, #artifacts, #concat, #download, environment, #filter, #group, #help, help, #install, #integration, options, #options, #read, #repositories, settings, #struct, #transitive, #unzip, #upload, #write, #zip

Methods included from Ant

#ant, dependencies, version

Methods included from Compile

#compile, #resources

Methods included from Checks

#check, #expectations

Methods included from Idea7x

generate_compile_output, generate_content, generate_ipr, generate_module_libs, generate_order_entries

Methods included from Build

#build, #clean, #reports, #reports=, #target, #target=

Methods included from Test

#integration, #test

Methods inherited from Rake::Task

#invoke, #invoke_with_call_chain

Constructor Details

#initialize(*args) ⇒ Project

:nodoc:



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/buildr/core/project.rb', line 426

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
  callbacks = Project.callbacks.uniq.map(&:new)
  @callbacks = [:before_define, :after_define].inject({}) do |hash, state|
    methods = callbacks.select { |callback| callback.respond_to?(state) }.map { |callback| callback.method(state) }
    hash.update(state=>methods)
  end
end

Instance Attribute Details

#nameObject (readonly)

The project name. For example, ‘foo’ for the top-level project, and ‘foo:bar’ for its sub-project.



421
422
423
# File 'lib/buildr/core/project.rb', line 421

def name
  @name
end

#parentObject (readonly)

The parent project if this is a sub-project.



424
425
426
# File 'lib/buildr/core/project.rb', line 424

def parent
  @parent
end

Class Method Details

.callbacksObject

Callback classes.



409
410
411
# File 'lib/buildr/core/project.rb', line 409

def callbacks #:nodoc:
  @callbacks ||= []
end

.clearObject

:call-seq:

clear

Discard all project definitions.



316
317
318
# File 'lib/buildr/core/project.rb', line 316

def clear
  @projects.clear if @projects
end

.define(name, properties, &block) ⇒ Object

:call-seq:

define(name, properties?) { |project| ... } => project

See Buildr#define.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/buildr/core/project.rb', line 209

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
    # Instantiate callbacks for this project, and setup to call before/after define.
    # Don't cache list of callbacks, since project may add new callbacks.
    project.enhance do |project|
      project.send :call_callbacks, :before_define
      project.enhance do |project|
        project.send :call_callbacks, :after_define
      end
    end
    project.enhance do |project|
      @on_define.each { |callback| callback[project] }
    end if @on_define
    # Enhance the project using the definition block.
    project.enhance {|project| 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.
    ### project.parent.enhance { project.invoke } if project.parent
  end
end

.find_local_projects(dir, projects) ⇒ Object

limitation: dir must match exactly the base dir (not a sub dir)



379
380
381
382
383
384
385
# File 'lib/buildr/core/project.rb', line 379

def find_local_projects(dir, projects)
  projects = projects.select{|p| dir.index(p.base_dir) == 0}
  result = projects.select {|p| p.base_dir == dir}
  sub_projects = projects.map {|p| p.projects(:immediate => true, :no_invoke => true)}.flatten
  result |= find_local_projects(dir, sub_projects) unless sub_projects.empty?
  result
end

.local_projects(dir = nil, &block) ⇒ Object

:nodoc:



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/buildr/core/project.rb', line 361

def local_projects(dir = nil, &block) #:nodoc:
  dir = File.expand_path(dir || Buildr.application.original_dir)
  projects = @projects ? @projects.values : []
  projects = find_local_projects(dir, projects)
  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

.local_task(args, &block) ⇒ Object

: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”.



342
343
344
345
346
347
348
349
# File 'lib/buildr/core/project.rb', line 342

def local_task(args, &block)
  task args do |task|
    local_projects do |project|
      info block.call(project.name) if block
      task("#{project.name}:#{task.name}").invoke
    end
  end
end

.on_define(&block) ⇒ Object

Deprecated Check the Extension module to see how extensions are handled.



352
353
354
355
# File 'lib/buildr/core/project.rb', line 352

def on_define(&block)
  Buildr.application.deprecated 'This method is deprecated, see Extension'
  (@on_define ||= []) << block if block
end

.parent_task(task_name) ⇒ Object

: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’.



392
393
394
395
396
397
# File 'lib/buildr/core/project.rb', line 392

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

.project(*args) ⇒ Object

:call-seq:

project(name) => project

See Buildr#project.

Raises:

  • (ArgumentError)


252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/buildr/core/project.rb', line 252

def project(*args) #:nodoc:
  options = Hash === args.last ? args.pop : {}
  rake_check_options options, :scope, :no_invoke if options
  raise ArgumentError, 'Only one project name at a time' unless args.size == 1
  @projects ||= {}
  name = args.first.to_s
  parent_name = name.sub(/:?[^:]*$/, '')
  unless parent_name.empty?
    begin
      project(parent_name, options)
    rescue NoSuchProject => detail
      raise NoSuchProject.new(name)
    end
  end
  if options && options[:scope]
    # We assume parent project is evaluated.
    project = options[:scope].to_s.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 NoSuchProject.new(name) unless project
  project.invoke unless options[:no_invoke]
  project
end

.project_from_task(task) ⇒ Object

:call-seq:

project_from_task(task) => project

Figure out project associated to this task and return it.



403
404
405
406
# File 'lib/buildr/core/project.rb', line 403

def project_from_task(task) #:nodoc:
  project = Buildr.application.lookup('rake:' + task.to_s.gsub(/:[^:]*$/, ''))
  project if Project === project
end

.projects(*names) ⇒ Object

:call-seq:

projects(*names, options?) => projects

options:

:scope - name of project from which name can be relative
:immediate - only return immediate children
:no_invoke - do not invoke the projects. only applicable if :immediate is true

See Buildr#projects.



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/buildr/core/project.rb', line 286

def projects(*names) #:nodoc:
  options = Hash === names.last ? names.pop : {}
  rake_check_options options, :scope, :immediate, :no_invoke if options
  @projects ||= {}
  names = names.flatten
  if options && options[:scope]
    if names.empty?
      # must invoke the parent. if the project is the one currently being invoked, then don't reinvoke
      parent = options[:scope].tap{|p| p.invoke unless Thread.current[:rake_chain].instance_variable_get(:@value) == p}
      projects = @projects.values.select { |project| project.parent == parent }
      projects.each { |project| project.invoke } unless options[:immediate] and options[:no_invoke]
      projects = projects.map { |project| [project] + projects(options.merge({:scope=>project})) }.flatten.sort_by(&:name) unless options[:immediate]
      projects
    else
      names.uniq.map { |name| project(name, options) }
    end
  elsif names.empty?
    # Parent project(s) not evaluated so we don't know all the projects yet.
    @projects.keys.map { |name| project(name, options) or raise NoSuchProject.new(name) }.
    map {|project| [project] + (options[:immediate] ? [] : project.projects(options))}.flatten.sort_by(&:name)
  else
    # Parent project(s) not evaluated, for the sub-projects we may need to find.
    names.uniq.map { |name| project(name) or raise NoSuchProject.new(name) }.sort_by(&:name)
  end
end

.scope_name(scope, task_name) ⇒ Object

:nodoc:



357
358
359
# File 'lib/buildr/core/project.rb', line 357

def scope_name(scope, task_name) #:nodoc:
  task_name
end

Instance Method Details

#base_dirObject

: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'


455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/buildr/core/project.rb', line 455

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

#file(*args, &block) ⇒ Object

: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'


517
518
519
520
521
522
# File 'lib/buildr/core/project.rb', line 517

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

#inspectObject

:nodoc:



618
619
620
# File 'lib/buildr/core/project.rb', line 618

def inspect #:nodoc:
  %Q{project(#{name.inspect})}
end

#layoutObject

Returns the layout associated with this project.



470
471
472
# File 'lib/buildr/core/project.rb', line 470

def layout
  @layout ||= (parent ? parent.layout : Layout.default).clone
end

#path_to(*names) ⇒ Object Also known as: _

: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


494
495
496
# File 'lib/buildr/core/project.rb', line 494

def path_to(*names)
  File.expand_path(layout.expand(*names), base_dir)
end

#project(*args) ⇒ Object

: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


591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/buildr/core/project.rb', line 591

def project(*args)
  if Hash === args.last
    options = args.pop
  else
    options = {}
  end
  if args.empty?
    self
  else
    Project.project *(args + [{ :scope=>self }.merge(options)])
  end
end

#projects(*args) ⇒ Object

: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.



609
610
611
612
613
614
615
616
# File 'lib/buildr/core/project.rb', line 609

def projects(*args)
  if Hash === args.last
    options = args.pop
  else
    options = {}
  end
  Project.projects *(args + [{ :scope=>self }.merge(options)])
end

#recursive_task(*args, &block) ⇒ Object

: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.



569
570
571
572
573
574
575
576
577
# File 'lib/buildr/core/project.rb', line 569

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)
  task.set_arg_names(arg_names) unless arg_names.empty?
  task.enhance Array(deps), &block
  task.extend RecursiveTask
  task.project = self
  task.task_name = task_name
end

#task(*args, &block) ⇒ Object

: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.



547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/buildr/core/project.rb', line 547

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