@gjsify/cli 0.41.0 → 0.43.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/README.md +79 -33
- package/dist/cli.gjs.mjs +1068 -405
- package/lib/actions/build.d.ts +20 -0
- package/lib/actions/build.js +118 -15
- package/lib/bundler-pick.js +10 -2
- package/lib/cli-app.js +2 -1
- package/lib/commands/browse.js +1 -1
- package/lib/commands/debug.js +1 -1
- package/lib/commands/dev.d.ts +13 -0
- package/lib/commands/dev.js +132 -0
- package/lib/commands/dlx.js +1 -1
- package/lib/commands/index.d.ts +1 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/run.js +38 -85
- package/lib/commands/ship.d.ts +2 -0
- package/lib/commands/ship.js +285 -88
- package/lib/commands/showcase.js +1 -1
- package/lib/commands/storybook.js +17 -74
- package/lib/commands/test.js +12 -16
- package/lib/commands/tsc.js +96 -45
- package/lib/config.js +4 -0
- package/lib/types/config-data.d.ts +59 -0
- package/lib/utils/app-metadata.js +27 -6
- package/lib/utils/check-system-deps.js +49 -7
- package/lib/utils/daemon-command.d.ts +7 -0
- package/lib/utils/daemon-command.js +30 -0
- package/lib/utils/dev-plan.d.ts +52 -0
- package/lib/utils/dev-plan.js +117 -0
- package/lib/utils/gi-runtime-paths.d.ts +27 -0
- package/lib/utils/gi-runtime-paths.js +84 -0
- package/lib/utils/gi-typelib.d.ts +49 -0
- package/lib/utils/gi-typelib.js +160 -0
- package/lib/utils/gjs-bundle-guard.d.ts +57 -3
- package/lib/utils/gjs-bundle-guard.js +122 -13
- package/lib/utils/jsx-config.d.ts +45 -0
- package/lib/utils/jsx-config.js +98 -0
- package/lib/utils/node-script.d.ts +4 -1
- package/lib/utils/node-script.js +27 -3
- package/lib/utils/npm-oidc.js +8 -5
- package/lib/utils/onboard-probe.d.ts +23 -0
- package/lib/utils/onboard-probe.js +65 -0
- package/lib/utils/run-gjs.d.ts +12 -1
- package/lib/utils/run-gjs.js +24 -31
- package/lib/utils/run-node.d.ts +9 -4
- package/lib/utils/run-node.js +34 -41
- package/lib/utils/ship/deb.d.ts +3 -3
- package/lib/utils/ship/deb.js +6 -2
- package/lib/utils/ship/depends.d.ts +14 -6
- package/lib/utils/ship/depends.js +45 -4
- package/lib/utils/ship/discover.js +178 -7
- package/lib/utils/ship/formats.js +9 -1
- package/lib/utils/ship/launcher.js +27 -4
- package/lib/utils/ship/mime.d.ts +19 -0
- package/lib/utils/ship/mime.js +89 -0
- package/lib/utils/ship/payload.d.ts +111 -0
- package/lib/utils/ship/payload.js +174 -0
- package/lib/utils/ship/plan.d.ts +30 -1
- package/lib/utils/ship/plan.js +74 -2
- package/lib/utils/ship/rpm.d.ts +3 -3
- package/lib/utils/ship/rpm.js +20 -3
- package/lib/utils/ship/scripts.d.ts +14 -4
- package/lib/utils/ship/scripts.js +26 -8
- package/lib/utils/ship/settings.d.ts +8 -1
- package/lib/utils/ship/settings.js +26 -1
- package/lib/utils/ship/stage-manifest.d.ts +154 -0
- package/lib/utils/ship/stage-manifest.js +408 -0
- package/lib/utils/ship/stage-writer.d.ts +30 -3
- package/lib/utils/ship/stage-writer.js +76 -9
- package/lib/utils/ship/types.d.ts +110 -18
- package/lib/utils/ship/types.js +5 -0
- package/lib/utils/simple-command.d.ts +16 -0
- package/lib/utils/simple-command.js +77 -0
- package/lib/utils/spawn.d.ts +7 -1
- package/lib/utils/spawn.js +34 -7
- package/lib/utils/system-gi.d.ts +24 -0
- package/lib/utils/system-gi.js +14 -3
- package/lib/utils/watch-loop.d.ts +91 -0
- package/lib/utils/watch-loop.js +207 -0
- package/package.json +19 -18
- package/lib/templates/app/desktop.tmpl +0 -10
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Defining a file type for the system — the half `MimeType=` cannot do on its own.
|
|
2
|
+
//
|
|
3
|
+
// A desktop entry's `MimeType=` says "I open this type". It does NOT say the type EXISTS. For a
|
|
4
|
+
// standard type that is fine, because the distribution's shared-mime-info already defines it; for a
|
|
5
|
+
// type of the project's own it is not, and the failure is the quietest kind there is: nothing knows
|
|
6
|
+
// what a `.bauplan` file is, so the file manager never assigns the type, `MimeType=` matches
|
|
7
|
+
// nothing, and a double-click does nothing at all. No error is printed and no log line is written —
|
|
8
|
+
// the association simply never fires, which is indistinguishable from "the app is not installed".
|
|
9
|
+
//
|
|
10
|
+
// So a project that invents a type also ships a shared-mime-info document, and the package refreshes
|
|
11
|
+
// the MIME cache on install (see `scripts.ts`) — an unrefreshed cache is the same silence again.
|
|
12
|
+
/** XML text escaping. Only the five that matter in element content and attributes. */
|
|
13
|
+
function esc(value) {
|
|
14
|
+
return value
|
|
15
|
+
.replace(/&/g, '&')
|
|
16
|
+
.replace(/</g, '<')
|
|
17
|
+
.replace(/>/g, '>')
|
|
18
|
+
.replace(/"/g, '"')
|
|
19
|
+
.replace(/'/g, ''');
|
|
20
|
+
}
|
|
21
|
+
const TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/;
|
|
22
|
+
/**
|
|
23
|
+
* Validate the declared types, throwing on anything that would install and then not work.
|
|
24
|
+
*
|
|
25
|
+
* Each refusal is a silent failure made loud:
|
|
26
|
+
* - a malformed type name is written into the XML and ignored by `update-mime-database`;
|
|
27
|
+
* - a glob without `*.` never matches (`bauplan` matches only a file called exactly that);
|
|
28
|
+
* - a type with no glob and no parent type can never be DETECTED, so it is registered and unused;
|
|
29
|
+
* - a duplicate definition makes which comment wins depend on document order.
|
|
30
|
+
*/
|
|
31
|
+
export function validateMimeTypes(types) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const entry of types) {
|
|
34
|
+
if (!TYPE_RE.test(entry.type)) {
|
|
35
|
+
throw new Error(`gjsify ship: \`gjsify.ship.mimeTypes\` has an invalid type "${entry.type}". ` +
|
|
36
|
+
'Expected `<media>/<subtype>`, e.g. `application/x-bauplan` — update-mime-database ' +
|
|
37
|
+
'ignores a malformed name, so the type would install and never resolve.');
|
|
38
|
+
}
|
|
39
|
+
if (seen.has(entry.type)) {
|
|
40
|
+
throw new Error(`gjsify ship: \`gjsify.ship.mimeTypes\` defines ${entry.type} twice. ` +
|
|
41
|
+
'Which comment and glob set win would depend on document order.');
|
|
42
|
+
}
|
|
43
|
+
seen.add(entry.type);
|
|
44
|
+
if (entry.comment.trim() === '') {
|
|
45
|
+
throw new Error(`gjsify ship: ${entry.type} has an empty \`comment\`. A file manager then shows the ` +
|
|
46
|
+
'user the raw type string instead of a name.');
|
|
47
|
+
}
|
|
48
|
+
for (const glob of entry.globs ?? []) {
|
|
49
|
+
if (!glob.includes('*') && !glob.includes('?') && !glob.includes('[')) {
|
|
50
|
+
throw new Error(`gjsify ship: ${entry.type} has the glob "${glob}", which matches only a file ` +
|
|
51
|
+
`named exactly that. A suffix pattern is written \`*.${glob.replace(/^\.+/, '')}\`.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if ((entry.globs ?? []).length === 0 && entry.subClassOf === undefined) {
|
|
55
|
+
throw new Error(`gjsify ship: ${entry.type} declares neither \`globs\` nor \`subClassOf\`, so nothing ` +
|
|
56
|
+
'can ever match it. The type would be registered and then never assigned to a file.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The shared-mime-info document for `types`.
|
|
62
|
+
*
|
|
63
|
+
* One file per package, named after the app id, because `share/mime/packages/` is shared: a generic
|
|
64
|
+
* name is a collision with whatever another package put there — the same reason the schema staging
|
|
65
|
+
* uses the app id.
|
|
66
|
+
*/
|
|
67
|
+
export function renderMimePackage(types) {
|
|
68
|
+
const body = types
|
|
69
|
+
.map((entry) => {
|
|
70
|
+
const lines = [` <mime-type type="${esc(entry.type)}">`, ` <comment>${esc(entry.comment)}</comment>`];
|
|
71
|
+
if (entry.subClassOf !== undefined) {
|
|
72
|
+
lines.push(` <sub-class-of type="${esc(entry.subClassOf)}"/>`);
|
|
73
|
+
}
|
|
74
|
+
for (const glob of entry.globs ?? []) {
|
|
75
|
+
lines.push(` <glob pattern="${esc(glob)}"/>`);
|
|
76
|
+
}
|
|
77
|
+
if (entry.genericIcon !== undefined) {
|
|
78
|
+
lines.push(` <generic-icon name="${esc(entry.genericIcon)}"/>`);
|
|
79
|
+
}
|
|
80
|
+
lines.push(' </mime-type>');
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
})
|
|
83
|
+
.join('\n');
|
|
84
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
85
|
+
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
|
86
|
+
${body}
|
|
87
|
+
</mime-info>
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
@@ -16,8 +16,84 @@ export interface PayloadEntry {
|
|
|
16
16
|
* what `rpm -qi` then shows a user, and an artifact that looks broken is a
|
|
17
17
|
* support question. The mtime keeps the property that matters (pack the same
|
|
18
18
|
* build twice, get the same bytes) while saying something true.
|
|
19
|
+
*
|
|
20
|
+
* Only the ASSEMBLING host runs this. A stage records the answer it got
|
|
21
|
+
* (`.gjsify-ship-stage.json` → `mtime`) and the packing host reuses it: an
|
|
22
|
+
* artifact upload does not carry mtimes, so re-stat'ing the stage there would
|
|
23
|
+
* stamp every header with "whenever the download finished" and quietly destroy
|
|
24
|
+
* the reproducibility this function exists to protect.
|
|
19
25
|
*/
|
|
20
26
|
export declare function buildTimestamp(bundlePath: string, env?: Record<string, string | undefined>): number;
|
|
27
|
+
/**
|
|
28
|
+
* What the payload itself says about what it installs.
|
|
29
|
+
*
|
|
30
|
+
* Every field here used to be answered from the SETTINGS — `settings.iconFiles.length > 0`,
|
|
31
|
+
* `settings.schemaFiles.length > 0`, `settings.typelibFiles` — i.e. from lists of absolute paths
|
|
32
|
+
* on the BUILD host. Two things were wrong with that, and the second is why this function exists
|
|
33
|
+
* at all:
|
|
34
|
+
*
|
|
35
|
+
* 1. It answered a different question than the one being asked. `cacheRefreshCommands` emits
|
|
36
|
+
* `gtk-update-icon-cache <prefix>/share/icons/hicolor` — the honest precondition is "did this
|
|
37
|
+
* package install anything into that directory", not "did the project have icon files lying
|
|
38
|
+
* around". They come apart for a `kind: 'cli'` project with a `data/icons/` folder: the
|
|
39
|
+
* planner stages no icon (icons are an `'app'` thing), the settings still listed them, and the
|
|
40
|
+
* postinst refreshed a cache for files that were never installed.
|
|
41
|
+
* 2. An absolute build-host path cannot cross to the host that packs the artifact
|
|
42
|
+
* (ADR 0024 § A2). Carrying `iconFiles: ["/home/…/icon.svg"]` in a stage manifest so that
|
|
43
|
+
* `.length > 0` can be read on another machine is authoring a value that is measurable right
|
|
44
|
+
* there in the tree.
|
|
45
|
+
*
|
|
46
|
+
* Path-only, on purpose — `isArchIndependent` is the sibling that reads BYTES, and the two answer
|
|
47
|
+
* different questions from the same payload. The same split as `plan.ts`'s `isExecutableAsset`
|
|
48
|
+
* (by name) versus this module's magic sniffing (by content).
|
|
49
|
+
*/
|
|
50
|
+
export interface PayloadFacts {
|
|
51
|
+
/** The payload installs a `share/applications/*.desktop`. */
|
|
52
|
+
hasDesktopEntry: boolean;
|
|
53
|
+
/** The payload installs into `share/icons/hicolor/`. */
|
|
54
|
+
hasIcons: boolean;
|
|
55
|
+
/** The payload installs a compiled-on-install `share/glib-2.0/schemas/*.gschema.xml`. */
|
|
56
|
+
hasSchemas: boolean;
|
|
57
|
+
/** The payload installs a `share/mime/packages/*.xml`, so the mime cache needs rebuilding. */
|
|
58
|
+
hasMimeTypes: boolean;
|
|
59
|
+
/** Prefix-relative paths of the typelibs the payload carries itself. */
|
|
60
|
+
bundledTypelibs: string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read {@link PayloadFacts} off a payload or off a plan.
|
|
64
|
+
*
|
|
65
|
+
* Takes anything with a `path`, so the assembling phase can ask the same
|
|
66
|
+
* question of the PLAN (before the tree is read back) that the packing phase
|
|
67
|
+
* asks of the payload. One function, so the two phases cannot disagree about
|
|
68
|
+
* whether a package installs a schema.
|
|
69
|
+
*/
|
|
70
|
+
export declare function readPayloadFacts(entries: readonly {
|
|
71
|
+
path: string;
|
|
72
|
+
}[]): PayloadFacts;
|
|
73
|
+
/**
|
|
74
|
+
* The interpreters the payload's own executables need, read off their shebangs.
|
|
75
|
+
*
|
|
76
|
+
* An interpreter is a dependency like any other, and rpm expects it declared:
|
|
77
|
+
* `rpmbuild`'s file-based generator emits one `Requires` per executable
|
|
78
|
+
* shebang, with the `RPMSENSE_FIND_REQUIRES` sense that says "derived, not
|
|
79
|
+
* declared". Measured on Fedora 44 against a package whose only file is a
|
|
80
|
+
* `#!/bin/sh` script: `rpm -qp --requires` → `/usr/bin/sh 16384`.
|
|
81
|
+
*
|
|
82
|
+
* The LITERAL path, not a resolved one. `rpmbuild` prints `/usr/bin/sh` there
|
|
83
|
+
* because it resolved `/bin` through the symlink of the usrmerged host it ran
|
|
84
|
+
* on; this writer has no target host to resolve against (ADR 0024 § A1 — the
|
|
85
|
+
* packers are pure JavaScript and run anywhere), and `/bin/sh` is satisfied on
|
|
86
|
+
* both layouts: measured on Fedora 44, `rpm -q --whatprovides /bin/sh` and
|
|
87
|
+
* `/usr/bin/sh` both answer `bash`. It is also the spelling the scriptlet
|
|
88
|
+
* requirements already use.
|
|
89
|
+
*
|
|
90
|
+
* EXECUTABLE files only, which is the same rule `rpmbuild` applies. A GJS
|
|
91
|
+
* bundle staged 0644 carries `#!/usr/bin/env -S gjs -m` for the days it is run
|
|
92
|
+
* directly, but nothing in the installed package executes it as a program — the
|
|
93
|
+
* launcher `exec`s `gjs` with it as an argument — so declaring `/usr/bin/env`
|
|
94
|
+
* for it would be a dependency on a path this package never uses.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readShebangInterpreters(payload: readonly PayloadEntry[]): string[];
|
|
21
97
|
/**
|
|
22
98
|
* Does the payload contain anything architecture-specific?
|
|
23
99
|
*
|
|
@@ -28,3 +104,38 @@ export declare function buildTimestamp(bundlePath: string, env?: Record<string,
|
|
|
28
104
|
* does not run.
|
|
29
105
|
*/
|
|
30
106
|
export declare function isArchIndependent(payload: readonly PayloadEntry[]): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* The `process.arch` token an image says it is built for, or `null` when the
|
|
109
|
+
* question cannot be answered from the bytes.
|
|
110
|
+
*
|
|
111
|
+
* `null` covers three different things on purpose, and all three must stay
|
|
112
|
+
* silent: a file that is not a native binary at all (most of a payload), a PE
|
|
113
|
+
* (whose COFF machine field this tree has never parsed — `isNativeBinary` reads
|
|
114
|
+
* `MZ` and stops), and a Mach-O fat archive, which carries several
|
|
115
|
+
* architectures and therefore matches any label a caller could pass.
|
|
116
|
+
*/
|
|
117
|
+
export declare function readBinaryArch(data: Uint8Array): string | null;
|
|
118
|
+
/**
|
|
119
|
+
* Refuse a payload whose binaries disagree with the label the package will carry.
|
|
120
|
+
*
|
|
121
|
+
* THE INCIDENT, measured on 0.41.0 before this existed. A project whose payload
|
|
122
|
+
* carries one x86-64 `.so`, packed on this x86-64 host:
|
|
123
|
+
*
|
|
124
|
+
* gjsify ship --skip-build --arch arm64
|
|
125
|
+
* → xarch-demo_1.2.3-1_arm64.deb, xarch-demo-1.2.3-1.aarch64.rpm
|
|
126
|
+
* rpm -qp --qf '%{ARCH}' → aarch64
|
|
127
|
+
* the .so inside it → ELF e_machine 0x3e (x86-64)
|
|
128
|
+
*
|
|
129
|
+
* `--arch` LABELS the payload; it does not cross-compile it, and nothing
|
|
130
|
+
* compared the two. The result installs on an arm64 machine — apt and dnf both
|
|
131
|
+
* believe the header — and then fails to load, which is this tree's most
|
|
132
|
+
* expensive failure class with an independent oracle actively confirming the
|
|
133
|
+
* lie: `rpm` reads the header, and the header was written from the caller's
|
|
134
|
+
* claim.
|
|
135
|
+
*
|
|
136
|
+
* Payload against LABEL, never payload against HOST. Assembling an arm64
|
|
137
|
+
* artifact on an x64 machine is a supported path — the packers are pure
|
|
138
|
+
* JavaScript and ADR 0024 § A1 turns it into a design commitment — so a host
|
|
139
|
+
* comparison would refuse the very case the split exists to allow.
|
|
140
|
+
*/
|
|
141
|
+
export declare function assertPayloadMatchesArch(payload: readonly PayloadEntry[], arch: string): void;
|
|
@@ -16,6 +16,12 @@ import { statSync } from 'node:fs';
|
|
|
16
16
|
* what `rpm -qi` then shows a user, and an artifact that looks broken is a
|
|
17
17
|
* support question. The mtime keeps the property that matters (pack the same
|
|
18
18
|
* build twice, get the same bytes) while saying something true.
|
|
19
|
+
*
|
|
20
|
+
* Only the ASSEMBLING host runs this. A stage records the answer it got
|
|
21
|
+
* (`.gjsify-ship-stage.json` → `mtime`) and the packing host reuses it: an
|
|
22
|
+
* artifact upload does not carry mtimes, so re-stat'ing the stage there would
|
|
23
|
+
* stamp every header with "whenever the download finished" and quietly destroy
|
|
24
|
+
* the reproducibility this function exists to protect.
|
|
19
25
|
*/
|
|
20
26
|
export function buildTimestamp(bundlePath, env = process.env) {
|
|
21
27
|
const raw = env.SOURCE_DATE_EPOCH;
|
|
@@ -28,6 +34,72 @@ export function buildTimestamp(bundlePath, env = process.env) {
|
|
|
28
34
|
}
|
|
29
35
|
return Math.floor(statSync(bundlePath).mtimeMs / 1000);
|
|
30
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Read {@link PayloadFacts} off a payload or off a plan.
|
|
39
|
+
*
|
|
40
|
+
* Takes anything with a `path`, so the assembling phase can ask the same
|
|
41
|
+
* question of the PLAN (before the tree is read back) that the packing phase
|
|
42
|
+
* asks of the payload. One function, so the two phases cannot disagree about
|
|
43
|
+
* whether a package installs a schema.
|
|
44
|
+
*/
|
|
45
|
+
export function readPayloadFacts(entries) {
|
|
46
|
+
const paths = entries.map((entry) => entry.path);
|
|
47
|
+
return {
|
|
48
|
+
hasDesktopEntry: paths.some((path) => path.startsWith('share/applications/') && path.endsWith('.desktop')),
|
|
49
|
+
hasIcons: paths.some((path) => path.startsWith('share/icons/hicolor/')),
|
|
50
|
+
hasSchemas: paths.some((path) => path.startsWith('share/glib-2.0/schemas/') && path.endsWith('.gschema.xml')),
|
|
51
|
+
hasMimeTypes: paths.some((path) => path.startsWith('share/mime/packages/') && path.endsWith('.xml')),
|
|
52
|
+
// Anywhere in the payload, not only `lib/<name>/gi/`: `gjsify.ship.extraFiles` can place
|
|
53
|
+
// one elsewhere, and a typelib the package carries is a typelib the package must not also
|
|
54
|
+
// declare a distro dependency for, wherever it sits.
|
|
55
|
+
bundledTypelibs: paths.filter((path) => path.endsWith('.typelib')),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The interpreters the payload's own executables need, read off their shebangs.
|
|
60
|
+
*
|
|
61
|
+
* An interpreter is a dependency like any other, and rpm expects it declared:
|
|
62
|
+
* `rpmbuild`'s file-based generator emits one `Requires` per executable
|
|
63
|
+
* shebang, with the `RPMSENSE_FIND_REQUIRES` sense that says "derived, not
|
|
64
|
+
* declared". Measured on Fedora 44 against a package whose only file is a
|
|
65
|
+
* `#!/bin/sh` script: `rpm -qp --requires` → `/usr/bin/sh 16384`.
|
|
66
|
+
*
|
|
67
|
+
* The LITERAL path, not a resolved one. `rpmbuild` prints `/usr/bin/sh` there
|
|
68
|
+
* because it resolved `/bin` through the symlink of the usrmerged host it ran
|
|
69
|
+
* on; this writer has no target host to resolve against (ADR 0024 § A1 — the
|
|
70
|
+
* packers are pure JavaScript and run anywhere), and `/bin/sh` is satisfied on
|
|
71
|
+
* both layouts: measured on Fedora 44, `rpm -q --whatprovides /bin/sh` and
|
|
72
|
+
* `/usr/bin/sh` both answer `bash`. It is also the spelling the scriptlet
|
|
73
|
+
* requirements already use.
|
|
74
|
+
*
|
|
75
|
+
* EXECUTABLE files only, which is the same rule `rpmbuild` applies. A GJS
|
|
76
|
+
* bundle staged 0644 carries `#!/usr/bin/env -S gjs -m` for the days it is run
|
|
77
|
+
* directly, but nothing in the installed package executes it as a program — the
|
|
78
|
+
* launcher `exec`s `gjs` with it as an argument — so declaring `/usr/bin/env`
|
|
79
|
+
* for it would be a dependency on a path this package never uses.
|
|
80
|
+
*/
|
|
81
|
+
export function readShebangInterpreters(payload) {
|
|
82
|
+
const found = new Set();
|
|
83
|
+
for (const entry of payload) {
|
|
84
|
+
if ((entry.mode & 0o111) === 0)
|
|
85
|
+
continue;
|
|
86
|
+
const interpreter = readShebang(entry.data);
|
|
87
|
+
if (interpreter !== null)
|
|
88
|
+
found.add(interpreter);
|
|
89
|
+
}
|
|
90
|
+
return [...found].sort();
|
|
91
|
+
}
|
|
92
|
+
/** The absolute interpreter path of a `#!` line, or `null` when there is none to read. */
|
|
93
|
+
function readShebang(data) {
|
|
94
|
+
if (data[0] !== 0x23 || data[1] !== 0x21)
|
|
95
|
+
return null; // `#!`
|
|
96
|
+
// A shebang is one LINE; reading further would let a long file's contents
|
|
97
|
+
// decide how much work this does. 256 bytes is above every real one and is
|
|
98
|
+
// what Linux itself truncates at (BINPRM_BUF_SIZE).
|
|
99
|
+
const line = new TextDecoder().decode(data.subarray(2, Math.min(data.byteLength, 256))).split('\n')[0] ?? '';
|
|
100
|
+
const interpreter = line.trim().split(/\s+/)[0] ?? '';
|
|
101
|
+
return interpreter.startsWith('/') ? interpreter : null;
|
|
102
|
+
}
|
|
31
103
|
/**
|
|
32
104
|
* Does the payload contain anything architecture-specific?
|
|
33
105
|
*
|
|
@@ -40,6 +112,108 @@ export function buildTimestamp(bundlePath, env = process.env) {
|
|
|
40
112
|
export function isArchIndependent(payload) {
|
|
41
113
|
return !payload.some((entry) => isNativeBinary(entry.data));
|
|
42
114
|
}
|
|
115
|
+
// `process.arch` tokens, keyed by what the image records about itself. Only the
|
|
116
|
+
// values this repository can actually produce are listed; an unknown one reads
|
|
117
|
+
// as "cannot tell" rather than as a mismatch, because refusing an artifact over
|
|
118
|
+
// a machine constant nobody here emits would be a guess wearing a gate's clothes.
|
|
119
|
+
// The machine values this project actually ships packages for — no more. A value
|
|
120
|
+
// missing here makes `readBinaryArch` return null, and null is SILENT, so an
|
|
121
|
+
// absent row costs nothing but a check that does not fire.
|
|
122
|
+
//
|
|
123
|
+
// `EM_MIPS` (0x08) is absent for that reason and no other. An earlier version of
|
|
124
|
+
// this comment claimed it was absent because one value covers `mips` and
|
|
125
|
+
// `mipsel` and the row would have to guess; that was wrong three times over, and
|
|
126
|
+
// is corrected here rather than deleted because it is the kind of reasoning that
|
|
127
|
+
// gets re-derived: (1) `mipsel` IS little-endian MIPS, so the discriminator is
|
|
128
|
+
// `EI_DATA`, which `readBinaryArch` reads four lines below; (2) it could not
|
|
129
|
+
// "refuse a correct pack" either way, because `formats.ts` has no `mips` row in
|
|
130
|
+
// `DEBIAN_ARCH`/`RPM_ARCH`, so `archName` throws before any label is written;
|
|
131
|
+
// and (3) the principle it invoked is already broken one row down — `0x16` maps
|
|
132
|
+
// to `s390x`, but `EM_S390` is emitted by 31-bit `s390` too, and its
|
|
133
|
+
// discriminator is `EI_CLASS` at offset 4, which this function does NOT read.
|
|
134
|
+
// That row is the ambiguous one. Unreachable today (nothing here builds s390),
|
|
135
|
+
// but it is the row to fix first if this table ever grows.
|
|
136
|
+
const ELF_MACHINE_TO_ARCH = {
|
|
137
|
+
0x03: 'ia32',
|
|
138
|
+
0x15: 'ppc64',
|
|
139
|
+
0x16: 's390x',
|
|
140
|
+
0x28: 'arm',
|
|
141
|
+
0x3e: 'x64',
|
|
142
|
+
0xb7: 'arm64',
|
|
143
|
+
0xf3: 'riscv64',
|
|
144
|
+
};
|
|
145
|
+
const MACHO_CPUTYPE_TO_ARCH = {
|
|
146
|
+
0x00000007: 'ia32',
|
|
147
|
+
0x0000000c: 'arm',
|
|
148
|
+
0x01000007: 'x64',
|
|
149
|
+
0x0100000c: 'arm64',
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* The `process.arch` token an image says it is built for, or `null` when the
|
|
153
|
+
* question cannot be answered from the bytes.
|
|
154
|
+
*
|
|
155
|
+
* `null` covers three different things on purpose, and all three must stay
|
|
156
|
+
* silent: a file that is not a native binary at all (most of a payload), a PE
|
|
157
|
+
* (whose COFF machine field this tree has never parsed — `isNativeBinary` reads
|
|
158
|
+
* `MZ` and stops), and a Mach-O fat archive, which carries several
|
|
159
|
+
* architectures and therefore matches any label a caller could pass.
|
|
160
|
+
*/
|
|
161
|
+
export function readBinaryArch(data) {
|
|
162
|
+
if (data.byteLength < 20)
|
|
163
|
+
return null;
|
|
164
|
+
const magic = ((data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]) >>> 0;
|
|
165
|
+
if (magic === 0x7f454c46) {
|
|
166
|
+
// ELF: EI_DATA at offset 5 says which end e_machine (offset 18) is written from.
|
|
167
|
+
const littleEndian = data[5] === 1;
|
|
168
|
+
const machine = littleEndian ? data[18] | (data[19] << 8) : (data[18] << 8) | data[19];
|
|
169
|
+
return ELF_MACHINE_TO_ARCH[machine] ?? null;
|
|
170
|
+
}
|
|
171
|
+
if (magic === 0xfeedface || magic === 0xfeedfacf) {
|
|
172
|
+
return MACHO_CPUTYPE_TO_ARCH[((data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]) >>> 0] ?? null;
|
|
173
|
+
}
|
|
174
|
+
if (magic === 0xcefaedfe || magic === 0xcffaedfe) {
|
|
175
|
+
return MACHO_CPUTYPE_TO_ARCH[((data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4]) >>> 0] ?? null;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Refuse a payload whose binaries disagree with the label the package will carry.
|
|
181
|
+
*
|
|
182
|
+
* THE INCIDENT, measured on 0.41.0 before this existed. A project whose payload
|
|
183
|
+
* carries one x86-64 `.so`, packed on this x86-64 host:
|
|
184
|
+
*
|
|
185
|
+
* gjsify ship --skip-build --arch arm64
|
|
186
|
+
* → xarch-demo_1.2.3-1_arm64.deb, xarch-demo-1.2.3-1.aarch64.rpm
|
|
187
|
+
* rpm -qp --qf '%{ARCH}' → aarch64
|
|
188
|
+
* the .so inside it → ELF e_machine 0x3e (x86-64)
|
|
189
|
+
*
|
|
190
|
+
* `--arch` LABELS the payload; it does not cross-compile it, and nothing
|
|
191
|
+
* compared the two. The result installs on an arm64 machine — apt and dnf both
|
|
192
|
+
* believe the header — and then fails to load, which is this tree's most
|
|
193
|
+
* expensive failure class with an independent oracle actively confirming the
|
|
194
|
+
* lie: `rpm` reads the header, and the header was written from the caller's
|
|
195
|
+
* claim.
|
|
196
|
+
*
|
|
197
|
+
* Payload against LABEL, never payload against HOST. Assembling an arm64
|
|
198
|
+
* artifact on an x64 machine is a supported path — the packers are pure
|
|
199
|
+
* JavaScript and ADR 0024 § A1 turns it into a design commitment — so a host
|
|
200
|
+
* comparison would refuse the very case the split exists to allow.
|
|
201
|
+
*/
|
|
202
|
+
export function assertPayloadMatchesArch(payload, arch) {
|
|
203
|
+
for (const entry of payload) {
|
|
204
|
+
const found = readBinaryArch(entry.data);
|
|
205
|
+
if (found === null || found === arch)
|
|
206
|
+
continue;
|
|
207
|
+
throw new Error(`gjsify ship: the payload is ${found}, but the package would be labelled ${arch} — ` +
|
|
208
|
+
`${entry.path} is built for ${found}.\n` +
|
|
209
|
+
' `--arch` names the architecture the PAYLOAD was built for; it does not cross-compile ' +
|
|
210
|
+
'anything.\n' +
|
|
211
|
+
` A package labelled ${arch} installs on ${arch} and then fails to load. Build the payload ` +
|
|
212
|
+
`for ${arch}\n` +
|
|
213
|
+
' (its own prebuild), or assemble the stage without `--arch` and label the payload you ' +
|
|
214
|
+
'actually have.');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
43
217
|
/** ELF, Mach-O (both endiannesses, both widths, and a fat archive) or PE. */
|
|
44
218
|
function isNativeBinary(data) {
|
|
45
219
|
if (data.byteLength < 4)
|
package/lib/utils/ship/plan.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FormatDescriptor, ShipSettings, StagedFile } from './types.js';
|
|
1
|
+
import type { FormatDescriptor, FormatId, ShipSettings, StagedFile } from './types.js';
|
|
2
2
|
/** Everything the planner needs that it cannot derive from the settings alone. */
|
|
3
3
|
export interface StageInputs {
|
|
4
4
|
/** Paths inside `settings.bundleDir`, relative and POSIX-separated. */
|
|
@@ -24,8 +24,37 @@ export declare function planStage(settings: ShipSettings, inputs: StageInputs):
|
|
|
24
24
|
* The per-format additions. Only the licence differs today, and it differs in
|
|
25
25
|
* BOTH place and shape: Debian policy wants a machine-readable `copyright` in
|
|
26
26
|
* `share/doc/<pkg>/`, RPM wants the licence text under `share/licenses/<pkg>/`.
|
|
27
|
+
*
|
|
28
|
+
* A project with no licence file is REFUSED here rather than packaged without
|
|
29
|
+
* one. Debian Policy § 12.5 makes the copyright file mandatory and lintian
|
|
30
|
+
* raises `no-copyright-file` as an error, but nothing between this function and
|
|
31
|
+
* the installed package says so: an empty overlay produced a `.deb` whose
|
|
32
|
+
* control file `dpkg-deb --info` prints cleanly, that `dpkg -i` installs, and
|
|
33
|
+
* that is simply unlicensed. That is what gjsify's own package was, every
|
|
34
|
+
* release of it, until the first CI leg that read one with a real `dpkg` said
|
|
35
|
+
* so on its eighth assertion.
|
|
36
|
+
*
|
|
37
|
+
* It throws on the host that can act on it. This runs during `--stage` as well
|
|
38
|
+
* as during a one-shot `ship`, so the refusal lands where the project — and the
|
|
39
|
+
* missing `LICENSE` — actually is; {@link assertOverlayIsLicensed} repeats it at
|
|
40
|
+
* pack time for a stage that arrived from somewhere else.
|
|
27
41
|
*/
|
|
28
42
|
export declare function planOverlay(settings: ShipSettings, format: FormatDescriptor, inputs: StageInputs): StagedFile[];
|
|
43
|
+
/**
|
|
44
|
+
* The same refusal, at pack time.
|
|
45
|
+
*
|
|
46
|
+
* `planOverlay` cannot cover `--from-stage`: that path never plans anything, it
|
|
47
|
+
* rehydrates the closure the assembling host wrote. A manifest whose
|
|
48
|
+
* `overlay.deb` is `[]` passes every structural check in `readStageManifest`
|
|
49
|
+
* — the key is present, the value is an array of the right shape — and the
|
|
50
|
+
* silent unlicensed package comes back. The stage manifest's own header lists
|
|
51
|
+
* "no `overlay`" among the omissions that fail at exit 0 with an artifact that
|
|
52
|
+
* installs; this is where that stops being a comment.
|
|
53
|
+
*
|
|
54
|
+
* Cheap enough to run on both paths, so it does, and a stage written by this
|
|
55
|
+
* version can no longer reach it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function assertOverlayIsLicensed(formatId: FormatId, binaryName: string, overlay: readonly StagedFile[]): void;
|
|
29
58
|
/**
|
|
30
59
|
* The hicolor subdirectory for an icon: `scalable` for SVG, otherwise the
|
|
31
60
|
* pixel size read from a `<n>x<n>` path component or a trailing number in the
|
package/lib/utils/ship/plan.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// case (including the ones that must be REFUSED) is unit-testable. Same split
|
|
3
3
|
// as `utils/copy-targets.ts`, and for the same reason: a stager that discovers
|
|
4
4
|
// its own inputs can only be tested by building a real project.
|
|
5
|
+
import { renderMimePackage } from './mime.js';
|
|
5
6
|
import { basename, extname, posix } from 'node:path';
|
|
6
7
|
/**
|
|
7
8
|
* Build the prefix-relative payload.
|
|
@@ -54,6 +55,36 @@ export function planStage(settings, inputs) {
|
|
|
54
55
|
source: { kind: 'file', path: schema },
|
|
55
56
|
});
|
|
56
57
|
}
|
|
58
|
+
// The typelibs the package carries itself, beside the bundle in `gi/`. The launcher points
|
|
59
|
+
// GI_TYPELIB_PATH and LD_LIBRARY_PATH here — see renderLauncher.
|
|
60
|
+
for (const file of settings.typelibFiles) {
|
|
61
|
+
files.push({
|
|
62
|
+
path: posix.join(libDir, 'gi', basename(file)),
|
|
63
|
+
// A shared library must be executable; a typelib need not be, but one mode for both
|
|
64
|
+
// keeps the staged tree free of a distinction nothing downstream reads.
|
|
65
|
+
mode: 0o755,
|
|
66
|
+
source: { kind: 'file', path: file },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// A shared-mime-info document, when the project DEFINES a type rather than only handling one.
|
|
70
|
+
// `share/mime/packages/` is shared between packages, so the app id is the filename for the same
|
|
71
|
+
// reason it is for the GSettings schema: a generic name collides with another package's file.
|
|
72
|
+
if (settings.mimeTypes.length > 0) {
|
|
73
|
+
files.push({
|
|
74
|
+
path: `share/mime/packages/${settings.appId}.xml`,
|
|
75
|
+
mode: 0o644,
|
|
76
|
+
source: { kind: 'text', text: renderMimePackage(settings.mimeTypes) },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Compiled gettext catalogues, layout preserved: `share/locale/<lang>/LC_MESSAGES/<domain>.mo`
|
|
80
|
+
// is where `bindtextdomain` looks, and nowhere else.
|
|
81
|
+
for (const locale of settings.localeFiles) {
|
|
82
|
+
files.push({
|
|
83
|
+
path: posix.join('share/locale', locale.rel),
|
|
84
|
+
mode: 0o644,
|
|
85
|
+
source: { kind: 'file', path: locale.abs },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
57
88
|
for (const [dest, source] of Object.entries(settings.extraFiles)) {
|
|
58
89
|
files.push({ path: assertInsidePrefix(dest), mode: 0o644, source: { kind: 'file', path: source } });
|
|
59
90
|
}
|
|
@@ -63,15 +94,56 @@ export function planStage(settings, inputs) {
|
|
|
63
94
|
* The per-format additions. Only the licence differs today, and it differs in
|
|
64
95
|
* BOTH place and shape: Debian policy wants a machine-readable `copyright` in
|
|
65
96
|
* `share/doc/<pkg>/`, RPM wants the licence text under `share/licenses/<pkg>/`.
|
|
97
|
+
*
|
|
98
|
+
* A project with no licence file is REFUSED here rather than packaged without
|
|
99
|
+
* one. Debian Policy § 12.5 makes the copyright file mandatory and lintian
|
|
100
|
+
* raises `no-copyright-file` as an error, but nothing between this function and
|
|
101
|
+
* the installed package says so: an empty overlay produced a `.deb` whose
|
|
102
|
+
* control file `dpkg-deb --info` prints cleanly, that `dpkg -i` installs, and
|
|
103
|
+
* that is simply unlicensed. That is what gjsify's own package was, every
|
|
104
|
+
* release of it, until the first CI leg that read one with a real `dpkg` said
|
|
105
|
+
* so on its eighth assertion.
|
|
106
|
+
*
|
|
107
|
+
* It throws on the host that can act on it. This runs during `--stage` as well
|
|
108
|
+
* as during a one-shot `ship`, so the refusal lands where the project — and the
|
|
109
|
+
* missing `LICENSE` — actually is; {@link assertOverlayIsLicensed} repeats it at
|
|
110
|
+
* pack time for a stage that arrived from somewhere else.
|
|
66
111
|
*/
|
|
67
112
|
export function planOverlay(settings, format, inputs) {
|
|
68
|
-
if (inputs.licenseText === undefined)
|
|
69
|
-
|
|
113
|
+
if (inputs.licenseText === undefined) {
|
|
114
|
+
throw new Error(`gjsify ship: no licence file found, so the ${format.id} package would ship no ` +
|
|
115
|
+
`${format.licenseDest(settings.binaryName)}. Debian Policy § 12.5 requires it and lintian ` +
|
|
116
|
+
'errors without it, and neither dpkg nor rpm will tell you it is missing. Add a LICENSE file ' +
|
|
117
|
+
`to ${settings.projectDir} or to the root of its repository, or point ` +
|
|
118
|
+
'`gjsify.ship.licenseFile` at the one this package ships under.');
|
|
119
|
+
}
|
|
70
120
|
const text = format.licenseKind === 'debian-copyright'
|
|
71
121
|
? renderDebianCopyright(settings, inputs.licenseText)
|
|
72
122
|
: inputs.licenseText;
|
|
73
123
|
return [{ path: format.licenseDest(settings.binaryName), mode: 0o644, source: { kind: 'text', text } }];
|
|
74
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* The same refusal, at pack time.
|
|
127
|
+
*
|
|
128
|
+
* `planOverlay` cannot cover `--from-stage`: that path never plans anything, it
|
|
129
|
+
* rehydrates the closure the assembling host wrote. A manifest whose
|
|
130
|
+
* `overlay.deb` is `[]` passes every structural check in `readStageManifest`
|
|
131
|
+
* — the key is present, the value is an array of the right shape — and the
|
|
132
|
+
* silent unlicensed package comes back. The stage manifest's own header lists
|
|
133
|
+
* "no `overlay`" among the omissions that fail at exit 0 with an artifact that
|
|
134
|
+
* installs; this is where that stops being a comment.
|
|
135
|
+
*
|
|
136
|
+
* Cheap enough to run on both paths, so it does, and a stage written by this
|
|
137
|
+
* version can no longer reach it.
|
|
138
|
+
*/
|
|
139
|
+
export function assertOverlayIsLicensed(formatId, binaryName, overlay) {
|
|
140
|
+
if (overlay.length > 0)
|
|
141
|
+
return;
|
|
142
|
+
throw new Error(`gjsify ship: this stage carries no ${formatId} licence overlay, so the package would ship no ` +
|
|
143
|
+
`licence file for ${binaryName} — Debian Policy § 12.5 requires one and lintian errors without ` +
|
|
144
|
+
'it. The fix is on the assembling host, not this one: give that project a LICENSE (or set ' +
|
|
145
|
+
'`gjsify.ship.licenseFile`) and re-run the `--stage` phase.');
|
|
146
|
+
}
|
|
75
147
|
/**
|
|
76
148
|
* Debian machine-readable copyright, format 1.0. The licence text is indented
|
|
77
149
|
* by one space and its blank lines become ` .` — a bare blank line ends the
|
package/lib/utils/ship/rpm.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
1
|
+
import { type PayloadEntry } from './payload.js';
|
|
2
|
+
import type { PackSettings } from './types.js';
|
|
3
3
|
export interface RpmInputs {
|
|
4
|
-
settings:
|
|
4
|
+
settings: PackSettings;
|
|
5
5
|
payload: readonly PayloadEntry[];
|
|
6
6
|
prefix: string;
|
|
7
7
|
depends: readonly string[];
|
package/lib/utils/ship/rpm.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createHash } from 'node:crypto';
|
|
|
15
15
|
import { concatBytes } from './bytes.js';
|
|
16
16
|
import { createCpioArchive, S_IFDIR, S_IFREG } from './cpio.js';
|
|
17
17
|
import { parseDepend } from './depends.js';
|
|
18
|
+
import { readPayloadFacts, readShebangInterpreters } from './payload.js';
|
|
18
19
|
import { buildRpmHeader, buildRpmLead, padToEight, RpmType, RPM_TAG_HEADERIMMUTABLE, RPM_TAG_HEADERSIGNATURES, } from './rpm-header.js';
|
|
19
20
|
import { gzipDeterministic } from './gzip.js';
|
|
20
21
|
import { renderRpmScriptlets } from './scripts.js';
|
|
@@ -80,6 +81,9 @@ const SENSE = {
|
|
|
80
81
|
INTERP: 1 << 8,
|
|
81
82
|
SCRIPT_POST: 1 << 10,
|
|
82
83
|
SCRIPT_POSTUN: 1 << 12,
|
|
84
|
+
// "This one was derived from the payload, not declared by the packager" —
|
|
85
|
+
// the sense `rpmbuild`'s own dependency generator uses (measured: 16384).
|
|
86
|
+
FIND_REQUIRES: 1 << 14,
|
|
83
87
|
RPMLIB: 1 << 24,
|
|
84
88
|
};
|
|
85
89
|
/** SHA-256 in rpm's digest-algorithm numbering (PGP hash algorithm ids). */
|
|
@@ -205,8 +209,9 @@ function mainHeaderEntries(inputs, files, cpio, compressed) {
|
|
|
205
209
|
dirIndexes.push(index);
|
|
206
210
|
baseNames.push(file.path.slice(slash + 1));
|
|
207
211
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
212
|
+
// From the PAYLOAD, not the settings — see the same call in `deb.ts`.
|
|
213
|
+
const scripts = renderRpmScriptlets(readPayloadFacts(inputs.payload), inputs.prefix);
|
|
214
|
+
const requires = buildRequires(inputs.depends, scripts, readShebangInterpreters(inputs.payload));
|
|
210
215
|
const entries = [
|
|
211
216
|
{ tag: TAG.HEADERI18NTABLE, type: RpmType.STRING_ARRAY, value: ['C'] },
|
|
212
217
|
{ tag: TAG.NAME, type: RpmType.STRING, value: settings.binaryName },
|
|
@@ -325,8 +330,15 @@ function assertParallelFileArrays(entries, fileCount) {
|
|
|
325
330
|
* A scriptlet's interpreter is a dependency like any other and rpm expects it
|
|
326
331
|
* declared with the matching `SCRIPT_*` sense bit — without it, a system
|
|
327
332
|
* without `/bin/sh` installs the package and then fails inside `%post`.
|
|
333
|
+
*
|
|
334
|
+
* The payload's own executables need the same treatment, and until the first
|
|
335
|
+
* real `rpm -qp --requires` ran against this writer nothing here said so: every
|
|
336
|
+
* `.rpm` it produced installed a `#!/bin/sh` launcher and declared no shell.
|
|
337
|
+
* The two senses are not redundant and rpm emits both — a package can need a
|
|
338
|
+
* shell to CONFIGURE (`Requires(post)`) and to RUN (a plain requirement), and
|
|
339
|
+
* dropping either is a different broken system.
|
|
328
340
|
*/
|
|
329
|
-
function buildRequires(depends, scripts) {
|
|
341
|
+
function buildRequires(depends, scripts, interpreters) {
|
|
330
342
|
const names = [];
|
|
331
343
|
const flags = [];
|
|
332
344
|
const versions = [];
|
|
@@ -346,6 +358,11 @@ function buildRequires(depends, scripts) {
|
|
|
346
358
|
flags.push(SENSE.INTERP | SENSE.SCRIPT_POSTUN);
|
|
347
359
|
versions.push('');
|
|
348
360
|
}
|
|
361
|
+
for (const interpreter of interpreters) {
|
|
362
|
+
names.push(interpreter);
|
|
363
|
+
flags.push(SENSE.FIND_REQUIRES);
|
|
364
|
+
versions.push('');
|
|
365
|
+
}
|
|
349
366
|
for (const [name, version] of RPMLIB_REQUIRES) {
|
|
350
367
|
names.push(name);
|
|
351
368
|
flags.push(SENSE.RPMLIB | SENSE.LESS | SENSE.EQUAL);
|