build 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 368f86c767a546f503b2ca38d977c5b724651f9d
4
- data.tar.gz: acc789f1b82117f1f506540bd679b13a8e5e246f
3
+ metadata.gz: ee861c5692f6b6afec96a8b96060141b8a36d96e
4
+ data.tar.gz: 7b0ff0dc360903ede211632cd9a0e23d91949c01
5
5
  SHA512:
6
- metadata.gz: 7030bee4d6ff7cbcf0ba264ae69455fde11a28b9e612d237695831d0da78c85822d5ae9a5ebd9c990831bd0a5fd46dd8f766c3eca85ddc7dbf50eb2709868483
7
- data.tar.gz: 08f9d3a362ae8c742ac5ab1f26f6b54ffc84909a82ce5a3ac475bfc79f705e01421c44e7ce076a5a1d043f82a0c91ba55ae1e2af507b605e6a3f417c9c9350a9
6
+ metadata.gz: bd215999b98b76f04aa2d0fc3388340c7c1d5907971315776e9953845ad06dfca29ff951ee460716356f99f22a407e3029a7e8599842fe0f44b2fac028396022
7
+ data.tar.gz: 8b447b51fec6f690d596a4a86353463a679e1a0900425bb0ca059e472fd2683d83a091fa1b3b0f582e1bf5741c1b63f2e9bb6b512f7f2185febf55f4a1828e42
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in build.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'simplecov'
8
+ gem 'coveralls', require: false
9
+ end
data/build.gemspec CHANGED
@@ -19,6 +19,12 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.required_ruby_version = '>= 2.0'
21
21
 
22
+ spec.add_dependency "build-graph", "~> 1.0.0"
23
+ spec.add_dependency "build-environment", "~> 1.0.0"
24
+ spec.add_dependency "build-makefile", "~> 1.0.0"
25
+
26
+ spec.add_dependency "graphviz"
27
+
22
28
  spec.add_development_dependency "bundler", "~> 1.3"
23
29
  spec.add_development_dependency "rspec", "~> 3.0.0.rc1"
24
30
  spec.add_development_dependency "rake"
