buildr 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,19 +10,18 @@ module Buildr
10
10
  # +foo:build+ and +foo:deploy+.
11
11
  #
12
12
  # Projects have properties, some of which they inherit from their
13
- # parent project. Built it tasks use these properties, for example,
13
+ # parent project. Built in tasks use these properties, for example,
14
14
  # the +clean+ task will remove the target directory specified by
15
15
  # the +target_dir+ property. The +compile+ tasks uses the compiler
16
16
  # option: you can set these options on the parent project and they
17
17
  # will be inherited by all sub-projects.
18
18
  #
19
- # You can only define a project once using #define. You can obtain
20
- # the project using #project. Note that, if you're obtain the project
21
- # object before the project is defined, the values of the project
22
- # properties are not set yet, neither are any of the default tasks.
23
- # However, many tasks perform late binding, and so you can pass a
24
- # project before defining it. For example, the +compile+ task can
25
- # take a project definition and use it as a classpath dependency.
19
+ # You can only define a project once using #define. Afterwards, you
20
+ # can obtain the project definition using #project. However, when
21
+ # working with sub-projects, one project may reference another ahead
22
+ # of its definition: the sub-project definitions are then evaluated
23
+ # based on their dependencies with each other. Circular dependencies
24
+ # are not allowed.
26
25
  #
27
26
  # For example:
28
27
  # define "project1" do
@@ -38,7 +37,9 @@ module Buildr
38
37
  # end
39
38
  # end
40
39
  #
41
- # project("project1").projects.map(&:name)
40
+ # projects.map(&:name)
41
+ # => [ "project", "project:module1", "project1:module2" ]
42
+ # project("project1").sub_projects.map(&:name)
42
43
  # => [ "project1:module1", "project1:module2" ]
43
44
  # project("project1:module1").parent.name
44
45
  # => "project1"
@@ -50,45 +51,66 @@ module Buildr
50
51
  # uses a sub-directory relative to the parent project.
51
52
  #
52
53
  # For the above example, the directory structure is:
53
- # .
54
- # |__module1
55
- # |__module2
54
+ # project1/
55
+ # |__Rakefile
56
+ # |__module1/
57
+ # |__module2/
56
58
  #
57
- # The project definition tasks a block and yields by passing the project
58
- # definition. For convenience, the block is also executed in the context
59
- # of the project object, as if with instance_eval.
60
- #
61
- # The following two are equivalent:
62
- # define "project1" do |project|
59
+ # The project definition tasks a block and executes it in the context of
60
+ # the project object. For example:
61
+ # define "project1" do
63
62
  # project.version = "1.1"
64
- # self.version = "1.1"
65
63
  # end
66
- class Project
64
+ # puts project("project1").version
65
+ # => "1.1"
66
+ class Project < Rake::Task
67
67
 
68
68
  class << self
69
69
 
70
70
  # See Buildr#define.
71
71
  def define(*args, &block)
72
72
  name, properties = name_and_properties_from_args(*args)
73
- raise "The name #{name} means sub-project #{name.split(':').last} belonging to the parent project #{name.split(':')[0..-2]}, and you can only define a sub-project inside the parent project" if name =~ /:/
74
- project(name)._define properties, &block
75
- end
73
+ # Make sure a sub-project is only defined within the parent project,
74
+ # to prevent silly mistakes that lead to inconsistencies (e.g.
75
+ # namespaces will be all out of whack).
76
+ Rake.application.current_scope == name.split(":")[0...-1] or
77
+ raise "You can only define a sub project (#{name}) within the definition of its parent process"
76
78
 
77
- # See Buildr#project.
78
- def project(name)
79
79
  @projects ||= {}
80
- name.split(":").inject(nil) do |parent, name|
81
- if parent
82
- @projects["#{parent.name}:#{name}"] ||= Project.new(name, parent)
80
+ raise "You cannot define the same project (#{name}) more than once" if @projects[name]
81
+ Project.define_task(name).tap do |project|
82
+ @projects[name] = project
83
+ project.enhance { |project| @on_define.each { |callback| callback[project] } } if @on_define
84
+ # Set the project properties first, actions may use them.
85
+ properties.each { |name, value| project.send "#{name}=", value }
86
+ # Enhance the project definition with the block.
87
+ if block
88
+ project.enhance { project.instance_eval &block }
89
+ end
90
+
91
+ if project.parent
92
+ project.parent.enhance { project.invoke }
83
93
  else
