Class: Buildr::Project

Inherits:
Rake::Task
  • Object
show all
Includes:
Attributes
Defined in:
lib/core/project.rb,
lib/java/test.rb,
lib/core/build.rb,
lib/java/jetty.rb,
lib/java/compile.rb,
lib/java/packaging.rb

Overview

A project is a convenient mechanism for managing all the tasks related to a given project. For complex applications, you may have several projects, or sub-projects for each of the modules.

A project definition creates its own set of tasks, prefixed with the project name. For example, each project has a clean, build and deploy task. For project foo the task names are foo:clean, foo:build and foo:deploy.

Projects have properties, some of which they inherit from their parent project. Built in tasks use these properties, for example, the clean task will remove the target directory specified by the target_dir property. The compile tasks uses the compiler option: you can set these options on the parent project and they will be inherited by all sub-projects.

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.

For example:

define "project1" do
  self.version = "1.1"

  define "module1" do
    package :jar
  end

  define "module2" do
    compile.with project("project1:module1")
    package :jar
  end
end

projects.map(&:name)
=> [ "project", "project:module1", "project1:module2" ]
project("project1").sub_projects.map(&:name)
=> [ "project1:module1", "project1:module2" ]
project("project1:module1").parent.name
=> "project1"
project("project1:module1").version
=> "1.1"

Each project has a base directory (see #base_dir). By default, a top-level project uses the current directory, and each sub-project uses a sub-directory relative to the parent project.

For the above example, the directory structure is: project1/ |__Rakefile |__module1/ |__module2/

The project definition tasks a block and yields by passing the project definition. For convenience, the block is also executed in the context of the project object, as if with instance_eval.

The following two are equivalent:

define "project1" do |project|
  project.version = "1.1"
  self.version = "1.1"
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Attributes

included, #inherited_attr

Constructor Details

#initialize(*args) ⇒ Project

:nodoc:



225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/core/project.rb', line 225

def initialize(*args)
  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).
    @parent = task(split[0...-1].join(":"))
    raise "No parent project #{split[0...-1].join(":")}" unless @parent && Project === parent
  end
  # We want to lazily evaluate base_dir, but default initialize
  # will set it to the current directory.
  @base_dir = nil
end

Instance Attribute Details

#nameObject (readonly)

The project name. If this is a sub-project, it will be prefixed by the parent project’s name. For example, “foo” and “foo:bar”.



219
220
221
# File 'lib/core/project.rb', line 219

def name
  @name
end

#parentObject (readonly)

The parent project if this is a sub-project.



222
223
224
# File 'lib/core/project.rb', line 222

def parent
  @parent
end

Class Method Details

.clearObject

Discard all project definitions.



120
121
122
# File 'lib/core/project.rb', line 120

def clear()
  @projects.clear if @projects
end

.define(*args, &block) ⇒ Object

See Buildr#define.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/core/project.rb', line 73

def define(*args, &block)
  name, properties = name_and_properties_from_args(*args)
  # 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).
  Rake.application.current_scope == name.split(":")[0...-1] or
    raise "You can only define a sub project (#{name}) within the definition of its parent process"

  @projects ||= {}
  raise "You cannot define the same project (#{name}) more than once" if @projects[name]
  returning(Project.define_task(name)) do |project|
    @projects[name] = project
    project.enhance { |project| @on_define.each { |callback| callback[project] } } if @on_define
    # Set the project properties first, actions may use them.
    properties.each { |name, value| project.send "#{name}=", value }
    # Enhance the project definition with the block.
    if block
      # Evaluate in context of project, and pass project.
      project.enhance { project.instance_exec project, &block }
    end
 
    if project.parent
      project.parent.enhance { project.invoke }
    else
      project.invoke
    end
  end
end

.local_task(task) ⇒ Object

Enhances this task into a local task. A local task executes the same task on the project in the local directory.

For example, if the current directory project is foo, then rake build executes rake foo:build.

The current directory project is a project with the base directory being the same as the current directory. For example:

