teapot 2.1.0 → 2.2.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: 916041044782b5244a6c8b7cb013e4a0e06e27fc
4
- data.tar.gz: adc4038ee1c679711ebf9840ad29337834f2e142
3
+ metadata.gz: 40d564684024a42a0762e51eef5fdcccea99eb81
4
+ data.tar.gz: 4dcc60428ead96eca66951c50ba891dcd366741f
5
5
  SHA512:
6
- metadata.gz: 4cbb0611a500ac31c0496a57c425e044466316b1da97d656be13f7e3ec8407663c51fdf6ceef3254db1f0c1502e7f8862b182217f7717a51ac3daac057df2cb5
7
- data.tar.gz: bc125462db83ab65e1bf344b83033aeb8e55e2a7b7ca29d766dd2f9d0b362ece8c565cffef3f5e0c7929598f8b01e9d525d00d360aa6e291026389fecc584d58
6
+ metadata.gz: 39344b2e907c0718480aeb96439bfd509c5bfabdfd8e859ea5185fd573e9b96d42594ecc4644699c7cec9270e771b598c2c381d0bc557905d3b6ed04c2f8836e
7
+ data.tar.gz: ab69319d540e01679dee8cd9ddfbb45d69fd22d363cc2dce531bc4b34c50fc198e9de35cb287516e2ddb37396ccb2dc9ca829ff7825e6e2497535684cfce015c
data/README.md CHANGED
@@ -98,19 +98,29 @@ Provided you are using an environment that supports sanitizers, you can test mor
98
98
 
99
99
  $ teapot "Test/*" variant-sanitize
100
100
 
