valise-sh 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +170 -9
- package/README.md +48 -24
- package/bin/valise +2453 -333
- package/docs/architecture.md +78 -9
- package/docs/contrib-modules.md +68 -15
- package/docs/installation.md +120 -33
- package/docs/module-abi.md +103 -39
- package/docs/release-policy.md +110 -6
- package/docs/shared-modules.md +124 -2
- package/docs/variants.md +318 -54
- package/examples/README.md +11 -0
- package/examples/e2e/README.md +7 -7
- package/examples/e2e/build-manifest.sh +45 -157
- package/examples/profile-project/README.md +181 -0
- package/examples/profile-project/shell/layers/demo/module.d/contrib_demo_greeting.meta +10 -0
- package/examples/profile-project/shell/layers/demo/modules/shared/contrib_demo_greeting.sh +4 -0
- package/examples/profile-project/shell/module.d/greeting.meta +10 -0
- package/examples/profile-project/shell/module.d/profile_main.meta +9 -0
- package/examples/profile-project/shell/modules/greeting.sh +5 -0
- package/examples/profile-project/shell/release/bundle-roots +9 -0
- package/examples/profile-project/shell/release/install-variants +3 -0
- package/examples/profile-project/shell/release/module-registry.tsv +4 -0
- package/examples/profile-project/shell/release/project.conf +3 -0
- package/examples/shared/README.md +2 -2
- package/lib/compiler.awk +341 -20
- package/lib/fish/loader.fish +94 -19
- package/lib/fish/metadata.fish +13 -14
- package/lib/loader.sh +737 -157
- package/lib/metadata.sh +27 -51
- package/package.json +2 -1
package/lib/compiler.awk
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# valise compile: pure POSIX awk static plan compiler.
|
|
5
5
|
# Input: single preprocessed stream from sh wrapper.
|
|
6
6
|
# Lines starting with SEED: space-separated seed module names
|
|
7
|
-
# Lines starting with MOD: identity<TAB>requires<TAB>load_path
|
|
7
|
+
# Lines starting with MOD: identity<TAB>requires<TAB>load_path<TAB>platforms<TAB>side_effect<TAB>exports<TAB>optional
|
|
8
8
|
# identity is module@meta_source when registry sources are qualified.
|
|
9
9
|
# Lines starting with PRELUDE: sp_cap function names (space-separated)
|
|
10
10
|
# Output: complete installed plan on stdout.
|
|
@@ -33,10 +33,14 @@ BEGIN {
|
|
|
33
33
|
/^MOD:/ {
|
|
34
34
|
line = substr($0, 5)
|
|
35
35
|
n = split(line, fields, "\t")
|
|
36
|
-
if (n >=
|
|
36
|
+
if (n >= 7) {
|
|
37
37
|
name = fields[1]
|
|
38
38
|
mod_req[name] = fields[2]
|
|
39
39
|
mod_load[name] = fields[3]
|
|
40
|
+
mod_platforms[name] = fields[4]
|
|
41
|
+
mod_side_effect[name] = fields[5]
|
|
42
|
+
mod_exports[name] = fields[6]
|
|
43
|
+
mod_opt[name] = fields[7]
|
|
40
44
|
display_name = name
|
|
41
45
|
at = index(name, "@")
|
|
42
46
|
if (at > 0) display_name = substr(name, 1, at - 1)
|
|
@@ -48,6 +52,19 @@ BEGIN {
|
|
|
48
52
|
next
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
/^EXCLUDED:/ {
|
|
56
|
+
# A module removed by the active build variant. It is not part of the
|
|
57
|
+
# closure, but it is named so a required dependency that points at it can
|
|
58
|
+
# be diagnosed precisely instead of as an unknown module. The record is
|
|
59
|
+
# "<identity>\t<name>"; the bare name is how a dependency refers to it.
|
|
60
|
+
line = substr($0, 10)
|
|
61
|
+
n = split(line, fields, "\t")
|
|
62
|
+
if (n >= 2) {
|
|
63
|
+
excluded_id[fields[1]] = fields[2]
|
|
64
|
+
}
|
|
65
|
+
next
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
/^PRELUDE:/ {
|
|
52
69
|
line = substr($0, 9)
|
|
53
70
|
n = split(line, arr, " ")
|
|
@@ -88,6 +105,14 @@ END {
|
|
|
88
105
|
|
|
89
106
|
if (name in discovered) continue
|
|
90
107
|
if (!(name in mod_exists)) {
|
|
108
|
+
if (name in excluded_id) {
|
|
109
|
+
# The module was removed by the active build variant, but a
|
|
110
|
+
# surviving module still requires it. Name both modules: the
|
|
111
|
+
# generic "unknown module" diagnostic would point at the
|
|
112
|
+
# dependency and hide the variant that caused it.
|
|
113
|
+
print "ERROR: variant exclusion breaks a required dependency: " mod_display[requiring_of[name]] " requires " excluded_id[name] > "/dev/stderr"
|
|
114
|
+
exit 1
|
|
115
|
+
}
|
|
91
116
|
print "ERROR: unknown module: " name > "/dev/stderr"
|
|
92
117
|
exit 1
|
|
93
118
|
}
|
|
@@ -109,6 +134,25 @@ END {
|
|
|
109
134
|
queue[q_tail] = dep
|
|
110
135
|
}
|
|
111
136
|
}
|
|
137
|
+
# Remember which module asked for a dependency, so an exclusion
|
|
138
|
+
# that breaks it can name the requiring module.
|
|
139
|
+
if (!(dep in requiring_of)) requiring_of[dep] = name
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# The preprocessor keeps only optional dependencies that exist for this
|
|
143
|
+
# shell and variant, so each of these is a module the plan must attempt.
|
|
144
|
+
n_opt = split(mod_opt[name], opts, " ")
|
|
145
|
+
for (ri = 1; ri <= n_opt; ri++) {
|
|
146
|
+
dep = opts[ri]
|
|
147
|
+
if (dep in discovered) continue
|
|
148
|
+
in_q = 0
|
|
149
|
+
for (qi = q_head; qi <= q_tail; qi++) {
|
|
150
|
+
if (queue[qi] == dep) { in_q = 1; break }
|
|
151
|
+
}
|
|
152
|
+
if (!in_q) {
|
|
153
|
+
q_tail++
|
|
154
|
+
queue[q_tail] = dep
|
|
155
|
+
}
|
|
112
156
|
}
|
|
113
157
|
|
|
114
158
|
# Re-sort remaining queue
|
|
@@ -141,6 +185,27 @@ END {
|
|
|
141
185
|
rem_count = discovered_count
|
|
142
186
|
ordered_count = 0
|
|
143
187
|
|
|
188
|
+
# The required closure is the roots and everything they require. A module
|
|
189
|
+
# reached only through an optional edge may be unavailable without failing
|
|
190
|
+
# the plan. Compute it before ordering so a stall can distinguish a cycle
|
|
191
|
+
# inside the required closure from an optional-only cycle.
|
|
192
|
+
for (i = 1; i <= seed_count; i++) {
|
|
193
|
+
required_stack[++required_top] = seeds[i]
|
|
194
|
+
}
|
|
195
|
+
while (required_top > 0) {
|
|
196
|
+
name = required_stack[required_top--]
|
|
197
|
+
if (name in required) continue
|
|
198
|
+
required[name] = 1
|
|
199
|
+
n_req = split(mod_req[name], reqs, " ")
|
|
200
|
+
for (rqi = 1; rqi <= n_req; rqi++) {
|
|
201
|
+
required_stack[++required_top] = reqs[rqi]
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Order by required and optional edges. When that stalls, retry on required
|
|
206
|
+
# edges alone, as the loader does, so an optional edge can never deadlock the
|
|
207
|
+
# graph. Only a stall on required edges is a cycle.
|
|
208
|
+
relaxed = 0
|
|
144
209
|
while (rem_count > 0) {
|
|
145
210
|
progress = 0
|
|
146
211
|
next_rem_count = 0
|
|
@@ -149,13 +214,20 @@ END {
|
|
|
149
214
|
all_loaded = 1
|
|
150
215
|
n_req = split(mod_req[name], reqs, " ")
|
|
151
216
|
for (rqi = 1; rqi <= n_req; rqi++) {
|
|
152
|
-
|
|
153
|
-
if (dep == "") continue
|
|
154
|
-
if (!(dep in loaded)) {
|
|
217
|
+
if (!(reqs[rqi] in loaded)) {
|
|
155
218
|
all_loaded = 0
|
|
156
219
|
break
|
|
157
220
|
}
|
|
158
221
|
}
|
|
222
|
+
if (all_loaded && !relaxed) {
|
|
223
|
+
n_opt = split(mod_opt[name], opts, " ")
|
|
224
|
+
for (rqi = 1; rqi <= n_opt; rqi++) {
|
|
225
|
+
if (!(opts[rqi] in loaded)) {
|
|
226
|
+
all_loaded = 0
|
|
227
|
+
break
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
159
231
|
if (all_loaded) {
|
|
160
232
|
ordered_count++
|
|
161
233
|
ordered[ordered_count] = name
|
|
@@ -167,18 +239,76 @@ END {
|
|
|
167
239
|
}
|
|
168
240
|
}
|
|
169
241
|
if (!progress) {
|
|
170
|
-
|
|
171
|
-
|
|
242
|
+
if (!relaxed) {
|
|
243
|
+
relaxed = 1
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
# Every remaining module is in a cycle. The loader quarantines a
|
|
247
|
+
# cycle that lies wholly outside the required closure and still
|
|
248
|
+
# loads the rest, so only a cycle containing a required module is
|
|
249
|
+
# a compile error.
|
|
250
|
+
stalled_required = 0
|
|
251
|
+
for (ri = 1; ri <= rem_count; ri++) {
|
|
252
|
+
if (remaining[ri] in required) {
|
|
253
|
+
stalled_required = 1
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (stalled_required) {
|
|
258
|
+
print "ERROR: cycle detected" > "/dev/stderr"
|
|
259
|
+
exit 1
|
|
260
|
+
}
|
|
261
|
+
for (ri = 1; ri <= rem_count; ri++) {
|
|
262
|
+
print "warning: optional dependency cycle ignored: " remaining[ri] > "/dev/stderr"
|
|
263
|
+
}
|
|
264
|
+
rem_count = 0
|
|
265
|
+
break
|
|
172
266
|
}
|
|
267
|
+
relaxed = 0
|
|
173
268
|
rem_count = next_rem_count
|
|
174
269
|
for (ri = 1; ri <= rem_count; ri++) {
|
|
175
270
|
remaining[ri] = next_remaining[ri]
|
|
176
271
|
}
|
|
177
272
|
}
|
|
178
273
|
|
|
274
|
+
# Validate the ordered closure before rendering. Unsafe side effects are a
|
|
275
|
+
# compile-time error, and a bodyless module must still be a dependency-only
|
|
276
|
+
# meta module.
|
|
277
|
+
for (oi = 1; oi <= ordered_count; oi++) {
|
|
278
|
+
name = ordered[oi]
|
|
279
|
+
side_effect = mod_side_effect[name]
|
|
280
|
+
if (side_effect == "" || side_effect == "generated-file" || side_effect == "update-network" || side_effect == "install-time") {
|
|
281
|
+
print "ERROR: module " mod_display[name] " has unsafe side_effect for compile: " side_effect > "/dev/stderr"
|
|
282
|
+
exit 1
|
|
283
|
+
}
|
|
284
|
+
if (mod_load[name] == "" && mod_req[name] == "") {
|
|
285
|
+
print "ERROR: module " mod_display[name] " has no load path and no dependencies" > "/dev/stderr"
|
|
286
|
+
exit 1
|
|
287
|
+
}
|
|
288
|
+
if (mod_load[name] == "" && mod_exports[name] != "") {
|
|
289
|
+
print "ERROR: meta module " mod_display[name] " declares exports without a body" > "/dev/stderr"
|
|
290
|
+
exit 1
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
# Collect the plan's declared exports once for VALISE_RELOAD support.
|
|
295
|
+
reload_export_count = 0
|
|
296
|
+
for (oi = 1; oi <= ordered_count; oi++) {
|
|
297
|
+
name = ordered[oi]
|
|
298
|
+
n_exports = split(mod_exports[name], export_values, " ")
|
|
299
|
+
for (ei = 1; ei <= n_exports; ei++) {
|
|
300
|
+
export_name = export_values[ei]
|
|
301
|
+
if (export_name != "" && !(export_name in reload_export_seen)) {
|
|
302
|
+
reload_export_seen[export_name] = 1
|
|
303
|
+
reload_export_count++
|
|
304
|
+
reload_exports[reload_export_count] = export_name
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
179
309
|
# --- Render plan ---
|
|
180
310
|
printf "# shell-profile-installed-modules-v1\n"
|
|
181
|
-
printf "# Generated by
|
|
311
|
+
printf "# Generated by valise compile; DO NOT EDIT.\n"
|
|
182
312
|
printf "# plan=%s\n", plan_name
|
|
183
313
|
printf "# roots="
|
|
184
314
|
for (i = 1; i <= seed_count; i++) {
|
|
@@ -192,25 +322,216 @@ END {
|
|
|
192
322
|
printf "%s", mod_display[ordered[i]]
|
|
193
323
|
}
|
|
194
324
|
printf "\n\n"
|
|
325
|
+
|
|
195
326
|
printf "%sstatus=0\n", prefix
|
|
327
|
+
printf "%sfailed=0\n", prefix
|
|
328
|
+
printf "%sfirst_status=0\n", prefix
|
|
329
|
+
printf "%sunavailable=\n", prefix
|
|
330
|
+
printf "%smode=${VALISE_MODE:-strict}\n", prefix
|
|
331
|
+
printf "case \"$%smode\" in\n", prefix
|
|
332
|
+
printf "strict | permissive) ;;\n"
|
|
333
|
+
printf "*)\n"
|
|
334
|
+
printf " printf '%%s\\n' \"valise: invalid mode: $%smode\" >&2\n", prefix
|
|
335
|
+
printf " unset %sstatus %sfailed %sfirst_status %sunavailable %smode\n", prefix, prefix, prefix, prefix, prefix
|
|
336
|
+
printf " return 1\n"
|
|
337
|
+
printf " ;;\n"
|
|
338
|
+
printf "esac\n"
|
|
339
|
+
|
|
340
|
+
printf "%splatform=$(command uname -s 2>/dev/null || printf unknown)\n", prefix
|
|
341
|
+
printf "case \"$%splatform\" in\n", prefix
|
|
342
|
+
printf "Darwin) %splatform=darwin ;;\n", prefix
|
|
343
|
+
printf "Linux) %splatform=linux ;;\n", prefix
|
|
344
|
+
printf "FreeBSD) %splatform=freebsd ;;\n", prefix
|
|
345
|
+
printf "OpenBSD) %splatform=openbsd ;;\n", prefix
|
|
346
|
+
printf "NetBSD) %splatform=netbsd ;;\n", prefix
|
|
347
|
+
printf "*BSD) %splatform=bsd ;;\n", prefix
|
|
348
|
+
printf "*) %splatform=unknown ;;\n", prefix
|
|
349
|
+
printf "esac\n"
|
|
350
|
+
|
|
351
|
+
if (reload_export_count > 0) {
|
|
352
|
+
printf "if [ \"${VALISE_RELOAD:-}\" = 1 ]; then\n"
|
|
353
|
+
printf " for %sexport in", prefix
|
|
354
|
+
for (i = 1; i <= reload_export_count; i++) {
|
|
355
|
+
printf " %s", reload_exports[i]
|
|
356
|
+
}
|
|
357
|
+
printf "; do\n"
|
|
358
|
+
printf " unset -f \"$%sexport\" 2>/dev/null || :\n", prefix
|
|
359
|
+
printf " done\n"
|
|
360
|
+
printf " unset %sexport\n", prefix
|
|
361
|
+
printf "fi\n"
|
|
362
|
+
}
|
|
363
|
+
|
|
196
364
|
if (prelude_fn_count > 0) {
|
|
197
365
|
printf ". \"${VALISE_MODULE_ROOT%%/}/%s\" || %sstatus=$?\n", prelude_dest, prefix
|
|
366
|
+
printf "if [ \"$%sstatus\" -ne 0 ]; then\n", prefix
|
|
367
|
+
printf " printf 'valise: capability prelude failed with status %%s\\n' \"$%sstatus\" >&2\n", prefix
|
|
368
|
+
printf " %sfailed=1\n", prefix
|
|
369
|
+
printf " %sfirst_status=$%sstatus\n", prefix, prefix
|
|
370
|
+
printf "fi\n"
|
|
198
371
|
}
|
|
372
|
+
|
|
373
|
+
printf "if [ \"$%sstatus\" -eq 0 ]; then\n", prefix
|
|
374
|
+
printf " for %smodule in", prefix
|
|
199
375
|
for (i = 1; i <= ordered_count; i++) {
|
|
200
|
-
|
|
201
|
-
printf "if [ \"${%sstatus}\" -eq 0 ]; then\n", prefix
|
|
202
|
-
printf " . \"${VALISE_MODULE_ROOT%%/}/%s\"\n", mod_load[name]
|
|
203
|
-
printf " %sstatus=$?\n", prefix
|
|
204
|
-
printf "fi\n"
|
|
376
|
+
printf " %s", ordered[i]
|
|
205
377
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
378
|
+
printf "; do\n"
|
|
379
|
+
printf " %srequired=1\n", prefix
|
|
380
|
+
printf " case \"$%smodule\" in\n", prefix
|
|
381
|
+
|
|
382
|
+
for (oi = 1; oi <= ordered_count; oi++) {
|
|
383
|
+
name = ordered[oi]
|
|
384
|
+
display = mod_display[name]
|
|
385
|
+
printf " %s)\n", name
|
|
386
|
+
platform_any = 0
|
|
387
|
+
n_platforms = split(mod_platforms[name], platform_values, " ")
|
|
388
|
+
for (pi = 1; pi <= n_platforms; pi++) {
|
|
389
|
+
if (platform_values[pi] == "any") platform_any = 1
|
|
210
390
|
}
|
|
211
|
-
|
|
391
|
+
export_count = split(mod_exports[name], module_exports, " ")
|
|
392
|
+
body_indent = platform_any == 0 ? " " : " "
|
|
393
|
+
|
|
394
|
+
# An optional-only module is expected to be unavailable on some hosts,
|
|
395
|
+
# so it fails quietly; the loop below does not treat it as a plan failure.
|
|
396
|
+
if (!(name in required)) printf " %srequired=0\n", prefix
|
|
397
|
+
|
|
398
|
+
# A module whose required dependency is unavailable must not run: its
|
|
399
|
+
# body would call functions that were never defined.
|
|
400
|
+
n_req = split(mod_req[name], reqs, " ")
|
|
401
|
+
if (n_req > 0) {
|
|
402
|
+
printf " %sblocked_by=\n", prefix
|
|
403
|
+
printf " case \" $%sunavailable \" in", prefix
|
|
404
|
+
for (rqi = 1; rqi <= n_req; rqi++) {
|
|
405
|
+
printf " *\" %s \"*) %sblocked_by=%s ;;", reqs[rqi], prefix, mod_display[reqs[rqi]]
|
|
406
|
+
}
|
|
407
|
+
printf " esac\n"
|
|
408
|
+
printf " if [ -n \"$%sblocked_by\" ]; then\n", prefix
|
|
409
|
+
if (name in required) {
|
|
410
|
+
printf " printf 'valise: module %%s skipped: required dependency unavailable: %%s\\n' '%s' \"$%sblocked_by\" >&2\n", display, prefix
|
|
411
|
+
}
|
|
412
|
+
printf " %sstatus=1\n", prefix
|
|
413
|
+
printf " else\n"
|
|
414
|
+
# A meta module on any platform has no statement for this branch.
|
|
415
|
+
if (mod_load[name] == "" && platform_any == 1) printf " :\n"
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (platform_any == 0) {
|
|
419
|
+
printf " %splatform_match=0\n", prefix
|
|
420
|
+
for (pi = 1; pi <= n_platforms; pi++) {
|
|
421
|
+
platform = platform_values[pi]
|
|
422
|
+
if (platform == "bsd") {
|
|
423
|
+
printf " case \"$%splatform\" in bsd | freebsd | openbsd | netbsd) %splatform_match=1 ;; esac\n", prefix, prefix
|
|
424
|
+
} else if (platform != "any") {
|
|
425
|
+
printf " [ \"$%splatform\" = %s ] && %splatform_match=1\n", prefix, platform, prefix
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
printf " if [ \"$%splatform_match\" -ne 1 ]; then\n", prefix
|
|
429
|
+
if (name in required) {
|
|
430
|
+
printf " printf 'valise: module %%s skipped: unsupported platform: %%s\\n' '%s' \"$%splatform\" >&2\n", display, prefix
|
|
431
|
+
}
|
|
432
|
+
printf " %sstatus=1\n", prefix
|
|
433
|
+
if (mod_load[name] != "") printf " else\n"
|
|
434
|
+
}
|
|
435
|
+
if (mod_load[name] != "") {
|
|
436
|
+
if (export_count == 0) {
|
|
437
|
+
printf "%s. \"${VALISE_MODULE_ROOT%%/}/%s\"\n", body_indent, mod_load[name]
|
|
438
|
+
printf "%s%sstatus=$?\n", body_indent, prefix
|
|
439
|
+
printf "%sif [ \"$%sstatus\" -ne 0 ]; then\n", body_indent, prefix
|
|
440
|
+
printf "%s printf 'valise: module %%s failed with status %%s\\n' '%s' \"$%sstatus\" >&2\n", body_indent, display, prefix
|
|
441
|
+
printf "%sfi\n", body_indent
|
|
442
|
+
} else {
|
|
443
|
+
printf "%s%swrapper_exports=\n", body_indent, prefix
|
|
444
|
+
printf "%s%scleanup_exports=\n", body_indent, prefix
|
|
445
|
+
printf "%sfor %sexport in", body_indent, prefix
|
|
446
|
+
for (ei = 1; ei <= export_count; ei++) {
|
|
447
|
+
printf " %s", module_exports[ei]
|
|
448
|
+
}
|
|
449
|
+
printf "; do\n"
|
|
450
|
+
printf "%s %sconflict=0\n", body_indent, prefix
|
|
451
|
+
printf "%s if command alias \"$%sexport\" >/dev/null 2>&1; then\n", body_indent, prefix
|
|
452
|
+
printf "%s %sconflict=1\n", body_indent, prefix
|
|
453
|
+
printf "%s fi\n", body_indent
|
|
454
|
+
printf "%s if %slookup=$(command -v \"$%sexport\" 2>/dev/null); then\n", body_indent, prefix, prefix
|
|
455
|
+
printf "%s case \"$%slookup\" in\n", body_indent, prefix
|
|
456
|
+
printf "%s alias\\ *) %sconflict=1 ;;\n", body_indent, prefix
|
|
457
|
+
printf "%s */*) %swrapper_exports=\"${%swrapper_exports:+$%swrapper_exports }$%sexport\" ;;\n", body_indent, prefix, prefix, prefix, prefix, prefix
|
|
458
|
+
printf "%s *) %sconflict=1 ;;\n", body_indent, prefix
|
|
459
|
+
printf "%s esac\n", body_indent
|
|
460
|
+
printf "%s else\n", body_indent
|
|
461
|
+
printf "%s %slookup=\n", body_indent, prefix
|
|
462
|
+
printf "%s fi\n", body_indent
|
|
463
|
+
printf "%s if [ \"$%sconflict\" -ne 0 ]; then\n", body_indent, prefix
|
|
464
|
+
printf "%s printf 'valise: %%s: declared export already exists before load: %%s\\n' '%s' \"$%sexport\" >&2\n", body_indent, display, prefix
|
|
465
|
+
printf "%s %sstatus=1\n", body_indent, prefix
|
|
466
|
+
printf "%s else\n", body_indent
|
|
467
|
+
printf "%s %scleanup_exports=\"${%scleanup_exports:+$%scleanup_exports }$%sexport\"\n", body_indent, prefix, prefix, prefix, prefix, prefix
|
|
468
|
+
printf "%s fi\n", body_indent
|
|
469
|
+
printf "%sdone\n", body_indent
|
|
470
|
+
printf "%sif [ \"$%sstatus\" -eq 0 ]; then\n", body_indent, prefix
|
|
471
|
+
printf "%s . \"${VALISE_MODULE_ROOT%%/}/%s\"\n", body_indent, mod_load[name]
|
|
472
|
+
printf "%s %sstatus=$?\n", body_indent, prefix
|
|
473
|
+
printf "%sfi\n", body_indent
|
|
474
|
+
printf "%sif [ \"$%sstatus\" -eq 0 ]; then\n", body_indent, prefix
|
|
475
|
+
printf "%s for %sexport in", body_indent, prefix
|
|
476
|
+
for (ei = 1; ei <= export_count; ei++) {
|
|
477
|
+
printf " %s", module_exports[ei]
|
|
478
|
+
}
|
|
479
|
+
printf "; do\n"
|
|
480
|
+
printf "%s if ! %slookup=$(command -v \"$%sexport\" 2>/dev/null); then\n", body_indent, prefix, prefix
|
|
481
|
+
printf "%s printf 'valise: %%s: declared export missing: %%s\\n' '%s' \"$%sexport\" >&2\n", body_indent, display, prefix
|
|
482
|
+
printf "%s %sstatus=1\n", body_indent, prefix
|
|
483
|
+
printf "%s else\n", body_indent
|
|
484
|
+
printf "%s case \" $%swrapper_exports \" in\n", body_indent, prefix
|
|
485
|
+
printf "%s *\" $%sexport \"*)\n", body_indent, prefix
|
|
486
|
+
printf "%s case \"$%slookup\" in\n", body_indent, prefix
|
|
487
|
+
printf "%s */*)\n", body_indent
|
|
488
|
+
printf "%s printf 'valise: %%s: declared export was not provided by module: %%s\\n' '%s' \"$%sexport\" >&2\n", body_indent, display, prefix
|
|
489
|
+
printf "%s %sstatus=1\n", body_indent, prefix
|
|
490
|
+
printf "%s ;;\n", body_indent
|
|
491
|
+
printf "%s esac\n", body_indent
|
|
492
|
+
printf "%s ;;\n", body_indent
|
|
493
|
+
printf "%s esac\n", body_indent
|
|
494
|
+
printf "%s fi\n", body_indent
|
|
495
|
+
printf "%s done\n", body_indent
|
|
496
|
+
printf "%sfi\n", body_indent
|
|
497
|
+
printf "%sif [ \"$%sstatus\" -ne 0 ]; then\n", body_indent, prefix
|
|
498
|
+
printf "%s printf 'valise: module %%s failed with status %%s\\n' '%s' \"$%sstatus\" >&2\n", body_indent, display, prefix
|
|
499
|
+
printf "%s for %sexport in $%scleanup_exports; do\n", body_indent, prefix, prefix
|
|
500
|
+
printf "%s unset -f \"$%sexport\" 2>/dev/null || :\n", body_indent, prefix
|
|
501
|
+
printf "%s done\n", body_indent
|
|
502
|
+
printf "%sfi\n", body_indent
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (platform_any == 0) {
|
|
506
|
+
printf " fi\n"
|
|
507
|
+
}
|
|
508
|
+
if (n_req > 0) {
|
|
509
|
+
printf " fi\n"
|
|
510
|
+
}
|
|
511
|
+
printf " ;;\n"
|
|
212
512
|
}
|
|
213
|
-
|
|
214
|
-
printf "
|
|
513
|
+
|
|
514
|
+
printf " esac\n"
|
|
515
|
+
# Strict mode stops at the first failure in the required closure. Anything
|
|
516
|
+
# else is recorded so its dependents are blocked, and the plan continues.
|
|
517
|
+
printf " if [ \"$%sstatus\" -ne 0 ]; then\n", prefix
|
|
518
|
+
printf " %sunavailable=\"$%sunavailable $%smodule\"\n", prefix, prefix, prefix
|
|
519
|
+
printf " if [ \"$%smode\" = strict ] && [ \"$%srequired\" -eq 1 ]; then\n", prefix, prefix
|
|
520
|
+
printf " %sfailed=1\n", prefix
|
|
521
|
+
printf " %sfirst_status=$%sstatus\n", prefix, prefix
|
|
522
|
+
printf " break\n"
|
|
523
|
+
printf " fi\n"
|
|
524
|
+
printf " %sstatus=0\n", prefix
|
|
525
|
+
printf " fi\n"
|
|
526
|
+
printf " done\n"
|
|
527
|
+
printf "fi\n"
|
|
528
|
+
|
|
529
|
+
printf "if [ \"$%sfailed\" -ne 0 ]; then\n", prefix
|
|
530
|
+
printf " %sreturn=$%sfirst_status\n", prefix, prefix
|
|
531
|
+
printf "else\n"
|
|
532
|
+
printf " %sreturn=0\n", prefix
|
|
533
|
+
printf "fi\n"
|
|
534
|
+
printf "unset %sstatus %sfailed %sfirst_status %sunavailable %smode %splatform\n", prefix, prefix, prefix, prefix, prefix, prefix
|
|
535
|
+
printf "unset %smodule %srequired %sblocked_by %splatform_match %sexport %slookup %sconflict %swrapper_exports %scleanup_exports\n", prefix, prefix, prefix, prefix, prefix, prefix, prefix, prefix, prefix
|
|
215
536
|
printf "eval \"unset %sreturn; return $%sreturn\"\n", prefix, prefix
|
|
216
537
|
}
|
package/lib/fish/loader.fish
CHANGED
|
@@ -728,8 +728,8 @@ function sp_fish_loader_validate_provider_conflicts --description 'Reject confli
|
|
|
728
728
|
end
|
|
729
729
|
set -l candidate_load_line (string match 'load=*' -- $candidate_parsed)
|
|
730
730
|
set -l candidate_load (string replace 'load=' '' -- $candidate_load_line)
|
|
731
|
-
# Generic .sh
|
|
732
|
-
#
|
|
731
|
+
# Generic .sh providers are graph metadata only for Fish; they
|
|
732
|
+
# have no native body whose ownership can be compared.
|
|
733
733
|
if not string match -q '*.fish' -- "$primary_load"; or not string match -q '*.fish' -- "$candidate_load"
|
|
734
734
|
continue
|
|
735
735
|
end
|
|
@@ -931,6 +931,8 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
931
931
|
set -l meta_provider_modules
|
|
932
932
|
set -l meta_provider_paths
|
|
933
933
|
set -l optional_modules
|
|
934
|
+
set -l platform_skipped_modules
|
|
935
|
+
set -l requires_edges
|
|
934
936
|
set -l shell_skipped_modules
|
|
935
937
|
set -l failed_modules
|
|
936
938
|
set -l runtime_failed_modules
|
|
@@ -1029,15 +1031,7 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
1029
1031
|
end
|
|
1030
1032
|
end
|
|
1031
1033
|
if test $platform_match -ne 1
|
|
1032
|
-
|
|
1033
|
-
sp_fish_loader_error "warning: optional module unsupported on platform: $current"
|
|
1034
|
-
set -a failed_modules $current
|
|
1035
|
-
continue
|
|
1036
|
-
end
|
|
1037
|
-
if test "$mode" = strict
|
|
1038
|
-
sp_fish_loader_error "unsupported platform for $current: $current_platform"
|
|
1039
|
-
return 1
|
|
1040
|
-
end
|
|
1034
|
+
set -a platform_skipped_modules $current
|
|
1041
1035
|
set -a failed_modules $current
|
|
1042
1036
|
continue
|
|
1043
1037
|
end
|
|
@@ -1063,12 +1057,20 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
1063
1057
|
continue
|
|
1064
1058
|
end
|
|
1065
1059
|
|
|
1066
|
-
# A Fish-advertised module must provide a native Fish body
|
|
1067
|
-
#
|
|
1068
|
-
# providers and must not satisfy required Fish dependents.
|
|
1060
|
+
# A Fish-advertised module must provide a native Fish body, or be a
|
|
1061
|
+
# dependency-only meta record. Generic POSIX bodies are not executable
|
|
1062
|
+
# Fish providers and must not satisfy required Fish dependents.
|
|
1069
1063
|
set -l load_line (string match 'load=*' -- $parsed)
|
|
1070
1064
|
set -l load_val (string replace 'load=' '' -- $load_line)
|
|
1071
|
-
|
|
1065
|
+
set -l meta_requires_line (string match 'requires=*' -- $parsed)
|
|
1066
|
+
set -l meta_requires_val (string replace 'requires=' '' -- $meta_requires_line)
|
|
1067
|
+
set -l is_meta 0
|
|
1068
|
+
if test -z "$load_val"; and test -n "$meta_requires_val"
|
|
1069
|
+
set is_meta 1
|
|
1070
|
+
end
|
|
1071
|
+
if test $is_meta -eq 0; and begin
|
|
1072
|
+
test -z "$load_val"; or not string match -q '*.fish' -- $load_val
|
|
1073
|
+
end
|
|
1072
1074
|
if builtin contains -- $current $optional_modules; and not builtin contains -- $current $explicit_seeds
|
|
1073
1075
|
sp_fish_loader_error "warning: optional Fish module has no native .fish load path: $current"
|
|
1074
1076
|
set -a failed_modules $current
|
|
@@ -1106,6 +1108,7 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
1106
1108
|
# Enqueue requires dependencies
|
|
1107
1109
|
set -l req_line (string match 'requires=*' -- $parsed)
|
|
1108
1110
|
set -l req_val (string replace 'requires=' '' -- $req_line)
|
|
1111
|
+
set -a requires_edges "$current|$req_val"
|
|
1109
1112
|
for dep in (string split ' ' -- $req_val)
|
|
1110
1113
|
if test -z "$dep"
|
|
1111
1114
|
continue
|
|
@@ -1134,6 +1137,42 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
1134
1137
|
end
|
|
1135
1138
|
end
|
|
1136
1139
|
|
|
1140
|
+
# A platform skip is expected for an optional-only module and stays
|
|
1141
|
+
# silent. Re-walk the required edges to a fixpoint first, because discovery
|
|
1142
|
+
# can mark a module optional before a later required edge promotes it; a
|
|
1143
|
+
# required-closure platform skip is a strict failure and must be named.
|
|
1144
|
+
set -l required_modules $seeds
|
|
1145
|
+
set -l closure_progress 1
|
|
1146
|
+
while test $closure_progress -eq 1
|
|
1147
|
+
set closure_progress 0
|
|
1148
|
+
for edge in $requires_edges
|
|
1149
|
+
set -l edge_parts (string split '|' -- $edge)
|
|
1150
|
+
set -l edge_from $edge_parts[1]
|
|
1151
|
+
set -l edge_deps (string split ' ' -- $edge_parts[2])
|
|
1152
|
+
if not builtin contains -- $edge_from $required_modules
|
|
1153
|
+
continue
|
|
1154
|
+
end
|
|
1155
|
+
for edge_dep in $edge_deps
|
|
1156
|
+
if test -z "$edge_dep"; or builtin contains -- $edge_dep $required_modules
|
|
1157
|
+
continue
|
|
1158
|
+
end
|
|
1159
|
+
set -a required_modules $edge_dep
|
|
1160
|
+
set closure_progress 1
|
|
1161
|
+
end
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
set -l report_platform (sp_fish_loader_platform_name)
|
|
1165
|
+
test -n "$report_platform"; or set report_platform unknown
|
|
1166
|
+
for skipped_module in $platform_skipped_modules
|
|
1167
|
+
if not builtin contains -- $skipped_module $required_modules
|
|
1168
|
+
continue
|
|
1169
|
+
end
|
|
1170
|
+
sp_fish_loader_error "unsupported platform for $skipped_module: $report_platform"
|
|
1171
|
+
if test "$mode" = strict
|
|
1172
|
+
return 1
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1137
1176
|
# Phase 2: Kahn's algorithm for deterministic topological sort
|
|
1138
1177
|
set -l remaining $discovered
|
|
1139
1178
|
set -l ordered
|
|
@@ -1343,6 +1382,33 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
1343
1382
|
end
|
|
1344
1383
|
|
|
1345
1384
|
if test -z "$load_path"
|
|
1385
|
+
# A meta module has no body: its dependencies have already loaded,
|
|
1386
|
+
# and it must not claim exports it cannot define.
|
|
1387
|
+
if test (builtin count $mod_reqs) -gt 0
|
|
1388
|
+
set -l meta_export_count 0
|
|
1389
|
+
for meta_export in $mod_exports
|
|
1390
|
+
if test -n "$meta_export"
|
|
1391
|
+
set meta_export_count (builtin math $meta_export_count + 1)
|
|
1392
|
+
end
|
|
1393
|
+
end
|
|
1394
|
+
if test $meta_export_count -gt 0
|
|
1395
|
+
if builtin contains -- $mod $optional_modules
|
|
1396
|
+
sp_fish_loader_error "warning: optional meta module declares exports without a body: $mod"
|
|
1397
|
+
set -a failed_modules $mod
|
|
1398
|
+
set -a runtime_failed_modules $mod
|
|
1399
|
+
continue
|
|
1400
|
+
end
|
|
1401
|
+
if test "$mode" = strict
|
|
1402
|
+
sp_fish_loader_error "meta module declares exports without a body: $mod"
|
|
1403
|
+
return 1
|
|
1404
|
+
end
|
|
1405
|
+
set -a runtime_failed_modules $mod
|
|
1406
|
+
sp_fish_loader_error "warning: meta module declares exports without a body: $mod"
|
|
1407
|
+
continue
|
|
1408
|
+
end
|
|
1409
|
+
set -g _sp_fish_loaded_modules $_sp_fish_loaded_modules $mod
|
|
1410
|
+
continue
|
|
1411
|
+
end
|
|
1346
1412
|
if builtin contains -- $mod $optional_modules
|
|
1347
1413
|
sp_fish_loader_error "warning: optional module has no load path: $mod"
|
|
1348
1414
|
set -a failed_modules $mod
|
|
@@ -1469,15 +1535,24 @@ function _sp_fish_loader_load_ordered_impl --description 'Load modules from seed
|
|
|
1469
1535
|
set -l caller_function_copy_failed 0
|
|
1470
1536
|
builtin functions --copy "$caller_function_name" "$caller_function_snapshot"; or set caller_function_copy_failed 1
|
|
1471
1537
|
set -l caller_function_snapshot_source (builtin functions --details "$caller_function_snapshot")
|
|
1538
|
+
set -l caller_function_copy_matches 0
|
|
1539
|
+
if _sp_fish_loader_function_copy_matches "$caller_function_name" "$caller_function_snapshot"; or _sp_fish_loader_is_trusted_system_function_source "$caller_function_source" "$caller_function_name"
|
|
1540
|
+
set caller_function_copy_matches 1
|
|
1541
|
+
end
|
|
1472
1542
|
set -l caller_function_source_authenticated 0
|
|
1473
1543
|
if builtin test "$caller_function_snapshot_source" = "$loader_source_file"
|
|
1474
1544
|
set caller_function_source_authenticated 1
|
|
1545
|
+
else if builtin test "$caller_function_snapshot_source" = "$caller_function_source"
|
|
1546
|
+
# Fish keeps the copy's original source; a copy that agrees
|
|
1547
|
+
# with the live function's source is a faithful custody copy.
|
|
1548
|
+
set caller_function_source_authenticated 1
|
|
1475
1549
|
else if _sp_fish_loader_is_trusted_system_function_source "$caller_function_source" "$caller_function_name"
|
|
1476
1550
|
set caller_function_source_authenticated 1
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1551
|
+
else if builtin test $caller_function_copy_matches -eq 1
|
|
1552
|
+
# Fish 3 reports a copied function's source as ``stdin``. The
|
|
1553
|
+
# copy was made by Fish's own primitive and matches the live
|
|
1554
|
+
# definition; later restores re-verify that definition.
|
|
1555
|
+
set caller_function_source_authenticated 1
|
|
1481
1556
|
end
|
|
1482
1557
|
if builtin functions -q "$caller_function_snapshot"; and builtin test $caller_function_copy_failed -eq 0; and builtin test $caller_function_source_authenticated -eq 1; and builtin test $caller_function_copy_matches -eq 1
|
|
1483
1558
|
# The source and complete definition were authenticated.
|