84
- @projects[name] ||= Project.new(name, nil)
94
+ project.invoke
85
95
  end
86
96
  end
87
97
  end
88
98
 
99
+ # See Buildr#project.
100
+ def project(name)
101
+ @projects && @projects[name] or raise "No such project #{name}"
102
+ returning(@projects[name]) { |project| project.invoke }
103
+ end
104
+
89
105
  # See Buildr#projects.
90
- def projects()
91
- (@projects ||= {}).map { |name, project| project }.select(&:defined?).sort_by(&:name)
106
+ def projects(*args)
107
+ @projects ||= {}
108
+ if args.empty?
109
+ @projects.keys.map { |name| project(name) }.sort_by(&:name)
110
+ else
111
+ args.map { |name| project(name) or raise "No such project #{name}" }.
112
+ uniq.sort_by(&:name)
113
+ end
92
114
  end
93
115
 
94
116
  # Discard all project definitions.
@@ -96,6 +118,30 @@ module Buildr
96
118
  @projects.clear if @projects
97
119
  end
98
120
 
121
+ # Enhances this task into a local task. A local task executes the same
122
+ # task on the project in the local directory.
123
+ #
124
+ # For example, if the current directory project is +foo+, then
125
+ # +rake build+ executes +rake foo:build+.
126
+ #
127
+ # The current directory project is a project with the base directory
128
+ # being the same as the current directory. For example:
129
+ # cd bar
130
+ # rake build
131
+ # Will execute the +foo:bar:build+ task, after switching to the directory
132
+ # of the sub-project +bar+.
133
+ def local_task(task)
134
+ task = task(task) unless Rake::Task === task
135
+ task.enhance do |task|
136
+ projects = Project.projects.select { |project| project.base_dir == Rake.application.original_dir }
137
+ if verbose && projects.empty?
138
+ warn "No projects defined for directory #{Rake.application.original_dir}"
139
+ end
140
+ projects.each { |project| task("#{project.name}:#{task.name}").invoke }
141
+ end
142
+ task
143
+ end
144
+
99
145
  # The Project class defines minimal behavior for new projects.
100
146
  # Use #on_define to add behavior when defining new projects.
101
147
  # Whenever a new project is defined, it will yield to the block
@@ -110,8 +156,20 @@ module Buildr
110
156
  # Keep in mind that the order in which #on_define blocks are
111
157
  # called is not determined. You cannot depend on a previous
112
158
  # #on_define to set properties or create new tasks. You would
113
- # want to use the #after_block method instead, by calling it
114
- # from within #after_block.
159
+ # want to use the #enhance method instead, by calling it
160
+ # from within #on_define.
161
+ #
162
+ # For example:
163
+ # Project.on_define do |project|
164
+ # puts "defining"
165
+ # project.enhance { puts "defined" }
166
+ # end
167
+ # define "foo" do
168
+ # puts "block"
169
+ # end
170
+ # => defining
171
+ # block
172
+ # defined
115
173
  def on_define(&block)
116
174
  (@on_define ||= []) << block if block
117
175
  end
@@ -119,7 +177,7 @@ module Buildr
119
177
  # :nodoc:
120
178
  def name_and_properties_from_args(*args)
121
179
  if Hash === args.last
122
- properties = args.pop.clone
180
+ properties = args.pop.dup
123
181
  else
124
182
  properties = {}
125
183
  end
@@ -141,12 +199,15 @@ module Buildr
141
199
  # * Are referenced but never defined. This is probably a typo.
142
200
  # * Do not have a base directory.
143
201
  (@projects || {}).each do |name, project|
144
- msgs << "Project #{name} is referenced but not defined; you probably have a typo somewhere" unless project.defined?
145
202
  msgs << "Project #{name} refers to the directory #{project.base_dir}, which does not exist" unless File.exist?(project.base_dir)
146
203
  end
147
204
  end
148
205
  end
149
206
 
207
+ def scope_name(scope, task_name)
208
+ task_name
209
+ end
210
+
150
211
  end
151
212
 
152
213
  include Attributes
@@ -158,6 +219,21 @@ module Buildr
158
219
  # The parent project if this is a sub-project.
159
220
  attr_reader :parent
160
221
 