101
- ### Build Project
102
-
103
- We can now build and run unit tests (althoght there aren't any yet):
104
-
105
- $ teapot build Test/MyProject
106
- ... snip ...
107
- [Summary] 0 passed out of 0 total
108
-
109
- When you build, you need to specify dependencies. If you haven't specified all dependencies, they will be suggested to you.
101
+ ### Run Project
102
+
103
+ We can now build and run the project executable:
104
+
105
+ $ teapot Run/MyProject
106
+ I'm a little teapot,
107
+ Short and stout,
108
+ Here is my handle (one hand on hip),
109
+ Here is my spout (other arm out with elbow and wrist bent).
110
+ When I get all steamed up,
111
+ Hear me shout,
112
+ Tip me over and pour me out! (lean over toward spout)
113
+
114
+ ~
115
+ ___^___ __
116
+ .- / \./ /
117
+ / / _/
118
+ \__| |
119
+ \_______/
110
120
 
111
121
  The resulting executables and libraries will be framework dependent, but are typically located in:
112
122
 
113
- $ cd teapot/$PROJECT_NAME/platforms/$PLATFORM_NAME/bin/
123
+ $ cd teapot/platforms/development/$PLATFORM-debug/bin
114
124
  $ ./$PROJECT_NAME
115
125
 
116
126
  ### Cloning Project
@@ -39,24 +39,22 @@ module Teapot
39
39
  many :targets, "Build these targets, or use them to help the dependency resolution process."
40
40
  split :argv, "Arguments passed to child process(es) of build if any."
41
41
 
42
- # The targets to build:
43
- def dependency_names(context)
42
+ def invoke(parent)
43
+ context = parent.context
44
+
45
+ # The targets to build:
44
46
  if @targets.any?
45
- @targets
47
+ selection = context.select(@targets)
46
48
  else
47
- context.configuration.targets[:build]
49
+ selection = context.select(context.configuration.targets[:build])
48
50
  end
49
- end
50
-
51
- def invoke(parent)
52
- context = parent.context
53
51
 
54
- chain = context.dependency_chain(dependency_names(context), context.configuration)
52
+ chain = selection.chain
55
53
 
56
54
  ordered = chain.ordered
57
55
 
58
56
  if @options[:only]
59
- ordered = context.direct_targets(ordered)
57
+ ordered = selection.direct_targets(ordered)
60
58
  end
61
59
 
62
60
  controller = ::Build::Controller.new(logger: parent.logger, limit: @options[:limit]) do |controller|
@@ -64,7 +62,7 @@ module Teapot
64
62
  target = resolution.provider
65
63
 
66
64
  if target.build
67
- environment = target.environment(context.configuration, chain)
65
+ environment = target.environment(selection.configuration, chain)
68
66
 
69
67
  controller.add_target(target, environment.flatten, self.argv)
70
68
  end
@@ -58,9 +58,8 @@ module Teapot
58
58
  Fetch[].invoke(nested)
59
59
 
60
60
  context = nested.context
61
-
62
- # The targets to build to create the initial project:
63
- target_names = context.configuration.targets[:create]
61
+ selection = context.select
62
+ target_names = selection.configuration.targets[:create]
64
63
 
65
64
  if target_names.any?
66
65
  # Generate the initial project files:
@@ -52,38 +52,34 @@ module Teapot
52
52
  logger = parent.logger
53
53
  context = parent.context
54
54
 
55
- resolved = Set.new
56
- configuration = context.configuration
57
- unresolved = context.unresolved(configuration.packages)
55
+ updated = Set.new
56
+ selection = context.select
58
57
 
59
- while true
60
- configuration.packages.each do |package|
61
- next if resolved.include? package
62
-
63
- # If specific packages were listed, limit updates to them.
64
- if @packages.nil? || @packages.empty? || @packages.include?(package.name)
65
- fetch_package(context, configuration, package, logger, **@options)
66
- end
67
-
68
- # We are done with this package, don't try to process it again:
69
- resolved << package
70
- end
71
-
72
- # Resolve any/all imports:
73
- configuration.materialize
58
+ packages = selection.configuration.packages
74
59
 
75
- previously_unresolved = unresolved
76
- unresolved = context.unresolved(configuration.packages)
60
+ if @packages.any?
61
+ packages = packages.slice(@packages)
62
+ end
77
63
 
78
- # No additional packages were resolved, we have reached a fixed point:
79
- if previously_unresolved == unresolved || unresolved.count == 0
64
+ # If no additional packages were resolved, we have reached a fixed point:
65
+ while packages.any?
66
+ packages.each do |package|
67
+ fetch_package(context, selection.configuration, package, logger, **@options)
68
+ end
69
+
70
+ selection = context.select
71
+
72
+ # If there are no unresolved packages, we are done.
73
+ if selection.unresolved.empty?
80
74
  break
81
75
  end
76
+
77
+ packages = selection.unresolved
82
78
  end
83
79
 
84
- if unresolved.count > 0
80
+ if selection.unresolved.count > 0
85
81
  logger.error "Could not fetch all packages!".color(:red)
86
- unresolved.each do |package|
82
+ selection.unresolved.each do |package|
87
83
  logger.error "\t#{package}".color(:red)
88
84
  end
89
85
  else
@@ -35,9 +35,9 @@ module Teapot
35
35
 
36
36
  def invoke(parent)
37
37
  context = parent.context
38
+
38
39
  logger = parent.logger
39
40
 
40
- # Should this somehow consider context.root_package?
41
41
  context.configuration.packages.each do |package|
42
42
  # The root package is the local package for this context:
43
43
  next unless only == nil or only.include?(package.name)
@@ -45,7 +45,8 @@ module Teapot
45
45
  logger.info "Package #{package.name} (from #{package.path}):".bright
46
46
 
47
47
  begin
48
- definitions = context.load(package)
48
+ script = context.load(package)
49
+ definitions = script.defined
49
50
 
50
51
  definitions.each do |definition|
51
52
  logger.info "\t#{definition}"
@@ -74,14 +75,12 @@ module Teapot
74
75
  logger.info "\t\t- #{provision}".color(:green)
75
76
  end
76
77
  when Configuration
77
- definition.materialize
78
-
79
78
  definition.packages.each do |package|
80
79
  logger.info "\t\t- #{package}".color(:green)
81
80
  end
82
81
 
83
82
  definition.imports.select(&:explicit).each do |import|
84
- logger.info "\t\t- unmaterialised import #{import.name}".color(:red)
83
+ logger.info "\t\t- import #{import.name}".color(:red)
85
84
  end
86
85
  end
87
86
  end
@@ -38,7 +38,6 @@ module Teapot
38
38
  context = parent.context
39
39
  logger = parent.logger
40
40
 
41
- # Should this somehow consider context.root_package?
42
41
  context.configuration.packages.each do |package|
43
42
  # The root package is the local package for this context:
44
43
  next unless only == nil or only.include?(package.name)
@@ -43,13 +43,11 @@ module Teapot
43
43
 
44
44
  def invoke(parent)
45
45
  context = parent.context
46
-
47
- configuration = context.configuration
48
-
49
- chain = context.dependency_chain(dependency_names, context.configuration)
46
+ selection = context.select(dependency_names)
47
+ chain = selection.chain
50
48
 
51
49
  if dependency_name
52
- provider = context.dependencies[dependency_name]
50
+ provider = selection.dependencies[dependency_name]
53
51
 
54
52
  # TODO The visualisation generated isn't quite right. It's introspecting too much from the packages and not reflecting #ordered and #provisions.
55
53
  chain = chain.partial(provider)
@@ -35,30 +35,29 @@ module Teapot
35
35
  :import => true
36
36
  }.freeze
