bozo 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,300 @@
1
+ module Bozo
2
+
3
+ # Class used for defining the configuration of a build.
4
+ class BozoConfiguration
5
+
6
+ include Bozo::ClassNameHelpers
7
+
8
+ # Creates a new instance
9
+ def initialize
10
+ @build_tools_location = nil
11
+ @compilers = []
12
+ @dependency_resolvers = []
13
+ @hooks = []
14
+ @packagers = []
15
+ @preparers = []
16
+ @publishers = []
17
+ @test_runners = []
18
+ @version = nil
19
+ end
20
+
21
+ # Loads the configuration from the specified file.
22
+ #
23
+ # @param [String] path
24
+ # The path to the configuration file.
25
+ def load(path)
26
+ instance_eval IO.read(path), path
27
+ end
28
+
29
+ # The version of the project to build.
30
+ def version
31
+ @version ||= Versioning::Version.load_from_file
32
+ end
33
+
34
+ # The location of the base build tools.
35
+ #
36
+ # Sets and returns the location if a value is provided, otherwise returns
37
+ # the current value.
38
+ #
39
+ # It is expected the build tools will be in sub-directory of this location
40
+ # according to the name of the tool and that the runner will be able to
41
+ # copy the contents of the sub-directory to a local directory via the
42
+ # `FileUtils.cp_r` method.
43
+ #
44
+ # @param [String] location
45
+ # The path to set as the build tools location.
46
+ def build_tools_location(location = nil)
47
+ @build_tools_location = location if location
48
+ @build_tools_location
49
+ end
50
+
51
+ # Returns the all the build tools required by the specified compilers,
52
+ # dependency resolvers, hooks, packagers, publishers and test runners.
53
+ def build_tools
54
+ build_tools = get_build_tools @compilers
55
+ build_tools |= get_build_tools @dependency_resolvers
56
+ build_tools |= get_build_tools @hooks
57
+ build_tools |= get_build_tools @packagers
58
+ build_tools |= get_build_tools @preparers
59
+ build_tools |= get_build_tools @publishers
60
+ build_tools | (get_build_tools @test_runners)
61
+ end
62
+
63
+ # Returns the configured dependency resolvers.
64
+ attr_reader :dependency_resolvers
65
+
66
+ # Adds an instance of the named preparer to the preparer collection and
67
+ # yields it to the configuration block when one is specified.
68
+ #
69
+ # @param [Symbol] type
70
+ # The name of the preparer.
71
+ # @param [Proc] block
72
+ # Optional block to refine the configuration of the preparer.
73
+ def prepare(type, &block) # :yields: preparer
74
+ add_instance @preparers, Bozo::Preparers, type, block
75
+ end
76
+
77
+ # Returns the configured preparers.
78
+ attr_reader :preparers
79
+
80
+ # Adds an instance of the named dependency resolver to the dependency
81
+ # resolver collection and yields it to the configuration block when one
82
+ # is specified.
83
+ #
84
+ # @param [Symbol] type
85
+ # The name of the dependency resolver.
86
+ # @param [Proc] block
87
+ # Optional block to refine the configuration of the dependency
88
+ # resolver.
89
+ def resolve_dependencies_with(type, &block) # :yields: dependency_resolver
90
+ add_instance @dependency_resolvers, Bozo::DependencyResolvers, type, block
91
+ end
92
+
93
+ # Returns the configured compilers.
94
+ attr_reader :compilers
95
+
96
+ # Adds an instance of the named compiler to the compiler collection and
97
+ # yields it to the configuration block when one is specified.
98
+ #
99
+ # @param [Symbol] type
100
+ # The name of the compiler.
101
+ # @param [Proc] block
102
+ # Optional block to refine the configuration of the compiler.
103
+ def compile_with(type, &block) # :yields: compiler
104
+ add_instance @compilers, Bozo::Compilers, type, block
105
+ end
106
+
107
+ # Returns the configured test runners.
108
+ attr_reader :test_runners
109
+
110
+ # Adds an instance of the named test runner to the test runner collection
111
+ # and yields it to the configuration block when one is specified.
112
+ #
113
+ # @param [Symbol] type
114
+ # The name of the test runner.
115
+ # @param [Proc] block
116
+ # Optional block to refine the configuration of the test runner.
117
+ def test_with(type, &block) # :yields: test_runner
118
+ add_instance @test_runners, Bozo::TestRunners, type, block
119
+ end
120
+
121
+ # Returns the configured packagers.
122
+ attr_reader :packagers
123
+
124
+ # Adds an instance of the named packager to the packager collection and
125
+ # yields it to the configuration block when one is specified.
126
+ #
127
+ # @param [Symbol] type
128
+ # The name of the packager.
129
+ # @param [Proc] block
130
+ # Optional block to refine the configuration of the packager.
131
+ def package_with(type, &block) # :yields: packager
132
+ add_instance @packagers, Bozo::Packagers, type, block
133
+ end
134
+
135
+ # Returns the configured publishers.
136
+ attr_reader :publishers
137
+
138
+ # Adds an instance of the named publisher to the publisher collection and
139
+ # yields it to the configuration block when one is specified.
140
+ #
141
+ # @param [Symbol] type
142
+ # The name of the publisher.
143
+ # @param [Proc] block
144
+ # Optional block to refine the configuration of the publisher.
145
+ def publish_with(type, &block) # :yields: publisher
146
+ add_instance @publishers, Bozo::Publishers, type, block
147
+ end
148
+
149
+ # Returns the configured hooks.
150
+ attr_reader :hooks
151
+
152
+ # Adds an instance of the named hook to the hook collection and yields it
153
+ # to the configuration block when one is specified.
154
+ #
155
+ # @param [Symbol] type
156
+ # The name of the hook.
157
+ # @param [Proc] block
158
+ # Optional block to refine the configuration of the hook.
159
+ def with_hook(type, &block) # :yields: hook
160
+ add_instance @hooks, Bozo::Hooks, type, block
161
+ end
162
+
163
+ # Adds an instance of the named hook to the hook collection and yields it
164
+ # to the configuration block when one is specified.
165
+ alias :pre_build :with_hook
166
+
167
+ # Adds an instance of the named hook to the hook collection and yields it
168
+ # to the configuration block when one is specified.
169
+ alias :pre_clean :with_hook
170
+
171
+ # Adds an instance of the named hook to the hook collection and yields it
172
+ # to the configuration block when one is specified.
173
+ alias :post_clean :with_hook
174
+
175
+ # Adds an instance of the named hook to the hook collection and yields it
176
+ # to the configuration block when one is specified.
177
+ alias :pre_uninstall :with_hook
178
+
179
+ # Adds an instance of the named hook to the hook collection and yields it
180
+ # to the configuration block when one is specified.
181
+ alias :post_uninstall :with_hook
182
+
183
+ # Adds an instance of the named hook to the hook collection and yields it
184
+ # to the configuration block when one is specified.
185
+ alias :pre_dependencies :with_hook
186
+
187
+ # Adds an instance of the named hook to the hook collection and yields it
188
+ # to the configuration block when one is specified.
189
+ alias :post_dependencies :with_hook
190
+
191
+ # Adds an instance of the named hook to the hook collection and yields it
192
+ # to the configuration block when one is specified.
193
+ alias :pre_prepare :with_hook
194
+
195
+ # Adds an instance of the named hook to the hook collection and yields it
196
+ # to the configuration block when one is specified.
197
+ alias :post_prepare :with_hook
198
+
199
+ # Adds an instance of the named hook to the hook collection and yields it
200
+ # to the configuration block when one is specified.
201
+ alias :pre_compile :with_hook
202
+
203
+ # Adds an instance of the named hook to the hook collection and yields it
204
+ # to the configuration block when one is specified.
205
+ alias :post_compile :with_hook
206
+
207
+ # Adds an instance of the named hook to the hook collection and yields it
208
+ # to the configuration block when one is specified.
209
+ alias :pre_test :with_hook
210
+
211
+ # Adds an instance of the named hook to the hook collection and yields it
212
+ # to the configuration block when one is specified.
213
+ alias :post_test :with_hook
214
+
215
+ # Adds an instance of the named hook to the hook collection and yields it
216
+ # to the configuration block when one is specified.
217
+ alias :pre_package :with_hook
218
+
219
+ # Adds an instance of the named hook to the hook collection and yields it
220
+ # to the configuration block when one is specified.
221
+ alias :post_package :with_hook
222
+
223
+ # Adds an instance of the named hook to the hook collection and yields it
224
+ # to the configuration block when one is specified.
225
+ alias :pre_publish :with_hook
226
+
227
+ # Adds an instance of the named hook to the hook collection and yields it
228
+ # to the configuration block when one is specified.
229
+ alias :post_publish :with_hook
230
+
231
+ # Adds an instance of the named hook to the hook collection and yields it
232
+ # to the configuration block when one is specified.
233
+ alias :post_build :with_hook
234
+
235
+ private
236
+
237
+ # Resolves the named class within the given namespace and then created an
238
+ # instance of the class before adding it to the given collection and
239
+ # yielding it to the configuration block when one is provided.
240
+ #
241
+ # @param [Array] collection
242
+ # The collection the step executor should be added to once created.
243
+ # @param [Module] namespace
244
+ # The module the named step executor should be found in.
245
+ # @param [Symbol] type
246
+ # The name of the step executor.
247
+ # @param [Proc] block
248
+ # Optional block to refine the configuration of the step executor.
249
+ def add_instance(collection, namespace, type, block)
250
+ instance = namespace.const_get(to_class_name(type)).new
251
+ instance.extend Bozo::Runner
252
+ collection << instance
253
+ block.call instance if block
254
+ end
255
+
256
+ # Returns the collection of build tools required for the configuration.
257
+ #
258
+ # Sourced from the required tools of compilers, dependency resolvers,
259
+ # hooks, publishers and test runners.
260
+ #
261
+ # @param [Array] providers
262
+ # Collection of providers that can define required tools.
263
+ def get_build_tools(providers)
264
+ providers.
265
+ select{|p| p.respond_to? :required_tools}.
266
+ map{|p| p.required_tools}.
267
+ flatten
268
+ end
269
+
270
+ end
271
+
272
+ # Module containing all dependency resolvers.
273
+ module DependencyResolvers
274
+ end
275
+
276
+ # Module containing all compilers.
277
+ module Compilers
278
+ end
279
+
280
+ # Module containing all test runners.
281
+ module TestRunners
282
+ end
283
+
284
+ # Module containing all packagers.
285
+ module Packagers
286
+ end
287
+
288
+ # Module containing all preparers.
289
+ module Preparers
290
+ end
291
+
292
+ # Module containing all publishers.
293
+ module Publishers
294
+ end
295
+
296
+ # Module containing all hooks.
297
+ module Hooks
298
+ end
299
+
300
+ end
@@ -0,0 +1,17 @@
1
+ module Bozo
2
+
3
+ module ClassNameHelpers
4
+
5
+ # Converts a symbol into a Pascal Case class name.
6
+ #
7
+ # eg. `:single` => `"Single"`, `:two_words` => `"TwoWords"`.
8
+ #
9
+ # @param [Symbol] type
10
+ # The name of a step executor.
11
+ def to_class_name(type)
12
+ type.to_s.split('_').map{|word| word.capitalize}.join
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,30 @@
1
+ module Bozo
2
+
3
+ # Error raised when a runner or hook finds the configuration or build in an
4
+ # unexpected state.
5
+ class ConfigurationError < StandardError
6
+
7
+ # The code the program should exit with upon handling this error.
8
+ attr_reader :exit_code
9
+
10
+ # A message explaining the corrective actions someone can take to avoid the
11
+ # error.
12
+ attr_reader :message
13
+
14
+ # Creates a new instance.
15
+ #
16
+ # @param [String] message
17
+ # A message explaining the corrective actions someone can take to avoid
18
+ # the error.
19
+ def initialize(message)
20
+ @message = message
21
+ @exit_code = -1
22
+ end
23
+
24
+ def inspect
25
+ "Configuration error: #{message}"
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,36 @@
1
+ module Bozo
2
+
3
+ # Error raised when a command line executable does not return an exit code of
4
+ # zero.
5
+ class ExecutionError < StandardError
6
+
7
+ # The array of parameters that made up the call.
8
+ attr_reader :command
9
+
10
+ # The exit code returned by the command.
11
+ attr_reader :exit_code
12
+
13
+ # The friendly name of the tool that failed.
14
+ attr_reader :tool
15
+
16
+ # Create a new instance.
17
+ #
18
+ # @param [Symbol] tool
19
+ # The friendly name of the tool that failed.
20
+ # @param [Array] command
21
+ # The array of parameters that made up the call.
22
+ # @param [Integer] exit_code
23
+ # The exit code returned by the command
24
+ def initialize(tool, command, exit_code)
25
+ @tool = tool
26
+ @command = command
27
+ @exit_code = exit_code
28
+ end
29
+
30
+ def inspect
31
+ "Execution error: Exit code of #{exit_code} returned from: #{tool} #{command[1..-1].join(' ')}"
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,282 @@
1
+ module Bozo
2
+
3
+ # Class defining the structure of the build and responsible for executing
4
+ # tasks and their dependencies.
5
+ class Executor
6
+ include Runner
7
+
8
+ # Create a new instance.
9
+ #
10
+ # @param [Configuration] build_configuration
11
+ # The build configuration.
12
+ # @param [Hash] global_params
13
+ # The global parameters
14
+ def initialize(build_configuration, global_params)
15
+ @build_configuration = build_configuration
16
+ @global_params = cli_keys_to_ruby global_params
17
+ end
18
+
19
+ # Executes the command using the given parameters and environment.
20
+ #
21
+ # @param [Symbol] command
22
+ # The name of the command to execute.
23
+ # @param [Hash] params
24
+ # The parameters for the command.
25
+ # @param [Hash] env
26
+ # The environment that the command is run within.
27
+ def execute(command, params, env)
28
+ with_hooks :build, params, env do
29
+ send command, params, env
30
+ end
31
+ end
32
+
33
+ # Runs the clean task.
34
+ #
35
+ # @param [Hash] params
36
+ # The parameters for the command.
37
+ # @param [Hash] env
38
+ # The environment that the command is run within.
39
+ def clean(params, env)
40
+ with_hooks :clean, params, env do
41
+ log_info 'Cleaning project'
42
+ clear_directory 'temp'
43
+ clear_directory 'dist'
44
+ end
45
+ end
46
+
47
+ # Runs the uninstall task.
48
+ #
49
+ # @param [Hash] params
50
+ # The parameters for the command.
51
+ # @param [Hash] env
52
+ # The environment that the command is run within.
53
+ def uninstall(params, env)
54
+ clean params, env
55
+ with_hooks :uninstall, params, env do
56
+ log_info 'Uninstalling project'
57
+ clear_directory 'packages'
58
+ clear_directory 'build'
59
+ end
60
+ end
61
+
62
+ # Runs the dependency retrieval task.
63
+ #
64
+ # @param [Hash] params
65
+ # The parameters for the command.
66
+ # @param [Hash] env
67
+ # The environment that the command is run within.
68
+ def dependencies(params, env)
69
+ clean params, env
70
+ retrieve_build_dependencies
71
+ execute_with_hooks :dependencies, params, env
72
+ end
73
+
74
+ # Runs the prepare task.
75
+ #
76
+ # @param [Hash] params
77
+ # The parameters for the command.
78
+ # @param [Hash] env
79
+ # The environment that the command is run within.
80
+ def prepare(params, env)
81
+ dependencies params, env
82
+ execute_with_hooks :prepare, params, env
83
+ end
84
+
85
+ # Runs the compile task.
86
+ #
87
+ # @param [Hash] params
88
+ # The parameters for the command.
89
+ # @param [Hash] env
90
+ # The environment that the command is run within.
91
+ def compile(params, env)
92
+ prepare params, env
93
+ execute_with_hooks :compile, params, env
94
+ end
95
+
96
+ # Runs the test task.
97
+ #
98
+ # @param [Hash] params
99
+ # The parameters for the command.
100
+ # @param [Hash] env
101
+ # The environment that the command is run within.
102
+ def test(params, env)
103
+ compile params, env
104
+ execute_with_hooks :test, params, env
105
+ end
106
+
107
+ # Runs the package task.
108
+ #
109
+ # @param [Hash] params
110
+ # The parameters for the command.
111
+ # @param [Hash] env
112
+ # The environment that the command is run within.
113
+ def package(params, env)
114
+ test params, env
115
+ execute_with_hooks :package, params, env
116
+ end
117
+
118
+ # Runs the publish task.
119
+ #
120
+ # @param [Hash] params
121
+ # The parameters for the command.
122
+ # @param [Hash] env
123
+ # The environment that the command is run within.
124
+ def publish(params, env)
125
+ if build_server?
126
+ package params, env
127
+ execute_with_hooks :publish, params, env
128
+ else
129
+ log_warn 'You can only publish a package from build server.'
130
+ log_warn ''
131
+ log_warn 'If you really need to publish the package then provide the --build-server switch but you should only be doing that as a last resort.'
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ # Executes the executors for the given stage logging all messages with the
138
+ # given action.
139
+ #
140
+ # @param [Symbol] stage
141
+ # The name of the stage to execute.
142
+ # @param [Hash] params
143
+ # The parameters for the command.
144
+ # @param [Hash] env
145
+ # The environment that the command is run within.
146
+ def execute_with_hooks(stage, params, env)
147
+ action, executors = stage_definition stage
148
+ with_hooks stage, params, env do
149
+ log_info '' # blank line for aesthetics
150
+ log_info action
151
+ log_debug 'No executors' if executors.empty?
152
+ executors.each do |executor|
153
+ log_debug "#{action} with #{executor.class}"
154
+ set_parameters executor, params, env
155
+ executor.execute
156
+ end
157
+ end
158
+ end
159
+
160
+ # Gets the description and runners associated with the given stage.
161
+ #
162
+ # @params [Symbol] stage
163
+ # The name of the stage.
164
+ def stage_definition(stage)
165
+ case stage
166
+ when :dependencies
167
+ return 'Resolving dependencies', @build_configuration.dependency_resolvers
168
+ when :compile
169
+ return 'Compiling', @build_configuration.compilers
170
+ when :test
171
+ return 'Running tests', @build_configuration.test_runners
172
+ when :package
173
+ return 'Packaging', @build_configuration.packagers
174
+ when :prepare
175
+ return 'Preparing', @build_configuration.preparers
176
+ when :publish
177
+ return 'Publishing', @build_configuration.publishers
178
+ else
179
+ raise "Unrecognized stage: #{stage}"
180
+ end
181
+ end
182
+
183
+ # Runs the given code block surrounding it with the hooks for the named
184
+ # stage.
185
+ #
186
+ # @param [Symbol] stage
187
+ # The name of the stage being run.
188
+ # @param [Hash] params
189
+ # The parameters for the command.
190
+ # @param [Hash] env
191
+ # The environment that the command is run within.
192
+ def with_hooks(stage, params, env)
193
+ call_receiving_hooks "pre_#{stage}".to_sym, params, env
194
+ yield
195
+ call_receiving_hooks "post_#{stage}".to_sym, params, env
196
+ end
197
+
198
+ # Invokes the method for the hook for all hook objects that define a
199
+ # method for it.
200
+ #
201
+ # @param [Symbol] stage
202
+ # The name of the hook to call the methods for.
203
+ # @param [Hash] params
204
+ # The parameters for the command.
205
+ # @param [Hash] env
206
+ # The environment that the command is run within.
207
+ def call_receiving_hooks(stage, params, env)
208
+ @build_configuration.hooks.each do |hook|
209
+ if hook.respond_to? stage
210
+ set_parameters hook, params, env
211
+ hook.send stage
212
+ end
213
+ end
214
+ end
215
+
216
+ # Assigns the state of the build system to the runner.
217
+ #
218
+ # @param [Runner] runner
219
+ # The runner to assign the state to.
220
+ # @param [Hash] params
221
+ # The parameters for the step.
222
+ # @param [Hash] env
223
+ # The environment that the step is run within.
224
+ def set_parameters(runner, params, env)
225
+ runner.build_configuration = @build_configuration
226
+ runner.global_params = @global_params
227
+ runner.params = cli_keys_to_ruby params
228
+ runner.env = env
229
+ end
230
+
231
+ # Removes the named directory from the root directory.
232
+ #
233
+ # @param [String] dir
234
+ # The name of the directory to remove.
235
+ def clear_directory(dir)
236
+ full_path = File.expand_path(dir)
237
+ log_debug "Clearing directory #{full_path}"
238
+ FileUtils.rm_rf full_path
239
+ end
240
+
241
+ # Retrieves the required build tools from the configured build tools
242
+ # location.
243
+ def retrieve_build_dependencies
244
+ tools_location = @build_configuration.build_tools_location
245
+ required_tools = @build_configuration.build_tools
246
+ tools_path = File.expand_path(File.join('build', 'tools'))
247
+
248
+ log_info 'Retrieving build tools'
249
+
250
+ required_tools.each do |tool|
251
+ tool_name = tool.to_s
252
+ tool_path = File.join(tools_location, tool_name)
253
+ tool_destination = File.join(tools_path, tool_name)
254
+
255
+ if File.exist? tool_destination
256
+ log_debug "#{tool_name.capitalize} already present"
257
+ else
258
+ log_debug "Retrieving #{tool_name.capitalize}"
259
+ FileUtils.mkdir_p tool_destination
260
+ FileUtils.cp_r tool_path, tools_path
261
+ log_debug "#{tool_name.capitalize} retrieved"
262
+ end
263
+ end
264
+ end
265
+
266
+ # Converts a hash containing CLI-style keys (:'multi-word') and converts
267
+ # them into Ruby-style symbols (:multi_word).
268
+ #
269
+ # @param [Hash] hash
270
+ # A hash containing CLI-style keys.
271
+ def cli_keys_to_ruby(hash)
272
+ hash.keys.to_a.each do |key|
273
+ new_key = key.to_s.gsub('-', '_').to_sym
274
+ hash[new_key] = hash[key] unless hash.has_key? new_key
275
+ end
276
+
277
+ hash
278
+ end
279
+
280
+ end
281
+
282
+ end
@@ -0,0 +1,40 @@
1
+ module Bozo
2
+
3
+ # Module that provides functionality for logging.
4
+ module Logging
5
+
6
+ # Records a `debug` log message.
7
+ #
8
+ # @param [String] msg
9
+ # The message to log.
10
+ def log_debug(msg)
11
+ puts " #{msg.bright.color(:black)}"
12
+ end
13
+
14
+ # Records a `fatal` log message.
15
+ #
16
+ # @param [String] msg
17
+ # The message to log.
18
+ def log_fatal(msg)
19
+ puts msg.color(:red).bright
20
+ end
21
+
22
+ # Records an `info` log message.
23
+ #
24
+ # @param [String] msg
25
+ # The message to log.
26
+ def log_info(msg)
27
+ puts msg.color(:cyan).bright
28
+ end
29
+
30
+ # Records a `warn` log message.
31
+ #
32
+ # @param [String] msg
33
+ # The message to log.
34
+ def log_warn(msg)
35
+ puts msg.color(:yellow).bright
36
+ end
37
+
38
+ end
39
+
40
+ end