222
+ # :nodoc:
223
+ def initialize(*args)
224
+ super
225
+ split = name.split(":")
226
+ if split.size > 1
227
+ # Get parent project, but do not invoke it's definition to
228
+ # prevent circular dependencies (it's being invoked right now).
229
+ @parent = task(split[0...-1].join(":"))
230
+ raise "No parent project #{split[0...-1].join(":")}" unless @parent && Project === parent
231
+ end
232
+ # We want to lazily evaluate base_dir, but default initialize
233
+ # will set it to the current directory.
234
+ @base_dir = nil
235
+ end
236
+
161
237
  # The base directory of this project. The default for a top-level project
162
238
  # is the same directory that holds the Rakefile. The default for a
163
239
  # sub-project is a child directory with the same name.
@@ -165,38 +241,34 @@ module Buildr
165
241
  # A project definition can change the base directory using the base_dir
166
242
  # hash value. Be advised that the base directory and all values that
167
243
  # depend on it can only be determined after the project is defined.
168
- attr_reader :base_dir
169
-
170
- # :nodoc:
171
- def initialize(name, parent)
172
- fail "Missing project name" unless name
173
- @name = parent ? "#{parent.name}:#{name}" : name
174
- @parent = parent
175
- if parent
176
- # For sub-project, a good default is a directory in the parent's base_dir,
177
- # using the same name as the project.
178
- @base_dir = File.join(parent.base_dir, name)
179
- else
180
- # For top-level project, a good default is the directory where we found the Rakefile.
181
- @base_dir = Dir.pwd
244
+ def base_dir()
245
+ unless @base_dir
246
+ if @parent
247
+ # For sub-project, a good default is a directory in the parent's base_dir,
248
+ # using the same name as the project.
249
+ sub_dir = File.join(@parent.base_dir, name.split(":").last)
250
+ @base_dir = File.exist?(sub_dir) ? sub_dir : @parent.base_dir
251
+ @base_dir = sub_dir
252
+ else
253
+ # For top-level project, a good default is the directory where we found the Rakefile.
254
+ @base_dir = Dir.pwd
255
+ end
182
256
  end
183
- @after_block = []
184
- @defined = false
257
+ @base_dir
258
+ end
259
+
260
+ # Set the base directory. Note: you can only do this once for a project,
261
+ # and only before accessing the base directory. If you try reading the
262
+ # value with #base_dir, the base directory cannot be set again.
263
+ def base_dir=(dir)
264
+ raise "Cannot set base directory twice, or after reading its value" if @base_dir
265
+ @base_dir = File.expand_path(dir)
185
266
  end
186
267
 
187
268
  # Define a new sub-project within this project.
188
269
  def define(*args, &block)
189
270
  name, properties = Project.name_and_properties_from_args(*args)
190
- project("#{self.name}:#{name}")._define properties, &block
191
- end
192
-
193
- # Returns true if the project was already defined.
194
- def defined?()
195
- @defined
196
- end
197
-
198
- def to_s()
199
- name
271
+ Project.define "#{self.name}:#{name}", properties, &block
200
272
  end
201
273
 
202
274
  # Returns a path made from multiple arguments. Relative paths are turned into
@@ -222,8 +294,8 @@ module Buildr
222
294
  end
223
295
 
224
296
  # Same as Buildr#projects.
225
- def projects()
226
- Project.projects
297
+ def projects(*args)
298
+ Project.projects(*args)
227
299
  end
228
300
 
229
301
  def sub_projects()
@@ -231,6 +303,44 @@ module Buildr
231
303
  Project.projects.select { |project| project.name.starts_with?(prefix) }.sort_by(&:name)
232
304
  end
233
305
 
