teapot 0.9.10 → 1.0.0.pre.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -3
  3. data/README.md +3 -4
  4. data/Rakefile +3 -6
  5. data/bin/teapot +3 -7
  6. data/lib/teapot/build.rb +166 -18
  7. data/lib/teapot/configuration.rb +0 -1
  8. data/lib/teapot/context.rb +37 -21
  9. data/lib/teapot/controller/build.rb +24 -7
  10. data/lib/teapot/controller/fetch.rb +1 -1
  11. data/lib/teapot/controller.rb +1 -0
  12. data/lib/teapot/definition.rb +1 -1
  13. data/lib/teapot/dependency.rb +2 -6
  14. data/lib/teapot/environment/base.rb +7 -15
  15. data/lib/teapot/environment/constructor.rb +28 -0
  16. data/lib/teapot/environment/flatten.rb +42 -1
  17. data/lib/teapot/environment/system.rb +3 -3
  18. data/lib/teapot/extractors/linker_extractor.rb +2 -2
  19. data/lib/teapot/loader.rb +9 -11
  20. data/lib/teapot/name.rb +4 -0
  21. data/lib/teapot/package.rb +5 -4
  22. data/lib/teapot/repository.rb +29 -1
  23. data/lib/teapot/rule.rb +196 -0
  24. data/lib/teapot/rulebook.rb +91 -0
  25. data/lib/teapot/target.rb +15 -40
  26. data/lib/teapot/version.rb +1 -1
  27. data/{lib/teapot/build/targets/application.rb → spec/teapot/build_spec.rb} +26 -29
  28. data/{test/test_teapot.rb → spec/teapot/context_spec.rb} +13 -13
  29. data/spec/teapot/dependency_spec.rb +113 -0
  30. data/spec/teapot/environment_spec.rb +91 -0
  31. data/{lib/teapot/build/graph.rb → spec/teapot/name_spec.rb} +26 -26
  32. data/{test/test_substitutions.rb → spec/teapot/substitutions_spec.rb} +36 -36
  33. data/{test → spec/teapot}/teapot.rb +1 -1
  34. data/teapot.gemspec +16 -9
  35. metadata +98 -51
  36. data/lib/teapot/build/component.rb +0 -69
  37. data/lib/teapot/build/file_list.rb +0 -67
  38. data/lib/teapot/build/linker.rb +0 -49
  39. data/lib/teapot/build/target.rb +0 -76
  40. data/lib/teapot/build/targets/compiler.rb +0 -83
  41. data/lib/teapot/build/targets/directory.rb +0 -63
  42. data/lib/teapot/build/targets/executable.rb +0 -56
  43. data/lib/teapot/build/targets/external.rb +0 -91
  44. data/lib/teapot/build/targets/files.rb +0 -82
  45. data/lib/teapot/build/targets/library.rb +0 -117
  46. data/lib/teapot/commands.rb +0 -139
  47. data/lib/teapot/controller/run.rb +0 -43
  48. data/lib/teapot/graph.rb +0 -136
  49. data/test/test_dependency.rb +0 -112
  50. data/test/test_environment.rb +0 -102
@@ -36,15 +36,15 @@ module Teapot
36
36
  end
37
37
  end
38
38
 