37
37
 
38
- def initialize(context, package, name, packages = [], options = nil)
38
+ def initialize(context, package, name, packages = [], **options)
39
39
  super context, package, name
40
40
 
41
- if options
42
- @options = options
43
- else
44
- @options = DEFAULT_OPTIONS.dup
45
- end
41
+ @options = DEFAULT_OPTIONS.merge(options)
46
42
 
47
43
  @packages = IdentitySet.new(packages)
48
44
  @imports = IdentitySet.new
49
45
 
50
46
  @visibility = :private
51
-
47
+
52
48
  # A list of named targets for specific purposes:
53
49
  @targets = Hash.new{|hash,key| hash[key] = Array.new}
54
50
  end
55
51
 
56
52
  def freeze
53
+ return if frozen?
54
+
57
55
  @options.freeze
58
56
  @packages.freeze
59
57
  @imports.freeze
60
58
  @visibility.freeze
61
59
 
60
+ @targets.default = [].freeze
62
61
  @targets.freeze
63
62
 
64
63
  super
@@ -88,8 +87,8 @@ module Teapot
88
87
  attr :imports
89
88
 
90
89
  # Specifies that this configuration depends on an external package of some sort.
91
- def require(name, options = nil)
92
- options = options ? @options.merge(options) : @options.dup
90
+ def require(name, **options)
91
+ options = @options.merge(options)
93
92
 
94
93
  @packages << Package.new(packages_path + name.to_s, name, options)
95
94
 
@@ -139,100 +138,55 @@ module Teapot
139
138
  end
140
139
 
141
140
  def lock_store
142
- @lock_store ||= YAML::Store.new(lock_path.to_s)
141
+ YAML::Store.new(lock_path.to_s)
143
142
  end
144
143
 
145
- # Load all packages defined by this configuration.
146
- def load_all
147
- @packages.each do |package|
148
- @context.load(package)
149
- end
150
- end
151
-
152
- # Conceptually, a configuration belongs to a package. Primarily, a configuration lists dependent packages, but this also includes itself as the dependencies are purely target based, e.g. this configuration has access to any targets exposed by its own package.
153
- def top!
154
- @packages << @package
144
+ def to_s
145
+ "#<#{self.class} #{@name.dump} visibility=#{@visibility}>"
155
146
  end
156
147
 
157
148
  # Process all import directives and return a new configuration based on the current configuration. Import directives bring packages and other import directives from the specififed configuration definition.
158
- def materialize
159
- # Potentially no materialization is required:
160
- return false if @imports.count == 0
161
-
162
- # Avoid loops in the dependency chain:
163
- imported = IdentitySet.new
149
+ def traverse(configurations, imported = IdentitySet.new, &block)
150
+ yield self # Whatever happens here, should ensure that...
164
151
 
165
- # Enumerate all imports and attempt to resolve the packages:
166
- begin
167
- updated = false
152
+ @imports.each do |import|
153
+ # So we don't get into some crazy cycle:
154
+ next if imported.include?(import)
168
155
 
169
- # Before trying to materialize, we should load all possible packages:
170
- @packages.each do |package|
171
- @context.load(package) rescue nil
172
- end
173
-
174
- imports = @imports
175
- @imports = IdentitySet.new
156
+ # Mark it as being imported:
157
+ imported << import
176
158
 
177
- imports.each do |import|
178
- named_configuration = @context.configurations[import.name]
179
-
180
- # So we don't get into some crazy cycle:
181
- next if imported.include? import
182
-
183
- # It would be nice if we could detect cycles and issue an error to the user. However, sometimes the case above is not hit at the point where the cycle begins - it isn't clear at what point the user explicitly created a cycle, and what configuration actually ends up being imported a second time.
184
-
185
- if named_configuration && named_configuration != self
186
- # Mark this as resolved
187
- imported << import
188
-
189
- updated = self.update(named_configuration, import.options) || updated
190
- else
191
- # It couldn't be resolved and hasn't already been resolved...
192
- @imports << import
193
- end
159
+ # ... by here, the configuration is available:
160
+ if configuration = configurations[import.name]
161
+ configuration.traverse(configurations, imported, &block)
194
162
  end
195
- end while updated
196
-
197
- return true
163
+ end
198
164
  end
199
165
 
200
166
  # Merge an external configuration into this configuration. We won't override already defined packages.
