litbuild 1.0.14 → 1.0.19
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/lib/litbuild/ascii_doc_visitor.rb +2 -2
- data/lib/litbuild/bash_script_visitor.rb +19 -10
- data/lib/litbuild/blueprint.rb +28 -81
- data/lib/litbuild/blueprint_library.rb +88 -52
- data/lib/litbuild/blueprint_parser.rb +114 -44
- data/lib/litbuild/driver.rb +0 -3
- data/lib/litbuild/package.rb +1 -1
- data/lib/litbuild/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fdf64e3774cac3d1e67cd815c79d78e048bdc76fd4ea95ea83225f28bbe1779
|
4
|
+
data.tar.gz: a4e2e13fdebdbb7d7c069a93b10b255c511dae2f2818a81c3aaf9067a6404594
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f5e42e9df787b906f49770dac68b7de9f43b4fc18f9d75e3bd275a8a1efc7b4edf037d492220ea6557885be95ee66e3edc923cbe38ebc4f949754883b5e2465
|
7
|
+
data.tar.gz: 6107bb2f698c7644d96241b45d570d82bf66897e40148055cc1af4330578a8e00d2f7f75230f03a3846861c8c9cf688198e89dd9d8c84a1050bd66a73309e7aa
|
@@ -285,14 +285,14 @@ module Litbuild
|
|
285
285
|
def write_parameter(doc, params)
|
286
286
|
params.each do |param|
|
287
287
|
pname = param['name'].first
|
288
|
-
pdefault = param['default'].first.gsub(
|
288
|
+
pdefault = param['default'].first.gsub(/[\\\s]+/m, ' ')
|
289
289
|
default_string = if pdefault == '(empty)'
|
290
290
|
'not set'
|
291
291
|
else
|
292
292
|
"`#{pdefault}`"
|
293
293
|
end
|
294
294
|
|
295
|
-
value = @parameters[pname].gsub(
|
295
|
+
value = @parameters[pname].gsub(/[\\\s]+/m, ' ')
|
296
296
|
val_string = if value == ''
|
297
297
|
'not set'
|
298
298
|
else
|
@@ -176,6 +176,7 @@ module Litbuild
|
|
176
176
|
@written[location].map do |target|
|
177
177
|
script.puts("echo \"At $(#{DATECMD}): Beginning #{target}:\"")
|
178
178
|
script.puts("./#{target}")
|
179
|
+
script.puts("echo \"At $(#{DATECMD}): Completed #{target}:\"")
|
179
180
|
end
|
180
181
|
end
|
181
182
|
|
@@ -390,6 +391,8 @@ module Litbuild
|
|
390
391
|
options.puts("cat > ~#{package.pkgusr_name}/options <<'LBEOF'")
|
391
392
|
options.puts("export version=#{package.version}")
|
392
393
|
options.puts("export LB_SOURCE_DIR=#{quote(srcdir)}")
|
394
|
+
phase = package.active_phase || 'default'
|
395
|
+
options.puts("export LB_PHASE=#{quote(phase)}")
|
393
396
|
environment_commands(package).each { |cmd| options.puts(cmd) }
|
394
397
|
package.build_dir && options.puts("export build_dir=#{package.build_dir}")
|
395
398
|
render_intree_commands(package, options)
|
@@ -510,9 +513,7 @@ module Litbuild
|
|
510
513
|
def render_service_pipeline(script, spipe)
|
511
514
|
pname = spipe['name'].first
|
512
515
|
spipe['bundle']&.each do |bundle|
|
513
|
-
script
|
514
|
-
script.puts("grep -q '^#{pname}$' #{bundle}/contents || " \
|
515
|
-
"echo #{pname} >> #{bundle}/contents")
|
516
|
+
render_add_to_bundle(script, bundle, pname)
|
516
517
|
end
|
517
518
|
sdirs = spipe['servicedirs']
|
518
519
|
sdirs.each_with_index do |sdir, i|
|
@@ -533,17 +534,12 @@ module Litbuild
|
|
533
534
|
|
534
535
|
def render_service_dir(script, sdir)
|
535
536
|
sd = ServiceDir.new(sdir)
|
536
|
-
if sd.bundle
|
537
|
-
script.puts("grep -q '^#{sd.name}$' #{sd.bundle}/contents || " \
|
538
|
-
"echo #{sd.name} >> #{sd.bundle}/contents")
|
539
|
-
end
|
537
|
+
render_add_to_bundle(script, sd.bundle, sd.name) if sd.bundle
|
540
538
|
script.puts("mkdir -p #{sd.name}")
|
541
539
|
sd.oneline_files.keys.sort.each do |fn|
|
542
540
|
script.puts("echo #{sd.oneline_files[fn]} > #{sd.name}/#{fn}")
|
543
541
|
end
|
544
542
|
multiline = sd.multiline_files
|
545
|
-
deps = sd.dependencies
|
546
|
-
multiline['dependencies'] = [deps.join("\n")] unless deps.empty?
|
547
543
|
multiline.keys.sort.each do |filename|
|
548
544
|
# I always terminate here documents in litbuild-generated
|
549
545
|
# scripts with "LBEOF", partly because I'm thinking "Litbuild
|
@@ -561,9 +557,22 @@ module Litbuild
|
|
561
557
|
script.puts("echo '#{env[envvar]}' > #{sd.name}/env/#{envvar}")
|
562
558
|
end
|
563
559
|
end
|
560
|
+
unless sd.dependencies.empty?
|
561
|
+
script.puts("mkdir -p #{sd.name}/dependencies.d")
|
562
|
+
sd.dependencies.each do |dep|
|
563
|
+
script.puts("touch #{sd.name}/dependencies.d/#{dep}")
|
564
|
+
end
|
565
|
+
end
|
564
566
|
skip_line(script)
|
565
567
|
end
|
566
568
|
|
569
|
+
def render_add_to_bundle(script, bundle, svc)
|
570
|
+
script.puts("mkdir -p #{bundle}")
|
571
|
+
script.puts("echo bundle > #{bundle}/type")
|
572
|
+
script.puts("mkdir -p #{bundle}/contents.d")
|
573
|
+
script.puts("touch #{bundle}/contents.d/#{svc}")
|
574
|
+
end
|
575
|
+
|
567
576
|
def render_cfgrepo_trailer(script, blueprint, log)
|
568
577
|
cfgs = blueprint['configuration-files']
|
569
578
|
return unless cfgs
|
@@ -574,7 +583,7 @@ module Litbuild
|
|
574
583
|
end
|
575
584
|
render_command(script, 'cfggit stageall', log)
|
576
585
|
bp = "#{blueprint.class.name.split('::').last} #{blueprint.name}"
|
577
|
-
cmd = "cfggit as-
|
586
|
+
cmd = "cfggit as-lb -m 'Configuration files for #{bp}'"
|
578
587
|
render_command(script, cmd, log)
|
579
588
|
end
|
580
589
|
|
data/lib/litbuild/blueprint.rb
CHANGED
@@ -9,13 +9,15 @@ require 'litbuild/visitor'
|
|
9
9
|
module Litbuild
|
10
10
|
# Blueprints are described in the `doc` directory.
|
11
11
|
#
|
12
|
-
# tl;dr:
|
13
|
-
# lines. Each chunk can be either directives or narrative. Narrative
|
14
|
-
# is AsciiDoc and does not get parsed or transformed. Directives are
|
15
|
-
# basically a simplified version of YAML.
|
12
|
+
# tl;dr:
|
16
13
|
#
|
17
|
-
#
|
18
|
-
#
|
14
|
+
# Blueprints are considered as "chunks" separated by blank lines.
|
15
|
+
# Each chunk can be either directives or narrative.
|
16
|
+
# Narrative is AsciiDoc and does not get parsed or transformed.
|
17
|
+
# Directives are basically a simplified version of YAML.
|
18
|
+
#
|
19
|
+
# After parsing, the narrative is found in the `grafs` variables
|
20
|
+
# and directives are found in `directive` hashes.
|
19
21
|
class Blueprint
|
20
22
|
class << self
|
21
23
|
# This should simply be the name of the directory where blueprints
|
@@ -31,45 +33,29 @@ module Litbuild
|
|
31
33
|
ObjectSpace.each_object(Class).select { |c| c < self }
|
32
34
|
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
def initialize(text:)
|
37
|
-
parser = BlueprintParser.new(text)
|
36
|
+
def initialize(text:, parameters:, logfile_namer:, library:)
|
37
|
+
parser = BlueprintParser.new(file_text: text, parameters: parameters)
|
38
38
|
@base_directives = parser.base_directives
|
39
39
|
@phase_directives = parser.phase_directives
|
40
40
|
@base_grafs = parser.base_grafs
|
41
41
|
@phase_grafs = parser.phase_grafs
|
42
42
|
@active_phase = @base_directives['default-phase']&.first
|
43
|
-
end
|
44
|
-
|
45
|
-
# This prepares a blueprint for use. This is separate from
|
46
|
-
# initialize because parameters are not available until after all
|
47
|
-
# blueprints have been parsed, and the logfile namer is not
|
48
|
-
# available until after the LOGFILE_DIR parameter can be resolved.
|
49
|
-
#
|
50
|
-
# Parameters is a set of configuration parameters
|
51
|
-
# Logfile_namer is used to generate log file names
|
52
|
-
# Library is the BlueprintLibrary, used (for example) to find
|
53
|
-
# dependencies.
|
54
|
-
def prepare(parameters:, logfile_namer:, library:)
|
55
43
|
@logfile_namer = logfile_namer
|
56
44
|
@bp_library = library
|
57
|
-
[@base_directives,
|
58
|
-
@phase_directives,
|
59
|
-
@base_grafs,
|
60
|
-
@phase_grafs].each { |component| resolve_all(parameters, component) }
|
61
45
|
end
|
62
46
|
|
63
|
-
# Dependencies need to be
|
64
|
-
# handled here.
|
65
|
-
#
|
66
|
-
#
|
47
|
+
# Dependencies need to be rendered before their dependants;
|
48
|
+
# that's handled here.
|
49
|
+
# Subclasses should run `super` before doing anything else
|
50
|
+
# with the Visitor,
|
51
|
+
# or call the send_to_dependencies method directly,
|
52
|
+
# so that this happens properly!
|
67
53
|
def accept(visitor:)
|
68
54
|
send_to_dependencies(visitor: visitor)
|
69
55
|
end
|
70
56
|
|
71
|
-
# Return a copy of this blueprint
|
72
|
-
# the specified phase.
|
57
|
+
# Return a copy of this blueprint
|
58
|
+
# that is initialized to operate on the specified phase.
|
73
59
|
def for_phase(new_active_phase)
|
74
60
|
return self unless phases?
|
75
61
|
|
@@ -102,10 +88,11 @@ module Litbuild
|
|
102
88
|
directives[directive]
|
103
89
|
end
|
104
90
|
|
105
|
-
# This is just like the [] operator method except that
|
106
|
-
# returns only the first value for a directive --
|
107
|
-
# for all the directives that
|
108
|
-
#
|
91
|
+
# This is just like the [] operator method except that
|
92
|
+
# it always returns only the first value for a directive --
|
93
|
+
# which is helpful for all the directives that
|
94
|
+
# are only ever supposed to have a single value,
|
95
|
+
# like `name` or `full-name`.
|
109
96
|
def value(directive)
|
110
97
|
self[directive]&.first
|
111
98
|
end
|
@@ -147,24 +134,6 @@ module Litbuild
|
|
147
134
|
@logfile_namer.path_for(name, phase, stage_name)
|
148
135
|
end
|
149
136
|
|
150
|
-
def parameter_defaults
|
151
|
-
values = {}
|
152
|
-
all_directive_sets = [@base_directives, @phase_directives.values].flatten
|
153
|
-
all_directive_sets.each do |dirset|
|
154
|
-
next unless dirset.key?('parameter')
|
155
|
-
|
156
|
-
dirset['parameter'].each do |a_param|
|
157
|
-
val = a_param['default'].first
|
158
|
-
values[a_param['name'].first] = if val == '(empty)'
|
159
|
-
''
|
160
|
-
else
|
161
|
-
val
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
values
|
166
|
-
end
|
167
|
-
|
168
137
|
def target_name
|
169
138
|
if active_phase
|
170
139
|
"#{name}::#{active_phase}"
|
@@ -181,9 +150,11 @@ module Litbuild
|
|
181
150
|
'FAILURE'
|
182
151
|
end
|
183
152
|
|
184
|
-
# If a dependency is declared both without a phase
|
185
|
-
# base directives for the blueprint)
|
186
|
-
# a phase
|
153
|
+
# If a dependency is declared both without a phase
|
154
|
+
# (typically in the base directives for the blueprint)
|
155
|
+
# and with a phase
|
156
|
+
# (typically in # a phase of that blueprint),
|
157
|
+
# throw away the phaseless declaration
|
187
158
|
# to avoid including two versions of the dependency.
|
188
159
|
def deduped_dependency_names
|
189
160
|
dep_names = self['depends-on'].clone || []
|
@@ -210,29 +181,5 @@ module Litbuild
|
|
210
181
|
|
211
182
|
@active_phase = phase
|
212
183
|
end
|
213
|
-
|
214
|
-
private
|
215
|
-
|
216
|
-
def resolve_all(parameters, something)
|
217
|
-
case something
|
218
|
-
when Hash
|
219
|
-
something.each_value { |element| resolve_all(parameters, element) }
|
220
|
-
when Array
|
221
|
-
something.each { |element| resolve_all(parameters, element) }
|
222
|
-
when String
|
223
|
-
resolve(parameters: parameters, a_string: something)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def resolve(parameters:, a_string:)
|
228
|
-
params = a_string.scan(/PARAM\[([A-Z_]+)\]/).flatten.sort.uniq
|
229
|
-
params.each do |p|
|
230
|
-
parameters[p] ||
|
231
|
-
raise(ParameterMissing, "Parameter #{p} is not defined")
|
232
|
-
|
233
|
-
a_string.gsub!(/PARAM\[#{p}\]/, parameters[p])
|
234
|
-
end
|
235
|
-
a_string
|
236
|
-
end
|
237
184
|
end
|
238
185
|
end
|
@@ -4,12 +4,15 @@ require 'litbuild/commands'
|
|
4
4
|
require 'litbuild/narrative'
|
5
5
|
require 'litbuild/package'
|
6
6
|
require 'litbuild/section'
|
7
|
+
require 'litbuild/blueprint_parser'
|
7
8
|
|
8
9
|
module Litbuild
|
9
10
|
# BlueprintLibrary initializes and sets configuration parameters for
|
10
|
-
# blueprints,
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# blueprints,
|
12
|
+
# and provides a common access point for blueprints and parameters.
|
13
|
+
# It always loads blueprints from the *current working directory*
|
14
|
+
# and uses config parameters from *the environment*
|
15
|
+
# (or the default values defined in blueprints).
|
13
16
|
class BlueprintLibrary
|
14
17
|
REQUIRED_PARAMS = %w[DOCUMENT_DIR LOGFILE_DIR PATCH_DIR
|
15
18
|
SCRIPT_DIR TARFILE_DIR WORK_SITE].freeze
|
@@ -17,17 +20,8 @@ module Litbuild
|
|
17
20
|
attr_reader :blueprints, :parameters
|
18
21
|
|
19
22
|
def initialize(logfile_namer_class:)
|
20
|
-
@blueprints = {}
|
21
|
-
Blueprint.descendants.each do |blueprint_type|
|
22
|
-
load_blueprints_of_type(blueprint_type)
|
23
|
-
end
|
24
23
|
@parameters = resolve_parameter_values
|
25
|
-
|
26
|
-
@blueprints.each_value do |bp|
|
27
|
-
bp.prepare(parameters: @parameters,
|
28
|
-
logfile_namer: log_namer,
|
29
|
-
library: self)
|
30
|
-
end
|
24
|
+
@blueprints = load_and_parse_all_blueprints(logfile_namer_class)
|
31
25
|
end
|
32
26
|
|
33
27
|
def blueprint_for(target:)
|
@@ -39,8 +33,10 @@ module Litbuild
|
|
39
33
|
end
|
40
34
|
|
41
35
|
# Convert the dependency declarations found in a `depends-on`
|
42
|
-
# directive to actual blueprints
|
43
|
-
#
|
36
|
+
# directive to actual blueprints
|
37
|
+
# (from the blueprint library).
|
38
|
+
# See the `Dependencies` section of doc/blueprints.txt
|
39
|
+
# for details on how this is supposed to work.
|
44
40
|
def dependencies_for(blueprint)
|
45
41
|
dep_names = blueprint.deduped_dependency_names
|
46
42
|
dep_names.map do |dep|
|
@@ -59,8 +55,8 @@ module Litbuild
|
|
59
55
|
|
60
56
|
private
|
61
57
|
|
62
|
-
# split a target name, like linux::headers,
|
63
|
-
# (linux) and a phase name (headers).
|
58
|
+
# split a target name, like linux::headers,
|
59
|
+
# into a blueprint name (linux) and a phase name (headers).
|
64
60
|
def split_name_and_phase(target:)
|
65
61
|
if target.include?('::')
|
66
62
|
target.strip.match(/(.*)::(.*)/)[1..2]
|
@@ -69,54 +65,94 @@ module Litbuild
|
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
72
|
-
# These methods are used during initialization,
|
73
|
-
# modifying them.
|
68
|
+
# These methods are used during initialization,
|
69
|
+
# be cautious when modifying them.
|
74
70
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
71
|
+
def blueprint_files_by_type
|
72
|
+
fbt = {}
|
73
|
+
Blueprint.descendants.each do |bptype|
|
74
|
+
fbt[bptype] = Dir.glob("./#{bptype.directory_name}/*.txt")
|
75
|
+
end
|
76
|
+
fbt
|
77
|
+
end
|
82
78
|
|
83
|
-
|
84
|
-
|
85
|
-
# load time.
|
86
|
-
automatic_directives = %w[name full-name]
|
79
|
+
def load_and_parse_all_blueprints(logfile_namer_class)
|
80
|
+
log_namer = logfile_namer_class.new(@parameters['LOGFILE_DIR'])
|
87
81
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
82
|
+
blueprints = {}
|
83
|
+
blueprint_files_by_type.each do |bp_type, bp_files|
|
84
|
+
bp_files.each do |bp_file|
|
85
|
+
bp_text = load_and_add_automatic_directives(bp_file)
|
86
|
+
begin
|
87
|
+
bp = bp_type.new(text: bp_text, parameters: @parameters,
|
88
|
+
logfile_namer: log_namer, library: self)
|
89
|
+
if blueprints[bp.name]
|
90
|
+
raise(DuplicateBlueprint,
|
91
|
+
"Duplicate blueprint #{bp.name} found in #{bp_file}")
|
95
92
|
end
|
93
|
+
blueprints[bp.name] = bp
|
94
|
+
rescue ArgumentError => e
|
95
|
+
raise(Litbuild::ParseError,
|
96
|
+
"Could not parse #{bp_file}}: #{e.message}")
|
96
97
|
end
|
97
|
-
bp = blueprint_type.new(text: bp_text)
|
98
|
-
rescue ArgumentError => e
|
99
|
-
raise(Litbuild::ParseError, "Could not parse #{a_file}: #{e.message}")
|
100
|
-
end
|
101
|
-
if @blueprints[bp.name]
|
102
|
-
raise(DuplicateBlueprint,
|
103
|
-
"Duplicate blueprint #{bp.name} found in #{a_file}")
|
104
|
-
else
|
105
|
-
@blueprints[bp.name] = bp
|
106
98
|
end
|
107
99
|
end
|
100
|
+
blueprints
|
101
|
+
end
|
102
|
+
|
103
|
+
# All blueprints have a name and a full-name.
|
104
|
+
# If a blueprint has no `name:` or `full-name:` directive,
|
105
|
+
# they will be provided at load time
|
106
|
+
# (the basename of the blueprint file is used for either
|
107
|
+
# or both missing directives)
|
108
|
+
def load_and_add_automatic_directives(filename)
|
109
|
+
text = File.read(filename)
|
110
|
+
name = File.basename(filename, '.txt')
|
111
|
+
automatic_directives = %w[name full-name]
|
112
|
+
automatic_directives.each do |dir|
|
113
|
+
text = "#{dir}: #{name}\n\n" + text unless text.match?(/^#{dir}:/)
|
114
|
+
end
|
115
|
+
text
|
116
|
+
end
|
117
|
+
|
118
|
+
# This does an initial parse of blueprints that declare parameters,
|
119
|
+
# and returns just the result of parsing those declarations.
|
120
|
+
def parameters_from(blueprint_text)
|
121
|
+
lines = blueprint_text.lines
|
122
|
+
|
123
|
+
return {} unless lines.detect { |l| l.start_with?('parameter:') }
|
124
|
+
|
125
|
+
# For purposes of finding parameters, phases, parameter
|
126
|
+
# references for substitution, and conditional blocks are
|
127
|
+
# irrelevant.
|
128
|
+
simplified = lines.grep_v(/^phase:/).grep_v(/^#/).join
|
129
|
+
simplified.gsub!(/PARAM\[[A-Z_]+\]/, 'UNSET')
|
130
|
+
|
131
|
+
parser = BlueprintParser.new(file_text: simplified)
|
132
|
+
param_directives = parser.base_directives['parameter']
|
133
|
+
params = {}
|
134
|
+
param_directives.each do |a_param|
|
135
|
+
default_val = a_param['default'].first
|
136
|
+
params[a_param['name'].first] = if default_val == '(empty)'
|
137
|
+
''
|
138
|
+
else
|
139
|
+
default_val.gsub(/[\\\s]+/m, ' ')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
params
|
108
143
|
end
|
109
144
|
|
110
145
|
def resolve_parameter_values
|
111
|
-
|
112
|
-
REQUIRED_PARAMS.each { |key|
|
113
|
-
|
114
|
-
|
146
|
+
params = {}
|
147
|
+
REQUIRED_PARAMS.each { |key| params[key] = 'UNSET' }
|
148
|
+
bp_files = blueprint_files_by_type.values.flatten
|
149
|
+
bp_files.each do |bp_file|
|
150
|
+
params.merge!(parameters_from(File.read(bp_file)))
|
115
151
|
end
|
116
152
|
ENV.each do |k, v|
|
117
|
-
|
153
|
+
params[k] = v if params.key?(k)
|
118
154
|
end
|
119
|
-
@parameters =
|
155
|
+
@parameters = params
|
120
156
|
end
|
121
157
|
end
|
122
158
|
end
|
@@ -5,36 +5,53 @@ require 'litbuild/string_indentation'
|
|
5
5
|
require 'json'
|
6
6
|
|
7
7
|
module Litbuild
|
8
|
-
# This is a kludgy hand-built parser
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
8
|
+
# This is a kludgy hand-built parser,
|
9
|
+
# written by someone who does not know how to write parsers.
|
10
|
+
# Blueprint structure is not
|
11
|
+
# (at least, at this point)
|
12
|
+
# complicated enough that I can see any point in defining a grammar
|
13
|
+
# and using a parser generator.
|
14
|
+
# The structure of blueprints,
|
15
|
+
# and the intended behavior of this parser,
|
16
|
+
# are described informally in
|
17
|
+
# `doc/blueprints.txt`
|
18
|
+
# (and blueprint-type-specific files under `doc` as well).
|
19
|
+
#
|
20
|
+
# The entire parse process occurs during initialization,
|
21
|
+
# so after creating a BlueprintParser you will either be able
|
22
|
+
# to ask it for the result of the parse operation,
|
23
|
+
# or have an exception to deal with.
|
24
|
+
#
|
25
|
+
# Essentially all methods are therefore used within initialization.
|
26
|
+
# Use great care when modifying this class!
|
13
27
|
class BlueprintParser
|
14
28
|
include StringIndentation
|
15
29
|
|
16
|
-
# _directives_ are for use in scripts.
|
17
|
-
# documents.
|
30
|
+
# _directives_ are for use in scripts.
|
31
|
+
# _grafs_ are for use in documents.
|
18
32
|
attr_reader :base_directives, :phase_directives, :base_grafs, :phase_grafs
|
19
33
|
|
20
|
-
def initialize(file_text)
|
21
|
-
@text = file_text
|
34
|
+
def initialize(file_text:, parameters: nil)
|
35
|
+
@text = file_text.dup
|
36
|
+
substitute_parameters(parameters) if parameters
|
37
|
+
resolve_conditional_blocks if @text.match?(/^#IF /)
|
38
|
+
|
22
39
|
@phase_directives = {}
|
23
40
|
@phase_grafs = {}
|
24
41
|
phase_chunks = @text.split(/^phase: ([a-z -_]+)$/)
|
25
42
|
|
26
|
-
# The first part of the blueprint,
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
43
|
+
# The first part of the blueprint,
|
44
|
+
# before any phase directive,
|
45
|
+
# becomes the base directives and base narrative for the blueprint.
|
46
|
+
# If there are no phase directives, this is the entire blueprint.
|
30
47
|
@base_grafs = []
|
31
48
|
@base_directives = parse_phase(phase_chunks.shift, {}, @base_grafs)
|
32
49
|
@base_directives['full-name'] ||= @base_directives['name']
|
33
50
|
|
34
|
-
# The rest of the blueprint, if any, consists of directives
|
35
|
-
# narrative for specific phases.
|
36
|
-
# include all the base directives,
|
37
|
-
# the phase.
|
51
|
+
# The rest of the blueprint, if any, consists of directives
|
52
|
+
# and narrative for specific phases.
|
53
|
+
# The directives for each phase include all the base directives,
|
54
|
+
# as well as those specific to the phase.
|
38
55
|
until phase_chunks.empty?
|
39
56
|
phase_name = phase_chunks.shift
|
40
57
|
phase_contents = phase_chunks.shift
|
@@ -45,9 +62,9 @@ module Litbuild
|
|
45
62
|
@phase_grafs[phase_name] = grafs
|
46
63
|
end
|
47
64
|
|
48
|
-
# Any directives at the beginning of a blueprint are
|
49
|
-
# file header
|
50
|
-
# narrative for it.
|
65
|
+
# Any directives at the beginning of a blueprint are
|
66
|
+
# actually a file header
|
67
|
+
# that should not be considered as part of the narrative for it.
|
51
68
|
@base_grafs.shift while @base_grafs[0].is_a?(Hash)
|
52
69
|
rescue StandardError => e
|
53
70
|
msg = "Cannot parse blueprint starting: #{@text.lines[0..3].join}"
|
@@ -56,9 +73,51 @@ module Litbuild
|
|
56
73
|
|
57
74
|
private
|
58
75
|
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
76
|
+
# Find all `PARAM[parameter_name]` patterns in the text to parse.
|
77
|
+
# Replace each pattern with the parameter value.
|
78
|
+
#
|
79
|
+
# This must be done before anything else so that the conditional
|
80
|
+
# block conditions can be evaluated.
|
81
|
+
def substitute_parameters(parameters)
|
82
|
+
to_substitute = @text.scan(/PARAM\[([A-Z_]+)\]/).flatten.sort.uniq
|
83
|
+
to_substitute.each do |p|
|
84
|
+
@text.gsub!(/PARAM\[#{p}\]/, parameters[p])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Find all #IF ... #ENDIF blocks.
|
89
|
+
# For each one, either remove it entirely,
|
90
|
+
# or remove the #IF and #ENDIF lines and leave the block.
|
91
|
+
def resolve_conditional_blocks
|
92
|
+
lines = @text.lines
|
93
|
+
resolved = []
|
94
|
+
in_conditional_block = false
|
95
|
+
condition_is_true = nil
|
96
|
+
lines.each do |line|
|
97
|
+
if in_conditional_block
|
98
|
+
if line.match?(/^#ENDIF/)
|
99
|
+
in_conditional_block = false
|
100
|
+
condition_is_true = nil
|
101
|
+
elsif condition_is_true
|
102
|
+
resolved << line
|
103
|
+
end
|
104
|
+
next
|
105
|
+
end
|
106
|
+
if line.match?(/^#IF/)
|
107
|
+
in_conditional_block = true
|
108
|
+
left, right = /^#IF (.*) = (.*)$/.match(line)[1..2]
|
109
|
+
condition_is_true = (left == right)
|
110
|
+
next
|
111
|
+
end
|
112
|
+
resolved << line
|
113
|
+
end
|
114
|
+
@text = resolved.join
|
115
|
+
end
|
116
|
+
|
117
|
+
# Parse all directive paragraphs found in blueprint_text.
|
118
|
+
# Also add all parsed directive paragraphs,
|
119
|
+
# and all narrative paragraphs,
|
120
|
+
# to a collecting parameter.
|
62
121
|
def parse_phase(blueprint_text, start_with_directives, graf_collector)
|
63
122
|
directives = JSON.parse(JSON.generate(start_with_directives))
|
64
123
|
paragraphs = blueprint_text.split(/\n\n+/m)
|
@@ -76,8 +135,8 @@ module Litbuild
|
|
76
135
|
end
|
77
136
|
|
78
137
|
# All s6-rc service directories should be tracked in the
|
79
|
-
# configuration file repository,
|
80
|
-
# `configuration-files`.
|
138
|
+
# configuration file repository,
|
139
|
+
# so add them to `configuration-files`.
|
81
140
|
def handle_servicedirs(directives)
|
82
141
|
cfgs = directives['configuration-files'] || []
|
83
142
|
if (spipes = directives['service-pipeline'])
|
@@ -114,6 +173,8 @@ module Litbuild
|
|
114
173
|
graf_directives = Hash.new { |h, k| h[k] = [] }
|
115
174
|
until lines_to_process.empty?
|
116
175
|
directive_line = lines_to_process.shift
|
176
|
+
next if directive_line.empty?
|
177
|
+
|
117
178
|
md = /^([A-Za-z0-9_-]+): *(.*)/.match(directive_line)
|
118
179
|
unless md
|
119
180
|
raise(Litbuild::ParseError,
|
@@ -134,11 +195,12 @@ module Litbuild
|
|
134
195
|
end
|
135
196
|
|
136
197
|
# Regardless of what kind of directive structure we're dealing with,
|
137
|
-
# all
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
198
|
+
# all lines that are indented more than the initial directive line
|
199
|
+
# are part of that directive.
|
200
|
+
# This method finds all those related lines
|
201
|
+
# and strips away the common initial indentation;
|
202
|
+
# it returns both the related lines
|
203
|
+
# and the amount of indentation that was removed.
|
142
204
|
def related_lines(directive_line, lines_to_process)
|
143
205
|
directive_indent = indent_for(directive_line)
|
144
206
|
related = []
|
@@ -149,9 +211,11 @@ module Litbuild
|
|
149
211
|
strip_indentation_from_array(related)
|
150
212
|
end
|
151
213
|
|
152
|
-
# What kind of directive are we dealing with?
|
153
|
-
# value (with zero or more continuation lines),
|
154
|
-
#
|
214
|
+
# What kind of directive are we dealing with?
|
215
|
+
# Could be a simple value (with zero or more continuation lines),
|
216
|
+
# or a multiline value,
|
217
|
+
# or an array,
|
218
|
+
# or a subdirective block.
|
155
219
|
def parse_directive_value(firstline_value, other_lines, indentation)
|
156
220
|
if firstline_value == '|'
|
157
221
|
multiline_value(other_lines, indentation)
|
@@ -167,20 +231,25 @@ module Litbuild
|
|
167
231
|
end
|
168
232
|
|
169
233
|
# any time a directive line is indented more than the previous line,
|
170
|
-
# without being a part of an array
|
171
|
-
# directive
|
234
|
+
# without being a part of an array
|
235
|
+
# or sub-directive
|
236
|
+
# or multi-line
|
237
|
+
# directive,
|
238
|
+
# it's a continuation of the previous line.
|
172
239
|
def folded_continuation_lines(first_line, related)
|
173
240
|
stripped = related.map(&:strip)
|
174
241
|
stripped.unshift(first_line)
|
175
242
|
stripped.join(" \\\n ")
|
176
243
|
end
|
177
244
|
|
178
|
-
# While parsing multiline values,
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
245
|
+
# While parsing multiline values,
|
246
|
+
# we want to restore the indentation
|
247
|
+
# that was previously stripped away --
|
248
|
+
# this is typically used for `file` directives,
|
249
|
+
# which often appear in multiple directive blocks
|
250
|
+
# and should be treated as a whole.
|
251
|
+
# The indentation for multi-line values must be removed *later*,
|
252
|
+
# during rendering of scripts and documents.
|
184
253
|
def multiline_value(lines, indentation)
|
185
254
|
lines_with_indentation = lines.map { |l| (' ' * indentation) + l }
|
186
255
|
"#{lines_with_indentation.join("\n")}\n"
|
@@ -199,9 +268,10 @@ module Litbuild
|
|
199
268
|
raise(Litbuild::ParseError, "Problem parsing: #{lines}")
|
200
269
|
end
|
201
270
|
|
202
|
-
# It turns out that sub-directives are
|
203
|
-
#
|
204
|
-
#
|
271
|
+
# It turns out that sub-directives are
|
272
|
+
# the easiest thing in the world to parse,
|
273
|
+
# since we can just take the value
|
274
|
+
# and parse it as a new directive block.
|
205
275
|
def subdirective_value(lines)
|
206
276
|
parse_lines(lines)
|
207
277
|
end
|
data/lib/litbuild/driver.rb
CHANGED
@@ -16,9 +16,6 @@ module Litbuild
|
|
16
16
|
# All the actual work is done by Visitors that are handed to the
|
17
17
|
# top-level blueprint target (typically specified on the command line).
|
18
18
|
class Driver
|
19
|
-
REQUIRED_PARAMS = %w[DOCUMENT_DIR LOGFILE_DIR PATCH_DIR
|
20
|
-
SCRIPT_DIR TARFILE_DIR WORK_SITE].freeze
|
21
|
-
|
22
19
|
def initialize(logfile_namer_class: Litbuild::LogfileNamer)
|
23
20
|
@bplib = BlueprintLibrary.new(logfile_namer_class: logfile_namer_class)
|
24
21
|
end
|
data/lib/litbuild/package.rb
CHANGED
data/lib/litbuild/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: litbuild
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.19
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Neumeier
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A build system based on Knuth's idea of literate programming.
|
14
14
|
email:
|
@@ -45,10 +45,10 @@ files:
|
|
45
45
|
- lib/litbuild/visitor.rb
|
46
46
|
homepage: http://git.freesa.org/freesa/litbuild
|
47
47
|
licenses:
|
48
|
-
- GPL-3.0
|
48
|
+
- GPL-3.0-only
|
49
49
|
metadata:
|
50
50
|
rubygems_mfa_required: 'true'
|
51
|
-
post_install_message:
|
51
|
+
post_install_message:
|
52
52
|
rdoc_options: []
|
53
53
|
require_paths:
|
54
54
|
- lib
|
@@ -63,8 +63,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: '0'
|
65
65
|
requirements: []
|
66
|
-
rubygems_version: 3.
|
67
|
-
signing_key:
|
66
|
+
rubygems_version: 3.5.9
|
67
|
+
signing_key:
|
68
68
|
specification_version: 4
|
69
69
|
summary: A literate build system
|
70
70
|
test_files: []
|