cd bar
rake build

Will execute the foo:bar:build task, after switching to the directory of the sub-project bar.



136
137
138
139
140
141
142
143
144
145
# File 'lib/core/project.rb', line 136

def local_task(task)
  task.enhance do |task|
    projects = Project.projects.select { |project| project.base_dir == Rake.application.original_dir }
    if verbose && projects.empty?
      warn "No projects defined for directory #{Rake.application.original_dir}"
    end
    projects.each { |project| task("#{project.name}:#{task.name}").invoke }
  end
  task
end

.name_and_properties_from_args(*args) ⇒ Object

:nodoc:

Raises:

  • (ArgumentError)


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/core/project.rb', line 180

def name_and_properties_from_args(*args)
  if Hash === args.last
    properties = args.pop.clone
  else
    properties = {}
  end
  if String === args.first
    name = args.shift
  else
    name = properties.delete(:name)
  end
  raise ArgumentError, "Expected project name followed by (optional) project properties." unless args.empty?
  raise ArgumentError, "Missing project name, this is the first argument to the define method" unless name
  [ name, properties ]
end

.on_define(&block) ⇒ Object

The Project class defines minimal behavior for new projects. Use #on_define to add behavior when defining new projects. Whenever a new project is defined, it will yield to the block with the project object.

For example:

# Set the default version of each project to "1.0".
Project.on_define do |project|
  project.version ||= "1.0"
end

Keep in mind that the order in which #on_define blocks are called is not determined. You cannot depend on a previous #on_define to set properties or create new tasks. You would want to use the #enhance method instead, by calling it from within #on_define.

For example:

Project.on_define do |project|
  puts "defining"
  project.enhance { puts "defined" }
end
define "foo" do
  puts "block"
end
=> defining
   block
   defined


175
176
177
# File 'lib/core/project.rb', line 175

def on_define(&block)
  (@on_define ||= []) << block if block
end

.project(name) ⇒ Object

See Buildr#project.



103
104
105
106
# File 'lib/core/project.rb', line 103

def project(name)
  @projects && @projects[name] or raise "No such project #{name}"
  returning(@projects[name]) { |project| project.invoke }
end

.projects(*args) ⇒ Object

See Buildr#projects.



109
110
111
112
113
114
115
116
117
# File 'lib/core/project.rb', line 109

def projects(*args)
  @projects ||= {}
  if args.empty?
    @projects.keys.map { |name| project(name) }.sort_by(&:name)
  else
    args.map { |name| project(name) or raise "No such project #{name}" }.
      uniq.sort_by(&:name)
  end
end

.scope_name(scope, task_name) ⇒ Object



209
210
211
# File 'lib/core/project.rb', line 209

def scope_name(scope, task_name)
  task_name
end

.warningsObject

:nodoc:



197
198
199
200
201
202
203
204
205
206
207
# File 'lib/core/project.rb', line 197

def warnings()
  returning([]) 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

Instance Method Details

#base_dirObject

The base directory of this project. The default for a top-level project is the same directory that holds the Rakefile. The default for a sub-project is a child directory with the same name.

A project definition can change the base directory using the base_dir hash value. Be advised that the base directory and all values that depend on it can only be determined after the project is defined.



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/core/project.rb', line 246

def base_dir()
  unless @base_dir
    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
    else
      # For top-level project, a good default is the directory where we found the Rakefile.
      @base_dir = Dir.pwd
    end
  end
  @base_dir
end

#base_dir=(dir) ⇒ Object

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.



265
266
267
268
# File 'lib/core/project.rb', line 265

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

#build(*args, &block) ⇒ Object



23
24
25
26
27
# File 'lib/core/build.rb', line 23

def build(*args, &block)
  returning(@build_task ||= task("build")) do |task|
    task.enhance args, &block
  end
end

#clean(*args, &block) ⇒ Object



29
30
31
32
33
# File 'lib/core/build.rb', line 29

def clean(*args, &block)
  returning(@clean_task ||= task("clean")) do |task|
    task.enhance args, &block
  end