201
- def update(configuration, options)
202
- updated = false
203
-
204
- configuration.packages.each do |external_package|
167
+ def merge(configuration)
168
+ configuration.packages.each do |package|
205
169
  # The top level configuration will override packages that are defined by imported configurations. This is desirable behaviour, as it allows us to flatten the configuration but provide overrides if required.
206
- unless @packages.include? external_package
207
- options = options.merge(external_package.options)
170
+ unless @packages.include? package
171
+ package = Package.new(packages_path + package.name, package.name, @options.merge(package.options))
208
172
 
209
- @packages << Package.new(packages_path + external_package.name, external_package.name, options)
173
+ @packages << package
210
174
 
211
- updated = true
175
+ yield package
212
176
  end
213
177
  end
214
178
 
215
- configuration.imports.each do |external_import|
216
- unless @imports.include? external_import
217
- options = options.merge(external_import.options)
218
-
219
- @imports << Import.new(external_import.name, external_import.explicit, options)
220
-
221
- updated = true
179
+ configuration.imports.each do |import|
180
+ unless @imports.include? import
181
+ @imports << Import.new(import.name, import.explicit, @options.merge(import.options))
222
182
  end
223
183
  end
224
184
 
225
185
  configuration.targets.each do |key, value|
226
186
  @targets[key] += value
227
-
228
- updated = true
229
187
  end
230
188
 
231
- return updated
232
- end
233
-
234
- def to_s
235
- "#<#{self.class} #{@name.dump} visibility=#{@visibility}>"
189
+ return self
236
190
  end
237
191
  end
238
192
  end
@@ -26,9 +26,6 @@ require 'build/text/substitutions'
26
26
  require 'build/text/merge'
27
27
 
28
28
  module Teapot
29
- TEAPOT_FILE = 'teapot.rb'.freeze
30
- DEFAULT_CONFIGURATION_NAME = 'default'.freeze
31
-
32
29
  class AlreadyDefinedError < StandardError
33
30
  def initialize(definition, previous)
34
31
  super "Definition #{definition.name} in #{definition.path} has already been defined in #{previous.path}!"
@@ -40,21 +37,124 @@ module Teapot
40
37
  raise self.new(definition, previous) if previous
41
38
  end
42
39
  end
43
-
44
- # A context represents a specific root package instance with a given configuration and all related definitions.
45
- # A context is stateful in the sense that package selection is specialized based on #select and #dependency_chain. These parameters are usually set up initially as part of the context setup.
46
- class Context
47
- def initialize(root, **options)
48
- @root = Path[root]
49
- @options = options
50
-
40
+
41
+ # A selection is a specific view of the data exposed by the context at a specific point in time.
42
+ class Select
43
+ def initialize(context, configuration, names = [])
44
+ @context = context
45
+ @configuration = Configuration.new(context, configuration.package, configuration.name, [], configuration.options)
46
+
51
47
  @targets = {}
52
48
  @configurations = {}
53
49
  @projects = {}
54
50
  @rules = Build::Rulebook.new
55
-
51
+
56
52
  @dependencies = []
57
53
  @selection = Set.new
