realityforge-buildr 1.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE +176 -0
  4. data/NOTICE +26 -0
  5. data/README.md +3 -0
  6. data/Rakefile +50 -0
  7. data/addon/buildr/checkstyle-report.xsl +104 -0
  8. data/addon/buildr/checkstyle.rb +254 -0
  9. data/addon/buildr/git_auto_version.rb +36 -0
  10. data/addon/buildr/gpg.rb +90 -0
  11. data/addon/buildr/gwt.rb +413 -0
  12. data/addon/buildr/jacoco.rb +161 -0
  13. data/addon/buildr/pmd.rb +185 -0
  14. data/addon/buildr/single_intermediate_layout.rb +71 -0
  15. data/addon/buildr/spotbugs.rb +265 -0
  16. data/addon/buildr/top_level_generate_dir.rb +37 -0
  17. data/addon/buildr/wsgen.rb +192 -0
  18. data/bin/buildr +20 -0
  19. data/buildr.gemspec +61 -0
  20. data/lib/buildr.rb +86 -0
  21. data/lib/buildr/core/application.rb +705 -0
  22. data/lib/buildr/core/assets.rb +96 -0
  23. data/lib/buildr/core/build.rb +587 -0
  24. data/lib/buildr/core/common.rb +167 -0
  25. data/lib/buildr/core/compile.rb +599 -0
  26. data/lib/buildr/core/console.rb +124 -0
  27. data/lib/buildr/core/doc.rb +275 -0
  28. data/lib/buildr/core/environment.rb +128 -0
  29. data/lib/buildr/core/filter.rb +405 -0
  30. data/lib/buildr/core/help.rb +114 -0
  31. data/lib/buildr/core/progressbar.rb +161 -0
  32. data/lib/buildr/core/project.rb +994 -0
  33. data/lib/buildr/core/test.rb +776 -0
  34. data/lib/buildr/core/transports.rb +456 -0
  35. data/lib/buildr/core/util.rb +77 -0
  36. data/lib/buildr/ide/idea.rb +1664 -0
  37. data/lib/buildr/java/commands.rb +230 -0
  38. data/lib/buildr/java/compiler.rb +85 -0
  39. data/lib/buildr/java/custom_pom.rb +300 -0
  40. data/lib/buildr/java/doc.rb +62 -0
  41. data/lib/buildr/java/packaging.rb +393 -0
  42. data/lib/buildr/java/pom.rb +191 -0
  43. data/lib/buildr/java/test_result.rb +54 -0
  44. data/lib/buildr/java/tests.rb +111 -0
  45. data/lib/buildr/packaging/archive.rb +586 -0
  46. data/lib/buildr/packaging/artifact.rb +1113 -0
  47. data/lib/buildr/packaging/artifact_namespace.rb +1010 -0
  48. data/lib/buildr/packaging/artifact_search.rb +138 -0
  49. data/lib/buildr/packaging/package.rb +237 -0
  50. data/lib/buildr/packaging/version_requirement.rb +189 -0
  51. data/lib/buildr/packaging/zip.rb +189 -0
  52. data/lib/buildr/packaging/ziptask.rb +387 -0
  53. data/lib/buildr/version.rb +18 -0
  54. data/rakelib/release.rake +99 -0
  55. data/spec/addon/checkstyle_spec.rb +58 -0
  56. data/spec/core/application_spec.rb +576 -0
  57. data/spec/core/build_spec.rb +922 -0
  58. data/spec/core/common_spec.rb +670 -0
  59. data/spec/core/compile_spec.rb +656 -0
  60. data/spec/core/console_spec.rb +65 -0
  61. data/spec/core/doc_spec.rb +194 -0
  62. data/spec/core/extension_spec.rb +200 -0
  63. data/spec/core/project_spec.rb +736 -0
  64. data/spec/core/test_spec.rb +1131 -0
  65. data/spec/core/transport_spec.rb +452 -0
  66. data/spec/core/util_spec.rb +154 -0
  67. data/spec/ide/idea_spec.rb +1952 -0
  68. data/spec/java/commands_spec.rb +79 -0
  69. data/spec/java/compiler_spec.rb +274 -0
  70. data/spec/java/custom_pom_spec.rb +165 -0
  71. data/spec/java/doc_spec.rb +55 -0
  72. data/spec/java/packaging_spec.rb +786 -0
  73. data/spec/java/pom_spec.rb +162 -0
  74. data/spec/java/test_coverage_helper.rb +257 -0
  75. data/spec/java/tests_spec.rb +224 -0
  76. data/spec/packaging/archive_spec.rb +686 -0
  77. data/spec/packaging/artifact_namespace_spec.rb +757 -0
  78. data/spec/packaging/artifact_spec.rb +1351 -0
  79. data/spec/packaging/packaging_helper.rb +63 -0
  80. data/spec/packaging/packaging_spec.rb +690 -0
  81. data/spec/sandbox.rb +166 -0
  82. data/spec/spec_helpers.rb +420 -0
  83. data/spec/version_requirement_spec.rb +145 -0
  84. data/spec/xpath_matchers.rb +123 -0
  85. metadata +295 -0
