simp-rake-helpers 1.3.0 → 2.0.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 +8 -8
- data/lib/simp/rake/build/build.rb +3 -3
- data/lib/simp/rake/build/deps.rb +219 -161
- data/lib/simp/rake/build/pkg.rb +1 -1
- data/lib/simp/rake/helpers/version.rb +1 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NjAwZjEzY2M5ODE0ODg3Y2E5NmI5ODcyYWY0OTc5OGQzNTNmMzc2YQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZDkwMzgzZGZiNWQ4YWI2NWMwM2Q3YjQ3MWZjY2JiMzM2MzcyN2FjMg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
N2UyNmI5ZjBiMGMzMDFjN2QyMmU0ODYyM2UwZGQ5ZDc4ODc4N2EzMDljOThm
|
10
|
+
NmQ1MDFlZDNmODQ1MWU2ZDdmNDZhNmQ1OWM0ODlmMzEwOTdjYjJkYmQ2ZDBl
|
11
|
+
MDZkZjFiNmVjMmVhZjRjNTU0YzI3Zjg1MzI1YmZkOWZhOGE2OWQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Yzk0YjMyNzZlNTc1MzU0MTlmZThkMTU1ODQwMmQ2MzlmNDM3NDdkNDZhNjZl
|
14
|
+
Mjk0MWM5YzNiZmUwZjcxNDA0ZmNmYzgwZDNhY2UxMmM5Y2YwYmFlMjA5M2U3
|
15
|
+
ZmIzMGU2YjNmNTMyYjRhZTY3OTA0NTQzNjNmNWJjZWNmYjk0ODk=
|
@@ -40,10 +40,10 @@ module Simp::Rake::Build
|
|
40
40
|
verbose = args.verbose == 'false' ? false : true
|
41
41
|
|
42
42
|
# Grab all currently tracked submodules.
|
43
|
-
|
44
|
-
$modules = fake_lp.modules
|
43
|
+
r10k = R10KHelper.new("Puppetfile.#{args[:method]}")
|
45
44
|
mod_list = []
|
46
|
-
|
45
|
+
r10k.each_module do |mod|
|
46
|
+
path = mod[:path]
|
47
47
|
if Dir.exists?(path)
|
48
48
|
mod_list.push(path)
|
49
49
|
end
|
data/lib/simp/rake/build/deps.rb
CHANGED
@@ -2,104 +2,162 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
|
5
|
-
class
|
6
|
-
attr_reader :forge, :environment
|
5
|
+
class R10KHelper
|
7
6
|
attr_accessor :puppetfile
|
8
|
-
|
7
|
+
attr_accessor :modules
|
8
|
+
attr_accessor :basedir
|
9
9
|
|
10
|
-
require '
|
11
|
-
include Librarian::Puppet::Util
|
10
|
+
require 'r10k/puppetfile'
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
# Horrible, but we need to be able to manipulate the cache
|
13
|
+
class R10K::Git::ShellGit::ThinRepository
|
14
|
+
def cache_repo
|
15
|
+
@cache_repo
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
# Return true if the repository has local modifications, false otherwise.
|
19
|
+
def dirty?
|
20
|
+
repo_status = false
|
21
|
+
|
22
|
+
return repo_status unless File.directory?(path)
|
23
|
+
|
24
|
+
Dir.chdir(path) do
|
25
|
+
%x(git update-index -q --ignore-submodules --refresh)
|
26
|
+
repo_status = "Could not update git index for '#{path}'" unless $?.success?
|
27
|
+
|
28
|
+
unless repo_status
|
29
|
+
%x(git diff-files --quiet --ignore-submodules --)
|
30
|
+
repo_status = "'#{path}' has unstaged changes" unless $?.success?
|
31
|
+
end
|
32
|
+
|
33
|
+
unless repo_status
|
34
|
+
%x(git diff-index --cached --quiet HEAD --ignore-submodules --)
|
35
|
+
repo_status = "'#{path}' has uncommitted changes" unless $?.success?
|
36
|
+
end
|
37
|
+
|
38
|
+
unless repo_status
|
39
|
+
untracked_files = %x(git ls-files -o -d --exclude-standard)
|
20
40
|
|
21
|
-
|
22
|
-
|
41
|
+
if $?.success?
|
42
|
+
unless untracked_files.empty?
|
43
|
+
untracked_files.strip!
|
44
|
+
|
45
|
+
if untracked_files.lines.count > 0
|
46
|
+
repo_status = "'#{path}' has untracked files"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
else
|
50
|
+
# We should never get here
|
51
|
+
raise Error, "Failure running 'git ls-files -o -d --exclude-standard' at '#{path}'"
|
52
|
+
end
|
53
|
+
end
|
23
54
|
end
|
55
|
+
|
56
|
+
repo_status
|
24
57
|
end
|
25
58
|
end
|
26
59
|
|
27
|
-
def
|
28
|
-
@modules
|
60
|
+
def initialize(puppetfile)
|
61
|
+
@modules = []
|
62
|
+
@basedir = File.dirname(File.expand_path(puppetfile))
|
63
|
+
|
64
|
+
Dir.chdir(@basedir) do
|
29
65
|
|
30
|
-
|
31
|
-
|
32
|
-
|
66
|
+
R10K::Git::Cache.settings[:cache_root] = File.join(@basedir,'.r10k_cache')
|
67
|
+
|
68
|
+
unless File.directory?(R10K::Git::Cache.settings[:cache_root])
|
69
|
+
FileUtils.mkdir_p(R10K::Git::Cache.settings[:cache_root])
|
70
|
+
end
|
71
|
+
|
72
|
+
r10k = R10K::Puppetfile.new(Dir.pwd, nil, puppetfile).load!
|
73
|
+
|
74
|
+
@modules = r10k.entries.collect do |mod|
|
75
|
+
mod_status = mod.repo.repo.dirty?
|
76
|
+
|
77
|
+
mod = {
|
78
|
+
:name => mod.name,
|
79
|
+
:path => mod.path.to_s,
|
80
|
+
:git_source => mod.repo.repo.origin,
|
81
|
+
:git_ref => mod.repo.head,
|
82
|
+
:module_dir => mod.basedir,
|
83
|
+
:status => mod_status ? mod_status : :known,
|
84
|
+
:r10k_module => mod,
|
85
|
+
:r10k_cache => mod.repo.repo.cache_repo
|
86
|
+
}
|
87
|
+
end
|
33
88
|
end
|
34
89
|
|
35
|
-
@modules
|
36
|
-
|
90
|
+
module_dirs = @modules.collect do |mod|
|
91
|
+
mod = mod[:module_dir]
|
92
|
+
end
|
37
93
|
|
38
|
-
|
39
|
-
# Puppetfile
|
40
|
-
def unknown_modules
|
41
|
-
known_modules = []
|
42
|
-
all_modules = Dir.glob(File.join(@environment.install_path,'*')).map{|x| x = File.basename(x)}
|
94
|
+
module_dirs.uniq!
|
43
95
|
|
44
|
-
|
45
|
-
|
96
|
+
module_dirs.each do |module_dir|
|
97
|
+
known_modules = @modules.select do |mod|
|
98
|
+
mod[:module_dir] == module_dir
|
99
|
+
end
|
46
100
|
|
47
|
-
|
48
|
-
|
49
|
-
known_modules << module_name(name)
|
101
|
+
known_modules.map! do |mod|
|
102
|
+
mod = mod[:name]
|
50
103
|
end
|
51
|
-
end
|
52
104
|
|
53
|
-
|
54
|
-
|
55
|
-
x = File.join(relative_path,x)
|
56
|
-
else
|
57
|
-
x = nil
|
105
|
+
current_modules = Dir.glob(File.join(module_dir,'*')).map do |mod|
|
106
|
+
mod = File.basename(mod)
|
58
107
|
end
|
59
|
-
end
|
60
108
|
|
61
|
-
|
109
|
+
(current_modules - known_modules).each do |mod|
|
110
|
+
# Did we find random git repos in our module spaces?
|
111
|
+
if File.exist?(File.join(module_dir, mod, '.git'))
|
112
|
+
@modules << {
|
113
|
+
:name => mod,
|
114
|
+
:path => File.join(module_dir, mod),
|
115
|
+
:module_dir => module_dir,
|
116
|
+
:status => :unknown,
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
62
121
|
end
|
63
122
|
|
64
123
|
def puppetfile
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
124
|
+
last_module_dir = nil
|
125
|
+
pupfile = Array.new
|
126
|
+
|
127
|
+
@modules.each do |mod|
|
128
|
+
module_dir = mod[:path].split(@basedir.to_s).last.split('/')[1..-2].join('/')
|
129
|
+
|
130
|
+
if last_module_dir != module_dir
|
131
|
+
pupfile << "moduledir '#{module_dir}'\n"
|
132
|
+
last_module_dir = module_dir
|
133
|
+
end
|
134
|
+
|
135
|
+
pupfile << "mod '#{mod[:git_source].split('/').last}',"
|
136
|
+
pupfile << " :git => '#{mod[:git_source]}'},"
|
137
|
+
pupfile << " :ref => '#{mod[:r10k_module].repo.head}'}\n"
|
70
138
|
end
|
71
|
-
|
139
|
+
|
140
|
+
pupfile << '# vim: ai ts=2 sts=2 et sw=2 ft=ruby'
|
141
|
+
|
142
|
+
pupfile.join("\n")
|
72
143
|
end
|
73
144
|
|
74
145
|
def each_module(&block)
|
75
|
-
Dir.chdir(@
|
76
|
-
modules.each do |
|
146
|
+
Dir.chdir(@basedir) do
|
147
|
+
@modules.each do |mod|
|
77
148
|
# This works for Puppet Modules
|
78
|
-
path = File.expand_path(module_name(name),environment.install_path)
|
79
|
-
unless File.directory?(path)
|
80
|
-
# This works for everything else
|
81
|
-
if mod[:path]
|
82
|
-
path = File.expand_path(mod[:path],environment.project_path)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
unless File.directory?(path)
|
86
|
-
$stderr.puts("Warning: Could not find path for module '#{name}'...skipping")
|
87
|
-
next
|
88
|
-
end
|
89
149
|
|
90
|
-
block.call(
|
150
|
+
block.call(mod)
|
91
151
|
end
|
92
152
|
end
|
93
153
|
end
|
94
154
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
def forge(forge)
|
102
|
-
@forge = forge
|
155
|
+
def unknown_modules
|
156
|
+
@modules.select do |mod|
|
157
|
+
mod[:status] == :unknown
|
158
|
+
end.map do |mod|
|
159
|
+
mod = mod[:name]
|
160
|
+
end
|
103
161
|
end
|
104
162
|
end
|
105
163
|
|
@@ -116,13 +174,12 @@ module Simp::Rake::Build
|
|
116
174
|
define_tasks
|
117
175
|
end
|
118
176
|
|
119
|
-
# define rake tasks
|
120
177
|
def define_tasks
|
121
178
|
namespace :deps do
|
122
179
|
desc <<-EOM
|
123
180
|
Checks out all dependency repos.
|
124
181
|
|
125
|
-
This task
|
182
|
+
This task used R10k to update all dependencies.
|
126
183
|
|
127
184
|
Arguments:
|
128
185
|
* :method => The update method to use (Default => 'tracking')
|
@@ -132,86 +189,29 @@ module Simp::Rake::Build
|
|
132
189
|
task :checkout, [:method] do |t,args|
|
133
190
|
args.with_defaults(:method => 'tracking')
|
134
191
|
|
135
|
-
|
136
|
-
FileUtils.ln_s( "Puppetfile.#{args[:method]}", 'Puppetfile', :force => true )
|
137
|
-
Bundler.with_clean_env do
|
138
|
-
sh 'bundle exec librarian-puppet-pr328 install --use-forge=false'
|
139
|
-
end
|
140
|
-
FileUtils.remove_entry_secure "Puppetfile"
|
141
|
-
end
|
192
|
+
r10k_helper = R10KHelper.new("Puppetfile.#{args[:method]}")
|
142
193
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
Arguments:
|
147
|
-
* :ref => The git ref to use as the oldest point for all logs.
|
148
|
-
EOM
|
149
|
-
task :changelog, [:ref] do |t,args|
|
150
|
-
method = 'tracking'
|
151
|
-
|
152
|
-
git_logs = Hash.new
|
153
|
-
|
154
|
-
Dir.chdir(@base_dir) do
|
155
|
-
|
156
|
-
ref = args[:ref]
|
157
|
-
refdate = nil
|
158
|
-
begin
|
159
|
-
refdate = %x(git log -1 --format=%ai #{ref}).chomp
|
160
|
-
refdate = nil unless $?.success?
|
161
|
-
rescue Exception
|
162
|
-
#noop
|
194
|
+
r10k_helper.each_module do |mod|
|
195
|
+
unless File.directory?(mod[:path])
|
196
|
+
FileUtils.mkdir_p(mod[:path])
|
163
197
|
end
|
164
198
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
# Need to fake this one to run at the top level
|
172
|
-
base_module = {
|
173
|
-
'simp-core' => {
|
174
|
-
:git => 'undef',
|
175
|
-
:ref => ref,
|
176
|
-
:path => '.'
|
177
|
-
}
|
178
|
-
}
|
179
|
-
|
180
|
-
# Gather up the top level first
|
181
|
-
log_output = %x(git log --since='#{refdate}' --stat --reverse).chomp
|
182
|
-
git_logs['__SIMP CORE__'] = log_output unless log_output.strip.empty?
|
183
|
-
|
184
|
-
fake_lp.each_module do |environment, name, path|
|
185
|
-
unless File.directory?(path)
|
186
|
-
$stderr.puts("Warning: '#{path}' is not a module...skipping")
|
187
|
-
next
|
199
|
+
# Since r10k is destructive, we're enumerating all valid states
|
200
|
+
# here
|
201
|
+
if [:absent, :mismatched, :outdated].include?(mod[:r10k_module].status)
|
202
|
+
unless mod[:r10k_cache].synced?
|
203
|
+
mod[:r10k_cache].sync
|
188
204
|
end
|
189
205
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
git_logs[name] = log_output unless log_output.strip.empty?
|
197
|
-
end
|
206
|
+
if mod[:status] == :known
|
207
|
+
mod[:r10k_module].sync
|
208
|
+
else
|
209
|
+
# If we get here, the module was dirty and should be skipped
|
210
|
+
puts "#{mod[:name]}: Skipping - #{mod[:status]}"
|
211
|
+
next
|
198
212
|
end
|
199
|
-
end
|
200
|
-
|
201
|
-
if git_logs.empty?
|
202
|
-
puts "No changes found for any components since '#{refdate}'"
|
203
213
|
else
|
204
|
-
|
205
|
-
|
206
|
-
git_logs.keys.sort.each do |mod_name|
|
207
|
-
puts <<-EOM
|
208
|
-
========
|
209
|
-
#{mod_name}:
|
210
|
-
|
211
|
-
#{git_logs[mod_name].gsub(/^/,' ')}
|
212
|
-
|
213
|
-
EOM
|
214
|
-
end
|
214
|
+
puts "#{mod[:name]}: Skipping - Unknown status type #{mod[:r10k_module].status}"
|
215
215
|
end
|
216
216
|
end
|
217
217
|
end
|
@@ -226,27 +226,26 @@ module Simp::Rake::Build
|
|
226
226
|
EOM
|
227
227
|
task :status, [:method] do |t,args|
|
228
228
|
args.with_defaults(:method => 'tracking')
|
229
|
-
Dir.chdir(@base_dir)
|
230
229
|
@dirty_repos = nil
|
231
230
|
|
232
|
-
|
231
|
+
r10k_helper = R10KHelper.new("Puppetfile.#{args[:method]}")
|
232
|
+
|
233
233
|
mods_with_changes = {}
|
234
234
|
|
235
|
-
|
236
|
-
unless File.directory?(path)
|
237
|
-
$stderr.puts("Warning: '#{path}' is not a module...skipping")
|
235
|
+
r10k_helper.each_module do |mod|
|
236
|
+
unless File.directory?(mod[:path])
|
237
|
+
$stderr.puts("Warning: '#{mod[:path]}' is not a module...skipping")
|
238
238
|
next
|
239
239
|
end
|
240
240
|
|
241
|
-
|
242
|
-
if repo.dirty?
|
241
|
+
if mod[:status] != :known
|
243
242
|
# Clean up the path a bit for printing
|
244
|
-
dirty_path = path.split(
|
243
|
+
dirty_path = mod[:path].split(r10k_helper.basedir.to_s).last
|
245
244
|
if dirty_path[0].chr == File::SEPARATOR
|
246
245
|
dirty_path[0] = ''
|
247
246
|
end
|
248
247
|
|
249
|
-
mods_with_changes[name] = dirty_path
|
248
|
+
mods_with_changes[mod[:name]] = dirty_path
|
250
249
|
end
|
251
250
|
end
|
252
251
|
|
@@ -260,28 +259,87 @@ module Simp::Rake::Build
|
|
260
259
|
@dirty_repos = true
|
261
260
|
end
|
262
261
|
|
263
|
-
unknown_mods =
|
262
|
+
unknown_mods = r10k_helper.unknown_modules
|
264
263
|
unless unknown_mods.empty?
|
265
264
|
puts "The following modules were unknown:"
|
266
265
|
puts unknown_mods.map{|k,v| " ? #{k}"}.join("\n")
|
267
266
|
end
|
268
267
|
end
|
269
268
|
|
270
|
-
desc
|
271
|
-
|
272
|
-
fake_lp = FakeLibrarian.new('Puppetfile.tracking')
|
273
|
-
modules = fake_lp.modules
|
269
|
+
desc <<-EOM
|
270
|
+
Records the current dependencies into Puppetfile.stable.
|
274
271
|
|
275
|
-
|
276
|
-
|
277
|
-
|
272
|
+
Arguments:
|
273
|
+
* :source => The source Puppetfile to use (Default => 'tracking')
|
274
|
+
EOM
|
275
|
+
task :record, [:method] do |t,args|
|
276
|
+
args.with_defaults(:source => 'tracking')
|
277
|
+
r10k_helper = R10KHelper.new("Puppetfile.#{args[:source]}")
|
278
|
+
|
279
|
+
File.open('Puppetfile.stable','w'){|f| f.puts r10k_helper.puppetfile }
|
280
|
+
end
|
281
|
+
|
282
|
+
desc <<-EOM
|
283
|
+
Provide a log of changes to all modules from the given top level Git reference.
|
284
|
+
|
285
|
+
Arguments:
|
286
|
+
* :ref => The top level git ref to use as the oldest point for all logs.
|
287
|
+
* :source => The source Puppetfile to use (Default => 'tracking')
|
288
|
+
EOM
|
289
|
+
task :changelog, [:ref] do |t,args|
|
290
|
+
args.with_defaults(:source => 'tracking')
|
291
|
+
|
292
|
+
r10k_helper = R10KHelper.new("Puppetfile.#{args[:source]}")
|
293
|
+
|
294
|
+
git_logs = Hash.new
|
295
|
+
|
296
|
+
Dir.chdir(r10k_helper.basedir) do
|
297
|
+
ref = args[:ref]
|
298
|
+
refdate = nil
|
299
|
+
begin
|
300
|
+
refdate = %x(git log -1 --format=%ai '#{ref}')
|
301
|
+
refdate = nil unless $?.success?
|
302
|
+
rescue Exception
|
303
|
+
#noop
|
304
|
+
end
|
305
|
+
|
306
|
+
fail("You must specify a valid reference") unless ref
|
307
|
+
fail("Could not find a Git log for #{ref}") unless refdate
|
308
|
+
|
309
|
+
mods_with_changes = {}
|
310
|
+
|
311
|
+
log_output = %x(git log --since='#{refdate}' --stat --reverse).chomp
|
312
|
+
git_logs['__SIMP CORE__'] = log_output unless log_output.strip.empty?
|
313
|
+
|
314
|
+
r10k_helper.each_module do |mod|
|
315
|
+
if File.directory?(mod[:path])
|
316
|
+
Dir.chdir(mod[:path]) do
|
317
|
+
log_output = %x(git log --since='#{refdate}' --stat --reverse).chomp
|
318
|
+
git_logs[mod[:name]] = log_output unless log_output.strip.empty?
|
319
|
+
end
|
320
|
+
end
|
278
321
|
end
|
279
|
-
end
|
280
322
|
|
281
|
-
|
282
|
-
|
323
|
+
if git_logs.empty?
|
324
|
+
puts( "No changes found for any components since #{refdate}")
|
325
|
+
else
|
326
|
+
page
|
327
|
+
|
328
|
+
git_logs.keys.sort.each do |mod_name|
|
329
|
+
puts <<-EOM
|
330
|
+
========
|
331
|
+
#{mod_name}:
|
332
|
+
|
333
|
+
#{git_logs[mod_name].gsub(/^/,' ')}
|
334
|
+
|
335
|
+
EOM
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
283
339
|
end
|
284
340
|
end
|
285
341
|
end
|
286
342
|
end
|
287
343
|
end
|
344
|
+
|
345
|
+
# vim: ai ts=2 sts=2 et sw=2 ft=ruby
|
data/lib/simp/rake/build/pkg.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simp-rake-helpers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Tessmer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-04-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -165,6 +165,20 @@ dependencies:
|
|
165
165
|
- - ~>
|
166
166
|
- !ruby/object:Gem::Version
|
167
167
|
version: '2.4'
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: r10k
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ~>
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '2.2'
|
175
|
+
type: :runtime
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ~>
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '2.2'
|
168
182
|
- !ruby/object:Gem::Dependency
|
169
183
|
name: pager
|
170
184
|
requirement: !ruby/object:Gem::Requirement
|