54
+ @unresolved = Set.new
55
+
56
+ load!(configuration, names)
57
+
58
+ @chain = nil
59
+ end
60
+
61
+ attr :context
62
+ attr :configuration
63
+
64
+ attr :targets
65
+ attr :projects
66
+
67
+ # Alises as defined by Configuration#targets
68
+ attr :aliases
69
+
70
+ # All public configurations.
71
+ attr :configurations
72
+
73
+ attr :rules
74
+
75
+ attr :dependencies
76
+ attr :selection
77
+ attr :unresolved
78
+
79
+ def chain
80
+ @chain ||= Build::Dependency::Chain.expand(@dependencies, @targets.values, @selection)
81
+ end
82
+
83
+ def direct_targets(ordered)
84
+ @dependencies.collect do |dependency|
85
+ ordered.find{|(package, _)| package.provides? dependency}
86
+ end.compact
87
+ end
88
+
89
+ private
90
+
91
+ # Add a definition to the current context.
92
+ def append definition
93
+ case definition
94
+ when Target
95
+ AlreadyDefinedError.check(definition, @targets)
96
+ @targets[definition.name] = definition
97
+ when Configuration
98
+ # We define configurations in two cases, if they are public, or if they are part of the root package of this context.
99
+ if definition.public? or definition.package == @context.root_package
100
+ AlreadyDefinedError.check(definition, @configurations)
101
+ @configurations[definition.name] = definition
102
+ end
103
+ when Project
104
+ AlreadyDefinedError.check(definition, @projects)
105
+ @projects[definition.name] = definition
106
+ when Rule
107
+ AlreadyDefinedError.check(definition, @rules)
108
+ @rules << definition
109
+ end
110
+ end
111
+
112
+ def load_package!(package)
113
+ begin
114
+ script = @context.load(package)
115
+
116
+ # Load the definitions into the current selection:
117
+ script.defined.each do |definition|
118
+ append(definition)
119
+ end
120
+ rescue NonexistantTeapotError, IncompatibleTeapotError
121
+ # If the package doesn't exist or the teapot version is too old, it failed:
122
+ @unresolved << package
123
+ end
124
+ end
125
+
126
+ def load!(configuration, names)
127
+ # Load the root package which makes all the named configurations and targets available.
128
+ load_package!(@context.root_package)
129
+
130
+ # Load all packages defined by this configuration.
131
+ configuration.traverse(@configurations) do |configuration|
132
+ @configuration.merge(configuration) do |package|
133
+ # puts "Load package: #{package} from #{configuration}"
134
+ load_package!(package)
135
+ end
136
+ end
137
+
138
+ @configuration.freeze
139
+
140
+ names.each do |name|
141
+ if @targets.key? name
142
+ @selection << name
143
+ else
144
+ @dependencies << name
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ # A context represents a specific root package instance with a given configuration and all related definitions. A context is stateful in the sense that package selection is specialized based on #select and #dependency_chain. These parameters are usually set up initially as part of the context setup.
151
+ class Context
152
+ def initialize(root, **options)
153
+ @root = Path[root]
154
+ @options = options
155
+
156
+ @configuration = nil
157
+ @project = nil
58
158
 
59
159
  @loaded = {}
60
160
 
@@ -64,29 +164,19 @@ module Teapot
64
164
  attr :root
65
165
  attr :options
66
166
 
67
- attr :targets
68
- attr :projects
69
-
70
- # Context metadata
71
- attr :metadata
72
-
73
- # All public configurations.
74
- attr :configurations
75
-
76
- attr :rules
77
-
78
- # The context's primary configuration.
167
+ # The primary configuration.
79
168
  attr :configuration
80
169
 
81
- # The context's primary project.
170
+ # The primary project.
82
171
  attr :project
83
172
 
84
- attr :dependencies
85
- attr :selection
86
-
87
173
  def repository
88
174
  @repository ||= Rugged::Repository.new(@root.to_s)
89
175
  end
176
+
177
+ def select(names = [], configuration = @configuration)
178
+ Select.new(self, configuration, names)
179
+ end
90
180
 
91
181
  def substitutions
92
182
  substitutions = Build::Text::Substitutions.new
@@ -119,92 +209,16 @@ module Teapot
119
209
  return substitutions
120
210
  end
121
211
 
122
- def select(names)
123
- names.each do |name|
124
- if @targets.key? name
125
- @selection << name
126
- else
127
- @dependencies << name
128
- end
129
- end
130
- end
131
-
132
- def dependency_chain(dependency_names, configuration = @configuration)
133
- configuration.load_all
134
-
135
- select(dependency_names)
136
-
137
- Build::Dependency::Chain.expand(@dependencies, @targets.values, @selection)
138
- end
139
-
140
- def direct_targets(ordered)
141
- @dependencies.collect do |dependency|
142
- ordered.find{|(package, _)| package.provides? dependency}
143
- end.compact
144
- end
145
-
146
- # Add a definition to the current context.
147
- def << definition
148
- case definition
149
- when Target
150
- AlreadyDefinedError.check(definition, @targets)
151
-
152
- @targets[definition.name] = definition
153
- when Configuration
154
- # We define configurations in two cases, if they are public, or if they are part of the root package of this context.
155
- if definition.public? or definition.package == @root_package
156
- # The root package implicitly defines the default configuration.
157
- if definition.name == DEFAULT_CONFIGURATION_NAME
158
- raise AlreadyDefinedError.new(definition, root_package)
159
- end
160
-
161
- AlreadyDefinedError.check(definition, @configurations)
162
-
163
- @configurations[definition.name] = definition
164
- end
165
- when Project
166
- AlreadyDefinedError.check(definition, @projects)
167
-
168
- @project ||= definition
169
-
170
- @projects[definition.name] = definition
171
- when Rule
172
- AlreadyDefinedError.check(definition, @rules)
173
-
174
- @rules << definition
175
- end
176
- end
177
-
178
212
  def load(package)