@@ -0,0 +1,161 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with this
3
+ # work for additional information regarding copyright ownership. The ASF
4
+ # licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ #
17
+ class ProgressBar
18
+
19
+ class << self
20
+
21
+ def start(args, &block)
22
+ new(args).start &block
23
+ end
24
+
25
+ def width
26
+ @width ||= Buildr::Console.output_cols || 0
27
+ end
28
+
29
+ end
30
+
31
+ def initialize(args = {})
32
+ @title = args[:title] || ''
33
+ @total = args[:total] || 0
34
+ @mark = args[:mark] || '.'
35
+ @format = args[:format] || default_format
36
+ @output = args[:output] || $stderr unless args[:hidden] || !$stdout.isatty
37
+ clear
38
+ end
39
+
40
+ def start
41
+ @start = @last_time = Time.now
42
+ @count = 0
43
+ @finished = false
44
+ render
45
+ if block_given?
46
+ result = yield(self) if block_given?
47
+ finish
48
+ result
49
+ else
50
+ self
51
+ end
52
+ end
53
+
54
+ def inc(count)
55
+ set @count + count
56
+ end
57
+
58
+ def <<(bytes)
59
+ inc bytes.size
60
+ end
61
+
62
+ def set(count)
63
+ @count = [count, 0].max
64
+ @count = [count, @total].min unless @total == 0
65
+ render if changed?
66
+ end
67
+
68
+ def title
69
+ return @title if ProgressBar.width <= 10
70
+ @title.size > ProgressBar.width / 5 ? (@title[0, ProgressBar.width / 5 - 2] + '..') : @title
71
+ end
72
+
73
+ def count
74
+ human(@count)
75
+ end
76
+
77
+ def total
78
+ human(@total)
79
+ end
80
+
81
+ def percentage
82
+ '%3d%%' % (@total == 0 ? 100 : (@count * 100 / @total))
83
+ end
84
+
85
+ def time
86
+ @finished ? elapsed : eta
87
+ end
88
+
89
+ def eta
90
+ return 'ETA: --:--:--' if @count == 0
91
+ elapsed = Time.now - @start
92
+ eta = elapsed * @total / @count - elapsed
93
+ 'ETA: %s' % duration(eta.ceil)
94
+ end
95
+
96
+ def elapsed
97
+ 'Time: %s' % duration(Time.now - @start)
98
+ end
99
+
100
+ def rate
101
+ '%s/s' % human(@count / (Time.now - @start))
102
+ end
103
+
104
+ def progress(width)
105
+ width -= 2
106
+ marks = @total == 0 ? width : (@count * width / @total)
107
+ "|%-#{width}s|" % (@mark * marks)
108
+ end
109
+
110
+ def human(bytes)
111
+ magnitude = (0..3).find { |i| bytes < (1024 << i * 10) } || 3
112
+ return '%dB' % bytes if magnitude == 0
113
+ return '%.1f%s' % [ bytes.to_f / (1 << magnitude * 10), [nil, 'KB', 'MB', 'GB'][magnitude] ]
114
+ end
115
+
116
+ def duration(seconds)
117
+ '%02d:%02d:%02d' % [seconds / 3600, (seconds / 60) % 60, seconds % 60]
118
+ end
119
+
120
+ def finish
121
+ unless @finished
122
+ @finished = true
123
+ render
124
+ end
125
+ end
126
+
127
+ protected
128
+
129
+ def clear
130
+ return if @output == nil || ProgressBar.width <= 0
131
+ @output.print "\r", " " * (ProgressBar.width - 1), "\r"
132
+ @output.flush
133
+ end
134
+
135
+ def render
136
+ return unless @output
137
+ format, *args = @format
138
+ line = format % args.map { |arg| send(arg) }
139
+ if ProgressBar.width >= line.size
140
+ @output.print line.sub('|--|') { progress(ProgressBar.width - line.size + 3) }
141
+ else
142
+ @output.print line.sub('|--|', '')
143
+ end
144
+ @output.print @finished ? "\n" : "\r"
145
+ @output.flush
146
+ @previous = @count
147
+ @last_time = Time.now
148
+ end
149
+
150
+ def changed?
151
+ return false unless @output && Time.now - @last_time > 0.1
152
+ return human(@count) != human(@previous) if @total == 0
153
+ return true if (@count - @previous) >= @total / 100
154
+ return Time.now - @last_time > 1
155
+ end
156
+
157
+ def default_format
158
+ @total == 0 ? ['%s %8s %s', :title, :count, :elapsed] : ['%s: %s |--| %8s/%s %s', :title, :percentage, :count, :total, :time]
159
+ end
160
+
161
+ end
@@ -0,0 +1,994 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with this
3
+ # work for additional information regarding copyright ownership. The ASF
4
+ # licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ module Buildr #:nodoc:
17
+
18
+ # Symbolic mapping for directory layout. Used for both the default and custom layouts.
19
+ #
20
+ # For example, the default layout maps [:source, :main, :java] to 'src/main/java', and
21
+ # [:target, :main, :classes] to 'target/classes'. You can use this to change the layout
22
+ # of your projects.
23
+ #
24
+ # To map [:source, :main] into the 'sources' directory:
25
+ # my_layout = Layout.new
26
+ # my_layout[:source, :main] = 'sources'
27
+ #
28
+ # define 'foo', :layout=>my_layout do
29
+ # ...
30
+ # end
31
+ #
32
+ # To map [:source, :main, :java] to 'java/main':
33
+ # class MainLast < Layout
34
+ # def expand(*args)
35
+ # if args[0..1] == [:source, :main]
36
+ # super args[2], :main, *args[3,]
37
+ # else
38
+ # super
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # define 'foo', :layout=>MainLast do
44
+ # ...
45
+ # end
46
+ class Layout
47
+
48
+ class << self
49
+
50
+ # Default layout used by new projects.
51
+ attr_accessor :default
52
+
53
+ end
54
+
55
+ def initialize #:nodoc:
56
+ @mapping = {}
57
+ end
58
+
59
+ # Expands list of symbols and path names into a full path, for example:
60
+ # puts default.expand(:source, :main, :java)
61
+ # => "src/main/java"
62
+ def expand(*args)
63
+ args = args.compact.reject { |s| s.to_s.empty? }.map(&:to_sym)
64
+ return '' if args.empty?
65
+ @mapping[args] ||= File.join(*[expand(*args[0..-2]), args.last.to_s].reject(&:empty?)) if args.size > 1
66
+ return @mapping[args] || args.first.to_s
67
+ end
68
+
69
+ # Resolves a list of symbols into a path.
70
+ def [](*args)
71
+ @mapping[args.map(&:to_sym)]
72
+ end
73
+
74
+ # Specifies the path resolved from a list of symbols.
75
+ def []=(*args)
76
+ @mapping[args[0...-1].map(&:to_sym)] = args.last
77
+ end
78
+
79
+ def initialize_copy(copy)
80
+ copy.instance_variable_set :@mapping, @mapping.clone
81
+ end
82
+
83
+ # Default layout has the following properties:
84
+ # * :source maps to the 'src' directory.
85
+ # * Anything under :source maps verbatim (e.g. :source, :main becomes 'src/main')
86
+ # * :target maps to the 'target' directory.
87
+ # * :target, :main maps to the 'target' directory as well.
88
+ # * Anything under :target, :main maps verbatim (e.g. :target, :main, :classes becomes 'target/classes')
89
+ # * Anything else under :target also maps verbatim (e.g. :target, :test becomes 'target/test')
90
+ class Default < Layout
91
+
92
+ def initialize
93
+ super
94
+ self[:source] = 'src'
95
+ self[:target, :main] = 'target'
96
+ end
97
+
98
+ end
99
+
100
+ self.default = Default.new
101
+
102
+ end
103
+
104
+
105
+ # A project definition is where you define all the tasks associated with
106
+ # the project you're building.
107
+ #
108
+ # The project itself will define several life cycle tasks for you. For example,
109
+ # it automatically creates a compile task that will compile all the source files
110
+ # found in src/main/java into target/classes, a test task that will compile source
111
+ # files from src/test/java and run all the tests found there, and a build
112
+ # task to compile and then run the tests.
113
+ #
114
+ # You use the project definition to enhance these tasks, for example, telling the
115
+ # compile task which class path dependencies to use. Or telling the project how
116
+ # to package an artifact, e.g. creating a JAR using <tt>package :jar</tt>.
117
+ #
118
+ # You can also define additional tasks that are executed by project tasks,
119
+ # or invoked from rake.
120
+ #
121
+ # Tasks created by the project are all prefixed with the project name, e.g.
122
+ # the project foo creates the task foo:compile. If foo contains a sub-project bar,
123
+ # the later will define the task foo:bar:compile. Since the compile task is
124
+ # recursive, compiling foo will also compile foo:bar.
125
+ #
126
+ # If you run:
127
+ # buildr compile
128
+ # from the command line, it will execute the compile task of the current project.
129
+ #
130
+ # Projects and sub-projects follow a directory hierarchy. The Buildfile is assumed to
131
+ # reside in the same directory as the top-level project, and each sub-project is
132
+ # contained in a sub-directory in the same name. For example:
133
+ # /home/foo
134
+ # |__ Buildfile
135
+ # |__ src/main/java
136
+ # |__ foo
137
+ # |__ src/main/java
138
+ #
139
+ # The default structure of each project is assumed to be:
140
+ # src
141
+ # |__main
142
+ # | |__java <-- Source files to compile
143
+ # | |__resources <-- Resources to copy
144
+ # | |__webapp <-- For WARs
145
+ # |__test
146
+ # | |__java <-- Source files to compile (tests)
147
+ # | |__resources <-- Resources to copy (tests)
148
+ # |__target <-- Packages created here
149
+ # | |__classes <-- Generated when compiling
150
+ # | |__resources <-- Copied (and filtered) from resources
151
+ # | |__test/classes <-- Generated when compiling tests
152
+ # | |__test/resources <-- Copied (and filtered) from resources
153
+ # |__reports <-- Test, coverage and other reports
154
+ #
155
+ # You can change the project layout by passing a new Layout to the project definition.
156
+ #
157
+ # You can only define a project once using #define. Afterwards, you can obtain the project
158
+ # definition using #project. The order in which you define projects is not important,
159
+ # project definitions are evaluated when you ask for them. Circular dependencies will not
160
+ # work. Rake tasks are only created after the project is evaluated, so if you need to access
161
+ # a task (e.g. compile) use <code>project('foo').compile</code> instead of <code>task('foo:compile')</code>.
162
+ #
163
+ # For example:
164
+ # define 'myapp', :version=>'1.1' do
165
+ #
166
+ # define 'wepapp' do
167
+ # compile.with project('myapp:beans')
168
+ # package :war
169
+ # end
170
+ #
171
+ # define 'beans' do
172
+ # compile.with DEPENDS
173
+ # package :jar
174
+ # end
175
+ # end
176
+ #
177
+ # puts projects.map(&:name)
178
+ # => [ 'myapp', 'myapp:beans', 'myapp:webapp' ]
179
+ # puts project('myapp:webapp').parent.name
180
+ # => 'myapp'
181
+ # puts project('myapp:webapp').compile.classpath.map(&:to_spec)
182
+ # => 'myapp:myapp-beans:jar:1.1'
183
+ class Project < Rake::Task
184
+
185
+ class << self
186
+
187
+ # :call-seq:
188
+ # define(name, properties?) { |project| ... } => project
189
+ #
190
+ # See Buildr#define.
191
+ def define(name, properties, &block) #:nodoc:
192
+ # Make sure a sub-project is only defined within the parent project,
193
+ # to prevent silly mistakes that lead to inconsistencies (e.g.
194
+ # namespaces will be all out of whack).
195
+ Buildr.application.current_scope == name.split(':')[0...-1] or
196
+ raise "You can only define a sub project (#{name}) within the definition of its parent project"
197
+
198
+ @projects ||= {}
199
+ raise "You cannot define the same project (#{name}) more than once" if @projects[name]
200
+ # Projects with names like: compile, test, build are invalid, so we have
201
+ # to make sure the project has not the name of an already defined task
202
+ raise "Invalid project name: #{name.inspect} is already used for a task" if Buildr.application.lookup(name)
203
+
204
+ Project.define_task(name).tap do |project|
205
+ # Define the project to prevent duplicate definition.
206
+ @projects[name] = project
207
+ # Set the project properties first, actions may use them.
208
+ properties.each { |name, value| project.send "#{name}=", value } if properties
209
+ # Setup to call before/after define extension callbacks
210
+ # Don't cache list of extensions, since project may add new extensions.
211
+ project.enhance do |project|
212
+ project.send :call_callbacks, :before_define
213
+ project.enhance do |project|
214
+ project.send :call_callbacks, :after_define
215
+ end
216
+ end
217
+ # Enhance the project using the definition block.
218
+ project.enhance { project.instance_exec project, &block } if block
219
+
220
+ # Mark the project as defined
221
+ project.enhance do |project|
222
+ project.send :define!
223
+ end
224
+
225
+ # Top-level project? Invoke the project definition. Sub-project? We don't invoke
226
+ # the project definition yet (allow project calls to establish order of evaluation),
227
+ # but must do so before the parent project's definition is done.
228
+ project.parent.enhance { project.invoke } if project.parent
229
+ end
230
+ end
231
+
232
+ # :call-seq:
233
+ # project(name) => project
234
+ #
235
+ # See Buildr#project.
236
+ def project(*args, &block) #:nodoc:
237
+ options = args.pop if Hash === args.last
238
+ return define(args.first, options, &block) if block
239
+ rake_check_options options, :scope, :no_invoke if options
240
+ no_invoke = options && options[:no_invoke]
241
+
242
+ raise ArgumentError, 'Only one project name at a time' unless args.size == 1
243
+ @projects ||= {}
244
+ name = args.first.to_s
245
+ # Make sure parent project is evaluated (e.g. if looking for foo:bar, find foo first)
246
+ unless @projects[name]
247
+ parts = name.split(':')
248
+ project(parts.first, options || {}) if parts.size > 1
249
+ end
250
+ if options && options[:scope]
251
+ # We assume parent project is evaluated.
252
+ project = options[:scope].split(':').inject([[]]) { |scopes, scope| scopes << (scopes.last + [scope]) }.
253
+ map { |scope| @projects[(scope + [name]).join(':')] }.
254
+ select { |project| project }.last
255
+ end
256
+ project ||= @projects[name] # Not found in scope.
257
+ raise "No such project #{name}" unless project
258
+ project.invoke unless project.defined? || no_invoke || Buildr.application.current_scope.join(":").to_s == project.name.to_s
259
+ project
260
+ end
261
+
262
+ # :call-seq:
263
+ # projects(*names) => projects
264
+ #
265
+ # See Buildr#projects.
266
+ def projects(*names) #:nodoc:
267
+ options = names.pop if Hash === names.last
268
+ rake_check_options options, :scope, :no_invoke if options
269
+
270
+ no_invoke = options && options[:no_invoke]
271
+
272
+ @projects ||= {}
273
+ names = names.flatten
274
+ if options && options[:scope]
275
+ # We assume parent project is evaluated.
276
+ if names.empty?
277
+ parent = @projects[options[:scope].to_s] or raise "No such project #{options[:scope]}"
278
+ @projects.values.select { |project| project.parent == parent }.each { |project| project.invoke unless no_invoke }.
279
+ map { |project| [project] + projects(:scope => project, :no_invoke => no_invoke) }.flatten.sort_by(&:name)
280
+ else
281
+ names.uniq.map { |name| project(name, :scope => options[:scope], :no_invoke => no_invoke) }
282
+ end
283
+ elsif names.empty?
284
+ # Parent project(s) not evaluated so we don't know all the projects yet.
285
+ @projects.values.each { |project| project.invoke unless no_invoke }
286
+ @projects.keys.map { |name| project(name, :no_invoke => no_invoke) or raise "No such project #{name}" }.sort_by(&:name)
287
+ else
288
+ # Parent project(s) not evaluated, for the sub-projects we may need to find.
289
+ names.map { |name| name.split(':') }.select { |name| name.size > 1 }.map(&:first).uniq.each { |name| project(name, :no_invoke => no_invoke) }
290
+ names.uniq.map { |name| project(name, :no_invoke => no_invoke) or raise "No such project #{name}" }.sort_by(&:name)
291
+ end
292
+ end
293
+
294
+ # :call-seq:
295
+ # clear
296
+ #
297
+ # Discard all project definitions.
298
+ def clear
299
+ @projects.clear if @projects
300
+ end
301
+
302
+ # :call-seq:
303
+ # local_task(name)
304
+ # local_task(name) { |name| ... }
305
+ #
306
+ # Defines a local task with an optional execution message.
307
+ #
308
+ # A local task is a task that executes a task with the same name, defined in the
309
+ # current project, the project's with a base directory that is the same as the
310
+ # current directory.
311
+ #
312
+ # Complicated? Try this:
313
+ # buildr build
314
+ # is the same as:
315
+ # buildr foo:build
316
+ # But:
317
+ # cd bar
318
+ # buildr build
319
+ # is the same as:
320
+ # buildr foo:bar:build
321
+ #
322
+ # The optional block is called with the project name when the task executes
323
+ # and returns a message that, for example "Building project #{name}".
324
+ def local_task(*args, &block)
325
+ task *args do |task, args|
326
+ args = task.arg_names.map {|n| args[n]}
327
+ local_projects do |project|
328
+ info block.call(project.name) if block
329
+ task("#{project.name}:#{task.name}").invoke *args
330
+ end
331
+ end
332
+ end
333
+
334
+ def scope_name(scope, task_name) #:nodoc:
335
+ task_name
336
+ end
337
+
338
+ def local_projects(dir = nil, &block) #:nodoc:
339
+ dir = File.expand_path(dir || Buildr.application.original_dir)
340
+ projects = @projects ? @projects.values : []
341
+ projects = projects.select { |project| project.base_dir == dir }
342
+ if projects.empty? && dir != Dir.pwd && File.dirname(dir) != dir
343
+ local_projects(File.dirname(dir), &block)
344
+ elsif block
345
+ if projects.empty?
346
+ warn "No projects defined for directory #{Buildr.application.original_dir}"
347
+ else
348
+ projects.each { |project| block[project] }
349
+ end
350
+ else
351
+ projects
352
+ end
353
+ end
354
+
355
+ # :call-seq:
356
+ # parent_task(task_name) => task_name or nil
357
+ #
358
+ # Returns a parent task, basically a task in a higher namespace. For example, the parent
359
+ # of 'foo:test:compile' is 'foo:compile' and the parent of 'foo:compile' is 'compile'.
360
+ def parent_task(task_name) #:nodoc:
361
+ namespace = task_name.split(':')
362
+ last_name = namespace.pop
363
+ namespace.pop
364
+ Buildr.application.lookup((namespace + [last_name]).join(':'), []) unless namespace.empty?
365
+ end
366
+
367
+ # :call-seq:
368
+ # project_from_task(task) => project
369
+ #
370
+ # Figure out project associated to this task and return it.
371
+ def project_from_task(task) #:nodoc:
372
+ project = Buildr.application.lookup('rake:' + task.to_s.gsub(/:[^:]*$/, ''))
373
+ project if Project === project
374
+ end
375
+
376
+ # Loaded extension modules.
377
+ def extension_modules #:nodoc:
378
+ @extension_modules ||= []
379
+ end
380
+
381
+ # Extension callbacks that apply to all projects
382
+ def global_callbacks #:nodoc:
383
+ @global_callbacks ||= []
384
+ end
385
+ end
386
+
387
+
388
+ # Project has visibility to everything in the Buildr namespace.
389
+ include Buildr
390
+
391
+ # The project name. For example, 'foo' for the top-level project, and 'foo:bar'
392
+ # for its sub-project.
393
+ attr_reader :name
394
+
395
+ # The parent project if this is a sub-project.
396
+ attr_reader :parent
397
+
398
+ def initialize(*args) #:nodoc:
399
+ super
400
+ split = name.split(':')
401
+ if split.size > 1
402
+ # Get parent project, but do not invoke it's definition to prevent circular
403
+ # dependencies (it's being invoked right now, so calling project will fail).
404
+ @parent = task(split[0...-1].join(':'))
405
+ raise "No parent project #{split[0...-1].join(':')}" unless @parent && Project === parent
406
+ end
407
+ # Inherit all global callbacks
408
+ @callbacks = Project.global_callbacks.dup
409
+ end
410
+
411
+ #
412
+ # Returns the root project for this project.
413
+ #
414
+ # If this project is a subproject it will find the top
415
+ # level project and return it, else it will return itself.
416
+ #
417
+ def root_project
418
+ p = project
419
+ while p.parent
420
+ p = p.parent
421
+ end
422
+ p
423
+ end
424
+
425
+ # :call-seq:
426
+ # base_dir => path
427
+ #
428
+ # Returns the project's base directory.
429
+ #
430
+ # The Buildfile defines top-level project, so it's logical that the top-level project's
431
+ # base directory is the one in which we find the Buildfile. And each sub-project has
432
+ # a base directory that is one level down, with the same name as the sub-project.
433
+ #
434
+ # For example:
435
+ # /home/foo/ <-- base_directory of project 'foo'
436
+ # /home/foo/Buildfile <-- builds 'foo'
437
+ # /home/foo/bar <-- sub-project 'foo:bar'
438
+ def base_dir
439
+ if @base_dir.nil?
440
+ if parent
441
+ # For sub-project, a good default is a directory in the parent's base_dir,
442
+ # using the same name as the project.
443
+ @base_dir = File.expand_path(name.split(':').last, parent.base_dir)
444
+ else
445
+ # For top-level project, a good default is the directory where we found the Buildfile.
446
+ @base_dir = Dir.pwd
447
+ end
448
+ end
449
+ @base_dir
450
+ end
451
+
452
+ # Returns the layout associated with this project.
453
+ def layout
454
+ @layout ||= (parent ? parent.layout : Layout.default).clone
455
+ end
456
+
457
+ # :call-seq:
458
+ # path_to(*names) => path
459
+ #
460
+ # Returns a path from a combination of name, relative to the project's base directory.
461
+ # Essentially, joins all the supplied names and expands the path relative to #base_dir.
462
+ # Symbol arguments are converted to paths based on the layout, so whenever possible stick
463
+ # to these. For example:
464
+ # path_to(:source, :main, :java)
465
+ # => 'src/main/java'
466
+ #
467
+ # Keep in mind that all tasks are defined and executed relative to the Buildfile directory,
468
+ # so you want to use #path_to to get the actual path within the project as a matter of practice.
469
+ #
470
+ # For example:
471
+ # path_to('foo', 'bar')
472
+ # => foo/bar
473
+ # path_to('/tmp')
474
+ # => /tmp
475
+ # path_to(:base_dir, 'foo') # same as path_to('foo")
476
+ # => /home/project1/foo
477
+ def path_to(*names)
478
+ File.expand_path(layout.expand(*names), base_dir)
479
+ end
480
+ alias :_ :path_to
481
+
482
+ # :call-seq:
483
+ # file(path) => Task
484
+ # file(path=>prereqs) => Task
485
+ # file(path) { |task| ... } => Task
486
+ #
487
+ # Creates and returns a new file task in the project. Similar to calling Rake's
488
+ # file method, but the path is expanded relative to the project's base directory,
489
+ # and the task executes in the project's base directory.
490
+ #
491
+ # For example:
492
+ # define 'foo' do
493
+ # define 'bar' do
494
+ # file('src') { ... }
495
+ # end
496
+ # end
497
+ #
498
+ # puts project('foo:bar').file('src').to_s
499
+ # => '/home/foo/bar/src'
500
+ def file(*args, &block)
501
+ task_name, arg_names, deps = Buildr.application.resolve_args(args)
502
+ task = Rake::FileTask.define_task(path_to(task_name))
503
+ task.set_arg_names(arg_names) unless arg_names.empty?
504
+ task.enhance Array(deps), &block
505
+ end
506
+
507
+ # :call-seq:
508
+ # task(name) => Task
509
+ # task(name=>prereqs) => Task
510
+ # task(name) { |task| ... } => Task
511
+ #
512
+ # Creates and returns a new task in the project. Similar to calling Rake's task
513
+ # method, but prefixes the task name with the project name and executes the task
514
+ # in the project's base directory.
515
+ #
516
+ # For example:
517
+ # define 'foo' do
518
+ # task 'doda'
519
+ # end
520
+ #
521
+ # puts project('foo').task('doda').name
522
+ # => 'foo:doda'
523
+ #
524
+ # When called from within the project definition, creates a new task if the task
525
+ # does not already exist. If called from outside the project definition, returns
526
+ # the named task and raises an exception if the task is not defined.
527
+ #
528
+ # As with Rake's task method, calling this method enhances the task with the
529
+ # prerequisites and optional block.
530
+ def task(*args, &block)
531
+ task_name, arg_names, deps = Buildr.application.resolve_args(args)
532
+ if task_name =~ /^:/
533
+ task = Buildr.application.switch_to_namespace [] do
534
+ Rake::Task.define_task(task_name[1..-1])
535
+ end
536
+ elsif Buildr.application.current_scope == name.split(':')
537
+ task = Rake::Task.define_task(task_name)
538
+ else
539
+ unless task = Buildr.application.lookup(task_name, name.split(':'))
540
+ raise "You cannot define a project task outside the project definition, and no task #{name}:#{task_name} defined in the project"
541
+ end
542
+ end
543
+ task.set_arg_names(arg_names) unless arg_names.empty?
544
+ task.enhance Array(deps), &block
545
+ end
546
+
547
+ # :call-seq:
548
+ # recursive_task(name=>prereqs) { |task| ... }
549
+ #
550
+ # Define a recursive task. A recursive task executes itself and the same task
551
+ # in all the sub-projects.
552
+ def recursive_task(*args, &block)
553
+ task_name, arg_names, deps = Buildr.application.resolve_args(args)
554
+ task = Buildr.options.parallel ? multitask(task_name) : task(task_name)
555
+ parent.task(task_name).enhance [task] if parent
556
+ task.set_arg_names(arg_names) unless arg_names.empty?
557
+ task.enhance Array(deps), &block
558
+ end
559
+
560
+ # :call-seq:
561
+ # project(name) => project
562
+ # project => self
563
+ #
564
+ # Same as Buildr#project. This method is called on a project, so a relative name is
565
+ # sufficient to find a sub-project.
566
+ #
567
+ # When called on a project without a name, returns the project itself. You can use that when
568
+ # setting project properties, for example:
569
+ # define 'foo' do
570
+ # project.version = '1.0'
571
+ # end
572
+ def project(*args, &block)
573
+ if Hash === args.last
574
+ options = args.pop
575
+ else
576
+ options = {}
577
+ end
578
+ if args.empty?
579
+ self
580
+ else
581
+ Project.project *(args + [{ :scope=>self.name }.merge(options)]), &block
582
+ end
583
+ end
584
+
585
+ # :call-seq:
586
+ # projects(*names) => projects
587
+ #
588
+ # Same as Buildr#projects. This method is called on a project, so relative names are
589
+ # sufficient to find sub-projects.
590
+ def projects(*args)
591
+ if Hash === args.last
592
+ options = args.pop
593
+ else
594
+ options = {}
595
+ end
596
+ Project.projects *(args + [{ :scope=>self.name }.merge(options)])
597
+ end
598
+
599
+ def inspect #:nodoc:
600
+ %Q{project(#{name.inspect})}
601
+ end
602
+
603
+ def callbacks #:nodoc:
604
+ # global + project_local callbacks for this project
605
+ @callbacks ||= []
606
+ end
607
+
608
+ def calledback #:nodoc:
609
+ # project-local callbacks that have been called
610
+ @calledback ||= {}
611
+ end
612
+
613
+ def defined?
614
+ @defined
615
+ end
616
+
617
+ protected
618
+ def define!
619
+ @defined = true
620
+ end
621
+
622
+
623
+ # :call-seq:
624
+ # base_dir = dir
625
+ #
626
+ # Sets the project's base directory. Allows you to specify a base directory by calling
627
+ # this accessor, or with the :base_dir property when calling #define.
628
+ #
629
+ # You can only set the base directory once for a given project, and only before accessing
630
+ # the base directory (for example, by calling #file or #path_to).
631
+ # Set the base directory. Note: you can only do this once for a project,
632
+ # and only before accessing the base directory. If you try reading the
633
+ # value with #base_dir, the base directory cannot be set again.
634
+ def base_dir=(dir)
635
+ raise 'Cannot set base directory twice, or after reading its value' if @base_dir
636
+ @base_dir = File.expand_path(dir)
637
+ end
638
+
639
+ # Sets the project layout. Accepts Layout object or class (or for that matter, anything
640
+ # that can expand).
641
+ def layout=(layout)
642
+ raise 'Cannot set directory layout twice, or after reading its value' if @layout
643
+ @layout = layout.is_a?(Class) ? layout.new : layout
644
+ end
645
+
646
+ # :call-seq:
647
+ # define(name, properties?) { |project| ... } => project
648
+ #
649
+ # Define a new sub-project within this project. See Buildr#define.
650
+ def define(name, properties = nil, &block)
651
+ Project.define "#{self.name}:#{name}", properties, &block
652
+ end
653
+
654
+ def execute(args) #:nodoc:
655
+ Buildr.application.switch_to_namespace name.split(':') do
656
+ super
657
+ end
658
+ end
659
+
660
+ # Call all extension callbacks for a particular phase, e.g. :before_define, :after_define.
661
+ def call_callbacks(phase) #:nodoc:
662
+ remaining = @callbacks.select { |cb| cb.phase == phase }
663
+ known_callbacks = remaining.map { |cb| cb.name }
664
+
665
+ # call each extension in order
666
+ until remaining.empty?
667
+ callback = first_satisfied(remaining, known_callbacks)
668
+ if callback.nil?
669
+ hash = remaining.map { |cb| { cb.name => cb.dependencies} }
670
+ fail "Unsatisfied dependencies in extensions for #{phase}: #{hash.inspect}"
671
+ end
672
+ callback.blocks.each { |b| b.call(self) }
673
+ end
674
+ end
675
+
676
+ private
677
+
678
+ # find first callback with satisfied dependencies
679
+ def first_satisfied(r, known_callbacks)
680
+ remaining_names = r.map { |cb| cb.name }
681
+ res = r.find do |cb|
682
+ cb.dependencies.each do |dep|
683
+ fail "Unknown #{phase.inspect} extension dependency: #{dep.inspect}" unless known_callbacks.index(dep)
684
+ end
685
+ satisfied = cb.dependencies.find { |dep| remaining_names.index(dep) } == nil
686
+ cb if satisfied
687
+ end
688
+ r.delete res
689
+ end
690
+
691
+ end
692
+
693
+
694
+ # The basic mechanism for extending projects in Buildr are Ruby modules. In fact,
695
+ # base features like compiling and testing are all developed in the form of modules,
696
+ # and then added to the core Project class.
697
+ #
698
+ # A module defines instance methods that are then mixed into the project and become
699
+ # instance methods of the project. There are two general ways for extending projects.
700
+ # You can extend all projects by including the module in Project:
701
+ # class Project
702
+ # include MyExtension
703
+ # end
704
+ # You can also extend a given project instance and only that instance by extending
705
+ # it with the module:
706
+ # define 'foo' do
707
+ # extend MyExtension
708
+ # end
709
+ #
710
+ # Some extensions require tighter integration with the project, specifically for
711
+ # setting up tasks and properties, or for configuring tasks based on the project
712
+ # definition. You can do that by adding callbacks to the process.
713
+ #
714
+ # The easiest way to add callbacks is by incorporating the Extension module in your
715
+ # own extension, and using the various class methods to define callback behavior:
716
+ # * first_time -- This block will be called once for any particular extension.
717
+ # You can use this to setup top-level and local tasks.
718
+ # * before_define -- This block is called once for the project with the project
719
+ # instance, right before running the project definition. You can use this
720
+ # to add tasks and set properties that will be used in the project definition.
721
+ # * after_define -- This block is called once for the project with the project
722
+ # instance, right after running the project definition. You can use this to
723
+ # do any post-processing that depends on the project definition.
724
+ #
725
+ # This example illustrates how to write a simple extension:
726
+ # module LinesOfCode
727
+ # include Extension
728
+ #
729
+ # first_time do
730
+ # # Define task not specific to any projet.
731
+ # desc 'Count lines of code in current project'
732
+ # Project.local_task('loc')
733
+ # end
734
+ #
735
+ # before_define do |project|
736
+ # # Define the loc task for this particular project.
737
+ # Rake::Task.define_task 'loc' do |task|
738
+ # lines = task.prerequisites.map { |path| Dir['#{path}/**/*'] }.flatten.uniq.
739
+ # inject(0) { |total, file| total + File.readlines(file).count }
740
+ # puts "Project #{project.name} has #{lines} lines of code"
741
+ # end
742
+ # end
743
+ #
744
+ # after_define do |project|
745
+ # # Now that we know all the source directories, add them.
746
+ # task('loc'=>compile.sources + compile.test.sources)
747
+ # end
748
+ #
749
+ # # To use this method in your project:
750
+ # # loc path_1, path_2
751
+ # def loc(*paths)
752
+ # task('loc'=>paths)
753
+ # end
754
+ #
755
+ # end
756
+ #
757
+ # class Buildr::Project
758
+ # include LinesOfCode
759
+ # end
760
+ module Extension
761
+
762
+ # Extension callback details
763
+ class Callback #:nodoc:
764
+ attr_accessor :phase, :name, :dependencies, :blocks
765
+
766
+ def initialize(phase, name, dependencies, blocks)
767
+ @phase = phase
768
+ @name = name
769
+ @dependencies = dependencies
770
+ @blocks = (blocks ? (Array === blocks ? blocks : [blocks]) : [])
771
+ end
772
+
773
+ def merge(callback)
774
+ Callback.new(phase, name, @dependencies + callback.dependencies, @blocks + callback.blocks)
775
+ end
776
+ end
777
+
778
+ def self.included(base) #:nodoc:
779
+ base.extend ClassMethods
780
+ end
781
+
782
+ # Methods added to the extension module when including Extension.
783
+ module ClassMethods
784
+
785
+ def included(base) #:nodoc:
786
+ # When included in Project, add module instance, merge callbacks and call first_time.
787
+ if Project == base && !base.extension_modules.include?(module_callbacks)
788
+ base.extension_modules << module_callbacks
789
+ merge_callbacks(base.global_callbacks, module_callbacks)
790
+ first_time = module_callbacks.select { |c| c.phase == :first_time }
791
+ first_time.each do |c|
792
+ c.blocks.each { |b| b.call }
793
+ end
794
+ end
795
+ end
796
+
797
+ def extended(base) #:nodoc:
798
+ # When extending project, merge after_define callbacks and call before_define callback(s)
799
+ # immediately
800
+ if Project === base
801
+ merge_callbacks(base.callbacks, module_callbacks.select { |cb| cb.phase == :after_define })
802
+ calls = module_callbacks.select { |cb| cb.phase == :before_define }
803
+ calls.each do |cb|
804
+ cb.blocks.each { |b| b.call(base) } unless base.calledback[cb]
805
+ base.calledback[cb] = cb
806
+ end
807
+ end
808
+ end
809
+
810
+ # This block will be called once for any particular extension included in Project.
811
+ # You can use this to setup top-level and local tasks.
812
+ def first_time(&block)
813
+ module_callbacks << Callback.new(:first_time, self.name, [], block)
814
+ end
815
+
816
+ # This block is called once for the project with the project instance,
817
+ # right before running the project definition. You can use this to add
818
+ # tasks and set properties that will be used in the project definition.
819
+ #
820
+ # The block may be named and dependencies may be declared similar to Rake
821
+ # task dependencies:
822
+ #
823
+ # before_define(:my_setup) do |project|
824
+ # # do stuff on project
825
+ # end
826
+ #
827
+ # # my_setup code must run before :compile
828
+ # before_define(:compile => :my_setup)
829
+ #
830
+ def before_define(*args, &block)
831
+ if args.empty?
832
+ name = self.name
833
+ deps = []
834
+ else
835
+ name, args, deps = Buildr.application.resolve_args(args)
836
+ end
837
+ module_callbacks << Callback.new(:before_define, name, deps, block)
838
+ end
839
+
840
+ # This block is called once for the project with the project instance,
841
+ # right after running the project definition. You can use this to do
842
+ # any post-processing that depends on the project definition.
843
+ #
844
+ # The block may be named and dependencies may be declared similar to Rake
845
+ # task dependencies:
846
+ #
847
+ # after_define(:my_setup) do |project|
848
+ # # do stuff on project
849
+ # end
850
+ #
851
+ # # my_setup code must run before :compile (but only after project is defined)
852
+ # after_define(:compile => :my_setup)
853
+ #
854
+ def after_define(*args, &block)
855
+ if args.empty?
856
+ name = self.name
857
+ deps = []
858
+ else
859
+ name, args, deps = Buildr.application.resolve_args(args)
860
+ end
861
+ module_callbacks << Callback.new(:after_define, name, deps, block)
862
+ end
863
+
864
+ private
865
+
866
+ def module_callbacks
867
+ begin
868
+ const_get('Callbacks')
869
+ rescue
870
+ callbacks = []
871
+ const_set('Callbacks', callbacks)
872
+ end
873
+ end
874
+
875
+ def merge_callbacks(base, merge)
876
+ # index by phase and name
877
+ index = base.inject({}) { |hash,cb| { [cb.phase, cb.name] => cb } }
878
+ merge.each do |cb|
879
+ existing = index[[cb.phase, cb.name]]
880
+ if existing
881
+ base[base.index(existing)] = existing.merge(cb)
882
+ else
883
+ base << cb
884
+ end
885
+ index[[cb.phase, cb.name]] = cb
886
+ end
887
+ base
888
+ end
889
+ end
890
+
891
+ end
892
+
893
+
894
+ # :call-seq:
895
+ # define(name, properties?) { |project| ... } => project
896
+ #
897
+ # Defines a new project.
898
+ #
899
+ # The first argument is the project name. Each project must have a unique name.
900
+ # For a sub-project, the actual project name is created by prefixing the parent
901
+ # project's name.
902
+ #
903
+ # The second argument is optional and contains a hash or properties that are set
904
+ # on the project. You can only use properties that are supported by the project
905
+ # definition, e.g. :group and :version. You can also set these properties from the
906
+ # project definition.
907
+ #
908
+ # You pass a block that is executed in the context of the project definition.
909
+ # This block is used to define the project and tasks that are part of the project.
910
+ # Do not perform any work inside the project itself, as it will execute each time
911
+ # the Buildfile is loaded. Instead, use it to create and extend tasks that are
912
+ # related to the project.
913
+ #
914
+ # For example:
915
+ # define 'foo', :version=>'1.0' do
916
+ #
917
+ # define 'bar' do
918
+ # compile.with 'org.apache.axis2:axis2:jar:1.1'
919
+ # end
920
+ # end
921
+ #
922
+ # puts project('foo').version
923
+ # => '1.0'
924
+ # puts project('foo:bar').compile.classpath.map(&:to_spec)
925
+ # => 'org.apache.axis2:axis2:jar:1.1'
926
+ # % buildr build
927
+ # => Compiling 14 source files in foo:bar
928
+ def define(name, properties = nil, &block) #:yields:project
929
+ Project.define(name, properties, &block)
930
+ end
931
+
932
+ # :call-seq:
933
+ # project(name) => project
934
+ #
935
+ # Returns a project definition.
936
+ #
937
+ # When called from outside a project definition, must reference the project by its
938
+ # full name, e.g. 'foo:bar' to access the sub-project 'bar' in 'foo'. When called
939
+ # from inside a project, relative names are sufficient, e.g. <code>project('foo').project('bar')</code>
940
+ # will find the sub-project 'bar' in 'foo'.
941
+ #
942
+ # You cannot reference a project before the project is defined. When working with
943
+ # sub-projects, the project definition is stored by calling #define, and evaluated
944
+ # before a call to the parent project's #define method returns.
945
+ #
946
+ # However, if you call #project with the name of another sub-project, its definition
947
+ # is evaluated immediately. So the returned project definition is always complete,
948
+ # and you can access its definition (e.g. to find files relative to the base directory,
949
+ # or packages created by that project).
950
+ #
951
+ # For example:
952
+ # define 'myapp' do
953
+ # self.version = '1.1'
954
+ #
955
+ # define 'webapp' do
956
+ # # webapp is defined first, but beans is evaluated first
957
+ # compile.with project('beans')
958
+ # package :war
959
+ # end
960
+ #
961
+ # define 'beans' do
962
+ # package :jar
963
+ # end
964
+ # end
965
+ #
966
+ # puts project('myapp:beans').version
967
+ def project(*args, &block)
968
+ Project.project *args, &block
969
+ end
970
+
971
+ # :call-seq:
972
+ # projects(*names) => projects
973
+ #
974
+ # With no arguments, returns a list of all projects defined so far. When called on a project,
975
+ # returns all its sub-projects (direct descendants).
976
+ #
977
+ # With arguments, returns a list of named projects, fails on any name that does not exist.
978
+ # As with #project, you can use relative names when calling this method on a project.
979
+ #
980
+ # Like #project, this method evaluates the definition of each project before returning it.
981
+ # Be advised of circular dependencies.
982
+ #
983
+ # For example:
984
+ # files = projects.map { |prj| FileList[prj.path_to('src/**/*.java') }.flatten
985
+ # puts "There are #{files.size} source files in #{projects.size} projects"
986
+ #
987
+ # puts projects('myapp:beans', 'myapp:webapp').map(&:name)
988
+ # Same as:
989
+ # puts project('myapp').projects.map(&:name)
990
+ def projects(*args)
991
+ Project.projects *args
992
+ end
993
+
994
+ end