@@ -0,0 +1,232 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'rulebook'
22
+
23
+ require 'build/files'
24
+ require 'build/graph'
25
+
26
+ require 'process/group'
27
+
28
+ require_relative 'logger'
29
+
30
+ module Build
31
+ class RuleNode < Graph::Node
32
+ def initialize(rule, arguments, &block)
33
+ @arguments = arguments
34
+ @rule = rule
35
+
36
+ @callback = block
37
+
38
+ inputs, outputs = @rule.files(@arguments)
39
+
40
+ super(inputs, outputs, @rule)
41
+ end
42
+
43
+ attr :arguments
44
+ attr :rule
45
+ attr :callback
46
+
47
+ def title
48
+ @rule.title
49
+ end
50
+
51
+ def apply!(scope)
52
+ @rule.apply!(scope, @arguments)
53
+
54
+ if @callback
55
+ scope.instance_exec(@arguments, &@callback)
56
+ end
57
+ end
58
+
59
+ def inspect
60
+ @rule.name.inspect
61
+ end
62
+ end
63
+
64
+ class TargetNode < Graph::Node
65
+ def initialize(task_class, &update)
66
+ @update = update
67
+ @task_class = task_class
68
+
69
+ super(Files::Paths::NONE, :inherit, @update)
70
+ end
71
+
72
+ attr :task_class
73
+
74
+ def apply!(scope)
75
+ scope.instance_exec(&@update)
76
+ end
77
+
78
+ def inspect
79
+ @task_class.name.inspect
80
+ end
81
+ end
82
+
83
+ # This task class serves as the base class for the environment specific task classes genearted when adding targets.
84
+ class Task < Graph::Task
85
+ def initialize(walker, node, group, logger: nil)
86
+ super(walker, node)
87
+
88
+ @group = group
89
+
90
+ @logger = logger || Logger.new($stderr)
91
+ end
92
+
93
+ def wet?
94
+ @node.dirty?
95
+ end
96
+
97
+ def run(*arguments)
98
+ if wet?
99
+ @logger.info('shell') {arguments}
100
+ status = @group.spawn(*arguments)
101
+
102
+ if status != 0
103
+ raise CommandError.new(status)
104
+ end
105
+ end
106
+ end
107
+
108
+ alias run! run
109
+
110
+ def touch(path)
111
+ return unless wet?
112
+
113
+ @logger.info('shell'){ ['touch', path] }
114
+ FileUtils.touch(path)
115
+ end
116
+
117
+ def cp(source_path, destination_path)
118
+ return unless wet?
119
+
120
+ @logger.info('shell'){ ['cp', source_path, destination_path]}
121
+ FileUtils.copy(source_path, destination_path)
122
+ end
123
+
124
+ def rm(path)
125
+ return unless wet?
126
+
127
+ @logger.info('shell'){ ['rm', path] }
128
+ FileUtils.rm(path)
129
+ end
130
+
131
+ def mkpath(path)
132
+ return unless wet?
133
+
134
+ unless File.exist?(path)
135
+ @logger.info('shell'){ ['mkpath', path] }
136
+
137
+ FileUtils.mkpath(path)
138
+ end
139
+ end
140
+
141
+ def install(source_path, destination_path)
142
+ return unless wet?
143
+
144
+ @logger.info('shell'){ ['install', source_path, destination_path]}
145
+ FileUtils.install(source_path, destination_path)
146
+ end
147
+
148
+ # Legacy FileUtils access, replaced with direct function calls.
149
+ def fs
150
+ self
151
+ end
152
+
153
+ def update
154
+ @node.apply!(self)
155
+ end
156
+
157
+ def invoke_rule(rule, arguments, &block)
158
+ arguments = rule.normalize(arguments, self)
159
+
160
+ @logger.debug('invoke') {"-> #{rule}: #{arguments.inspect}"}
161
+
162
+ node = RuleNode.new(rule, arguments, &block)
163
+ task = invoke(node)
164
+
165
+ @logger.debug('invoke') {"<- #{rule}: #{rule.result(arguments)}"}
166
+
167
+ return rule.result(arguments)
168
+ end
169
+ end
170
+
171
+ class Controller
172
+ def initialize
173
+ @module = Module.new
174
+
175
+ @logger = Logger.new($stdout)
176
+ @logger.level = Logger::INFO
177
+ @logger.formatter = CompactFormatter.new
178
+
179
+ # Top level nodes:
180
+ @nodes = []
181
+
182
+ yield self
183
+
184
+ @nodes.freeze
185
+
186
+ @group = Process::Group.new
187
+
188
+ # The task class is captured as we traverse all the top level targets:
189
+ @task_class = nil
190
+
191
+ @walker = Graph::Walker.new do |walker, node|
192
+ # Instantiate the task class here:
193
+ task = @task_class.new(walker, node, @group, logger: @logger)
194
+
195
+ task.visit do
196
+ task.update
197
+ end
198
+ end
199
+ end
200
+
201
+ attr :nodes
202
+ attr :visualisation
203
+
204
+ def add_target(target, environment)
205
+ task_class = Rulebook.for(environment).with(Task, environment: environment, target: target)
206
+
207
+ # Not sure if this is a good idea - makes debugging slightly easier.
208
+ Object.const_set("TaskClassFor#{Name.from_target(target.name).identifier}_#{self.object_id}", task_class)
209
+
210
+ @nodes << TargetNode.new(task_class, &target.build)
211
+ end
212
+
213
+ def update
214
+ @nodes.each do |node|
215
+ # Update the task class here:
216
+ @task_class = node.task_class
217
+
218
+ @walker.call(node)
219
+ end
220
+
221
+ @group.wait
222
+
223
+ yield @walker if block_given?
224
+ end
225
+
226
+ def run(&block)
227
+ @walker.run do
228
+ self.update(&block)
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,49 @@
1
+ # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Build
22
+ def self.graph_visualisation(walker)
23
+ viz = Graphviz::Graph.new('G', rankdir: "LR")
24
+
25
+ walker.tasks.each do |node, task|
26
+ input_nodes = []
27
+ output_nodes = []
28
+
29
+ task.inputs.each do |path|
30
+ input_nodes << viz.add_node(path.basename)
31
+ end
32
+
33
+ task.outputs.each do |path|
34
+ output_nodes << viz.add_node(path.basename)
35
+ end
36
+
37
+ if output_nodes.size == 1
38
+ input_nodes.each do |input_node|
39
+ edge = input_node.connect(output_nodes.first)
40
+ edge.attributes[:label] = node.title
41
+ end
42
+ end
43
+ end
44
+
45
+ return viz
46
+ #Graphviz::output(viz, path: ENV['BUILD_GRAPH_PDF']) rescue nil
47
+ #`dot -Tpdf graph.dot > graph.pdf && open graph.pdf`
48
+ end
49
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'logger'
22
+ require 'rainbow'
23
+
24
+ module Build
25
+ class CompactFormatter
26
+ def initialize
27
+ @start = Time.now
28
+ end
29
+
30
+ def time_offset_string
31
+ offset = Time.now - @start
32
+
33
+ "T+#{offset.round(2).to_s.ljust(5)}"
34
+ end
35
+
36
+ def chdir_string(options)
37
+ if options[:chdir]
38
+ " in #{options[:chdir]}"
39
+ else
40
+ ""
41
+ end
42
+ end
43
+
44
+ def format_command(args)
45
+ options = Hash === args.last ? args.pop : {}
46
+ args = args.flatten.collect &:to_s
47
+
48
+ Rainbow(args.join(' ')).blue + chdir_string(options)
49
+ end
50
+
51
+ def call(severity, datetime, progname, msg)
52
+ if progname == 'shell' and Array === msg
53
+ "#{time_offset_string}: #{format_command(msg)}\n"
54
+ else
55
+ "#{time_offset_string}: #{msg}\n"
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/build/name.rb ADDED
@@ -0,0 +1,49 @@
1
+ # Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Build
22
+ class Name
23
+ def initialize(text)
24
+ @text = text
25
+ end
26
+
27
+ def self.from_target(string)
28
+ self.new(string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}.strip)
29
+ end
30
+
31
+ attr :text
32
+
33
+ def identifier
34
+ @identifier ||= @text.gsub(/\s+/, '')
35
+ end
36
+
37
+ def target
38
+ @target ||= @text.gsub(/\s+/, '-').downcase
39
+ end
40
+
41
+ def macro(prefix = [])
42
+ (Array(prefix) + [@text]).collect{|name| name.upcase.gsub(/\s+/, '_')}.join('_')
43
+ end
44
+
45
+ def header_guard(path)
46
+ macro(path) + '_H'
47
+ end
48
+ end
49
+ end
data/lib/build/rule.rb ADDED
@@ -0,0 +1,208 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Build
22
+ # A rule is a function with a specific set of input and output parameters, which can match against a given set of specific inputs and outputs. For example, there might be several rules for compiling, but the specific rules depend on the language being compiled.
23
+ class Rule
24
+ class Parameter
25
+ def initialize(direction, name, options = {}, &block)
26
+ @direction = direction
27
+ @name = name
28
+
29
+ @options = options
30
+
31
+ @dynamic = block_given? ? Proc.new(&block) : nil
32
+ end
33
+
34
+ attr :direction
35
+ attr :name
36
+
37
+ attr :options
38
+
39
+ def input?
40
+ @direction == :input
41
+ end
42
+
43
+ def output?
44
+ @direction == :output
45
+ end
46
+
47
+ def dynamic?
48
+ @dynamic != nil
49
+ end
50
+
51
+ def implicit?
52
+ dynamic? and @options[:implicit]
53
+ end
54
+
55
+ # Optional parameters are those that are either defined as optional or implicit.
56
+ def optional?
57
+ @options[:optional] || implicit?
58
+ end
59
+
60
+ def applicable? arguments
61
+ value = arguments.fetch(@name) do
62
+ # Value couldn't be found, if it wasn't optional, this parameter didn't apply:
63
+ return optional?
64
+ end
65
+
66
+ # If a pattern is provided, we must match it.
67
+ if pattern = @options[:pattern]
68
+ return Array(value).all? {|item| pattern.match(item)}
69
+ end
70
+
71
+ return true
72
+ end
73
+
74
+ def compute(arguments, scope)
75
+ if implicit?
76
+ # Can be replaced if supplied:
77
+ arguments[@name] || scope.instance_exec(arguments, &@dynamic)
78
+ elsif dynamic?
79
+ # Argument is optional:
80
+ scope.instance_exec(arguments[@name], arguments, &@dynamic)
81
+ else
82
+ arguments[@name]
83
+ end
84
+ end
85
+
86
+ def inspect
87
+ "#{direction}:#{@name} (#{options.inspect})"
88
+ end
89
+ end
90
+
91
+ def initialize(process_name, type)
92
+ @name = process_name + "." + type
93
+ @full_name = @name.gsub(/[^\w]/, '_')
94
+
95
+ @process_name = process_name.gsub('-', '_').to_sym
96
+ @type = type
97
+
98
+ @apply = nil
99
+
100
+ @parameters = []
101
+ end
102
+
103
+ # compile.cpp
104
+ attr :name
105
+
106
+ attr :parameters
107
+
108
+ # compile
109
+ attr :process_name
110
+
111
+ # compile_cpp
112
+ attr :full_name
113
+
114
+ attr :primary_output
115
+
116
+ def input(name, options = {}, &block)
117
+ self << Parameter.new(:input, name, options, &block)
118
+ end
119
+
120
+ def parameter(name, options = {}, &block)
121
+ self << Parameter.new(:argument, name, options, &block)
122
+ end
123
+
124
+ def output(name, options = {}, &block)
125
+ self << Parameter.new(:output, name, options, &block)
126
+ end
127
+
128
+ def << parameter
129
+ @parameters << parameter
130
+
131
+ if parameter.output?
132
+ @primary_output ||= parameter
133
+ end
134
+ end
135
+
136
+ # Check if this rule can process these parameters
137
+ def applicable?(arguments)
138
+ @parameters.each do |parameter|
139
+ next if parameter.implicit?
140
+
141
+ return false unless parameter.applicable?(arguments)
142
+ end
143
+
144
+ return true
145
+ end
146
+
147
+ # The scope is the context in which the dynamic rule computation is done, usually an instance of Task.
148
+ def normalize(arguments, scope)
149
+ Hash[
150
+ @parameters.collect do |parameter|
151
+ [parameter.name, parameter.compute(arguments, scope)]
152
+ end
153
+ ]
154
+ end
155
+
156
+ def files(arguments)
157
+ input_files = []
158
+ output_files = []
159
+
160
+ @parameters.each do |parameter|
161
+ # This could probably be improved a bit, we are assuming all parameters are file based:
162
+ value = arguments[parameter.name]
163
+
164
+ next unless value
165
+
166
+ case parameter.direction
167
+ when :input
168
+ input_files << value
169
+ when :output
170
+ output_files << value
171
+ end
172
+ end
173
+
174
+ return Build::Files::Composite.new(input_files), Build::Files::Composite.new(output_files)
175
+ end
176
+
177
+ def apply(&block)
178
+ @apply = Proc.new(&block)
179
+ end
180
+
181
+ def apply!(scope, arguments)
182
+ scope.instance_exec(arguments, &@apply) if @apply
183
+ end
184
+
185
+ def result(arguments)
186
+ if @primary_output
187
+ arguments[@primary_output.name]
188
+ end
189
+ end
190
+
191
+ def ==(other)
192
+ other.kind_of?(self.class) and @name == other.name and @parameters == other.parameters
193
+ end
194
+
195
+ def to_s
196
+ "<#{self.class.name} #{@name.dump}>"
197
+ end
198
+ end
199
+
200
+ class NoApplicableRule < StandardError
201
+ def initialize(name, arguments)
202
+ super "No applicable rule with name #{name}.* for parameters: #{arguments.inspect}"
203
+
204
+ @name = name
205
+ @arguments = arguments
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,93 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'rule'
22
+
23
+ module Build
24
+ class Rulebook
25
+ def initialize
26
+ @rules = {}
27
+ @processes = {}
28
+ end
29
+
30
+ attr :rules
31
+
32
+ def << rule
33
+ @rules[rule.name] = rule
34
+
35
+ # A cache for fast process/file-type lookup:
36
+ processes = @processes[rule.process_name] ||= []
37
+ processes << rule
38
+ end
39
+
40
+ def [] name
41
+ @rules[name]
42
+ end
43
+
44
+ def with(superclass, state = {})
45
+ task_class = Class.new(superclass)
46
+
47
+ # Define methods for all processes, e.g. task_class#compile
48
+ @processes.each do |key, rules|
49
+ # Define general rules, which use rule applicability for disambiguation:
50
+ task_class.send(:define_method, key) do |arguments, &block|
51
+ rule = rules.find{|rule| rule.applicable? arguments }
52
+
53
+ if rule
54
+ invoke_rule(rule, arguments, &block)
55
+ else
56
+ raise NoApplicableRule.new(key, arguments)
57
+ end
58
+ end
59
+ end
60
+
61
+ # Define methods for all rules, e.g. task_class#compile_cpp
62
+ @rules.each do |key, rule|
63
+ task_class.send(:define_method, rule.full_name) do |arguments, &block|
64
+ invoke_rule(rule, arguments, &block)
65
+ end
66
+ end
67
+
68
+ state.each do |key, value|
69
+ task_class.send(:define_method, key) do
70
+ value
71
+ end
72
+ end
73
+
74
+ return task_class
75
+ end
76
+
77
+ def self.for(environment)
78
+ rulebook = self.new
79
+
80
+ environment.defined.each do |name, define|
81
+ object = define.klass.new(*name.split('.', 2))
82
+
83
+ object.instance_eval(&define.block)
84
+
85
+ object.freeze
86
+
87
+ rulebook << object
88
+ end
89
+
90
+ return rulebook
91
+ end
92
+ end
93
+ end
data/lib/build/version.rb CHANGED
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Build
22
- VERSION = "0.0.1"
22
+ VERSION = "1.0.0"
23
23
  end