179
- # In certain cases, a package record might be loaded twice. This typically occurs when multiple configurations are loaded in the same context, or if a package has already been loaded (as is typical with the root package).
180
- @loaded.fetch(package) do
181
- loader = Loader.new(self, package)
182
-
183
- loader.load(TEAPOT_FILE)
184
-
185
- # Load the definitions into the current context:
186
- loader.defined.each do |definition|
187
- self << definition
188
- end
189
-
190
- # Save the definitions per-package:
191
- @loaded[package] = loader.defined
213
+ if loader = @loaded[package]
214
+ return loader.script unless loader.changed?
192
215
  end
193
- end
194
-
195
- def unresolved(packages)
196
- failed_to_load = Set.new
197
216
 
198
- packages.collect do |package|
199
- begin
200
- definitions = load(package)
201
- rescue NonexistantTeapotError, IncompatibleTeapotError
202
- # If the package doesn't exist or the teapot version is too old, it failed:
203
- failed_to_load << package
204
- end
205
- end
217
+ loader = Loader.new(self, package)
206
218
 
207
- return failed_to_load
219
+ @loaded[package] = loader
220
+
221
+ return loader.script
208
222
  end
209
223
 
210
224
  # The root package is a special package which is used to load definitions from a given root path.
@@ -216,23 +230,20 @@ module Teapot
216
230
 
217
231
  def load_root_package(options)
218
232
  # Load the root package:
219
- defined = load(root_package)
233
+ script = load(root_package)
220
234
 
221
235
  # Find the default configuration, if it exists:
222
- @default_configuration = defined.default_configuration
223
-
224
236
  if configuration_name = options[:configuration]
225
237
  @configuration = @configurations[configuration_name]
226
238
  else
227
- @configuration = @default_configuration
239
+ @configuration = script.default_configuration
228
240
  end
229
241
 
242
+ @project = script.default_project
243
+
230
244
  if @configuration.nil?
231
245
  raise ArgumentError.new("Could not load configuration: #{configuration_name.inspect}")
232
246
  end
233
-
234
- # Materialize the configuration:
235
- @configuration.materialize if @configuration
236
247
  end
237
248
  end
238
249
  end
@@ -33,6 +33,8 @@ module Teapot
33
33
  end
34
34
  end
35
35
 
36
+ attr :table
37
+
36
38
  def freeze
37
39
  @table.freeze
38
40
 
@@ -47,8 +49,6 @@ module Teapot
47
49
  object.name
48
50
  end
49
51
 
50
- attr :table
51
-
52
52
  def add(object)
53
53
  @table[identity(object)] = object
54
54
  end
@@ -67,6 +67,10 @@ module Teapot
67
67
  @table.each_value(&block)
68
68
  end
69
69
 
70
+ def slice(names)
71
+ names.collect{|name| @table[name]}
72
+ end
73
+
70
74
  extend Forwardable
71
75
 
72
76
  def_delegators :@table, :size, :empty?, :clear, :count, :[], :to_s, :inspect
@@ -35,6 +35,9 @@ module Teapot
35
35
  # Cannot load packages older than this.
36
36
  MINIMUM_LOADER_VERSION = "1.0"
37
37
 
38
+ # The package relative path to the file to load:
39
+ TEAPOT_FILE = 'teapot.rb'.freeze
40
+
38
41
  class IncompatibleTeapotError < StandardError
39
42
  def initialize(package, version)
40
43
  super "Unsupported teapot_version #{version} in #{package.path}!"
@@ -51,29 +54,32 @@ module Teapot
51
54
  attr :path
52
55
  end
53
56
 
54
- class Loader
57
+ # The DSL exposed to the `teapot.rb` file.
58
+ class Script
55
59
  Files = Build::Files
56
60
  Rule = Build::Rule
57
61
 
58
- class Definitions < Array
59
- def default_configuration
60
- find{|definition| Configuration === definition}
61
- end
62
- end
63
-
64
- def initialize(context, package)
62
+ def initialize(context, package, path = TEAPOT_FILE)
65
63
  @context = context
66
64
  @package = package
67
-
68
- @defined = Definitions.new
65
+
66
+ @defined = []
69
67
  @version = nil
68
+
69
+ @default_project = nil
70
+ @default_configuration = nil
71
+
72
+ @mtime = nil
70
73
  end
71
-
74
+
72
75
  attr :context
73
76
  attr :package
74
77
  attr :defined
75
78
  attr :version
76
-
79
+
80
+ attr :default_project
81
+ attr :default_configuration
82
+
77
83
  def teapot_version(version)
78
84
  version = version[0..2]