306
+ # Create or return a file task. This is similar to Rake's file method,
307
+ # with the exception that all relative paths are resolved relative to
308
+ # the project's base directory.
309
+ #
310
+ # You can call this from within or outside the project definition.
311
+ def file(args, &block)
312
+ task_name, deps = Rake.application.resolve_args(args)
313
+ unless task = Rake.application.lookup(task_name, [])
314
+ task = Rake::FileTask.define_task(File.expand_path(task_name, base_dir))
315
+ task.base_dir = base_dir
316
+ end
317
+ deps = [deps] unless deps.respond_to?(:to_ary)
318
+ task.enhance deps, &block
319
+ end
320
+
321
+ # Create or return a task. This is similar to Rake's task method,
322
+ # with the exception that the task is always defined within the project's
323
+ # namespace.
324
+ #
325
+ # If called from within the project definition, it returns a task,
326
+ # creating a new one no such task exists. If called from outside the
327
+ # project definition, it returns a task and raises an error if the
328
+ # task does not exist.
329
+ def task(args, &block)
330
+ task_name, deps = Rake.application.resolve_args(args)
331
+ if Rake.application.current_scope == name.split(":")
332
+ Rake::Task.define_task(task_name=>deps, &block)
333
+ else
334
+ if task = Rake.application.lookup(task_name, name.split(":"))
335
+ deps = [deps] unless deps.respond_to?(:to_ary)
336
+ task.enhance deps, &block
337
+ else
338
+ full_name = "#{name}:#{task_name}"
339
+ raise "You cannot define a project task outside the project definition, and no task #{full_name} defined in the project"
340
+ end
341
+ end
342
+ end
343
+
234
344
  # Define a recursive task.
235
345
  #
236
346
  # A recursive task executes the task with the same name in the project,
@@ -254,62 +364,23 @@ module Buildr
254
364
  #
255
365
  # This method defines a RakeTask. If you need a different type of task,
256
366
  # define the task first and then call #recursive_task.
257
- def recursive_task(arg, &block)
258
- name = Hash === arg ? arg.keys.first : arg
259
- returning(task(arg)) do |task|
367
+ def recursive_task(args, &block)
368
+ task_name, deps = Rake.application.resolve_args(args)
369
+ deps = [deps] unless deps.respond_to?(:to_ary)
370
+ returning(task(task_name=>deps)) do |task|
260
371
  if parent
261
- Rake::Task["^#{name}"].enhance([ task ])
372
+ Rake.application.lookup(task_name, parent.name.split(":")).enhance [task]
373
+ #Rake::Task["^#{name}"].enhance([ task ])
262
374
  end
263
375
  task.enhance &block
264
376
  end
265
377
  end
266
378
 
267
- # Use this from Project#on_define to make each project definition yield
268
- # to this block after it's done with the project block.
269
- #
270
- # Project.on_define do |project|
271
- # puts "defining"
272
- # project.after_block { puts "defined" }
273
- # end
274
- # define "foo" do
275
- # puts "block"
276
- # end
277
- # => defining
278
- # block
279
- # defined
280
- def after_block(&block)
281
- @after_block << block if block
282
- end
283
-
284
- # :nodoc:
285
- def _define(properties, &block)
286
- fail "Project #{name} already defined" if @defined
287
- @defined = true
288
- @base_dir = File.expand_path(properties.delete(:base_dir)) if properties.has_key?(:base_dir)
289
- # How convenient: project name is used to create compound namespace for task.
290
- # Keep in mind that we're already in a namespace context if it's a sub-project.
291
- namespace name.split(":").last do
379
+ def execute()
380
+ Rake.application.in_namespace ":#{name}" do
292
381
  # Everything we do inside the project is relative to its working directory.
293
- Dir.chdir File.exist?(@base_dir) ? @base_dir : Dir.pwd do
294
- # on_define blocks may have use for project properties.
295
- properties.each { |name, value| send "#{name}=", value }
296
- (self.class.instance_variable_get(:@on_define) || []).each { |callback| callback.call self }
297
- if block
298
- begin
299
- # Evaluate in context of project, and pass project. And for that we need
300
- # a method definition, on the singleton so we don't conflict with anyone else.
301
- singleton = (class << self ; self ; end)
302
- singleton.send :define_method, :__enhance__, &block
303
- self.__enhance__ self
304
- ensure
305
- singleton.send :remove_method, :__enhance__
306
- end
307
- end
308
- # And now for all the callbacks created by on_define.
309
- @after_block.each { |callback| callback.call self }
310
- end
382
+ Dir.chdir(base_dir) { super }
311
383
  end
312
- self
313
384
  end
314
385
 
315
386
  end
@@ -329,22 +400,18 @@ module Buildr
329
400
  # You can also pass the project name in the properties hash.
330
401
  #
331
402
  # The easiest way to define a project and configure its tasks is by passing
332
- # a block. The #define method executes the block within the context of the
333
- # project, as if with instance_eval. It also passes the project to the block.
403
+ # a block. The #define method executes the block in the context of the project.
334
404
  #
335
405
  # For example:
336
406
  # define "foo", :version=>"1.0" do
