Buildr 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ require "open3"
2
+
3
+
4
+ module Buildr
5
+
6
+ BUILD_TASKS = {
7
+ :build =>"Build the project",
8
+ :clean =>"Clean files generated during a build",
9
+ :package =>"Create packages",
10
+ :install =>"Install packages created by the project",
11
+ :uninstall=>"Remove previously installed packages",
12
+ :deploy =>"Deploy packages created by the project"
13
+ }
14
+
15
+ # Handles the build and clean tasks.
16
+ BUILD_TASKS.each { |name, comment| Project.local_task(task(name)).add_comment(comment) }
17
+
18
+ Project.on_define do |project|
19
+ BUILD_TASKS.each { |name, comment| project.recursive_task name }
20
+ end
21
+
22
+ class Project
23
+ def build(*args, &block)
24
+ returning(@build_task ||= task("build")) do |task|
25
+ task.enhance args, &block
26
+ end
27
+ end
28
+
29
+ def clean(*args, &block)
30
+ returning(@clean_task ||= task("clean")) do |task|
31
+ task.enhance args, &block
32
+ end
33
+ end
34
+ end
35
+
36
+ Project.on_define do |project|
37
+ project.build
38
+ project.clean
39
+ end
40
+
41
+
42
+ class ReleaseTask < Rake::Task
43
+
44
+ VERSION_NUMBER_PATTERN = /VERSION_NUMBER\s*=\s*(["'])(.*)\1/
45
+ NEXT_VERSION_PATTERN = /NEXT_VERSION\s*=\s*(["'])(.*)\1/
46
+
47
+ class << self
48
+ def svn_ignores()
49
+ @ignores = (@ignores || []).map { |pat| pat.is_a?(Regexp) ? pat : Regexp.new("^.*\s+#{Regexp.escape pat}$") }
50
+ end
51
+ end
52
+
53
+ def initialize(*args)
54
+ super
55
+ enhance do |task|
56
+ # Make sure we don't have anything uncommitted in SVN.
57
+ check_status
58
+ # Update current version to next version before deploying.
59
+ next_ver = update_version
60
+ # Run the deployment externally using the new version number
61
+ # (from the modified Rakefile).
62
+ sh "rake deploy"
63
+ # Update the next version as well to the next increment and commit.
64
+ update_next_version next_ver
65
+ # Tag the repository for this release.
66
+ tag_repository next_ver
67
+ # Update the next version to end with -SNAPSHOT.
68
+ update_version_to_snapshot next_ver
69
+ end
70
+ end
71
+
72
+ def check_status()
73
+ ignores = ReleaseTask.svn_ignores
74
+ status = svn("status", "--ignore-externals", :verbose=>false).
75
+ reject { |line| line =~ /^X\s/ || ignores.any? { |pat| line =~ pat } }
76
+ fail "Uncommitted SVN files violate the First Principle Of Release!\n#{status}" unless
77
+ status.empty?
78
+ end
79
+
80
+ # Change the Rakefile and update the current version number to the
81
+ # next version number (VERSION_NUMBER = NEXT_VERSION). We need this
82
+ # before making a release with the next version. Return the next version.
83
+ def update_version()
84
+ rakefile = File.read(Rake.application.rakefile)
85
+ version = rakefile.scan(VERSION_NUMBER_PATTERN)[0][1] or
86
+ fail "Looking for VERSION_NUMBER = \"...\" in your Rakefile, none found"
87
+ next_ver = rakefile.scan(NEXT_VERSION_PATTERN)[0][1] or
88
+ fail "Looking for NEXT_VERSION = \"...\" in your Rakefile, none found"
89
+ if verbose
90
+ puts "Current version: #{version}"
91
+ puts "Next version: #{next_ver}"
92
+ end
93
+
94
+ # Switch version numbers.
95
+ rakefile.gsub!(VERSION_NUMBER_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_ver}"}) }
96
+ File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
97
+
98
+ next_ver
99
+ end
100
+
101
+ # Change the Rakefile and update the next version number to one after
102
+ # (NEXT_VERSION = NEXT_VERSION + 1). We do this to automatically increment
103
+ # future version number after each successful release.
104
+ def update_next_version(version)
105
+ # Update to new version number.
106
+ nums = version.split(".")
107
+ nums[-1] = nums[-1].to_i + 1
108
+ next_ver = nums.join(".")
109
+ rakefile = File.read(Rake.application.rakefile)
110
+ rakefile.gsub!(NEXT_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_ver}"}) }
111
+ File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
112
+
113
+ # Commit new version number.
114
+ svn "commit", "-m", "Changed version number to #{version}", Rake.application.rakefile
115
+ end
116
+
117
+ # Create a tag in the SVN repository.
118
+ def tag_repository(version)
119
+ # Copy to tag.
120
+ cur_url = svn("info").scan(/URL: (.*)/)[0][0]
121
+ new_url = cur_url.sub(/trunk$/, "tags/#{version}")
122
+ svn "remove", new_url, "-m", "Removing old copy" rescue nil
123
+ svn "copy", cur_url, new_url, "-m", "Release #{version}"
124
+ end
125
+
126
+ def update_version_to_snapshot(version)
127
+ version += "-SNAPSHOT"
128
+ rakefile = File.read(Rake.application.rakefile)
129
+ rakefile.gsub!(VERSION_NUMBER_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{version}"}) }
130
+ File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
131
+ # Commit new version number.
132
+ svn "commit", "-m", "Changed version number to #{version}", Rake.application.rakefile
133
+ end
134
+
135
+ def svn(*args)
136
+ if Hash === args.last
137
+ options = args.pop
138
+ else
139
+ options = { :verbose=>verbose }
140
+ end
141
+ puts ["svn", *args].join(" ") if options[:verbose]
142
+ Open3.popen3("svn", *args) do |stdin, stdout, stderr|
143
+ stdin.close
144
+ error = stderr.read
145
+ fail error unless error.empty?
146
+ returning(stdout.read) { |output| puts output if Rake.application.options.trace }
147
+ end
148
+ end
149
+ end
150
+
151
+ desc "Make a release"
152
+ ReleaseTask.define_task "release"
153
+ end
@@ -0,0 +1,152 @@
1
+ require "highline"
2
+
3
+
4
+ module Kernel
5
+ def warn_with_color(message)
6
+ warn_without_color HighLine.new.color(message.to_s, :red)
7
+ end
8
+ alias_method_chain :warn, :color
9
+ end
10
+
11
+
12
+ module Buildr
13
+ module Attributes
14
+
15
+ def self.included(mod)
16
+ mod.extend(self)
17
+ end
18
+
19
+ # An inherited attribute gets it value from an instance variable
20
+ # with the same name. If the value is not set it will defer to the
21
+ # parent object. If there is no parent object, it will use the
22
+ # default value; with a block, it evaluates the block by calling
23
+ # instance_eval on the object.
24
+ #
25
+ # For example:
26
+ # inherited_attr :version
27
+ # inherited_attr :src_dir, "src"
28
+ # inherited_attr :java_src_dir do src_dir + "/main/java"
29
+ def inherited_attr(symbol, default = nil, &block)
30
+ block ||= proc { default }
31
+ define_method symbol do
32
+ instance_variable_get("@#{symbol}") || (parent ? parent.send(symbol) : self.instance_eval(&block))
33
+ end
34
+ define_method "#{symbol}=" do |value|
35
+ instance_variable_set("@#{symbol}", value)
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ class Rake::Task
43
+ def invoke
44
+ if application.options.trace
45
+ puts "** Invoke #{name} #{format_trace_flags}"
46
+ end
47
+ tasks = (Thread.current[:tasks] || [])
48
+ if tasks.include?(name)
49
+ fail "Circular dependency " + (tasks + [name]).join("=>")
50
+ end
51
+ @lock.synchronize do
52
+ return if @already_invoked
53
+ begin
54
+ Thread.current[:tasks] = tasks + [name]
55
+ @already_invoked = true
56
+ invoke_prerequisites
57
+ execute if needed?
58
+ ensure
59
+ Thread.current[:tasks] = tasks
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ class Rake::Task
66
+
67
+ # Access the base directory. The base directory is set when the class
68
+ # is defined from the current directory. The current directory is set
69
+ # to the base directory when the class is executed.
70
+ attr_accessor :base_dir
71
+
72
+ # :nodoc:
73
+ def invoke_with_base_dir()
74
+ Dir.chdir(@base_dir || Dir.pwd) { invoke_without_base_dir }
75
+ end
76
+ alias_method_chain :invoke, :base_dir
77
+
78
+ # :nodoc:
79
+ def initialize_with_base_dir(*args)
80
+ @base_dir = Dir.pwd
81
+ initialize_without_base_dir *args
82
+ end
83
+ alias_method_chain :initialize, :base_dir
84
+
85
+ end
86
+
87
+
88
+ class Rake::Application
89
+
90
+ def in_namespace_with_global_scope(name, &block)
91
+ if name =~ /^:/
92
+ begin
93
+ scope, @scope = @scope, name.split(":")[1...-1]
94
+ in_namespace_without_global_scope name.split(":").last, &block
95
+ ensure
96
+ @scope = scope
97
+ end
98
+ else
99
+ in_namespace_without_global_scope name, &block
100
+ end
101
+ end
102
+ alias_method_chain :in_namespace, :global_scope
103
+
104
+ end
105
+
106
+
107
+ class CheckTask < Rake::Task
108
+
109
+ def execute()
110
+ @warnings = []
111
+ super
112
+ report if verbose
113
+ end
114
+
115
+ def note(*msg)
116
+ @warnings += msg
117
+ end
118
+
119
+ def report()
120
+ if @warnings.empty?
121
+ puts HighLine.new.color("No warnings", :green)
122
+ else
123
+ warn "These are possible problems with your Rakefile"
124
+ @warnings.each { |msg| warn " #{msg}" }
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+
131
+ desc "Check your Rakefile for common errors"
132
+ CheckTask.define_task "check"
133
+
134
+ # Check for circular dependencies
135
+ task "check" do
136
+ depends = {}
137
+ expand = lambda do |stack, checking|
138
+ if depends[checking]
139
+ if stack.include?(checking)
140
+ fail "Circular " + (stack + [checking]).join("=>")
141
+ end
142
+ else
143
+ depends[checking] = []
144
+ depends[checking] |= Rake.application[checking].prerequisites.
145
+ map { |prereq| expand[stack + [checking.to_s], prereq.to_s] }.flatten.map(&:to_s)
146
+ end
147
+ depends[checking]
148
+ end
149
+ Rake.application.tasks.each do |checking|
150
+ expand[ [], checking.to_s ]
151
+ end
152
+ end
@@ -0,0 +1,486 @@
1
+ module Buildr
2
+
3
+ # A project is a convenient mechanism for managing all the tasks
4
+ # related to a given project. For complex applications, you may have
5
+ # several projects, or sub-projects for each of the modules.
6
+ #
7
+ # A project definition creates its own set of tasks, prefixed with
8
+ # the project name. For example, each project has a clean, build
9
+ # and deploy task. For project +foo+ the task names are +foo:clean+,
10
+ # +foo:build+ and +foo:deploy+.
11
+ #
12
+ # Projects have properties, some of which they inherit from their
13
+ # parent project. Built in tasks use these properties, for example,
14
+ # the +clean+ task will remove the target directory specified by
15
+ # the +target_dir+ property. The +compile+ tasks uses the compiler
16
+ # option: you can set these options on the parent project and they
17
+ # will be inherited by all sub-projects.
18
+ #
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.
25
+ #
26
+ # For example:
27
+ # define "project1" do
28
+ # self.version = "1.1"
29
+ #
30
+ # define "module1" do
31
+ # package :jar
32
+ # end
33
+ #
34
+ # define "module2" do
35
+ # compile.with project("project1:module1")
36
+ # package :jar
37
+ # end
38
+ # end
39
+ #
40
+ # projects.map(&:name)
41
+ # => [ "project", "project:module1", "project1:module2" ]
42
+ # project("project1").sub_projects.map(&:name)
43
+ # => [ "project1:module1", "project1:module2" ]
44
+ # project("project1:module1").parent.name
45
+ # => "project1"
46
+ # project("project1:module1").version
47
+ # => "1.1"
48
+ #
49
+ # Each project has a base directory (see #base_dir). By default,
50
+ # a top-level project uses the current directory, and each sub-project
51
+ # uses a sub-directory relative to the parent project.
52
+ #
53
+ # For the above example, the directory structure is:
54
+ # project1/
55
+ # |__Rakefile
56
+ # |__module1/
57
+ # |__module2/
58
+ #
59
+ # The project definition tasks a block and yields by passing the project
60
+ # definition. For convenience, the block is also executed in the context
61
+ # of the project object, as if with instance_eval.
62
+ #
63
+ # The following two are equivalent:
64
+ # define "project1" do |project|
65
+ # project.version = "1.1"
66
+ # self.version = "1.1"
67
+ # end
68
+ class Project < Rake::Task
69
+
70
+ class << self
71
+
72
+ # See Buildr#define.
73
+ def define(*args, &block)
74
+ name, properties = name_and_properties_from_args(*args)
75
+ # Make sure a sub-project is only defined within the parent project,
76
+ # to prevent silly mistakes that lead to inconsistencies (e.g.
77
+ # namespaces will be all out of whack).
78
+ Rake.application.current_scope == name.split(":")[0...-1] or
79
+ raise "You can only define a sub project (#{name}) within the definition of its parent process"
80
+
81
+ @projects ||= {}
82
+ raise "You cannot define the same project (#{name}) more than once" if @projects[name]
83
+ returning(Project.define_task(name)) do |project|
84
+ @projects[name] = project
85
+ project.enhance { |project| @on_define.each { |callback| callback[project] } } if @on_define
86
+ # Set the project properties first, actions may use them.
87
+ properties.each { |name, value| project.send "#{name}=", value }
88
+ # Enhance the project definition with the block.
89
+ if block
90
+ # Evaluate in context of project, and pass project.
91
+ project.enhance { project.instance_exec project, &block }
92
+ end
93
+
94
+ if project.parent
95
+ project.parent.enhance { project.invoke }
96
+ else
97
+ project.invoke
98
+ end
99
+ end
100
+ end
101
+
102
+ # See Buildr#project.
103
+ def project(name)
104
+ @projects && @projects[name] or raise "No such project #{name}"
105
+ returning(@projects[name]) { |project| project.invoke }
106
+ end
107
+
108
+ # See Buildr#projects.
109
+ def projects(*args)
110
+ @projects ||= {}
111
+ if args.empty?
112
+ @projects.keys.map { |name| project(name) }.sort_by(&:name)
113
+ else
114
+ args.map { |name| project(name) or raise "No such project #{name}" }.
115
+ uniq.sort_by(&:name)
116
+ end
117
+ end
118
+
119
+ # Discard all project definitions.
120
+ def clear()
121
+ @projects.clear if @projects
122
+ end
123
+
124
+ # Enhances this task into a local task. A local task executes the same
125
+ # task on the project in the local directory.
126
+ #
127
+ # For example, if the current directory project is +foo+, then
128
+ # +rake build+ executes +rake foo:build+.
129
+ #
130
+ # The current directory project is a project with the base directory
131
+ # being the same as the current directory. For example:
132
+ # cd bar
133
+ # rake build
134
+ # Will execute the +foo:bar:build+ task, after switching to the directory
135
+ # of the sub-project +bar+.
136
+ def local_task(task)
137
+ task.enhance do |task|
138
+ projects = Project.projects.select { |project| project.base_dir == Rake.application.original_dir }
139
+ if verbose && projects.empty?
140
+ warn "No projects defined for directory #{Rake.application.original_dir}"
141
+ end
142
+ projects.each { |project| task("#{project.name}:#{task.name}").invoke }
143
+ end
144
+ task
145
+ end
146
+
147
+ # The Project class defines minimal behavior for new projects.
148
+ # Use #on_define to add behavior when defining new projects.
149
+ # Whenever a new project is defined, it will yield to the block
150
+ # with the project object.
151
+ #
152
+ # For example:
153
+ # # Set the default version of each project to "1.0".
154
+ # Project.on_define do |project|
155
+ # project.version ||= "1.0"
156
+ # end
157
+ #
158
+ # Keep in mind that the order in which #on_define blocks are
159
+ # called is not determined. You cannot depend on a previous
160
+ # #on_define to set properties or create new tasks. You would
161
+ # want to use the #enhance method instead, by calling it
162
+ # from within #on_define.
163
+ #
164
+ # For example:
165
+ # Project.on_define do |project|
166
+ # puts "defining"
167
+ # project.enhance { puts "defined" }
168
+ # end
169
+ # define "foo" do
170
+ # puts "block"
171
+ # end
172
+ # => defining
173
+ # block
174
+ # defined
175
+ def on_define(&block)
176
+ (@on_define ||= []) << block if block
177
+ end
178
+
179
+ # :nodoc:
180
+ def name_and_properties_from_args(*args)
181
+ if Hash === args.last
182
+ properties = args.pop.clone
183
+ else
184
+ properties = {}
185
+ end
186
+ if String === args.first
187
+ name = args.shift
188
+ else
189
+ name = properties.delete(:name)
190
+ end
191
+ raise ArgumentError, "Expected project name followed by (optional) project properties." unless args.empty?
192
+ raise ArgumentError, "Missing project name, this is the first argument to the define method" unless name
193
+ [ name, properties ]
194
+ end
195
+
196
+ # :nodoc:
197
+ def warnings()
198
+ returning([]) do |msgs|
199
+ msgs << "There are no project definitions in your Rakefile" if @projects.nil? || @projects.empty?
200
+ # Find all projects that:
201
+ # * Are referenced but never defined. This is probably a typo.
202
+ # * Do not have a base directory.
203
+ (@projects || {}).each do |name, project|
204
+ msgs << "Project #{name} refers to the directory #{project.base_dir}, which does not exist" unless File.exist?(project.base_dir)
205
+ end
206
+ end
207
+ end
208
+
209
+ def scope_name(scope, task_name)
210
+ task_name
211
+ end
212
+
213
+ end
214
+
215
+ include Attributes
216
+
217
+ # The project name. If this is a sub-project, it will be prefixed
218
+ # by the parent project's name. For example, "foo" and "foo:bar".
219
+ attr_reader :name
220
+
221
+ # The parent project if this is a sub-project.
222
+ attr_reader :parent
223
+
224
+ # :nodoc:
225
+ def initialize(*args)
226
+ super
227
+ split = name.split(":")
228
+ if split.size > 1
229
+ # Get parent project, but do not invoke it's definition to
230
+ # prevent circular dependencies (it's being invoked right now).
231
+ @parent = task(split[0...-1].join(":"))
232
+ raise "No parent project #{split[0...-1].join(":")}" unless @parent && Project === parent
233
+ end
234
+ # We want to lazily evaluate base_dir, but default initialize
235
+ # will set it to the current directory.
236
+ @base_dir = nil
237
+ end
238
+
239
+ # The base directory of this project. The default for a top-level project
240
+ # is the same directory that holds the Rakefile. The default for a
241
+ # sub-project is a child directory with the same name.
242
+ #
243
+ # A project definition can change the base directory using the base_dir
244
+ # hash value. Be advised that the base directory and all values that
245
+ # depend on it can only be determined after the project is defined.
246
+ def base_dir()
247
+ unless @base_dir
248
+ if @parent
249
+ # For sub-project, a good default is a directory in the parent's base_dir,
250
+ # using the same name as the project.
251
+ sub_dir = File.join(@parent.base_dir, name.split(":").last)
252
+ @base_dir = File.exist?(sub_dir) ? sub_dir : @parent.base_dir
253
+ @base_dir = sub_dir
254
+ else
255
+ # For top-level project, a good default is the directory where we found the Rakefile.
256
+ @base_dir = Dir.pwd
257
+ end
258
+ end
259
+ @base_dir
260
+ end
261
+
262
+ # Set the base directory. Note: you can only do this once for a project,
263
+ # and only before accessing the base directory. If you try reading the
264
+ # value with #base_dir, the base directory cannot be set again.
265
+ def base_dir=(dir)
266
+ raise "Cannot set base directory twice, or after reading its value" if @base_dir
267
+ @base_dir = File.expand_path(dir)
268
+ end
269
+
270
+ # Define a new sub-project within this project.
271
+ def define(*args, &block)
272
+ name, properties = Project.name_and_properties_from_args(*args)
273
+ Project.define "#{self.name}:#{name}", properties, &block
274
+ end
275
+
276
+ # Returns a path made from multiple arguments. Relative paths are turned into
277
+ # absolute paths using this project's base directory.
278
+ #
279
+ # Symbol arguments are converted to paths by calling the attribute accessor
280
+ # on the project. For example:
281
+ #
282
+ # For example:
283
+ # path_to("foo", "bar")
284
+ # => /projects/project1/foo/bar
285
+ # path_to(:target_dir, "foo")
286
+ # => /projects/project1/target/foo
287
+ # path_to("/tmp")
288
+ # => /tmp
289
+ def path_to(*args)
290
+ File.expand_path(File.join(args.map { |arg| Symbol === arg ? send(arg) : arg.to_s }), base_dir)
291
+ end
292
+
293
+ # Same as Buildr#project.
294
+ def project(name)
295
+ Project.project(name)
296
+ end
297
+
298
+ # Same as Buildr#projects.
299
+ def projects(*args)
300
+ Project.projects(*args)
301
+ end
302
+
303
+ def sub_projects()
304
+ prefix = name + ":"
305
+ Project.projects.select { |project| project.name.starts_with?(prefix) }.sort_by(&:name)
306
+ end
307
+
308
+ # Create or return a file task. This is similar to Rake's file method,
309
+ # with the exception that all relative paths are resolved relative to
310
+ # the project's base directory.
311
+ #
312
+ # You can call this from within or outside the project definition.
313
+ def file(args, &block)
314
+ task_name, deps = Rake.application.resolve_args(args)
315
+ unless task = Rake.application.lookup(task_name, [])
316
+ task = Rake::FileTask.define_task(File.expand_path(task_name, base_dir)=>deps, &block)
317
+ task.base_dir = base_dir
318
+ end
319
+ deps = [deps] unless deps.respond_to?(:to_ary)
320
+ task.enhance deps, &block
321
+ end
322
+
323
+ # Create or return a task. This is similar to Rake's task method,
324
+ # with the exception that the task is always defined within the project's
325
+ # namespace.
326
+ #
327
+ # If called from within the project definition, it returns a task,
328
+ # creating a new one no such task exists. If called from outside the
329
+ # project definition, it returns a task and raises an error if the
330
+ # task does not exist.
331
+ def task(args, &block)
332
+ task_name, deps = Rake.application.resolve_args(args)
333
+ if Rake.application.current_scope == name.split(":")
334
+ Rake::Task.define_task(task_name=>deps, &block)
335
+ else
336
+ if task = Rake.application.lookup(task_name, name.split(":"))
337
+ deps = [deps] unless deps.respond_to?(:to_ary)
338
+ task.enhance deps, &block
339
+ else
340
+ full_name = "#{name}:#{task_name}"
341
+ raise "You cannot define a project task outside the project definition, and no task #{full_name} defined in the project"
342
+ end
343
+ end
344
+ end
345
+
346
+ # Define a recursive task.
347
+ #
348
+ # A recursive task executes the task with the same name in the project,
349
+ # and in all its sub-projects. In fact, a recursive task actually adds
350
+ # itself as a prerequisite on the parent task.
351
+ #
352
+ # For example:
353
+ # define "foo" do
354
+ # define "bar" do
355
+ # define "baz" do
356
+ # end
357
+ # end
358
+ # end
359
+ #
360
+ # rake foo:build
361
+ # Will execute foo:build, foo:bar:build and foo:baz:build
362
+ #
363
+ # Inside the bar directory:
364
+ # rake build
365
+ # Will execute foo:bar:build and foo:baz:build.
366
+ #
367
+ # This method defines a RakeTask. If you need a different type of task,
368
+ # define the task first and then call #recursive_task.
369
+ def recursive_task(args, &block)
370
+ task_name, deps = Rake.application.resolve_args(args)
371
+ deps = [deps] unless deps.respond_to?(:to_ary)
372
+ returning(task(task_name=>deps)) do |task|
373
+ if parent
374
+ Rake.application.lookup(task_name, parent.name.split(":")).enhance [task]
375
+ #Rake::Task["^#{name}"].enhance([ task ])
376
+ end
377
+ task.enhance &block
378
+ end
379
+ end
380
+
381
+ def execute()
382
+ Rake.application.in_namespace ":#{name}" do
383
+ # Everything we do inside the project is relative to its working directory.
384
+ Dir.chdir(base_dir) { super }
385
+ end
386
+ end
387
+
388
+ end
389
+
390
+ # :call-seq:
391
+ # define name { |project| ... }
392
+ # define name, properties { |project| ... }
393
+ # define properties { |project| ... }
394
+ #
395
+ # Defines a new project.
396
+ #
397
+ # The first argument is the project name. Each project must have a unique name,
398
+ # and you can only define a project once.
399
+ #
400
+ # The second argument contains any number of properties that are set on the
401
+ # project. The project must have attribute accessors to support these properties.
402
+ # You can also pass the project name in the properties hash.
403
+ #
404
+ # The easiest way to define a project and configure its tasks is by passing
405
+ # a block. The #define method executes the block within the context of the
406
+ # project, as if with instance_eval. It also passes the project to the block.
407
+ #
408
+ # For example:
409
+ # define "foo", :version=>"1.0" do
410
+ # . . .
411
+ # end
412
+ #
413
+ # define "bar" do |project|
414
+ # project.version = "1.0"
415
+ # . . .
416
+ # end
417
+ #
418
+ # define "baz" do
419
+ # self.version = "1.0"
420
+ # end
421
+ #
422
+ # Each project also has a #define method that operates the same way, but
423
+ # defines a sub-project. A sub-project has a compound name using the parent
424
+ # project's name, and also inherits some of its properties. You can only
425
+ # define a sub-project as part of the parent project's definition.
426
+ #
427
+ # For example:
428
+ # define "foo", :version=>"1.0" do
429
+ # define "bar"
430
+ # puts name
431
+ # puts version
432
+ # end
433
+ # end
434
+ # => "foo:bar"
435
+ # "1.0"
436
+ def define(*args, &block)
437
+ Project.define(*args, &block)
438
+ end
439
+
440
+ # Returns the named project.
441
+ #
442
+ # For a sub-project, use the full name, for example "foo:bar" to find
443
+ # the sub-project "bar" of the parent project "foo".
444
+ #
445
+ # You cannot reference a project before the project is defined, with one
446
+ # exception. When working with sub-projects, the project definitions are
447
+ # stored but not executed until the parent project is defined. So within
448
+ # a sub-project definition you can reference another sub-project definition.
449
+ # The definitions are then performed (invoked) based on that dependency.
450
+ # You cannot have circular references between project definitions.
451
+ #
452
+ # For example:
453
+ # define "project1" do
454
+ # self.version = "1.1"
455
+ #
456
+ # define "module1" do
457
+ # package :jar
458
+ # end
459
+ #
460
+ # define "module2" do
461
+ # compile.with project("project1:module1")
462
+ # package :jar
463
+ # end
464
+ # end
465
+ def project(name)
466
+ Project.project(name)
467
+ end
468
+
469
+ # With no arguments, returns a list of all the projects defined so far.
470
+ #
471
+ # With arguments, returns a list of these projects. Equivalent to calling #project
472
+ # for each named project.
473
+ #
474
+ # For example:
475
+ # files = projects.map { |prj| FileList[prj.path_to("src/**/*.java") }.flatten
476
+ # puts "There are #{files.size} source files in #{projects.size} projects"
477
+ #
478
+ # projects("project1", "project2").map(&:base_dir)
479
+ def projects(*args)
480
+ Project.projects *args
481
+ end
482
+
483
+ # Add project definition tests.
484
+ task("check") { |task| task.note *Project.warnings }
485
+
486
+ end