79
85
 
@@ -83,17 +89,18 @@ module Teapot
83
89
  raise IncompatibleTeapotError.new(package, version)
84
90
  end
85
91
  end
86
-
92
+
87
93
  alias required_version teapot_version
88
-
94
+
89
95
  def define_project(*args)
90
96
  project = Project.new(@context, @package, *args)
91
97
 
92
98
  yield project
93
99
 
100
+ @default_project = project
94
101
  @defined << project
95
102
  end
96
-
103
+
97
104
  def define_target(*args)
98
105
  target = Target.new(@context, @package, *args)
99
106
 
@@ -101,21 +108,16 @@ module Teapot
101
108
 
102
109
  @defined << target
103
110
  end
104
-
111
+
105
112
  def define_configuration(*args)
106
113
  configuration = Configuration.new(@context, @package, *args)
107
114
 
108
- configuration.top!
109
-
110
115
  yield configuration
111
116
 
117
+ @default_configuration ||= configuration
112
118
  @defined << configuration
113
119
  end
114
120
 
115
- def define_generator(*args)
116
- warn "define_generator(#{args.inspect}) is deprecated and will have no effect. Please use define_target."
117
- end
118
-
119
121
  # Checks the host patterns and executes the block if they match.
120
122
  def host(*args, &block)
121
123
  name = @context.options[:host_platform] || RUBY_PLATFORM
@@ -128,18 +130,50 @@ module Teapot
128
130
  name
129
131
  end
130
132
  end
131
-
133
+ end
134
+
135
+ # Loads the teapot.rb script and can reload it if it was changed.
136
+ class Loader
137
+ def initialize(context, package, path = TEAPOT_FILE)
138
+ @context = context
139
+ @package = package
140
+
141
+ @path = path
142
+ @mtime = nil
143
+
144
+ @script, @mtime = load!
145
+ end
146
+
147
+ attr :script
148
+
149
+ def teapot_path
150
+ @package.path + @path
151
+ end
152
+
153
+ def changed?
154
+ File.mtime(teapot_path) > @mtime
155
+ end
156
+
157
+ def reload
158
+ self.class.new(@context, @package, @path)
159
+ end
160
+
161
+ private
162
+
132
163
  # Load a teapot.rb file relative to the root of the @package.
133
- def load(path)
134
- absolute_path = @package.path + path
164
+ def load!(path = teapot_path)
165
+ raise NonexistantTeapotError.new(path) unless File.exist?(path)
135
166
 
136
- raise NonexistantTeapotError.new(absolute_path) unless File.exist?(absolute_path)
167
+ script = Script.new(@context, @package)
137
168
 
138
- self.instance_eval(absolute_path.read, absolute_path.to_s)
169
+ mtime = File.mtime(path)
170
+ script.instance_eval(path.read, path.to_s)
139
171
 
140
- if @version == nil
172
+ if script.version == nil
141
173
  raise IncompatibleTeapotError.new(@package, "<unspecified>")
142
174
  end
175
+
176
+ return script, mtime
143
177
  end
144
178
  end
145
179
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Teapot
22
- VERSION = "2.1.0"
22
+ VERSION = "2.2.0"
23
23
  end
@@ -39,8 +39,10 @@ RSpec.describe Teapot::Command::Clone, order: :defined do
39
39
 
40
40
  expect(File).to be_exist(root + "teapot.rb")
41
41
 
42
+ selection = top.context.select
43
+
42
44
  # Check that we actually fetched some remote targets.
