buildr 0.14.0

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