337
- # . . .
338
- # end
339
- #
340
- # define "bar" do |project|
341
- # project.version = "1.0"
342
- # . . .
407
+ # puts version
343
408
  # end
344
409
  #
345
- # define "baz" do
346
- # self.version = "1.0"
410
+ # define "bar" do
411
+ # puts name
347
412
  # end
413
+ 3 # => "1.0"
414
+ # "bar"
348
415
  #
349
416
  # Each project also has a #define method that operates the same way, but
350
417
  # defines a sub-project. A sub-project has a compound name using the parent
@@ -369,86 +436,45 @@ module Buildr
369
436
  # For a sub-project, use the full name, for example "foo:bar" to find
370
437
  # the sub-project "bar" of the parent project "foo".
371
438
  #
372
- # You can reference a project before its definition. Be careful, though,
373
- # since the referenced project will not have all its properties and tasks
374
- # properly set. However, some tasks handle this well by accepting the
375
- # project object and accessing it only when invoked, after all projects
376
- # are defined.
439
+ # You cannot reference a project before the project is defined, with one
440
+ # exception. When working with sub-projects, the project definitions are
441
+ # stored but not executed until the parent project is defined. So within
442
+ # a sub-project definition you can reference another sub-project definition.
443
+ # The definitions are then performed (invoked) based on that dependency.
444
+ # You cannot have circular references between project definitions.
377
445
  #
378
446
  # For example:
379
- # def "project1" do
380
- # compile.with project("project2")
381
- # end
447
+ # define "project1" do
448
+ # self.version = "1.1"
382
449
  #
383
- # def "project2" do
384
- # . . .
450
+ # define "module1" do
451
+ # package :jar
452
+ # end
453
+ #
454
+ # define "module2" do
455
+ # compile.with project("project1:module1")
456
+ # package :jar
457
+ # end
385
458
  # end
386
459
  def project(name)
387
460
  Project.project(name)
388
461
  end
389
462
 
390
- # Returns a list of all the projects defined so far.
463
+ # With no arguments, returns a list of all the projects defined so far.
464
+ #
465
+ # With arguments, returns a list of these projects. Equivalent to calling #project
466
+ # for each named project.
391
467
  #
392
468
  # For example:
393
469
  # files = projects.map { |prj| FileList[prj.path_to("src/**/*.java") }.flatten
394
470
  # puts "There are #{files.size} source files in #{projects.size} projects"
395
- def projects()
396
- Project.projects
397
- end
398
-
399
-
400
- # The local directory task executes the same task on the project
401
- # in the local directory.
402
- #
403
- # For example, if the current directory project is +foo+, then
404
- # +rake task+ executes +rake foo:task+.
405
471
  #
406
- # The current directory project is a project with the base directory
407
- # being the same as the current directory. For example:
408
- # cd bar
409
- # rake build
410
- # Will execute the +foo:bar:build+ task, after switching to the directory
411
- # of the sub-project +bar+.
412
- class LocalDirectoryTask < Rake::Task
413
-
414
- def initialize(*args)
415
- super
416
- enhance do |task|
417
- projects = Project.projects.select { |project| project.base_dir == Rake.application.original_dir }
418
- if verbose && projects.empty?
419
- warn "No projects defined for directory #{Rake.application.original_dir}"
420
- end
421
- projects.each { |project| task("#{project.name}:#{task.name}").invoke }
422
- end
423
- end
424
-
425
- end
426
-
427
-
428
- class CheckTask < Rake::Task
429
-
430
- def execute()
431
- @warnings = []
432
- super
433
- report
434
- end
435
-
436
- def note(*msg)
437
- @warnings += msg
438
- end
439
-
440
- def report()
441
- if @warnings.empty?
442
- puts HighLine.new.color("No warnings", :green)
443
- else
444
- warn "These are possible problems with your Rakefile"
445
- @warnings.each { |msg| warn " #{msg}" }
446
- end
447
- end
448
-
472
+ # projects("project1", "project2").map(&:base_dir)
473
+ def projects(*args)
474
+ Project.projects *args
449
475
  end
450
476
 
451
- desc "Check your Rakefile for common errors"
452
- CheckTask.define_task("check") { |task| task.note *Project.warnings }
477
+ # Add project definition tests.
478
+ task("check") { |task| task.note *Project.warnings }
453
479
 
454
480
  end