43
- expect(top.context.targets).to include(
45
+ expect(selection.targets).to include(
44
46
  "tagged-format-library",
45
47
  "tagged-format-executable",
46
48
  "tagged-format-tests",
@@ -22,21 +22,12 @@ require 'teapot/context'
22
22
  require 'teapot/configuration'
23
23
 
24
24
  RSpec.describe Teapot::Configuration do
25
- include_context Teapot::Context
25
+ let(:root) {Build::Files::Path.new(__dir__) + "configuration_spec"}
26
+ let(:context) {Teapot::Context.new(root)}
26
27
 
27
- let(:master) {Teapot::Configuration.new(context, Teapot::Package.new(root + 'master', 'master'), 'master')}
28
- let(:embedded) {Teapot::Configuration.new(context, Teapot::Package.new(root + 'embedded', 'embedded'), 'embedded')}
29
-
30
- context "with create targets" do
31
- before(:each) do
32
- master.targets[:create] << "hello"
33
- embedded.targets[:create] << "world"
34
- end
28
+ it "merged targets" do
29
+ selection = context.select
35
30
 
36
- it "can merge packages" do
37
- expect(master.update(embedded, {})).to be_truthy
38
-
39
- expect(master.targets[:create]).to be == ["hello", "world"]
40
- end
31
+ expect(selection.configuration.targets[:build]).to be == ["Bar", "Foo"]
41
32
  end
42
33
  end
@@ -0,0 +1,13 @@
1
+
2
+ teapot_version "2.0"
3
+
4
+ define_configuration "build-bar" do |configuration|
5
+ configuration.import "build-foo"
6
+
7
+ configuration.targets[:build] << "Bar"
8
+ end
9
+
10
+ define_configuration "build-foo" do |configuration|
11
+ configuration.targets[:build] << "Foo"
12
+ end
13
+
@@ -24,15 +24,20 @@ RSpec.describe Teapot::Context do
24
24
  let(:root) {Build::Files::Path.new(__dir__) + "context_spec"}
25
25
  let(:context) {Teapot::Context.new(root)}
26
26
 
27
- it "should load teapot.rb file" do
28
- # There is one configuration:
29
- expect(context.configurations.count).to be == 1
30
-
31
- expect(context.targets.count).to be == 1
32
-
27
+ it "should specify correct number of packages" do
33
28
  default_configuration = context.configuration
29
+
30
+ expect(default_configuration.packages.count).to be == 13
31
+ end
34
32
 
35
- # 13 defined packages + 1 implicit package.
36
- expect(default_configuration.packages.count).to be == 14
33
+ it "should load teapot.rb file" do
34
+ selection = context.select
35
+
36
+ # There is one configuration:
37
+ expect(selection.configurations.count).to be == 1
38
+ expect(selection.targets.count).to be == 1
39
+
40
+ # We didn't expect any of them to actually load...
41
+ expect(selection.unresolved.count).to be > 0
37
42
  end
38
43
  end
@@ -25,11 +25,12 @@ RSpec.describe Teapot::Target do
25
25
 
26
26
  it "should generate environment for configuration" do
27
27
  context = Teapot::Context.new(root)
28
+ selection = context.select(["Test/TargetSpec"])
28
29
 
29
- target = context.targets['target_spec']
30
+ target = selection.targets['target_spec']
30
31
  expect(target).to_not be == nil
31
32
 
32
- chain = context.dependency_chain(["Test/TargetSpec"])
33
+ chain = selection.chain
33
34
  expect(chain.providers.size).to be == 4
34
35
  expect(chain.providers).to include target
35
36
 
@@ -39,7 +40,7 @@ RSpec.describe Teapot::Target do
39
40
  expect(chain.ordered[2].name).to be == 'Test/TargetSpec'
40
41
  expect(chain.ordered[2].provider).to be == target
41
42
 
42
- environment = target.environment(context.configuration, chain)
43
+ environment = target.environment(selection.configuration, chain)
43
44
  # Environment#to_hash flattens the environment and evaluates all values:
44
45
  hash = environment.to_hash
45
46
 
@@ -51,11 +52,12 @@ RSpec.describe Teapot::Target do
51
52
 
52
53
  it "should match wildcard packages" do
53
54
  context = Teapot::Context.new(root)
55
+ selection = context.select(["Test/*"])
54
56
 
55
- target = context.targets['target_spec']
57
+ target = selection.targets['target_spec']
56
58
  expect(target).to_not be == nil
57
59
 
58
- chain = context.dependency_chain(["Test/*"])
60
+ chain = selection.chain
59
61
  expect(chain.providers.size).to be == 4
60
62
  expect(chain.providers).to include target
61
63
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teapot
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -228,6 +228,7 @@ files:
228
228
  - spec/teapot/command/fetch_spec/test-project/teapot.rb
229
229
  - spec/teapot/command_spec.rb
230
230
  - spec/teapot/configuration_spec.rb
231
+ - spec/teapot/configuration_spec/teapot.rb
231
232
  - spec/teapot/context_spec.rb
232
233
  - spec/teapot/context_spec/teapot.rb
233
234
  - spec/teapot/identity_set_spec.rb
@@ -267,6 +268,7 @@ test_files:
267
268
  - spec/teapot/command/fetch_spec/test-project/teapot.rb
268
269
  - spec/teapot/command_spec.rb
269
270
  - spec/teapot/configuration_spec.rb
271
+ - spec/teapot/configuration_spec/teapot.rb
270
272
  - spec/teapot/context_spec.rb
271
273
  - spec/teapot/context_spec/teapot.rb
272
274
  - spec/teapot/identity_set_spec.rb