bee 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -62,7 +62,7 @@ targets Targets to run (default target if omitted).'
62
62
  | |__ ___ ___
63
63
  ____ | '_ \ / _ \/ _ \ _____ _____ _____ _____ _____ _____ _____ _____ _____
64
64
  |____| | |_) | __/ __/ |_____|_____|_____|_____|_____|_____|_____|_____|_____|
65
- |_.__/ \___|\___| 0.9.0 http://bee.rubyforge.org
65
+ |_.__/ \___|\___| 0.10.0 http://bee.rubyforge.org
66
66
 
67
67
  EOF
68
68
 
@@ -180,7 +180,12 @@ EOF
180
180
  puts "ERROR: parsing command line (type 'bee -h' for help)"
181
181
  exit(EXIT_PARSING_CMDLINE)
182
182
  end
183
- formatter = Formatter.new(style, color)
183
+ begin
184
+ formatter = Formatter.new(style, color)
185
+ rescue
186
+ puts "ERROR: bad format string '#{style}'"
187
+ exit(EXIT_PARSING_CMDLINE)
188
+ end
184
189
  begin
185
190
  if logo
186
191
  puts BEE_LOGO
@@ -372,7 +377,7 @@ EOF
372
377
  help << "build: #{build.name}\n"
373
378
  end
374
379
  if build.extends
375
- help << "extends: #{build.extends}\n"
380
+ help << "extends: #{build.extends.map{|b| b.name}.join(', ')}\n"
376
381
  end
377
382
  if build.description
378
383
  help << format_description('description', build.description, 0, false)
@@ -434,10 +439,8 @@ EOF
434
439
  end
435
440
  description = 'No description found'
436
441
  if properties
437
- for property in properties
438
- if property.keys == ['description']
439
- description = property['description']
440
- end
442
+ if properties['description']
443
+ description = properties['description']
441
444
  end
442
445
  end
443
446
  help << format_title(name)
@@ -475,33 +478,27 @@ EOF
475
478
  return colorized
476
479
  end
477
480
 
478
- # Parse style from command line. If error occurs parsing style, return
479
- # nil (which means default style).
481
+ # Parse style from command line.
480
482
  # - string: style to parse.
481
483
  def parse_style_from_command_line(string)
482
484
  return if not string
483
485
  style = {}
484
- begin
485
- for pair in string.split(',')
486
- key, value = pair.split(':')
487
- key = SHORT_STYLE_KEYS[key] || key
488
- key = key.to_sym
489
- if key == :line_length
490
- value = value.to_i
491
- elsif key == :line_character
492
- value = ' ' if not value or value.length == 0
493
- else
494
- value = value.to_sym
495
- error "Unkown color or style '#{value}'" if
496
- not COLORS.member?(value) and not STYLES.member?(value)
497
- end
498
- style[key] = value
486
+ for pair in string.split(',')
487
+ key, value = pair.split(':')
488
+ key = SHORT_STYLE_KEYS[key] || key
489
+ key = key.to_sym
490
+ if key == :line_length
491
+ value = value.to_i
492
+ elsif key == :line_character
493
+ value = ' ' if not value or value.length == 0
494
+ else
495
+ value = value.to_sym
496
+ error "Unkown color or style '#{value}'" if
497
+ not COLORS.member?(value) and not STYLES.member?(value)
499
498
  end
500
- return style
501
- rescue
502
- # if parsing fails, return default style (nil)
503
- return nil
499
+ style[key] = value
504
500
  end
501
+ return style
505
502
  end
506
503
 
507
504
  # Format a description.