end

#compile(*sources, &block) ⇒ Object



275
276
277
278
279
280
# File 'lib/java/compile.rb', line 275

def compile(*sources, &block)
  returning(@compile_task ||= Java::CompileTask.define_task("compile")) do |task|
    task.sources |= sources
    task.enhance &block if block
  end
end

#define(*args, &block) ⇒ Object

Define a new sub-project within this project.



271
272
273
274
# File 'lib/core/project.rb', line 271

def define(*args, &block)
  name, properties = Project.name_and_properties_from_args(*args)
  Project.define "#{self.name}:#{name}", properties, &block
end

#executeObject



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

def execute()
  Rake.application.in_namespace ":#{name}" do
    # Everything we do inside the project is relative to its working directory.
    Dir.chdir(base_dir) { super }
  end
end

#file(args, &block) ⇒ Object

Create or return a file task. This is similar to Rake’s file method, with the exception that all relative paths are resolved relative to the project’s base directory.

You can call this from within or outside the project definition.



313
314
315
316
317
318
319
320
321
# File 'lib/core/project.rb', line 313

def file(args, &block)
  task_name, deps = Rake.application.resolve_args(args)
  unless task = Rake.application.lookup(task_name, [])
    task = Rake::FileTask.define_task(File.expand_path(task_name, base_dir)=>deps, &block)
    task.base_dir = base_dir
  end
  deps = [deps] unless deps.respond_to?(:to_ary)
  task.enhance deps, &block
end

#idObject

The project ID is the project name, and for a sub-project the parent project ID followed by the project name, separated with a hyphen. For example, “foo” and “foo-bar”.



93
94
95
# File 'lib/java/packaging.rb', line 93

def id()
  name.gsub(":", "-")
end

#package(*args) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/java/packaging.rb', line 98

def package(*args)
  if Hash === args.last
    options = args.pop.dup
  else
    options = {}
  end
  options[:type] = args.shift.to_s if Symbol === args.first
  fail "No packaging type specified" unless options[:type]
  options[:group] ||= self.group
  options[:version] ||= self.version
  options[:id] ||= self.id
  if String === args.first
    file = args.shift
  else
    file = options.delete(:file) || path_to(:target_dir, Artifact.hash_to_file_name(options))
  end
  fail "One argument too many; expecting at most type, file name, and hash of options" unless args.empty?

  packager = method("package_as_#{options[:type]}") rescue
    fail("Do not know how to create a package of type #{options[:type]}")
  package = packager.call(file, options) or fail("Do not know how to create a package of type #{options[:type]}")

  task "package"=>package
  package.enhance [ task("build")]

  task "install"=>(file(repositories.locate(package)=>package) { |task|
    mkpath File.dirname(task.name), :verbose=>false
    cp package.name, task.name
  })
  task "install"=>package.pom

  task "uninstall" do |task|
    verbose(Rake.application.options.trace) do
      [ package, package.pom ].map { |artifact| repositories.locate(artifact) }.
        each { |file| rm file if File.exist?(file) } 
    end
  end

  task("deploy") { deploy(package, package.pom) }

  packages << package
  Artifact.register package, package.pom
  package
end

#packagesObject



143
144
145
# File 'lib/java/packaging.rb', line 143

def packages()
  @packages ||= []
end

#path_to(*args) ⇒ Object

Returns a path made from multiple arguments. Relative paths are turned into absolute paths using this project’s base directory.

Symbol arguments are converted to paths by calling the attribute accessor on the project. For example:

For example:

path_to("foo", "bar")
=> /projects/project1/foo/bar
path_to(:target_dir, "foo")
=> /projects/project1/target/foo
path_to("/tmp")
=> /tmp


289
290
291
# File 'lib/core/project.rb', line 289

def path_to(*args)
  File.expand_path(File.join(args.map { |arg| Symbol === arg ? send(arg) : arg.to_s }), base_dir)
end

#prepare(*tasks, &block) ⇒ Object



