buildr 0.14.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.
data/lib/core/build.rb ADDED
@@ -0,0 +1,108 @@
1
+ module Buildr
2
+
3
+ class ReleaseTask < Rake::Task
4
+
5
+ VERSION_NUMBER_PATTERN = /VERSION_NUMBER\s*=\s*(["'])(.*)\1/
6
+ NEXT_VERSION_PATTERN = /NEXT_VERSION\s*=\s*(["'])(.*)\1/
7
+
8
+ def initialize(*args)
9
+ super
10
+ enhance do |task|
11
+ # Make sure we don't have anything uncommitted in SVN.
12
+ fail "Uncommitted SVN files violate the First Principle Of Release!" unless
13
+ svn("status").empty?
14
+ # Load the Rakefile and find the version numbers.
15
+ next_ver = update_version
16
+ # Run the deployment externally using the new version number.
17
+ sh "rake deploy"
18
+ update_next_version next_ver
19
+ tag_repository
20
+ end
21
+ end
22
+
23
+ # Change the Rakefile and update the current version number to the
24
+ # next version number (VERSION_NUMBER = NEXT_VERSION). We need this
25
+ # before making a release with the next version. Return the next version.
26
+ def update_version()
27
+ rakefile = File.read(Rake.application.rakefile)
28
+ version = rakefile.scan(VERSION_NUMBER_PATTERN)[0][1] or
29
+ fail "Looking for VERSION_NUMBER = \"...\" in your Rakefile, none found"
30
+ next_ver = rakefile.scan(NEXT_VERSION_PATTERN)[0][1] or
31
+ fail "Looking for NEXT_VERSION = \"...\" in your Rakefile, none found"
32
+ if verbose
33
+ puts "Current version: #{version}"
34
+ puts "Next version: #{next_ver}"
35
+ end
36
+
37
+ # Switch version numbers.
38
+ rakefile.gsub!(VERSION_NUMBER_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_ver}"}) }
39
+ File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
40
+
41
+ next_ver
42
+ end
43
+
44
+ # Change the Rakefile and update the next version number to one after
45
+ # (NEXT_VERSION = NEXT_VERSION + 1). We do this to automatically increment
46
+ # future version number after each successful release.
47
+ def update_next_version(version)
48
+ # Update to new version number.
49
+ nums = version.split(".")
50
+ nums[-1] = nums[-1].to_i + 1
51
+ next_ver = nums.join(".")
52
+ rakefile = File.read(Rake.application.rakefile)
53
+ rakefile.gsub!(NEXT_VERSION_PATTERN) { |ver| ver.sub(/(["']).*\1/, %Q{"#{next_ver}"}) }
54
+ File.open(Rake.application.rakefile, "w") { |file| file.write rakefile }
55
+
56
+ # Commit new version number.
57
+ svn "commit", "-m", "Changed release number to #{version}"
58
+ end
59
+
60
+ # Create a tag in the SVN repository.
61
+ def tag_repository()
62
+ # Copy to tag.
63
+ cur_url = svn("info").scan(/URL: (.*)/)[0][0]
64
+ new_url = cur_url.sub(/trunk$/, "tags/#{cur_ver}")
65
+ svn "copy", cur_url, new_url
66
+ end
67
+
68
+ def svn(*args)
69
+ #args << { :verbose=>Rake.application.options.trace }
70
+ stdin, stdout, stderr = Open3.popen3("svn", *args)
71
+ stdin.close
72
+ error = stderr.read
73
+ fail error unless error.empty?
74
+ stdout.read
75
+ end
76
+ end
77
+
78
+ # Handles the build and clean tasks.
79
+ desc "Clean all projects"
80
+ LocalDirectoryTask.define_task("clean")
81
+ desc "Build all projects"
82
+ LocalDirectoryTask.define_task("build")
83
+ desc "Make a release"
84
+ ReleaseTask.define_task "release"
85
+
86
+ class Project
87
+ def build(*args, &block)
88
+ returning(@build_task ||= recursive_task("build")) do |task|
89
+ task.enhance args, &block
90
+ end
91
+ end
92
+
93
+ def clean(*args, &block)
94
+ returning(@clean_task ||= recursive_task("clean")) do |task|
95
+ task.enhance args, &block
96
+ end
97
+ end
98
+ end
99
+
100
+ Project.on_create do |project|
101
+ desc "Clean all files generated during the build process"
102
+ project.clean
103
+
104
+ desc "Build this project"
105
+ project.build
106
+ end
107
+
108
+ end
data/lib/core/core.rb ADDED
@@ -0,0 +1,49 @@
1
+ # returning(obj) and with(obj)
2
+ require "facet/kernel/with"
3
+ # &:symbol goodness.
4
+ require "facet/symbol/to_proc"
5
+ # blank? on string and nil
6
+ require "facet/string/blank"
7
+ require "facet/nilclass/blank"
8
+ # What it says.
9
+ require "facet/module/alias_method_chain"
10
+ require "highline"
11
+
12
+
13
+ module Kernel
14
+ def warn_with_color(message)
15
+ warn_without_color HighLine.new.color(message.to_s, :red)
16
+ end
17
+ alias_method_chain :warn, :color
18
+ end
19
+
20
+
21
+ module Buildr
22
+ module Attributes
23
+
24
+ def self.included(mod)
25
+ mod.extend(self)
26
+ end
27
+
28
+ # An inherited attribute gets it value from an instance variable
29
+ # with the same name. If the value is not set it will defer to the
30
+ # parent object. If there is no parent object, it will use the
31
+ # default value; with a block, it evaluates the block by calling
32
+ # instance_eval on the object.
33
+ #
34
+ # For example:
35
+ # inherited_attr :version
36
+ # inherited_attr :src_dir, "src"
37
+ # inherited_attr :java_src_dir do src_dir + "/main/java"
38
+ def inherited_attr(symbol, default = nil, &block)
39
+ block ||= proc { default }
40
+ define_method symbol do
41
+ instance_variable_get("@#{symbol}") || (parent ? parent.send(symbol) : self.instance_eval(&block))
42
+ end
43
+ define_method "#{symbol}=" do |value|
44
+ instance_variable_set("@#{symbol}", value)
45
+ end
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,313 @@
1
+ module Buildr
2
+
3
+ # A project is a means to assemble multiple related tasks.
4
+ #
5
+ # A project will create its own set of internal tasks, such as
6
+ # compile, build, clean, install. These tasks are fed with project
7
+ # properties, e.g. the clean task will remove the project.target_dir
8
+ # directory, where files are created during the build process.
9
+ #
10
+ # Projects can be organized hierarchically such that sub-projects
11
+ # inherit default properties from their parent project, and
12
+ # participate in tasks executed on the parent project.
13
+ #
14
+ # For example:
15
+ # define "parent" do |project|
16
+ # project.version = "1.1"
17
+ #
18
+ # define "child1"
19
+ # define "child2"
20
+ # end
21
+ #
22
+ # This definition will work for a directory structure of:
23
+ # . -- Parent project
24
+ # |__child 1
25
+ # |__child 2
26
+ #
27
+ # The sub-projects child1 and child2 inherit the project version number
28
+ # from the parent project. In addition, for certain tasks such as build,
29
+ # running the task on the parent project will also run it on all
30
+ # sub-projects.
31
+ #
32
+ # Use the #define method to define a new project, and within the context
33
+ # of a project to define a sub-project. Use the #project method to find
34
+ # an existing project, and within the context of a project, one of its
35
+ # sub-projects.
36
+ #
37
+ # The main block in the project definition is executed during the project
38
+ # definition. Use it to set project properties, and configure tasks.
39
+ # Do not do any actual work in there.
40
+ class Project
41
+
42
+ class << self
43
+
44
+ # See Buildr#define.
45
+ def define(*args, &block)
46
+ name, properties = name_and_properties_from_args(*args)
47
+ project(name).send :_define, properties, &block
48
+ end
49
+
50
+ # Returns the specified project (top-level only).
51
+ def project(name)
52
+ @projects ||= {}
53
+ name.split(":").inject(nil) do |parent, name|
54
+ if parent
55
+ @projects["#{parent.name}:#{name}"] ||= Project.new(name, parent)
56
+ else
57
+ @projects[name] ||= Project.new(name, nil)
58
+ end
59
+ end
60
+ end
61
+
62
+ # Returns all project definitions, including sub-projects.
63
+ def projects()
64
+ (@projects || []).map { |name, project| project }
65
+ end
66
+
67
+ # Discard all project definitions.
68
+ def clear()
69
+ @projects.clear if @projects
70
+ end
71
+
72
+ # The Project class defines very little behavior for new process instances.
73
+ # Use #on_create to add behavior to new process instances.
74
+ #
75
+ # Every block you register with #on_create will be called with a project
76
+ # instance whenever a new project is created. You can then define tasks,
77
+ # set project properties, etc.
78
+ #
79
+ # Keep in mind that the order on which #on_create blocks are called is
80
+ # not determined. You cannot depend on tasks defined by another block to
81
+ # exist at the time your block is called. Use Project#enhance for that.
82
+ def on_create(&block)
83
+ (@on_create ||= []) << block if block
84
+ end
85
+
86
+ # :nodoc:
87
+ def name_and_properties_from_args(*args)
88
+ if Hash === args.last
89
+ properties = args.pop.clone
90
+ else
91
+ properties = {}
92
+ end
93
+ if String === args.first
94
+ name = args.shift
95
+ else
96
+ name = properties.delete(:name)
97
+ end
98
+ raise ArgumentError, "Expected project name followed by (optional) project properties." unless args.empty?
99
+ raise ArgumentError, "Missing project name." unless name
100
+ [ name, properties ]
101
+ end
102
+ end
103
+
104
+ include Attributes
105
+
106
+ # The project name.
107
+ attr_reader :name
108
+
109
+ # The parent project if this is a sub-project.
110
+ attr_reader :parent
111
+
112
+ # The base directory of this project.
113
+ attr_reader :base_dir
114
+
115
+ # Always construct a project using Object#project or Project#project.
116
+ def initialize(name, parent)
117
+ fail "Missing project name" unless name
118
+ @name = parent ? "#{parent.name}:#{name}" : name
119
+ @parent = parent
120
+ if parent
121
+ # For sub-project, a good default is a directory in the parent's base_dir,
122
+ # using the same name as the project.
123
+ @base_dir = File.join(parent.base_dir, name)
124
+ else
125
+ # For top-level project, a good default is the directory where we found the Rakefile.
126
+ @base_dir = Dir.pwd
127
+ end
128
+ @actions = []
129
+ end
130
+
131
+ # Define a new sub-project within this project.
132
+ def define(*args, &block)
133
+ name, properties = Project.name_and_properties_from_args(*args)
134
+ Project.define("#{self.name}:#{name}", properties, &block)
135
+ end
136
+
137
+ def defined?()
138
+ @defined
139
+ end
140
+
141
+ # Returns a path made from the specified arguments. Relative paths are turned
142
+ # into absolute paths using the based directory of this project.
143
+ #
144
+ # If you pass multiple arguments, they are combined into a path using File#join.
145
+ # If one of the arguments is a symbol, it is used to retrieve that process
146
+ # property.
147
+ #
148
+ # For example:
149
+ # path_to("foo", "bar")
150
+ # path_to(:target_dir, "foo")
151
+ # path_to("/tmp")
152
+ # are equivalent to:
153
+ # File.join(base_dir, "foo", "bar")
154
+ # File.join(base_dir, project.target_dir, "foo")
155
+ # "/tmp"
156
+ def path_to(*args)
157
+ File.expand_path(File.join(args.map { |arg| Symbol === arg ? send(arg) : arg.to_s }), base_dir)
158
+ end
159
+
160
+ # Returns the specified sub-project of this project, or any of its descendant,
161
+ # or a top-level project.
162
+ #
163
+ # For example:
164
+ # bar.project("baz")
165
+ # will find the first match from:
166
+ # foo:bar:baz
167
+ # foo:baz
168
+ # baz
169
+ def project(name)
170
+ Project.project(name)
171
+ end
172
+
173
+ # Returns all sub-projects defined in this project, including their
174
+ # sub-projects.
175
+ def projects()
176
+ prefix = name + ":"
177
+ Project.projects.map { |project| project.starts_with?(prefix) }
178
+ end
179
+
180
+ # The project ID is the project name, and for a sub-project the
181
+ # parent project ID followed by the project name, separated with a
182
+ # hyphen. For example, "foo" and "foo-bar".
183
+ def id()
184
+ name.gsub(":", "-")
185
+ end
186
+
187
+ # Define a recursive task.
188
+ #
189
+ # A recursive task for a project will execute all sub-project tasks of
190
+ # the same name before it executes itself. In addition, if a task with
191
+ # the same name exists (not prefixed with a project), it will execute
192
+ # same task but only for the current project -- as determined by the
193
+ # current working directory.
194
+ #
195
+ # For example:
196
+ # rake foo:build
197
+ # Will execute foo:build, foo:bar:build and foo:baz:build
198
+ #
199
+ # Inside the bar directory:
200
+ # rake build
201
+ # Will execute foo:bar:build.
202
+ #
203
+ # Note, this method is used to define the task as recursive. It will
204
+ # also define the task if the task does not already exist. However,
205
+ # you can define the task before calling #recursive_task, e.g. to
206
+ # create a file task, or any other special purpose task.
207
+ def recursive_task(arg, &block)
208
+ name = Hash === arg ? arg.keys.first : arg
209
+ returning(task(arg)) do |task|
210
+ if parent
211
+ Rake::Task["^#{parent.name}:#{name}"].enhance([ task ])
212
+ end
213
+ task.enhance &block
214
+ end
215
+ end
216
+
217
+ def enhance(&block)
218
+ @actions << block if block
219
+ end
220
+
221
+ protected
222
+
223
+ def _define(properties, &block)
224
+ fail "Project #{name} already defined" if @defined
225
+ @defined = true
226
+ @base_dir = properties.delete(:base_dir) if properties.has_key?(:base_dir)
227
+ # How convenient: project name is used to create compound namespace for tasks.
228
+ namespace name.split(":").last do
229
+ # On_create requires so we have attribute accessors for the properties.
230
+ (self.class.instance_variable_get(:@on_create) || []).each { |callback| callback.call self }
231
+ properties.each { |name, value| send "#{name}=", value }
232
+ if block
233
+ begin
234
+ # Evaluate in context of project, and pass project. And for that we need
235
+ # a method definition, on the singleton so we don't conflict with anyone else.
236
+ singleton = (class << self ; self ; end)
237
+ singleton.send :define_method, :__enhance__, &block
238
+ self.__enhance__ self
239
+ ensure
240
+ singleton.send :remove_method, :__enhance__
241
+ end
242
+ end
243
+ @actions.each { |callback| callback.call self }
244
+ @actions.clear
245
+ end
246
+ self
247
+ end
248
+
249
+ end
250
+
251
+ # :call-seq:
252
+ # define name { |project| ... }
253
+ # define name, properties { |project| ... }
254
+ # define properties { |project| ... }
255
+ #
256
+ # Defines a new project.
257
+ #
258
+ # The first argument is the project name. Each project must have a unique name,
259
+ # to distinguish it from other projects. The name must be unique either across
260
+ # all top-level projects, or all sub-projects belonging to the same parent.
261
+ #
262
+ # The second argument contains any number of properties that are set on the
263
+ # project at creation, before calling the block. There is no special preference
264
+ # to properties passed as arguments.
265
+ #
266
+ # The second argument is optional. You may omit the first argument, by passing
267
+ # the project name as the property :name.
268
+ #
269
+ # If a block is given, it is executed in the context of the project, and passed
270
+ # a reference to the project. These two are equivalent:
271
+ # define "foo", :version=>"1" do
272
+ # self.group = "foo-s"
273
+ # end
274
+ #
275
+ # define "foo" do |project|
276
+ # project.version = "1"
277
+ # project.group = "foo-s"
278
+ # end
279
+ def define(*args, &block)
280
+ Project.define(*args, &block)
281
+ end
282
+
283
+ # Returns a top-level project.
284
+ def project(name)
285
+ Project.project(name)
286
+ end
287
+
288
+ task "check" do |task|
289
+ # Find all projects that:
290
+ # - Are referenced but never defined.
291
+ # - Do not have a base directory.
292
+ Project.projects.each do |project|
293
+ warn "Project #{project.name} referenced but not defined" unless project.defined?
294
+ warn "Project #{project.name} does not have a base directory" unless File.exist?(project.base_dir)
295
+ end
296
+ end
297
+
298
+ class LocalDirectoryTask < Rake::Task
299
+
300
+ def initialize(*args)
301
+ super
302
+ enhance do |task|
303
+ projects = Project.projects.select { |project| project.base_dir == Rake.application.original_dir }
304
+ if verbose && projects.empty?
305
+ warn "No projects defined for directory #{Rake.application.original_dir}"
306
+ end
307
+ projects.each { |project| task("#{project.name}:#{task.name}").invoke }
308
+ end
309
+ end
310
+
311
+ end
312
+
313
+ end