@@ -0,0 +1,85 @@
1
+ # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'build/environment'
22
+ require 'build/rulebook'
23
+ require 'build/controller'
24
+
25
+ module Build::ControllerSpec
26
+ class Target
27
+ def initialize(name)
28
+ @name = name
29
+ end
30
+
31
+ attr :name
32
+
33
+ def build(&block)
34
+ @build = block if block_given?
35
+
36
+ return @build
37
+ end
38
+ end
39
+
40
+ describe Build::Controller do
41
+ it "should execute the build graph" do
42
+ environment = Build::Environment.new do
43
+ define Build::Rule, "make.file" do
44
+ output :destination
45
+
46
+ apply do |parameters|
47
+ fs.touch parameters[:destination]
48
+ end
49
+ end
50
+
51
+ define Build::Rule, "copy.file" do
52
+ input :source
53
+ output :destination
54
+
55
+ apply do |parameters|
56
+ fs.cp parameters[:source], parameters[:destination]
57
+ end
58
+ end
59
+ end
60
+
61
+ target = Target.new('copy')
62
+ target.build do
63
+ foo_path = Build::Files::Path['foo']
64
+ bar_path = Build::Files::Path['bar']
65
+
66
+ make destination: foo_path
67
+
68
+ copy source: foo_path, destination: bar_path
69
+ end
70
+
71
+ controller = Build::Controller.new do |controller|
72
+ controller.add_target(target, environment)
73
+ end
74
+
75
+ expect(controller.nodes.size).to be 1
76
+
77
+ controller.update
78
+
79
+ expect(File).to be_exist('foo')
80
+ expect(File).to be_exist('bar')
81
+
82
+ FileUtils.rm ['foo', 'bar']
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'build/name'
22
+
23
+ module Build::NameSpec
24
+ describe Build::Name do
25
+ let(:name) {Build::Name.new('Foo Bar')}
26
+ it "retains the original text" do
27
+ expect(name.text).to be == 'Foo Bar'
28
+ end
29
+
30
+ it "should generate useful identifiers" do
31
+ expect(name.identifier).to be == 'FooBar'
32
+ end
33
+
34
+ it "should generate useful target names" do
35
+ expect(name.target).to be == 'foo-bar'
36
+ end
37
+
38
+ it "should generate useful macro names" do
39
+ expect(name.macro).to be == 'FOO_BAR'
40
+ end
41
+
42
+ it "should generate useful macro names" do
43
+ expect(name.macro).to be == 'FOO_BAR'
44
+ end
45
+
46
+ it "can be constructed from target name" do
47
+ expect(Build::Name.from_target(name.target).text).to be == name.text
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'build/rule'
22
+
23
+ module Build::RuleSpec
24
+ describe Build::Rule do
25
+ it "should validate input and output parameters" do
26
+ rule = Build::Rule.new("compile", "cpp")
27
+
28
+ rule.input :source
29
+ rule.output :destination
30
+
31
+ expect(rule.parameters.size).to be 2
32
+
33
+ expect(rule.applicable?(source: 'foo', destination: 'bar')).to be_truthy
34
+ expect(rule.applicable?(source: 'foo')).to be_falsey
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'build/environment'
22
+ require 'build/rulebook'
23
+
24
+ module Build::RulebookSpec
25
+ describe Build::Rulebook do
26
+ it "should generate a valid rulebook" do
27
+ environment = Build::Environment.new do
28
+ define Build::Rule, "copy.file" do
29
+ input :source
30
+ output :destination
31
+
32
+ apply do |parameters|
33
+ fs.cp parameters[:source], parameters[:destination]
34
+ end
35
+ end
36
+
37
+ define Build::Rule, "delete.file" do
38
+ input :target
39
+
40
+ apply do |parameters|
41
+ fs.rm parameters[:target]
42
+ end
43
+ end
44
+ end
45
+
46
+ rulebook = Build::Rulebook.for(environment)
47
+
48
+ expect(rulebook.rules.size).to be 2
49
+
50
+ expect(rulebook.rules).to be_include 'copy.file'
51
+ expect(rulebook.rules).to be_include 'delete.file'
52
+ end
53
+ end
54
+ end
metadata CHANGED
@@ -1,15 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-20 00:00:00.000000000 Z
11
+ date: 2015-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: build-graph
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: build-environment
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: build-makefile
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: graphviz
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
13
69
  - !ruby/object:Gem::Dependency
14
70
  name: bundler
15
71
  requirement: !ruby/object:Gem::Requirement
@@ -68,7 +124,17 @@ files:
68
124
  - Rakefile
69
125
  - build.gemspec
70
126
  - lib/build.rb
127
+ - lib/build/controller.rb
128
+ - lib/build/graphviz.rb
129
+ - lib/build/logger.rb
130
+ - lib/build/name.rb
131
+ - lib/build/rule.rb
132
+ - lib/build/rulebook.rb
71
133
  - lib/build/version.rb
134
+ - spec/build/controller_spec.rb
135
+ - spec/build/name_spec.rb
136
+ - spec/build/rule_spec.rb
137
+ - spec/build/rulebook_spec.rb
72
138
  homepage: ''
73
139
  licenses:
74
140
  - MIT
@@ -93,4 +159,8 @@ rubygems_version: 2.2.2
93
159
  signing_key:
94
160
  specification_version: 4
95
161
  summary: Build is a framework for working with task based build systems.
96
- test_files: []
162
+ test_files:
163
+ - spec/build/controller_spec.rb
164
+ - spec/build/name_spec.rb
165
+ - spec/build/rule_spec.rb
166
+ - spec/build/rulebook_spec.rb