teapot 2.1.0 → 2.2.0
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.
- checksums.yaml +4 -4
- data/README.md +20 -10
- data/lib/teapot/command/build.rb +9 -11
- data/lib/teapot/command/create.rb +2 -3
- data/lib/teapot/command/fetch.rb +20 -24
- data/lib/teapot/command/list.rb +4 -5
- data/lib/teapot/command/status.rb +0 -1
- data/lib/teapot/command/visualize.rb +3 -5
- data/lib/teapot/configuration.rb +32 -78
- data/lib/teapot/context.rb +128 -117
- data/lib/teapot/identity_set.rb +6 -2
- data/lib/teapot/loader.rb +62 -28
- data/lib/teapot/version.rb +1 -1
- data/spec/teapot/command/clone_spec.rb +3 -1
- data/spec/teapot/configuration_spec.rb +5 -14
- data/spec/teapot/configuration_spec/teapot.rb +13 -0
- data/spec/teapot/context_spec.rb +13 -8
- data/spec/teapot/target_spec.rb +7 -5
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40d564684024a42a0762e51eef5fdcccea99eb81
|
4
|
+
data.tar.gz: 4dcc60428ead96eca66951c50ba891dcd366741f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
###
|
102
|
-
|
103
|
-
We can now build and run
|
104
|
-
|
105
|
-
$ teapot
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
123
|
+
$ cd teapot/platforms/development/$PLATFORM-debug/bin
|
114
124
|
$ ./$PROJECT_NAME
|
115
125
|
|
116
126
|
### Cloning Project
|
data/lib/teapot/command/build.rb
CHANGED
@@ -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
|
-
|
43
|
-
|
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 =
|
52
|
+
chain = selection.chain
|
55
53
|
|
56
54
|
ordered = chain.ordered
|
57
55
|
|
58
56
|
if @options[:only]
|
59
|
-
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(
|
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
|
-
|
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:
|
data/lib/teapot/command/fetch.rb
CHANGED
@@ -52,38 +52,34 @@ module Teapot
|
|
52
52
|
logger = parent.logger
|
53
53
|
context = parent.context
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
unresolved = context.unresolved(configuration.packages)
|
55
|
+
updated = Set.new
|
56
|
+
selection = context.select
|
58
57
|
|
59
|
-
|
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
|
-
|
76
|
-
|
60
|
+
if @packages.any?
|
61
|
+
packages = packages.slice(@packages)
|
62
|
+
end
|
77
63
|
|
78
|
-
|
79
|
-
|
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
|
data/lib/teapot/command/list.rb
CHANGED
@@ -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
|
-
|
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-
|
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
|
-
|
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 =
|
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)
|
data/lib/teapot/configuration.rb
CHANGED
@@ -35,30 +35,29 @@ module Teapot
|
|
35
35
|
:import => true
|
36
36
|
}.freeze
|
37
37
|
|
38
|
-
def initialize(context, package, name, packages = [], options
|
38
|
+
def initialize(context, package, name, packages = [], **options)
|
39
39
|
super context, package, name
|
40
40
|
|
41
|
-
|
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
|
92
|
-
options =
|
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
|
-
|
141
|
+
YAML::Store.new(lock_path.to_s)
|
143
142
|
end
|
144
143
|
|
145
|
-
|
146
|
-
|
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
|
159
|
-
#
|
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
|
-
|
166
|
-
|
167
|
-
|
152
|
+
@imports.each do |import|
|
153
|
+
# So we don't get into some crazy cycle:
|
154
|
+
next if imported.include?(import)
|
168
155
|
|
169
|
-
#
|
170
|
-
|
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
|
-
|
178
|
-
|
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
|
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
|
202
|
-
|
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?
|
207
|
-
|
170
|
+
unless @packages.include? package
|
171
|
+
package = Package.new(packages_path + package.name, package.name, @options.merge(package.options))
|
208
172
|
|
209
|
-
@packages <<
|
173
|
+
@packages << package
|
210
174
|
|
211
|
-
|
175
|
+
yield package
|
212
176
|
end
|
213
177
|
end
|
214
178
|
|
215
|
-
configuration.imports.each do |
|
216
|
-
unless @imports.include?
|
217
|
-
|
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
|
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
|
data/lib/teapot/context.rb
CHANGED
@@ -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
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@
|
49
|
-
|
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
|
-
|
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
|
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
|
-
|
180
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
data/lib/teapot/identity_set.rb
CHANGED
@@ -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
|
data/lib/teapot/loader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
164
|
+
def load!(path = teapot_path)
|
165
|
+
raise NonexistantTeapotError.new(path) unless File.exist?(path)
|
135
166
|
|
136
|
-
|
167
|
+
script = Script.new(@context, @package)
|
137
168
|
|
138
|
-
|
169
|
+
mtime = File.mtime(path)
|
170
|
+
script.instance_eval(path.read, path.to_s)
|
139
171
|
|
140
|
-
if
|
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
|
data/lib/teapot/version.rb
CHANGED
@@ -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(
|
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
|
-
|
25
|
+
let(:root) {Build::Files::Path.new(__dir__) + "configuration_spec"}
|
26
|
+
let(:context) {Teapot::Context.new(root)}
|
26
27
|
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
+
|
data/spec/teapot/context_spec.rb
CHANGED
@@ -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
|
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
|
-
|
36
|
-
|
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
|
data/spec/teapot/target_spec.rb
CHANGED
@@ -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 =
|
30
|
+
target = selection.targets['target_spec']
|
30
31
|
expect(target).to_not be == nil
|
31
32
|
|
32
|
-
chain =
|
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(
|
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 =
|
57
|
+
target = selection.targets['target_spec']
|
56
58
|
expect(target).to_not be == nil
|
57
59
|
|
58
|
-
chain =
|
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.
|
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
|