litbuild 1.0.11 → 1.0.16
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 +22 -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 +7 -2
- data/lib/litbuild/source_code_manager.rb +34 -7
- data/lib/litbuild/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8e6cf1338409a6f92412c572ddca22dfea69dec035c023ec029aa73b56f833f
|
4
|
+
data.tar.gz: 9fe0eb1366a7404cfe258081c3ef4f3f96e5214cba7f6d428dceac520d35da0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f79a4716874345084ec1ac7fe7c41049cd09db46d46e7349125767a9598b7a079314dac3630fcc221c0365a0a6bdb033bede9a1c41ce772b5740e01c15b3a53
|
7
|
+
data.tar.gz: e4db23446ce1334ec23ad2e1b566195ca0eb3515cbf7876089498f144af5e9e92302b87ec8ad9c6495c61935d4dfc90b5803acb979b603cddbbefcc8890e4054
|
@@ -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
|
@@ -167,9 +167,14 @@ module Litbuild
|
|
167
167
|
script.string
|
168
168
|
end
|
169
169
|
|
170
|
+
# Timestamps are written with default format plus
|
171
|
+
# seconds-since-epoch (in parentheses), to make it as easy as
|
172
|
+
# possible to calculate how long things take.
|
173
|
+
DATECMD = "date '+%a %b %e %H:%M:%S %Z %Y (%s)'"
|
174
|
+
|
170
175
|
def write_components(location:, script:)
|
171
176
|
@written[location].map do |target|
|
172
|
-
script.puts("echo \"At $(
|
177
|
+
script.puts("echo \"At $(#{DATECMD}): Beginning #{target}:\"")
|
173
178
|
script.puts("./#{target}")
|
174
179
|
end
|
175
180
|
end
|
@@ -339,7 +344,7 @@ module Litbuild
|
|
339
344
|
render_restart_header(script, restart_file, package.version, pkgusr_dir)
|
340
345
|
pkgusr_srcdir = File.join(pkgusr_dir, package.name_and_version)
|
341
346
|
render_add_package_user(package, script, log)
|
342
|
-
@scm.
|
347
|
+
@scm.copy_files_commands(package).each do |cp_command|
|
343
348
|
render_command(script, cp_command, log)
|
344
349
|
end
|
345
350
|
script.puts("export LB_SOURCE_DIR=#{quote(pkgusr_srcdir)}")
|
@@ -505,8 +510,7 @@ module Litbuild
|
|
505
510
|
def render_service_pipeline(script, spipe)
|
506
511
|
pname = spipe['name'].first
|
507
512
|
spipe['bundle']&.each do |bundle|
|
508
|
-
script
|
509
|
-
"echo #{pname} >> #{bundle}/contents")
|
513
|
+
render_add_to_bundle(script, bundle, pname)
|
510
514
|
end
|
511
515
|
sdirs = spipe['servicedirs']
|
512
516
|
sdirs.each_with_index do |sdir, i|
|
@@ -527,17 +531,12 @@ module Litbuild
|
|
527
531
|
|
528
532
|
def render_service_dir(script, sdir)
|
529
533
|
sd = ServiceDir.new(sdir)
|
530
|
-
if sd.bundle
|
531
|
-
script.puts("grep -q '^#{sd.name}$' #{sd.bundle}/contents || " \
|
532
|
-
"echo #{sd.name} >> #{sd.bundle}/contents")
|
533
|
-
end
|
534
|
+
render_add_to_bundle(script, sd.bundle, sd.name) if sd.bundle
|
534
535
|
script.puts("mkdir -p #{sd.name}")
|
535
536
|
sd.oneline_files.keys.sort.each do |fn|
|
536
537
|
script.puts("echo #{sd.oneline_files[fn]} > #{sd.name}/#{fn}")
|
537
538
|
end
|
538
539
|
multiline = sd.multiline_files
|
539
|
-
deps = sd.dependencies
|
540
|
-
multiline['dependencies'] = [deps.join("\n")] unless deps.empty?
|
541
540
|
multiline.keys.sort.each do |filename|
|
542
541
|
# I always terminate here documents in litbuild-generated
|
543
542
|
# scripts with "LBEOF", partly because I'm thinking "Litbuild
|
@@ -555,9 +554,22 @@ module Litbuild
|
|
555
554
|
script.puts("echo '#{env[envvar]}' > #{sd.name}/env/#{envvar}")
|
556
555
|
end
|
557
556
|
end
|
557
|
+
unless sd.dependencies.empty?
|
558
|
+
script.puts("mkdir -p #{sd.name}/dependencies.d")
|
559
|
+
sd.dependencies.each do |dep|
|
560
|
+
script.puts("touch #{sd.name}/dependencies.d/#{dep}")
|
561
|
+
end
|
562
|
+
end
|
558
563
|
skip_line(script)
|
559
564
|
end
|
560
565
|
|
566
|
+
def render_add_to_bundle(script, bundle, svc)
|
567
|
+
script.puts("mkdir -p #{bundle}")
|
568
|
+
script.puts("echo bundle > #{bundle}/type")
|
569
|
+
script.puts("mkdir -p #{bundle}/contents.d")
|
570
|
+
script.puts("touch #{bundle}/contents.d/#{svc}")
|
571
|
+
end
|
572
|
+
|
561
573
|
def render_cfgrepo_trailer(script, blueprint, log)
|
562
574
|
cfgs = blueprint['configuration-files']
|
563
575
|
return unless cfgs
|
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
@@ -16,7 +16,7 @@ module Litbuild
|
|
16
16
|
'packages'
|
17
17
|
end
|
18
18
|
|
19
|
-
def initialize(text:)
|
19
|
+
def initialize(text:, parameters:, logfile_namer:, library:)
|
20
20
|
super
|
21
21
|
if phases?
|
22
22
|
phases.each do |p|
|
@@ -131,11 +131,16 @@ module Litbuild
|
|
131
131
|
end
|
132
132
|
|
133
133
|
# otherwise, the default version goes right *before* the *first
|
134
|
-
# directive of the next stage* (which
|
134
|
+
# directive of the next stage* (which *should* always be present)...
|
135
135
|
next_stage = STAGE_DIRECTIVES[STAGE_DIRECTIVES.index(stg) + 1]
|
136
136
|
first_idx = grafs.index do |graf|
|
137
137
|
graf.respond_to?(:key) && graf.key?(next_stage)
|
138
138
|
end
|
139
|
+
|
140
|
+
# ...but if, for whatever reason, we haven't found a place to
|
141
|
+
# put the default directives, just put them at the beginning.
|
142
|
+
first_idx ||= 0
|
143
|
+
|
139
144
|
grafs.insert(first_idx, stg => to_add[stg])
|
140
145
|
end
|
141
146
|
end
|
@@ -13,7 +13,8 @@ module Litbuild
|
|
13
13
|
#
|
14
14
|
# Note, SourceCodeManager does not recurse into additional levels of
|
15
15
|
# subdirectories -- it turns out that makes the test suite really
|
16
|
-
# slow
|
16
|
+
# slow, and recursive search is not useful to me, so I'm just
|
17
|
+
# skipping it.
|
17
18
|
class SourceCodeManager
|
18
19
|
def initialize(*dirs)
|
19
20
|
all_pkgfiles = dirs.map do |d|
|
@@ -55,13 +56,39 @@ module Litbuild
|
|
55
56
|
end
|
56
57
|
|
57
58
|
##
|
58
|
-
# Package Users expect to have tarfiles for the top-level
|
59
|
-
# and any in-tree packages in their `src` directory, and
|
60
|
-
# their `patches` directory. This
|
61
|
-
#
|
62
|
-
#
|
63
|
-
|
59
|
+
# Typically, Package Users expect to have tarfiles for the top-level
|
60
|
+
# package and any in-tree packages in their `src` directory, and
|
61
|
+
# patches in their `patches` directory. This method arranges things
|
62
|
+
# that way: it produces commands that copy all the necessary files
|
63
|
+
# from TARFILE_DIR and/or PATCH_DIR to the destination directory,
|
64
|
+
# skipping any that are already present where the Package Users
|
65
|
+
# build script expects to find them.
|
66
|
+
#
|
67
|
+
# Binary package files are an exception to the typical case. A
|
68
|
+
# binary package file has name `binary-#{packagename}.tar.lz` (no
|
69
|
+
# version number, and always lzip-compressed), and contains the
|
70
|
+
# files produced by the compilation and installation process. If
|
71
|
+
# such a file is present in the Package User home directory, the
|
72
|
+
# build script simply unpacks it and does nothing else.
|
73
|
+
#
|
74
|
+
# So: if a binary package file is present in the tarfile directory,
|
75
|
+
# this method simply emits a command to copy it to the Package User
|
76
|
+
# home directory; and if there is already a binary package file
|
77
|
+
# present in the Package User home directory, this method does
|
78
|
+
# nothing.
|
79
|
+
def copy_files_commands(package)
|
64
80
|
pkgusr = pkgusr_name(package)
|
81
|
+
|
82
|
+
# copy binary file if available, then return
|
83
|
+
binfile = "binary-#{package.name}.tar.lz"
|
84
|
+
return ["cp #{find_file(binfile)} ~#{pkgusr}"] \
|
85
|
+
if @available_files.detect { |f| /#{binfile}/ =~ f }
|
86
|
+
|
87
|
+
# do nothing if binary file is present in home dir already
|
88
|
+
return [] if homedir(package) && File.exist?(
|
89
|
+
File.join(homedir(package), binfile)
|
90
|
+
)
|
91
|
+
|
65
92
|
mkdir_commands = %w[src patches].map do |dir|
|
66
93
|
"mkdir -p ~#{pkgusr}/#{dir}"
|
67
94
|
end
|
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.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Neumeier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-11-18 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:
|
@@ -63,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: '0'
|
65
65
|
requirements: []
|
66
|
-
rubygems_version: 3.
|
66
|
+
rubygems_version: 3.4.22
|
67
67
|
signing_key:
|
68
68
|
specification_version: 4
|
69
69
|
summary: A literate build system
|