build-dependency 1.1.0 → 1.2.1
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/.rspec +0 -1
- data/README.md +141 -0
- data/build-dependency.gemspec +1 -1
- data/build_system_rules_and_algorithms.pdf +0 -0
- data/lib/build/dependency/chain.rb +28 -110
- data/lib/build/dependency/partial_chain.rb +1 -1
- data/lib/build/dependency/provider.rb +55 -17
- data/lib/build/dependency/resolver.rb +133 -0
- data/lib/build/dependency/version.rb +1 -1
- data/spec/build/dependency/chain_spec.rb +16 -8
- data/spec/build/dependency/partial_chain_spec.rb +12 -12
- data/spec/build/dependency/provider_spec.rb +2 -2
- data/spec/build/dependency/wildcard_spec.rb +43 -0
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6f603e98eff139ae1a2c9441230b28d3202441c
|
4
|
+
data.tar.gz: f86fea3553677fee83f22c35d6629cde9c43c23e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68c4d6b209f8162094851ffb5f7ee605832ba1549941953357d971274950ae4d76b179a9b3925ced730a0a55307a3648522c9d537813cee946feb2a6832ce62d
|
7
|
+
data.tar.gz: 1dfb5cbc65105925cf37ee020619db04bed17e9649cc79f66c354cd325efb67227ae891ccdce52f0b6de84c90cd0237f3b538fa9f646867bd636aa44b1af55a5
|
data/.rspec
CHANGED
data/README.md
CHANGED
@@ -40,6 +40,147 @@ The orange box is the top level dependency, the grey box is an alias, the blue b
|
|
40
40
|
|
41
41
|
To create your own dependency graph, you need to expose a model object which represents something that has dependencies and can be depended on.
|
42
42
|
|
43
|
+
Here is an example of a package model for Arch Linux `PKGBUILD` files:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# A specific package.
|
47
|
+
class Package
|
48
|
+
include Build::Dependency
|
49
|
+
|
50
|
+
def initialize(path, metadata)
|
51
|
+
@path = path
|
52
|
+
@metadata = metadata
|
53
|
+
|
54
|
+
metadata.each do |key, value|
|
55
|
+
case key
|
56
|
+
when 'pkgname'
|
57
|
+
@name = value
|
58
|
+
when 'depends'
|
59
|
+
self.depends(value)
|
60
|
+
when 'provides'
|
61
|
+
self.provides(value)
|
62
|
+
when 'pkgver'
|
63
|
+
@pkgver = value
|
64
|
+
when 'pkgrel'
|
65
|
+
@pkgrel = value
|
66
|
+
when 'arch'
|
67
|
+
@arch = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@name ||= File.basename(path)
|
72
|
+
self.provides(@name)
|
73
|
+
end
|
74
|
+
|
75
|
+
attr :path
|
76
|
+
attr :name
|
77
|
+
attr :metadata
|
78
|
+
|
79
|
+
def package_path
|
80
|
+
File.join(@path, package_file)
|
81
|
+
end
|
82
|
+
|
83
|
+
def package_file
|
84
|
+
"#{@name}-#{@pkgver}-#{@pkgrel}-#{@arch}.pkg.tar.xz"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# A context represents a directory full of packages.
|
89
|
+
class Context
|
90
|
+
def initialize(path)
|
91
|
+
@path = path
|
92
|
+
@packages = {}
|
93
|
+
|
94
|
+
load_packages!
|
95
|
+
end
|
96
|
+
|
97
|
+
def packages_path
|
98
|
+
@path
|
99
|
+
end
|
100
|
+
|
101
|
+
attr :packages
|
102
|
+
|
103
|
+
def load_packages!
|
104
|
+
Dir.foreach(packages_path) do |package_name|
|
105
|
+
next if package_name.start_with?('.')
|
106
|
+
|
107
|
+
package_path = File.join(packages_path, package_name)
|
108
|
+
next unless File.directory?(package_path)
|
109
|
+
|
110
|
+
LOGGER.info "Loading #{package_path}..."
|
111
|
+
output, status = Open3.capture2("makepkg", "--printsrcinfo", chdir: package_path)
|
112
|
+
|
113
|
+
metadata = output.lines.collect(&:strip).delete_if(&:empty?).collect{|line| line.split(/\s*=\s*/, 2)}
|
114
|
+
|
115
|
+
package = Package.new(package_path, metadata)
|
116
|
+
@packages[package.name] = package
|
117
|
+
|
118
|
+
if package.name != package_name
|
119
|
+
LOGGER.warn "Package in directory #{package_name} has pkgname of #{package.name}!"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Compute the dependency chain for the selection of packages.
|
125
|
+
def provision_chain(selection)
|
126
|
+
Build::Dependency::Chain.new(selection, @packages.values, selection)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
### Chains
|
132
|
+
|
133
|
+
A chain represents a list of resolved packages. You generate a chain from a list of dependencies, a list of all available packages, and a selection of packages which help to resolve ambiguities (e.g. if two packages provide the same target, selection and then priority is used to resolve the ambiguity).
|
134
|
+
|
135
|
+
Here is a rake task for the above model which can build a directory of packages including both local PKGBUILDs and upstream packages:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
desc "Build a deployment of packages, specify the root package using TARGET="
|
139
|
+
task :collect do
|
140
|
+
target = ENV['TARGET'] or fail("Please supply TARGET=")
|
141
|
+
|
142
|
+
LOGGER.info "Resolving packages for #{target}"
|
143
|
+
|
144
|
+
context = Servers::Context.new(__dir__)
|
145
|
+
|
146
|
+
chain = context.provision_chain([target])
|
147
|
+
|
148
|
+
deploy_root = File.join(__dir__, "../deploy", target)
|
149
|
+
|
150
|
+
FileUtils::Verbose.rm_rf deploy_root
|
151
|
+
FileUtils::Verbose.mkdir_p deploy_root
|
152
|
+
|
153
|
+
system_packages = Set.new
|
154
|
+
|
155
|
+
# Depdencies that could not be resolved by our local packages must be resolved the system:
|
156
|
+
chain.unresolved.each do |(depends, source)|
|
157
|
+
output, status = Open3.capture2("pactree", "-lsu", depends.name)
|
158
|
+
|
159
|
+
abort "Failed to resolve dependency tree for package #{depends.name}" unless status.success?
|
160
|
+
|
161
|
+
system_packages += output.split(/\s+/)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Copy system packages from pacman repositories:
|
165
|
+
Dir.chdir(deploy_root) do
|
166
|
+
Open3.pipeline(
|
167
|
+
["pacman", "-Sp", *system_packages.to_a],
|
168
|
+
['wget', '-nv', '-i', '-'],
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Copy local packages:
|
173
|
+
chain.ordered.each do |resolution|
|
174
|
+
package = resolution.provider
|
175
|
+
FileUtils::Verbose.cp package.package_path, File.join(deploy_root, package.package_file)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
# Wildcards
|
181
|
+
|
182
|
+
It's possible to include wildcards in the dependency name. This is useful if you use scoped names, e.g. `Test/*` would depend on all test targets. The wildcard matching is done by `File.fnmatch?`.
|
183
|
+
|
43
184
|
## Contributing
|
44
185
|
|
45
186
|
1. Fork it
|
data/build-dependency.gemspec
CHANGED
@@ -18,6 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.add_dependency "graphviz"
|
19
19
|
|
20
20
|
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
-
spec.add_development_dependency "rspec", "~> 3.
|
21
|
+
spec.add_development_dependency "rspec", "~> 3.6"
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
end
|
Binary file
|
@@ -18,112 +18,10 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
|
21
|
+
require_relative 'resolver'
|
22
22
|
|
23
23
|
module Build
|
24
24
|
module Dependency
|
25
|
-
class UnresolvedDependencyError < StandardError
|
26
|
-
def initialize(chain)
|
27
|
-
super "Unresolved dependency chain: #{chain.unresolved.inspect}!"
|
28
|
-
|
29
|
-
@chain = chain
|
30
|
-
end
|
31
|
-
|
32
|
-
attr :chain
|
33
|
-
end
|
34
|
-
|
35
|
-
TOP = Depends.new("<top>").freeze
|
36
|
-
|
37
|
-
class Resolver
|
38
|
-
def initialize
|
39
|
-
@resolved = {}
|
40
|
-
@ordered = []
|
41
|
-
@provisions = []
|
42
|
-
@unresolved = []
|
43
|
-
@conflicts = {}
|
44
|
-
end
|
45
|
-
|
46
|
-
attr :resolved
|
47
|
-
attr :ordered
|
48
|
-
attr :provisions
|
49
|
-
attr :unresolved
|
50
|
-
attr :conflicts
|
51
|
-
|
52
|
-
def freeze
|
53
|
-
return unless frozen?
|
54
|
-
|
55
|
-
@resolved.freeze
|
56
|
-
@ordered.freeze
|
57
|
-
@provisions.freeze
|
58
|
-
@unresolved.freeze
|
59
|
-
@conflicts.freeze
|
60
|
-
|
61
|
-
super
|
62
|
-
end
|
63
|
-
|
64
|
-
protected
|
65
|
-
|
66
|
-
def expand_nested(dependencies, provider)
|
67
|
-
dependencies.each do |dependency|
|
68
|
-
expand(Depends[dependency], provider)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def expand(dependency, parent)
|
73
|
-
# puts "** Expanding #{dependency.inspect} from #{parent.inspect} (private: #{dependency.private?})"
|
74
|
-
|
75
|
-
if @resolved.include?(dependency)
|
76
|
-
# puts "** Already resolved dependency!"
|
77
|
-
|
78
|
-
return
|
79
|
-
end
|
80
|
-
|
81
|
-
provider = find_provider(dependency, parent)
|
82
|
-
|
83
|
-
if provider == nil
|
84
|
-
# puts "** Couldn't find provider -> unresolved"
|
85
|
-
@unresolved << [dependency, parent]
|
86
|
-
return nil
|
87
|
-
end
|
88
|
-
|
89
|
-
provision = provision_for(provider, dependency)
|
90
|
-
|
91
|
-
# We will now satisfy this dependency by satisfying any dependent dependencies, but we no longer need to revisit this one.
|
92
|
-
# puts "** Resolved #{dependency} (#{provision.inspect})"
|
93
|
-
@resolved[dependency] = provider
|
94
|
-
|
95
|
-
# If the provision was an Alias, make sure to resolve the alias first:
|
96
|
-
if provision.alias?
|
97
|
-
# puts "** Resolving alias #{provision} (#{provision.dependencies.inspect})"
|
98
|
-
expand_nested(provision.dependencies, provider)
|
99
|
-
end
|
100
|
-
|
101
|
-
# puts "** Checking for #{provider.inspect} in #{resolved.inspect}"
|
102
|
-
unless @resolved.include?(provider)
|
103
|
-
# We are now satisfying the provider by expanding all its own dependencies:
|
104
|
-
@resolved[provider] = provision
|
105
|
-
|
106
|
-
# Make sure we satisfy the provider's dependencies first:
|
107
|
-
expand_nested(provider.dependencies, provider)
|
108
|
-
|
109
|
-
# puts "** Appending #{dependency} -> ordered"
|
110
|
-
|
111
|
-
# Add the provider to the ordered list.
|
112
|
-
@ordered << Resolution.new(provider, dependency)
|
113
|
-
end
|
114
|
-
|
115
|
-
# This goes here because we want to ensure 1/ that if
|
116
|
-
unless provision == nil or provision.alias?
|
117
|
-
# puts "** Appending #{dependency} -> provisions"
|
118
|
-
|
119
|
-
# Add the provision to the set of required provisions.
|
120
|
-
@provisions << provision
|
121
|
-
end
|
122
|
-
|
123
|
-
# For both @ordered and @provisions, we ensure that for [...xs..., x, ...], x is satisfied by ...xs....
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
25
|
class Chain < Resolver
|
128
26
|
# An `UnresolvedDependencyError` will be thrown if there are any unresolved dependencies.
|
129
27
|
def self.expand(*args)
|
@@ -190,13 +88,32 @@ module Build
|
|
190
88
|
return viable_providers.select{|provider| @selection.include? provider.name}
|
191
89
|
end
|
192
90
|
|
193
|
-
def
|
91
|
+
def expand_wildcard(dependency, parent)
|
92
|
+
@providers.flat_map do |provider|
|
93
|
+
provider.filter(dependency).flat_map do |name, provision|
|
94
|
+
expand_dependency(Depends[name], parent)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Resolve a dependency into one or more provisions:
|
100
|
+
def expand_dependency(dependency, parent)
|
101
|
+
if dependency.wildcard?
|
102
|
+
return expand_wildcard(dependency, parent)
|
103
|
+
end
|
104
|
+
|
194
105
|
# Mostly, only one package will satisfy the dependency...
|
195
106
|
viable_providers = @providers.select{|provider| provider.provides? dependency}
|
196
107
|
|
197
108
|
# puts "** Found #{viable_providers.collect(&:name).join(', ')} viable providers."
|
198
109
|
|
199
|
-
if viable_providers.size
|
110
|
+
if viable_providers.size == 1
|
111
|
+
provider = viable_providers.first
|
112
|
+
provision = provision_for(provider, dependency)
|
113
|
+
|
114
|
+
# The best outcome, a specific provider was named:
|
115
|
+
return [provision]
|
116
|
+
elsif viable_providers.size > 1
|
200
117
|
# ... however in some cases (typically where aliases are being used) an explicit selection must be made for the build to work correctly.
|
201
118
|
explicit_providers = filter_by_selection(viable_providers)
|
202
119
|
|
@@ -212,18 +129,19 @@ module Build
|
|
212
129
|
if explicit_providers.size == 0
|
213
130
|
# No provider was explicitly specified, thus we require explicit conflict resolution:
|
214
131
|
@conflicts[dependency] = viable_providers
|
215
|
-
return nil
|
216
132
|
elsif explicit_providers.size == 1
|
133
|
+
provider = explicit_providers.first
|
134
|
+
provision = provision_for(provider, dependency)
|
135
|
+
|
217
136
|
# The best outcome, a specific provider was named:
|
218
|
-
return
|
137
|
+
return [provision]
|
219
138
|
else
|
220
139
|
# Multiple providers were explicitly mentioned that satisfy the dependency.
|
221
140
|
@conflicts[dependency] = explicit_providers
|
222
|
-
return nil
|
223
141
|
end
|
224
|
-
else
|
225
|
-
return viable_providers.first
|
226
142
|
end
|
143
|
+
|
144
|
+
return []
|
227
145
|
end
|
228
146
|
|
229
147
|
def provision_for(provider, dependency)
|
@@ -47,11 +47,15 @@ module Build
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
Resolution = Struct.new(:
|
50
|
+
Resolution = Struct.new(:provision, :dependency) do
|
51
51
|
def name
|
52
52
|
dependency.name
|
53
53
|
end
|
54
54
|
|
55
|
+
def provider
|
56
|
+
provision.provider
|
57
|
+
end
|
58
|
+
|
55
59
|
def to_s
|
56
60
|
"resolution #{provider.name.inspect} -> #{dependency.name.inspect}"
|
57
61
|
end
|
@@ -64,6 +68,18 @@ module Build
|
|
64
68
|
@options = options
|
65
69
|
end
|
66
70
|
|
71
|
+
def wildcard?
|
72
|
+
self.name.is_a?(String) and self.name.include?('*')
|
73
|
+
end
|
74
|
+
|
75
|
+
def match?(name)
|
76
|
+
if wildcard? and name.is_a?(String)
|
77
|
+
File.fnmatch?(self.name, name)
|
78
|
+
else
|
79
|
+
self.name == name
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
67
83
|
attr :options
|
68
84
|
|
69
85
|
def to_s
|
@@ -82,8 +98,12 @@ module Build
|
|
82
98
|
name.is_a?(Symbol)
|
83
99
|
end
|
84
100
|
|
85
|
-
|
86
|
-
|
101
|
+
class << self
|
102
|
+
undef []
|
103
|
+
|
104
|
+
def [](name_or_dependency)
|
105
|
+
name_or_dependency.is_a?(self) ? name_or_dependency : self.new(name_or_dependency)
|
106
|
+
end
|
87
107
|
end
|
88
108
|
end
|
89
109
|
|
@@ -97,12 +117,12 @@ module Build
|
|
97
117
|
super
|
98
118
|
end
|
99
119
|
|
100
|
-
# Assign a priority
|
120
|
+
# Assign a priority.
|
101
121
|
def priority= value
|
102
122
|
@priority = value
|
103
123
|
end
|
104
124
|
|
105
|
-
# The
|
125
|
+
# The default priority.
|
106
126
|
def priority
|
107
127
|
@priority ||= 0
|
108
128
|
end
|
@@ -117,30 +137,48 @@ module Build
|
|
117
137
|
@dependencies ||= Set.new
|
118
138
|
end
|
119
139
|
|
140
|
+
def filter(dependency)
|
141
|
+
provisions.select{|name, provision| dependency.match?(name)}
|
142
|
+
end
|
143
|
+
|
120
144
|
# Does this unit provide the named thing?
|
121
145
|
def provides?(dependency)
|
122
146
|
provisions.key?(dependency.name)
|
123
147
|
end
|
124
148
|
|
125
149
|
def provision_for(dependency)
|
126
|
-
provisions[dependency.name]
|
150
|
+
return provisions[dependency.name]
|
151
|
+
end
|
152
|
+
|
153
|
+
def resolution_for(dependency)
|
154
|
+
return Resolution.new(provision_for(dependency), dependency)
|
127
155
|
end
|
128
156
|
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
157
|
+
# Add one or more provisions to the provider.
|
158
|
+
# @param [Array<String>] names the named provisions to add.
|
159
|
+
# @param [Hash<Symbol, Array>] aliases the aliases to add.
|
160
|
+
# @example A named provision.
|
161
|
+
# target.provides "Compiler/clang" do
|
162
|
+
# cxx "clang"
|
163
|
+
# end
|
164
|
+
# @example A symbolic provision.
|
165
|
+
# target.provides compiler: "Compiler/clang"
|
166
|
+
def provides(*names, **aliases, &block)
|
167
|
+
names.each do |name|
|
134
168
|
provisions[name] = Provision.new(name, self, block)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
provisions[name] = Alias.new(name, self, Array(dependencies))
|
140
|
-
end
|
169
|
+
end
|
170
|
+
|
171
|
+
aliases.each do |name, dependencies|
|
172
|
+
provisions[name] = Alias.new(name, self, Array(dependencies))
|
141
173
|
end
|
142
174
|
end
|
143
175
|
|
176
|
+
# Add one or more dependencies to the provider.
|
177
|
+
# @param [Array<String>] names the dependency names to add.
|
178
|
+
# @example A named dependency.
|
179
|
+
# target.depends "Compiler/clang"
|
180
|
+
# @example A symbolic dependency.
|
181
|
+
# target.depends :compiler
|
144
182
|
def depends(*names, **options)
|
145
183
|
names.each do |name|
|
146
184
|
dependencies << Depends.new(name, **options)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Copyright, 2017, 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 'set'
|
22
|
+
|
23
|
+
module Build
|
24
|
+
module Dependency
|
25
|
+
class UnresolvedDependencyError < StandardError
|
26
|
+
def initialize(chain)
|
27
|
+
super "Unresolved dependency chain: #{chain.unresolved.inspect}!"
|
28
|
+
|
29
|
+
@chain = chain
|
30
|
+
end
|
31
|
+
|
32
|
+
attr :chain
|
33
|
+
end
|
34
|
+
|
35
|
+
TOP = Depends.new("<top>").freeze
|
36
|
+
|
37
|
+
class Resolver
|
38
|
+
def initialize
|
39
|
+
@resolved = {}
|
40
|
+
@ordered = []
|
41
|
+
@provisions = []
|
42
|
+
@unresolved = []
|
43
|
+
@conflicts = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
attr :resolved
|
47
|
+
attr :ordered
|
48
|
+
attr :provisions
|
49
|
+
attr :unresolved
|
50
|
+
attr :conflicts
|
51
|
+
|
52
|
+
def freeze
|
53
|
+
return unless frozen?
|
54
|
+
|
55
|
+
@resolved.freeze
|
56
|
+
@ordered.freeze
|
57
|
+
@provisions.freeze
|
58
|
+
@unresolved.freeze
|
59
|
+
@conflicts.freeze
|
60
|
+
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def expand_nested(dependencies, provider)
|
67
|
+
dependencies.each do |dependency|
|
68
|
+
expand(Depends[dependency], provider)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def expand_provision(provision, dependency)
|
73
|
+
provider = provision.provider
|
74
|
+
|
75
|
+
# If the provision was an Alias, make sure to resolve the alias first:
|
76
|
+
if provision.alias?
|
77
|
+
# puts "** Resolving alias #{provision} (#{provision.dependencies.inspect})"
|
78
|
+
expand_nested(provision.dependencies, provider)
|
79
|
+
end
|
80
|
+
|
81
|
+
# puts "** Checking for #{provider.inspect} in #{resolved.inspect}"
|
82
|
+
unless @resolved.include?(provider)
|
83
|
+
# We are now satisfying the provider by expanding all its own dependencies:
|
84
|
+
@resolved[provider] = provision
|
85
|
+
|
86
|
+
# Make sure we satisfy the provider's dependencies first:
|
87
|
+
expand_nested(provider.dependencies, provider)
|
88
|
+
|
89
|
+
# puts "** Appending #{dependency} -> ordered"
|
90
|
+
|
91
|
+
# Add the provider to the ordered list.
|
92
|
+
@ordered << Resolution.new(provision, dependency)
|
93
|
+
end
|
94
|
+
|
95
|
+
# This goes here because we want to ensure 1/ that if
|
96
|
+
unless provision == nil or provision.alias?
|
97
|
+
# puts "** Appending #{dependency} -> provisions"
|
98
|
+
|
99
|
+
# Add the provision to the set of required provisions.
|
100
|
+
@provisions << provision
|
101
|
+
end
|
102
|
+
|
103
|
+
# For both @ordered and @provisions, we ensure that for [...xs..., x, ...], x is satisfied by ...xs....
|
104
|
+
end
|
105
|
+
|
106
|
+
def expand(dependency, parent)
|
107
|
+
# puts "** Expanding #{dependency.inspect} from #{parent.inspect} (private: #{dependency.private?})"
|
108
|
+
|
109
|
+
if @resolved.include?(dependency)
|
110
|
+
# puts "** Already resolved dependency!"
|
111
|
+
|
112
|
+
return nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# The find_provider method is abstract in this base class.
|
116
|
+
provisions = expand_dependency(dependency, parent)
|
117
|
+
|
118
|
+
if provisions.empty?
|
119
|
+
# puts "** Couldn't resolve #{dependency}"
|
120
|
+
@unresolved << [dependency, parent]
|
121
|
+
else
|
122
|
+
# We will now satisfy this dependency by satisfying any dependent dependencies, but we no longer need to revisit this one.
|
123
|
+
# puts "** Resolved #{dependency}"
|
124
|
+
@resolved[dependency] = provisions
|
125
|
+
|
126
|
+
provisions.each do |provision|
|
127
|
+
expand_provision(provision, dependency)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -49,7 +49,13 @@ RSpec.describe Build::Dependency do
|
|
49
49
|
|
50
50
|
it "should resolve direct dependency chain" do
|
51
51
|
chain = Build::Dependency::Chain.expand(['fruit-juice'], [a, b, c])
|
52
|
-
expect(chain.ordered.collect(&:
|
52
|
+
expect(chain.ordered.collect(&:provider)).to be == [a, b, c]
|
53
|
+
expect(chain.unresolved).to be == []
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should resolve wildcard dependency chain" do
|
57
|
+
chain = Build::Dependency::Chain.expand(['fruit-*'], [a, b, c])
|
58
|
+
expect(chain.ordered.collect(&:provider)).to be == [a, b, c]
|
53
59
|
expect(chain.unresolved).to be == []
|
54
60
|
end
|
55
61
|
|
@@ -64,7 +70,7 @@ RSpec.describe Build::Dependency do
|
|
64
70
|
chain = Build::Dependency::Chain.expand(['pie'], [a, b, c, d])
|
65
71
|
|
66
72
|
expect(chain.unresolved).to be == []
|
67
|
-
expect(chain.ordered.collect(&:
|
73
|
+
expect(chain.ordered.collect(&:provider)).to be == [a, d]
|
68
74
|
end
|
69
75
|
|
70
76
|
it "should format nicely" do
|
@@ -156,8 +162,8 @@ RSpec.describe Build::Dependency do
|
|
156
162
|
expect(chain.conflicts).to be == {}
|
157
163
|
|
158
164
|
expect(chain.ordered.size).to be == 2
|
159
|
-
expect(chain.ordered[0]).to be ==
|
160
|
-
expect(chain.ordered[1]).to be ==
|
165
|
+
expect(chain.ordered[0].provider).to be == apple
|
166
|
+
expect(chain.ordered[1].provider).to be == salad
|
161
167
|
end
|
162
168
|
|
163
169
|
it "should select dependencies with high priority" do
|
@@ -175,7 +181,9 @@ RSpec.describe Build::Dependency do
|
|
175
181
|
expect(chain.conflicts).to be == {}
|
176
182
|
|
177
183
|
# Should select higher priority package by default:
|
178
|
-
expect(chain.ordered).to be == [
|
184
|
+
expect(chain.ordered).to be == [good_apple.resolution_for(
|
185
|
+
Build::Dependency::Depends['apple']
|
186
|
+
)]
|
179
187
|
end
|
180
188
|
|
181
189
|
it "should expose direct dependencies" do
|
@@ -200,9 +208,9 @@ RSpec.describe Build::Dependency do
|
|
200
208
|
expect(chain.unresolved).to be == []
|
201
209
|
expect(chain.conflicts).to be == {}
|
202
210
|
expect(chain.ordered).to be == [
|
203
|
-
|
204
|
-
|
205
|
-
|
211
|
+
system.resolution_for(Build::Dependency::Depends.new('clang')),
|
212
|
+
library.resolution_for(Build::Dependency::Depends.new('library')),
|
213
|
+
application.resolution_for(Build::Dependency::Depends.new('application')),
|
206
214
|
]
|
207
215
|
end
|
208
216
|
end
|
@@ -26,11 +26,11 @@ RSpec.describe Build::Dependency::PartialChain do
|
|
26
26
|
|
27
27
|
it "should generate full list of ordered providers" do
|
28
28
|
expect(chain.ordered).to be == [
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
variant.resolution_for(Build::Dependency::Depends.new('Variant/debug')),
|
30
|
+
platform.resolution_for(Build::Dependency::Depends.new('Platform/linux')),
|
31
|
+
compiler.resolution_for(Build::Dependency::Depends.new("Language/C++17")),
|
32
|
+
lib.resolution_for(Build::Dependency::Depends.new('lib')),
|
33
|
+
app.resolution_for(Build::Dependency::Depends.new('app')),
|
34
34
|
]
|
35
35
|
end
|
36
36
|
|
@@ -53,10 +53,10 @@ RSpec.describe Build::Dependency::PartialChain do
|
|
53
53
|
|
54
54
|
it "should select app packages" do
|
55
55
|
expect(subject.ordered).to be == [
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
variant.resolution_for(Build::Dependency::Depends.new('Variant/debug')),
|
57
|
+
platform.resolution_for(Build::Dependency::Depends.new('Platform/linux')),
|
58
|
+
lib.resolution_for(Build::Dependency::Depends.new('lib')),
|
59
|
+
compiler.resolution_for(Build::Dependency::Depends.new("Language/C++14")),
|
60
60
|
]
|
61
61
|
|
62
62
|
graph = visualization.generate(subject)
|
@@ -98,19 +98,19 @@ RSpec.describe Build::Dependency::PartialChain do
|
|
98
98
|
it "should include direct private dependencies" do
|
99
99
|
partial_chain = chain.partial(b)
|
100
100
|
|
101
|
-
expect(partial_chain.ordered.collect(&:
|
101
|
+
expect(partial_chain.ordered.collect(&:provider)).to be == [a]
|
102
102
|
end
|
103
103
|
|
104
104
|
it "shouldn't include nested private dependencies" do
|
105
105
|
partial_chain = chain.partial(c)
|
106
106
|
|
107
|
-
expect(partial_chain.ordered.collect(&:
|
107
|
+
expect(partial_chain.ordered.collect(&:provider)).to be == [b]
|
108
108
|
end
|
109
109
|
|
110
110
|
it "should follow non-private dependencies" do
|
111
111
|
partial_chain = chain.partial(d)
|
112
112
|
|
113
|
-
expect(partial_chain.ordered.collect(&:
|
113
|
+
expect(partial_chain.ordered.collect(&:provider)).to be == [b, c]
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
@@ -55,7 +55,7 @@ RSpec.describe Build::Dependency::Provider do
|
|
55
55
|
end
|
56
56
|
|
57
57
|
it "should format nicely" do
|
58
|
-
expect(subject.to_s).to be == '
|
58
|
+
expect(subject.to_s).to be == 'provides "c"'
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -83,7 +83,7 @@ RSpec.describe Build::Dependency::Provider do
|
|
83
83
|
end
|
84
84
|
|
85
85
|
it "should format nicely" do
|
86
|
-
expect(subject.to_s).to be == '
|
86
|
+
expect(subject.to_s).to be == 'provides :platform -> "linux"'
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Copyright, 2017, 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
|
+
RSpec.describe Build::Dependency do
|
22
|
+
describe "test packages" do
|
23
|
+
let(:a) do
|
24
|
+
Package.new('Library/Frobulate').tap do |package|
|
25
|
+
package.provides 'Test/Frobulate' do
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:b) do
|
31
|
+
Package.new('Library/Barbulate').tap do |package|
|
32
|
+
package.provides 'Test/Barbulate' do
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should resolve all tests" do
|
38
|
+
chain = Build::Dependency::Chain.expand(['Test/*'], [a, b])
|
39
|
+
expect(chain.ordered.collect(&:provider)).to be == [a, b]
|
40
|
+
expect(chain.unresolved).to be == []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: build-dependency
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphviz
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 3.
|
47
|
+
version: '3.6'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.
|
54
|
+
version: '3.6'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,11 +81,13 @@ files:
|
|
81
81
|
- README.md
|
82
82
|
- Rakefile
|
83
83
|
- build-dependency.gemspec
|
84
|
+
- build_system_rules_and_algorithms.pdf
|
84
85
|
- full.svg
|
85
86
|
- lib/build/dependency.rb
|
86
87
|
- lib/build/dependency/chain.rb
|
87
88
|
- lib/build/dependency/partial_chain.rb
|
88
89
|
- lib/build/dependency/provider.rb
|
90
|
+
- lib/build/dependency/resolver.rb
|
89
91
|
- lib/build/dependency/version.rb
|
90
92
|
- lib/build/dependency/visualization.rb
|
91
93
|
- partial.svg
|
@@ -93,6 +95,7 @@ files:
|
|
93
95
|
- spec/build/dependency/partial_chain_spec.rb
|
94
96
|
- spec/build/dependency/provider_spec.rb
|
95
97
|
- spec/build/dependency/visualization_spec.rb
|
98
|
+
- spec/build/dependency/wildcard_spec.rb
|
96
99
|
- spec/spec_helper.rb
|
97
100
|
- visualization.svg
|
98
101
|
homepage: ''
|
@@ -115,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
118
|
version: '0'
|
116
119
|
requirements: []
|
117
120
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.6.
|
121
|
+
rubygems_version: 2.6.12
|
119
122
|
signing_key:
|
120
123
|
specification_version: 4
|
121
124
|
summary: A set of data structures and algorithms for dependency resolution.
|
@@ -124,4 +127,5 @@ test_files:
|
|
124
127
|
- spec/build/dependency/partial_chain_spec.rb
|
125
128
|
- spec/build/dependency/provider_spec.rb
|
126
129
|
- spec/build/dependency/visualization_spec.rb
|
130
|
+
- spec/build/dependency/wildcard_spec.rb
|
127
131
|
- spec/spec_helper.rb
|