@@ -0,0 +1,138 @@
1
+ # Copyright 2006-2010 Michel Casabianca <michel.casabianca@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Module for Bee core classes.
16
+ module Bee
17
+
18
+ # Class for Ruby scripts context. All embedded Ruby scripts run in this
19
+ # context where build properties are defined as Ruby variables.
20
+ class Context
21
+
22
+ include Bee::Util::BuildErrorMixin
23
+
24
+ # The binding of this context.
25
+ attr_reader :context_binding
26
+
27
+ # Constructor.
28
+ def initialize(properties)
29
+ @properties = properties
30
+ @context_binding = get_binding
31
+ end
32
+
33
+ # Set a given property in context.
34
+ # - name: the property name.
35
+ # - value: the property value.
36
+ # - override: tells if we can overwrite an existing value.
37
+ def set_property(name, value, overwrite=true)
38
+ error "Property '#{name}' was already defined" if
39
+ !overwrite and properties.include?(name.to_s)
40
+ begin
41
+ eval("#{name} = #{value.inspect}", @context_binding)
42
+ rescue Exception
43
+ error "Error setting property '#{name} = #{value.inspect}': #{$!}"
44
+ end
45
+ end
46
+
47
+ # Get a given property in context.
48
+ # - name: the property name.
49
+ # - strict: raise an error if given property was not set.
50
+ def get_property(name, strict=false)
51
+ error "Property '#{name}' was not set" if
52
+ strict and !properties.include?(name.to_s)
53
+ begin
54
+ eval("#{name}", @context_binding)
55
+ rescue NameError
56
+ error "Property '#{name}' was not set"
57
+ rescue Exception
58
+ error "Error getting property '#{name}': #{$!}"
59
+ end
60
+ end
61
+
62
+ # Return list of properties (as local variables of binding).
63
+ def properties
64
+ return eval('local_variables', @context_binding).map{ |var| var.to_s }
65
+ end
66
+
67
+ # Evaluate a script in context.
68
+ # - script: source of the script to evaluate.
69
+ def evaluate_script(script)
70
+ eval(script, @context_binding)
71
+ end
72
+
73
+ # Process a given object, replacing properties references with their
74
+ # string value, symbol with their raw value. Property references have
75
+ # same form than variable references in ruby strings: '#{variable}'
76
+ # will be replaced with variable string value.
77
+ # - object: object to process.
78
+ def evaluate_object(object)
79
+ case object
80
+ when NilClass
81
+ # nil: return nil
82
+ return nil
83
+ when String
84
+ # string: replace embedded Ruby expressions
85
+ object = object.gsub(/#\{.+?\}/) do |match|
86
+ expression = match[2..-2]
87
+ begin
88
+ value = evaluate_script(expression)
89
+ rescue
90
+ error "Error evaluating expression '#{expression}': #{$!}"
91
+ end
92
+ value
93
+ end
94
+ return object
95
+ when Symbol
96
+ # symbol: return property object
97
+ property = object.to_s
98
+ begin
99
+ value = eval("#{property}", @context_binding)
100
+ rescue
101
+ error "Property '#{property}' was not set"
102
+ end
103
+ return evaluate_object(value)
104
+ when Array
105
+ # array: evaluate each element
106
+ return object.collect { |element| evaluate_object(element) }
107
+ when Hash
108
+ # hash: evaluate all keys and values
109
+ evaluated = {}
110
+ object.each_pair do |key, value|
111
+ evaluated[evaluate_object(key)] = evaluate_object(value)
112
+ end
113
+ return evaluated
114
+ else
115
+ return object
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ # Get a binding as script context.
122
+ def get_binding
123
+ return binding
124
+ end
125
+
126
+ # Catch missing properties
127
+ def method_missing(name, *args, &block)
128
+ stack = Thread.current[:stack]
129
+ if stack
130
+ @properties.evaluate_property(name, self)
131
+ else
132
+ raise NoMethodError.new("undefined method `#{name}'", name, args)
133
+ end
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,145 @@
1
+ # Copyright 2006-2010 Michel Casabianca <michel.casabianca@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'bee_context'
16
+
17
+ # Module for Bee core classes.
18
+ module Bee
19
+
20
+ # Class to manage properties.
21
+ class Properties
22
+
23
+ include Bee::Util::BuildErrorMixin
24
+
25
+ # Key for properties entry.
26
+ KEY = 'properties'
27
+
28
+ # List of system properties
29
+ SYSTEM_PROPERTIES = [:base, :here]
30
+
31
+ # Properties expressions as a hash.
32
+ attr_reader :expressions
33
+ # Properties values as a hash
34
+ attr_reader :values
35
+
36
+ # Constructor.
37
+ # - build: build object.
38
+ def initialize(build)
39
+ @build = build
40
+ @expressions = {}
41
+ @values = {}
42
+ defaults
43
+ end
44
+
45
+ # Write properties. Will raise an error on duplicate definitions.
46
+ # - object: object tree resulting from YAML build file parsing or string
47
+ # for a properties file to load.
48
+ def write(object)
49
+ properties = object[Properties::KEY]
50
+ if properties.kind_of?(String)
51
+ begin
52
+ source = Bee::Util::get_file(properties, @build.base)
53
+ hash = YAML::load(source)
54
+ set_properties(hash, false)
55
+ rescue Exception
56
+ error "Error loading properties file '#{properties}': #{$!}"
57
+ end
58
+ else
59
+ set_properties(properties, false)
60
+ end
61
+ end
62
+
63
+ # Overwrite properties with those passed.
64
+ # - properties: properties as a hash.
65
+ def overwrite(properties)
66
+ set_properties(properties, true)
67
+ end
68
+
69
+ # Extend with properties of parent build.
70
+ # - properties: properties of parent build.
71
+ def extend(properties)
72
+ for name in properties.expressions.keys
73
+ @expressions[name] = properties.expressions[name] if
74
+ !@expressions.include?(name)
75
+ end
76
+ end
77
+
78
+ # Evaluate properties in context.
79
+ # - context: build context where properties must be evaluated.
80
+ def evaluate(context)
81
+ for name in @expressions.keys
82
+ Thread.current[:stack] = []
83
+ evaluate_property(name, context) if !@values[name]
84
+ end
85
+ Thread.current[:stack] = nil
86
+ end
87
+
88
+ # Evaluate a given property.
89
+ # - name: the name of the property to evaluate.
90
+ # - context: the context in which the property must be evaluated.
91
+ # - stack: the stack of evaluated objects.
92
+ def evaluate_property(name, context)
93
+ stack = Thread.current[:stack]
94
+ error "Unknown property '#{name}'" if !@expressions.keys.include?(name)
95
+ error "Circular properties: #{stack.join(', ')}" if stack.include?(name)
96
+ begin
97
+ stack.push(name)
98
+ value = context.evaluate_object(@expressions[name])
99
+ stack.pop
100
+ @values[name] = value
101
+ context.set_property(name, value, true)
102
+ rescue
103
+ error "Error evaluating property '#{name}': #{$!}"
104
+ end
105
+ value
106
+ end
107
+
108
+ private
109
+
110
+ # Set default properties.
111
+ def defaults
112
+ set(:base, @build.base, false)
113
+ set(:here, @build.here, false)
114
+ end
115
+
116
+ # Set properties.
117
+ # - properties: properties as a hash.
118
+ # - overwrite: tells if we can overwrite existing properties.
119
+ def set_properties(properties, overwrite)
120
+ error "Properties must be a hash" if not properties.kind_of?(Hash)
121
+ for name in properties.keys
122
+ expression = properties[name]
123
+ set(name, expression, overwrite)
124
+ end
125
+ end
126
+
127
+ # Set a property.
128
+ # - name: property name (as a string or symbol).
129
+ # - expression: property expression (as a string) to be evaluated in
130
+ # context to get value.
131
+ # - overwrite: tells if we can overwrite existing properties.
132
+ def set(name, expression, overwrite)
133
+ error "Property '#{name}' collides with a function of the context" if
134
+ Bee::Context.method_defined?(name) or
135
+ Bee::Context.private_method_defined?(name)
136
+ return if expression == nil
137
+ name = name.to_sym
138
+ error "Duplicate property definition: '#{name}'" if
139
+ not overwrite and @expressions.has_key?(name)
140
+ @expressions[name] = expression
141
+ end
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,287 @@
1
+ # Copyright 2006-2010 Michel Casabianca <michel.casabianca@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Module for Bee core classes.
16
+ module Bee
17
+
18
+ # Class for a target. It is built from the YAML build file and manages a
19
+ # target, in particular, tasks execution.
20
+ class Target
21
+
22
+ include Bee::Util::BuildErrorMixin
23
+ include Bee::Util::HashCheckerMixin
24
+
25
+ # Target key.
26
+ KEY = 'target'
27
+ # Target entry description.
28
+ DESCRIPTION = {
29
+ 'target' => :mandatory,
30
+ 'depends' => :optional,
31
+ 'description' => :optional,
32
+ 'script' => :optional}
33
+
34
+ # Targets for build.
35
+ attr_accessor :targets
36
+ # Name of the target.
37
+ attr_reader :name
38
+ # Target dependencies.
39
+ attr_accessor :depends
40
+ # Target description.
41
+ attr_accessor :description
42
+ # Script that run in the target.
43
+ attr_reader :script
44
+
45
+ # Constructor.
46
+ # - object: object for target, resulting from YAML parsing.
47
+ # - targets: build targets.
48
+ def initialize(object, targets)
49
+ check_hash(object, Target::DESCRIPTION)
50
+ @targets = targets
51
+ @name = object[Target::KEY]
52
+ error "Target name cannot be 'null'" if @name == nil
53
+ @depends = object['depends']||[]
54
+ @depends = Array(@depends)
55
+ @description = object['description']
56
+ @script = object['script']
57
+ @script = [@script] if @script.kind_of?(String)
58
+ @script = [] if not @script
59
+ end
60
+
61
+ # Run target.
62
+ # - dry: tells if we run in dry mode. Defaults to false.
63
+ def run(dry=false)
64
+ current_dir = Dir.pwd
65
+ begin
66
+ for depend in @depends
67
+ @targets.run_target(depend, dry)
68
+ end
69
+ @targets.build.listener.target(self) if
70
+ @targets.build.listener and @targets.is_last(self)
71
+ run_block(@script, dry)
72
+ ensure
73
+ Dir.chdir(current_dir)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Run a task.
80
+ # - task: the task to run.
81
+ # - dry: whether to just print.
82
+ def run_task(task, dry=false)
83
+ @targets.build.listener.task(task) if @targets.build.listener
84
+ case task
85
+ when String
86
+ # shell script
87
+ run_shell(task, dry)
88
+ when Hash
89
+ if task.keys.length > 1
90
+ # construct
91
+ run_construct(task, dry)
92
+ else
93
+ if task.key?('rb')
94
+ # ruby script
95
+ script = task['rb']
96
+ run_ruby(script, dry)
97
+ elsif task.key?('sh')
98
+ # shell script
99
+ script = task['sh']
100
+ run_shell(script, dry)
101
+ elsif task.key?('super')
102
+ # call super target
103
+ targets.call_super(self, dry)
104
+ else
105
+ # must be a task
106
+ run_bee_task(task, dry)
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ # Run a given shell script.
113
+ # - script: the scrip to run.
114
+ def run_shell(script, dry=false)
115
+ @listener.task(script) if @listener
116
+ return if dry
117
+ evaluated_script = @targets.build.context.evaluate_object(script)
118
+ if evaluated_script != ''
119
+ system(evaluated_script)
120
+ error "Script exited with value #{$?}" if $? != 0
121
+ end
122
+ end
123
+
124
+ # Run a given shell script.
125
+ # - script: the scrip to run.
126
+ # - dry: tells if we run in dry mode.
127
+ def run_ruby(script, dry=false)
128
+ @listener.task(script) if @listener
129
+ return if dry
130
+ begin
131
+ @targets.build.context.evaluate_script(script)
132
+ rescue Exception
133
+ error "Error running Ruby script: #{$!}"
134
+ end
135
+ end
136
+
137
+ # Run a given bee task.
138
+ # - task: task to run as a Hash.
139
+ def run_bee_task(task, dry=false)
140
+ @listener.task(task) if @listener
141
+ return if dry
142
+ @targets.build.package_manager.run_task(task)
143
+ end
144
+
145
+ # Run a given construct.
146
+ # - construct: construct to run as a Hash.
147
+ def run_construct(construct, dry=false)
148
+ @listener.task(construct) if @listener
149
+ return if dry
150
+ # if construct
151
+ if construct.keys.include?('if')
152
+ construct_if(construct, dry)
153
+ # while construct
154
+ elsif construct.keys.include?('while')
155
+ construct_while(construct, dry)
156
+ # for construct
157
+ elsif construct.keys.include?('for')
158
+ construct_for(construct, dry)
159
+ # try construct
160
+ elsif construct.keys.include?('try')
161
+ construct_try(construct, dry)
162
+ else
163
+ error "Unknown construct '#{construct.keys.join('-')}'"
164
+ end
165
+ end
166
+
167
+ # Run if construct.
168
+ # - task: the construct as a hash.
169
+ # - dry: tells if we run in dry mode.
170
+ def construct_if(task, dry)
171
+ # test entries
172
+ error "If-then-else construct must include 'then' entry" if
173
+ not task.keys.include?('then')
174
+ unknown_keys = task.keys - ['if', 'then', 'else']
175
+ error "If-then-else construct may only include 'if', 'then' and 'else' entries" if
176
+ unknown_keys.length > 0
177
+ error "If entry in if-then-else construct must be a string" if
178
+ not task['if'].kind_of?(String)
179
+ error "Then entry in if-then-else construct must be a list" if
180
+ not task['then'].kind_of?(Array)
181
+ error "Else entry in if-then-else construct must be a list" if
182
+ task['else'] and not task['else'].kind_of?(Array)
183
+ # evaluate condition in the build context
184
+ if evaluate(task['if'])
185
+ run_block(task['then'], dry)
186
+ else
187
+ run_block(task['else'], dry) if task['else']
188
+ end
189
+ end
190
+
191
+ # Run while construct.
192
+ # - task: the construct as a hash.
193
+ # - dry: tells if we run in dry mode.
194
+ def construct_while(task, dry)
195
+ # test entries
196
+ error "While-do construct must include 'do' entry" if
197
+ not task.keys.include?('do')
198
+ unknown_keys = task.keys - ['while', 'do']
199
+ error "While-do construct may only include 'while' and 'do' entries" if
200
+ unknown_keys.length > 0
201
+ error "While entry in while-do construct must be a string" if
202
+ not task['while'].kind_of?(String)
203
+ error "Do entry in while-do construct must be a list" if
204
+ not task['do'].kind_of?(Array)
205
+ # evaluate condition in the build context
206
+ while evaluate(task['while'])
207
+ run_block(task['do'], dry)
208
+ end
209
+ end
210
+
211
+ # Run for construct.
212
+ # - task: the construct as a hash.
213
+ # - dry: tells if we run in dry mode.
214
+ def construct_for(task, dry)
215
+ # test entries
216
+ error "For-in-do construct must include 'in' and 'do' entries" if
217
+ not task.keys.include?('in') or not task.keys.include?('do')
218
+ unknown_keys = task.keys - ['for', 'in', 'do']
219
+ error "For-in-do construct may only include 'for', 'in' and 'do' entries" if
220
+ unknown_keys.length > 0
221
+ error "For entry in for-in-do construct must be a string" if
222
+ not task['for'].kind_of?(String)
223
+ error "In entry in for-in-do construct must be a list or a string" if
224
+ not task['in'].kind_of?(Enumerable) and not task['in'].kind_of?(String)
225
+ error "Do entry in for-in-do construct must be a list" if
226
+ not task['do'].kind_of?(Array)
227
+ # iterate over list
228
+ if task['in'].kind_of?(String)
229
+ enumerable = evaluate(@targets.build.context.
230
+ evaluate_object(task['in']))
231
+ else
232
+ enumerable = task['in']
233
+ end
234
+ for element in enumerable
235
+ begin
236
+ @targets.build.context.set_property(task['for'], element)
237
+ rescue
238
+ error "Error setting property '#{task['for']}'"
239
+ end
240
+ run_block(task['do'], dry)
241
+ end
242
+ end
243
+
244
+ # Run try-catch construct.
245
+ # - task: the construct as a hash.
246
+ # - dry: tells if we run in dry mode.
247
+ def construct_try(task, dry)
248
+ # test entries
249
+ error "Try-catch construct must include 'catch' entry" if
250
+ not task.keys.include?('catch')
251
+ unknown_keys = task.keys - ['try', 'catch']
252
+ error "Try-catch construct may only include 'try' and 'catch' entries" if
253
+ unknown_keys.length > 0
254
+ error "Try entry in try-catch construct must be a list" if
255
+ not task['try'].kind_of?(Array)
256
+ error "Catch entry in try-catch construct must be a list" if
257
+ not task['catch'].kind_of?(Array)
258
+ # try and catch errors
259
+ begin
260
+ run_block(task['try'], dry)
261
+ rescue
262
+ @targets.build.listener.recover if @targets.build.listener
263
+ run_block(task['catch'], dry)
264
+ end
265
+ end
266
+
267
+ # Run a block, that is a list of tasks.
268
+ # - block: the block to run as a list of tasks.
269
+ # - dry: tells if we run in dry mode.
270
+ def run_block(block, dry)
271
+ for task in block
272
+ run_task(task, dry)
273
+ end
274
+ end
275
+
276
+ # Evaluate a given expression and raise a BuildError if an error happens.
277
+ def evaluate(expression)
278
+ begin
279
+ return @targets.build.context.evaluate_script(expression)
280
+ rescue
281
+ error "Error evaluating expression: #{$!}"
282
+ end
283
+ end
284
+
285
+ end
286
+
287
+ end