buildr 0.16.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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