Buildr 0.17.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.
@@ -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