282
283
284
285
286
# File 'lib/java/compile.rb', line 282

def prepare(*tasks, &block)
  returning(@prepare_task ||= task("prepare")) do |task|
    task.enhance tasks, &block
  end
end

#project(name) ⇒ Object

Same as Buildr#project.



294
295
296
# File 'lib/core/project.rb', line 294

def project(name)
  Project.project(name)
end

#projects(*args) ⇒ Object

Same as Buildr#projects.



299
300
301
# File 'lib/core/project.rb', line 299

def projects(*args)
  Project.projects(*args)
end

#recursive_task(args, &block) ⇒ Object

Define a recursive task.

A recursive task executes the task with the same name in the project, and in all its sub-projects. In fact, a recursive task actually adds itself as a prerequisite on the parent task.

For example:

define "foo" do
  define "bar" do
    define "baz" do
    end
  end
end

rake foo:build

Will execute foo:build, foo:bar:build and foo:baz:build

Inside the bar directory:

rake build

Will execute foo:bar:build and foo:baz:build.

This method defines a RakeTask. If you need a different type of task, define the task first and then call #recursive_task.



369
370
371
372
373
374
375
376
377
378
379
# File 'lib/core/project.rb', line 369

def recursive_task(args, &block)
  task_name, deps = Rake.application.resolve_args(args)
  deps = [deps] unless deps.respond_to?(:to_ary)
  returning(task(task_name=>deps)) do |task|
    if parent
      Rake.application.lookup(task_name, parent.name.split(":")).enhance [task]
      #Rake::Task["^#{name}"].enhance([ task ])
    end
    task.enhance &block
  end
end

#resources(*tasks, &block) ⇒ Object



269
270
271
272
273
# File 'lib/java/compile.rb', line 269

def resources(*tasks, &block)
  returning(@resources_task ||= Filter.define_task("resources")) do |task|
    task.enhance tasks, &block
  end
end

#sub_projectsObject



303
304
305
306
# File 'lib/core/project.rb', line 303

def sub_projects()
  prefix = name + ":"
  Project.projects.select { |project| project.name.starts_with?(prefix) }.sort_by(&:name)
end

#task(args, &block) ⇒ Object

Create or return a task. This is similar to Rake’s task method, with the exception that the task is always defined within the project’s namespace.

If called from within the project definition, it returns a task, creating a new one no such task exists. If called from outside the project definition, it returns a task and raises an error if the task does not exist.



331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/core/project.rb', line 331

def task(args, &block)
  task_name, deps = Rake.application.resolve_args(args)
  if Rake.application.current_scope == name.split(":")
    Rake::Task.define_task(task_name=>deps, &block)
  else
    if task = Rake.application.lookup(task_name, name.split(":"))
      deps = [deps] unless deps.respond_to?(:to_ary)
      task.enhance deps, &block
    else
      full_name = "#{name}:#{task_name}"
      raise "You cannot define a project task outside the project definition, and no task #{full_name} defined in the project"
    end
  end
end

#test(*tasks, &block) ⇒ Object



67
68
69
# File 'lib/java/test.rb', line 67

def test(*tasks, &block)
  @tests.junit *tasks, &block
end

#testsObject



63
64
65
# File 'lib/java/test.rb', line 63

def tests()
  @tests ||= Tests.new
end

#webserve(&block) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/java/jetty.rb', line 85

def webserve(&block)
  task "jetty:shutdown" do
    begin
      res = Jetty.jetty_call('/stop', :put, @webserve_task.options)
      verbose { puts "Response #{res}" }
    rescue Exception => e
      if (e.class == Errno::ECONNREFUSED)
        puts "Jetty server couldn't be contacted, nothing done."
      elsif (e.class == EOFError)
        puts "Shutdown successful."
      else
        puts "Unexpected error: #{e.class}"
      end
    end
  end
  returning(@webserve_task ||= Jetty::JettyTask.define_task("jetty:bounce")) do |task|
    task.enhance &block if block
  end
end