39
- def self.convert_to_shell(values)
40
- Hash[values.map{|key, value| [
39
+ def self.convert_to_shell(environment)
40
+ Hash[environment.values.map{|key, value| [
41
41
  key.to_s.upcase,
42
42
  shell_escape(value)
43
43
  ]}]
44
44
  end
45
45
 
46
46
  def self.dump(environment, io = STDOUT)
47
- environment.to_hash.each do |key, value|
47
+ environment.values.each do |key, value|
48
48
  io.puts "#{key}:".rjust(20).color(:magenta) + " #{value.inspect}"
49
49
  end
50
50
  end
@@ -28,7 +28,7 @@ module Teapot
28
28
  roots = []
29
29
  libraries = []
30
30
  paths = []
31
-
31
+
32
32
  # Extract include directories:
33
33
  flags.each do |option|
34
34
  if option.to_s =~ /^-L(.+)/
@@ -37,7 +37,7 @@ module Teapot
37
37
  libraries << Pathname($1)
38
38
  end
39
39
  end
40
-
40
+
41
41
  libraries.each do |name|
42
42
  archive_name = "lib#{name}.a"
43
43
 
data/lib/teapot/loader.rb CHANGED
@@ -22,13 +22,15 @@ require 'teapot/project'
22
22
  require 'teapot/target'
23
23
  require 'teapot/generator'
24
24
  require 'teapot/configuration'
25
+ require 'teapot/rule'
25
26
 
26
27
  require 'teapot/name'
27
- require 'teapot/build'
28
+
29
+ require 'build/files'
28
30
 
29
31
  module Teapot
30
- LOADER_VERSION = "0.9"
31
- MINIMUM_LOADER_VERSION = "0.8"
32
+ LOADER_VERSION = "1.0.0"
33
+ MINIMUM_LOADER_VERSION = "1.0"
32
34
 
33
35
  class IncompatibleTeapotError < StandardError
34
36
  def initialize(package, version)
@@ -47,18 +49,14 @@ module Teapot
47
49
  end
48
50
 
49
51
  class Loader
52
+ Files = ::Build::Files
53
+
50
54
  class Definitions < Array
51
55
  def default_configuration
52
56
  find{|definition| Configuration === definition}
53
57
  end
54
58
  end
55
59
 
56
- # Provides build_directory and build_external methods
57
- include Build::Helpers
58
-
59
- # Provides run_executable and other related methods.
60
- include Commands::Helpers
61
-
62
60
  def initialize(context, package)
63
61
  @context = context
64
62
  @package = package
@@ -134,9 +132,9 @@ module Teapot
134
132
  # Load a teapot.rb file relative to the root of the @package.
135
133
  def load(path)
136
134
  absolute_path = @package.path + path
137
-
135
+
138
136
  raise NonexistantTeapotError.new(absolute_path) unless File.exist?(absolute_path)
139
-
137
+
140
138
  self.instance_eval(absolute_path.read, absolute_path.to_s)
141
139
 
142
140
  if @version == nil
data/lib/teapot/name.rb CHANGED
@@ -24,6 +24,10 @@ module Teapot
24
24
  @text = text
25
25
  end
26
26
 
27
+ def self.from_target(string)
28
+ self.new(string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}.strip)
29
+ end
30
+
27
31
  attr :text
28
32
 
29
33
  def identifier
@@ -18,19 +18,20 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'pathname'
21
+ require 'build/files'
22
22
 
23
23
  require 'teapot/context'
24
24
  require 'teapot/environment'
25
- require 'teapot/commands'
26
25
 
27
26
  require 'teapot/definition'
28
27
 
29
28
  module Teapot
29
+ Path = Build::Files::Path
30
+
30
31
  class Package
31
32
  def initialize(path, name, options = {})
32
33
  # The path where the package is (or will be) located:
33
- @path = path
34
+ @path = Path[path]
34
35
 
35
36
  # Get the name of the package from the options, if provided:
36
37
  if options[:name]
@@ -49,7 +50,7 @@ module Teapot
49
50
 
50
51
  # Copy the options provided:
51
52
  @options = options
52
- end
53
+ end
53
54
 
54
55
  attr :name
55
56
  attr :path
@@ -18,10 +18,38 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'teapot/commands'
21
+ require 'rexec'
22
22
 
23
23
  module Teapot
24
24
  module Git
25
+ module Commands
26
+ class CommandError < StandardError
27
+ end
28
+
29
+ def self.run(*args, &block)
30
+ options = Hash === args.last ? args.pop : {}
31
+ options[:passthrough] ||= :all
32
+
33
+ args = args.flatten.collect &:to_s
34
+
35
+ puts args.join(' ').color(:blue) + " in #{options[:chdir] || Dir.getwd}"
36
+
37
+ task = RExec::Task.open(args, options, &block)
38
+
39
+ if task.wait == 0
40
+ true
41
+ else
42
+ raise CommandError.new("Non-zero exit status: #{args.join(' ')}!")
43
+ end
44
+ end
45
+
46
+ def self.run!(*args, &block)
47
+ run(*args, &block)
48
+ rescue CommandError
49
+ false
50
+ end
51
+ end
52
+
25
53
  class Repository
26
54
  def initialize(root, options = {})
27
55
  @root = root
@@ -0,0 +1,196 @@
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 Teapot
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 dynamic?
40
+ @dynamic != nil
41
+ end
42
+
43
+ def implicit?
44
+ dynamic? and @options[:implicit]
45
+ end
46
+
47
+ def typed?
48
+ @options[:typed]
49
+ end
50
+
51
+ def applicable? arguments
52
+ # The parameter is either optional, or is included in the argument list, otherwise we fail.
53
+ unless @options[:optional] or arguments.include?(@name)
54
+ return false
55
+ end
56
+
57
+ value = arguments[@name]
58
+
59
+ # If the parameter is optional, and wasn't provided, we are okay.
60
+ if @options[:optional]
61
+ return true if value == nil
62
+ end
63
+
64
+ # If the parameter is typed, and we don't match the expected type, we fail.
65
+ if type = @options[:typed]
66
+ return false unless type === value
67
+ end
68
+
69
+ # If a pattern is provided, we must match it.
70
+ if pattern = @options[:pattern]
71
+ return Array(value).all? {|item| pattern.match(item)}
72
+ end
73
+
74
+ return true
75
+ end
76
+
77
+ def compute(arguments)
78
+ if implicit?
79
+ @dynamic.call(arguments)
80
+ elsif dynamic?
81
+ @dynamic.call(arguments[@name], arguments)
82
+ else
83
+ arguments[@name]
84
+ end
85
+ end
86
+
87
+ def inspect
88
+ "#{direction}:#{@name} (#{options.inspect})"
89
+ end
90
+ end
91
+
92
+ def initialize(process_name, type)
93
+ @name = process_name + "." + type
94
+ @full_name = @name.gsub(/[^\w]/, '_')
95
+
96
+ @process_name = process_name.gsub('-', '_').to_sym
97
+ @type = type
98
+
99
+ @apply = nil
100
+
101
+ @parameters = []
102
+ end
103
+
104
+ # compile.cpp
105
+ attr :name
106
+
107
+ # compile
108
+ attr :process_name
109
+
110
+ # compile_cpp
111
+ attr :full_name
112
+
113
+ attr :primary_output
114
+
115
+ def input(name, options = {}, &block)
116
+ @parameters << Parameter.new(:input, name, options, &block)
117
+ end
118
+
119
+ def parameter(name, options = {}, &block)
120
+ @parameters << Parameter.new(:argument, name, options, &block)
121
+ end
122
+
123
+ def output(name, options = {}, &block)
124
+ @parameters << Parameter.new(:output, name, options, &block)
125
+
126
+ @primary_output ||= @parameters.last
127
+ end
128
+
129
+ # Check if this rule can process these parameters
130
+ def applicable?(arguments)
131
+ @parameters.each do |parameter|
132
+ next if parameter.implicit?
133
+
134
+ return false unless parameter.applicable?(arguments)
135
+ end
136
+
137
+ return true
138
+ end
139
+
140
+ def normalize(arguments)
141
+ Hash[
142
+ @parameters.collect do |parameter|
143
+ [parameter.name, parameter.compute(arguments)]
144
+ end
145
+ ]
146
+ end
147
+
148
+ def files(arguments)
149
+ input_files = []
150
+ output_files = []
151
+
152
+ @parameters.each do |parameter|
153
+ # This could probably be improved a bit, we are assuming all parameters are file based:
154
+ value = arguments[parameter.name]
155
+
156
+ next unless value
157
+
158
+ case parameter.direction
159
+ when :input
160
+ input_files << value
161
+ when :output
162
+ output_files << value
163
+ end
164
+ end
165
+
166
+ return Build::Files::Composite.new(input_files), Build::Files::Composite.new(output_files)
167
+ end
168
+
169
+ def apply(&block)
170
+ @apply = Proc.new(&block)
171
+ end
172
+
173
+ def apply!(scope, arguments)
174
+ scope.instance_exec(arguments, &@apply) if @apply
175
+ end
176
+
177
+ def result(arguments)
178
+ if @primary_output
179
+ arguments[@primary_output.name]
180
+ end
181
+ end
182
+
183
+ def to_s
184
+ "<#{self.class.name} #{@name.dump}>"
185
+ end
186
+ end
187
+
188
+ class NoApplicableRule < StandardError
189
+ def initialize(name, arguments)
190
+ super "No applicable rule with name #{name}.* for parameters: #{arguments.inspect}"
191
+
192
+ @name = name
193
+ @arguments = arguments
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,91 @@
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 'teapot/rule'
22
+
23
+ module Teapot
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
+ update(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
+ update(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
+ rulebook << object
86
+ end
87
+
88
+ return rulebook
89
+ end
90
+ end
91
+ end
data/lib/teapot/target.rb CHANGED
@@ -19,10 +19,11 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'pathname'
22
- require 'teapot/build'
23
22
  require 'teapot/dependency'
24
23
  require 'teapot/definition'
25
24
 
25
+ require 'teapot/rulebook'
26
+
26
27
  module Teapot
27
28
  class BuildError < StandardError
28
29
  end
@@ -32,19 +33,23 @@ module Teapot
32
33
 
33
34
  def initialize(context, package, name)
34
35
  super context, package, name
35
-
36
+
36
37
  @build = nil
38
+
39
+ @rulebook = Rulebook.new
37
40
  end
38
-
39
- def builder
40
- Build.top(@path)
41
- end
42
-
43
- def environment_for_configuration(configuration)
41
+
42
+ attr :rulebook
43
+
44
+ def provision_chain(configuration)
44
45
  # Reduce the number of keystrokes for good health:
45
46
  context = configuration.context
46
47
 
47
48
  chain = Dependency::chain(context.selection, dependencies, context.targets.values)
49
+ end
50
+
51
+ def environment_for_configuration(configuration)
52
+ chain = provision_chain(configuration)
48
53
 
49
54
  environments = []
50
55
 
@@ -63,45 +68,15 @@ module Teapot
63
68
  default platforms_path configuration.platforms_path
64
69
  default build_prefix {platforms_path + "cache/#{platform_name}-#{variant}"}
65
70
  default install_prefix {platforms_path + "#{platform_name}-#{variant}"}
66
-
67
- append buildflags {"-I#{install_prefix + "include"}"}
68
- append linkflags {"-L#{install_prefix + "lib"}"}
69
71
  end
70
72
  end
71
-
73
+
72
74
  def build(&block)
73
75
  if block_given?
74
- @build = Proc.new(&block)
76
+ @build = block
75
77
  end
76
78
 
77
79
  return @build
78
80
  end
79
-
80
- def build!(configuration)
81
- return unless @build
82
-
83
- local_environment = environment_for_configuration(configuration)
84
-
85
- @build.call(local_environment)
86
- end
87
-
88
- # Specify a default block which is used to run the configuration.
89
- def run(&block)
90
- if block_given?
91
- @run = block
92
- end
93
-
94
- return @run
95
- end
96
-
97
- def run!(configuration)
98
- return unless @run
99
-
100
- local_environment = environment_for_configuration(configuration)
101
-
102
- if @run
103
- @run.call(local_environment)
104
- end
105
- end
106
81
  end
107
82
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Teapot
22
- VERSION = "0.9.10"
22
+ VERSION = "1.0.0-rc1"
23
23
  end
@@ -1,4 +1,4 @@
1
- # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -18,38 +18,35 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'teapot/build/targets/library'
22
- require 'teapot/build/targets/directory'
21
+ require 'teapot/build'
22
+ require 'teapot/environment'
23
23
 
24
- module Teapot
25
- module Build
26
- module Targets
27
- class Application < Directory
28
- def initialize(parent, name, options = {})
29
- super parent
30
-
31
- @name = name
32
- @options = options
33
- end
34
-
35
- def subdirectory
36
- "Applications/#{@name}"
37
- end
38
-
39
- def << target
40
- if target.respond_to? :subdirectory
41
- target.options[:subdirectory] = subdirectory
42
- end
43
-
44
- super
45
- end
24
+ module Teapot::BuildSpec
25
+ class DummyTarget
26
+ def name
27
+ "dummy-target"
28
+ end
29
+
30
+ def build
31
+ lambda do
32
+ # This is technically incorrect, because this Top graph node specifies no outputs. But, for testing, it's fine.
33
+ fs.touch "bob"
46
34
  end
35
+ end
36
+ end
37
+
38
+ describe Teapot::Build do
39
+ let(:environment) {Teapot::Environment.hash(foo: 'bar')}
40
+
41
+ it "should create a simple build graph" do
42
+ expect(FileUtils::NoWrite).to receive(:touch).with("bob").once
43
+ expect(FileUtils::Verbose).to receive(:touch).with("bob").once
47
44
 
48
- class Directory
49
- def add_application(*args, &block)
50
- self << Application.target(self, *args, &block)
51
- end
45
+ controller = Teapot::Build::Controller.new do |controller|
46
+ controller.add_target(DummyTarget.new, environment)
52
47
  end
48
+
49
+ controller.update!
53
50
  end
54
51
  end
55
52
  end
@@ -18,25 +18,25 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require "minitest/autorun"
22
-
23
21
  require 'teapot/context'
24
22
 
25
- class TestConfig < MiniTest::Test
26
- ROOT = Pathname.new(__FILE__).dirname
23
+ module Teapot::ContextSpec
24
+ ROOT = Build::Files::Path.new(__dir__)
27
25
 
28
- def test_context
29
- context = Teapot::Context.new(ROOT)
26
+ describe Teapot::Context do
27
+ it "should load teapot.rb file" do
28
+ context = Teapot::Context.new(ROOT)
30
29
 
31
- # There is one configuration:
32
- assert_equal 1, context.configurations.count
30
+ # There is one configuration:
31
+ expect(context.configurations.count).to be == 1
33
32
 
34
- # No targets were defined:
35
- assert_equal 0, context.targets.count
33
+ # No targets were defined:
34
+ expect(context.targets.count).to be == 0
36
35
 
37
- default_configuration = context.configuration
36
+ default_configuration = context.configuration
38
37
 
39
- # 13 defined packages + 1 implicit package.
40
- assert_equal 14, default_configuration.packages.count
38
+ # 13 defined packages + 1 implicit package.
39
+ expect(default_configuration.packages.count).to be == 14
40
+ end
41
41
  end
42
42
  end