dshm-compat 0.1.2 → 0.1.4
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/AGENTS.md +17 -0
- package/CHANGELOG.md +36 -0
- package/INSTALL.md +6 -6
- package/README.md +27 -10
- package/assets/qemu-x86_64/NOTICE.md +17 -0
- package/assets/qemu-x86_64/qemu-x86_64 +0 -0
- package/assets/qemu-x86_64/rootfs/lib/x86_64-linux-gnu/libc.so.6 +0 -0
- package/assets/qemu-x86_64/rootfs/lib/x86_64-linux-gnu/libgcc_s.so.1 +0 -0
- package/assets/qemu-x86_64/rootfs/lib64/ld-linux-x86-64.so.2 +0 -0
- package/docs/ARCHITECTURE.md +14 -8
- package/docs/RECIPES.md +50 -81
- package/index.js +46 -1
- package/lib/analyze.js +6 -1
- package/lib/convert.js +5 -4
- package/lib/elf.js +41 -0
- package/lib/guest.js +3 -2
- package/lib/plan.js +10 -0
- package/lib/release.js +118 -0
- package/lib/rules.js +7 -22
- package/lib/s2-native.js +254 -0
- package/lib/sidecar.js +174 -0
- package/lib/zip.js +59 -0
- package/package.json +3 -1
- package/lib/recipes/dsh-ops.js +0 -200
package/lib/recipes/dsh-ops.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-ops native recipe.
|
|
3
|
-
*
|
|
4
|
-
* dsh-ops is the first S2 sample: its Rust worker supports Linux, but the
|
|
5
|
-
* published package carries a win32-only runtime and a support matrix that
|
|
6
|
-
* rejects every other target before `DSH_OPS_WORKER_BIN` is consulted. This
|
|
7
|
-
* recipe applies the minimal source patches and stages an externally built
|
|
8
|
-
* `aarch64-unknown-linux-musl` worker. It never builds the worker itself.
|
|
9
|
-
*
|
|
10
|
-
* @module dshm-compat/recipes/dsh-ops
|
|
11
|
-
*/
|
|
12
|
-
import { createHash } from 'node:crypto'
|
|
13
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
14
|
-
import { join } from 'node:path'
|
|
15
|
-
import { analyze } from '../analyze.js'
|
|
16
|
-
import { loadArchive, resolveSpec } from '../fetch.js'
|
|
17
|
-
import { packTarGz } from '../pack.js'
|
|
18
|
-
import { compatDir } from '../state.js'
|
|
19
|
-
import { extractTar, findRootPrefix } from '../tar.js'
|
|
20
|
-
import { validateTarball } from '../validate.js'
|
|
21
|
-
|
|
22
|
-
const BUILD_COMMAND = 'cross build --release --locked --manifest-path native/Cargo.toml --target aarch64-unknown-linux-musl'
|
|
23
|
-
const TARGET = 'aarch64-unknown-linux-musl'
|
|
24
|
-
|
|
25
|
-
/** @param {import('../tar.js').TarEntry[]} entries */
|
|
26
|
-
function selectRoot(entries) {
|
|
27
|
-
const candidates = entries
|
|
28
|
-
.filter(entry => entry.type === 'file' && (entry.path === 'package.json' || entry.path.endsWith('/package.json')))
|
|
29
|
-
.map(entry => ({ dir: entry.path === 'package.json' ? '' : entry.path.slice(0, -'/package.json'.length), depth: entry.path.split('/').length }))
|
|
30
|
-
.sort((a, b) => a.depth - b.depth)
|
|
31
|
-
return candidates.length === 0 ? '' : (candidates[0].dir === '' ? '' : `${candidates[0].dir}/`)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** @param {Buffer} buffer */
|
|
35
|
-
function isAarch64Elf(buffer) {
|
|
36
|
-
return buffer.length >= 64
|
|
37
|
-
&& buffer[0] === 0x7f && buffer[1] === 0x45 && buffer[2] === 0x4c && buffer[3] === 0x46
|
|
38
|
-
&& buffer[4] === 2 && buffer[5] === 1
|
|
39
|
-
&& buffer.readUInt16LE(18) === 183
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {string} spec
|
|
44
|
-
* @param {{ report?: any, workerPath?: string, workerBinary?: Buffer, signal?: AbortSignal }} [options]
|
|
45
|
-
*/
|
|
46
|
-
export async function convertDshOps(spec, options = {}) {
|
|
47
|
-
const report = options.report ?? await analyze(spec, { textScan: false, signal: options.signal })
|
|
48
|
-
const packageName = report.package?.name ?? report.resolved?.name
|
|
49
|
-
const packageVersion = report.package?.version ?? report.resolved?.version
|
|
50
|
-
if (packageName !== 'dsh-ops') {
|
|
51
|
-
return { status: 'not-dsh-ops', message: 'the dsh-ops recipe only accepts the dsh-ops package' }
|
|
52
|
-
}
|
|
53
|
-
if (!String(packageVersion ?? '').startsWith('0.3.')) {
|
|
54
|
-
return { status: 'recipe-mismatch', message: `dsh-ops ${packageVersion} is outside the 0.3.x recipe; inspect the package before patching` }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let workerBinary = options.workerBinary
|
|
58
|
-
if (workerBinary === undefined && typeof options.workerPath === 'string') {
|
|
59
|
-
try { workerBinary = readFileSync(options.workerPath) } catch (error) {
|
|
60
|
-
return { status: 'needs-native-build', target: TARGET, command: BUILD_COMMAND, message: `cannot read worker: ${String(error?.message ?? error)}` }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (workerBinary === undefined) {
|
|
64
|
-
return {
|
|
65
|
-
status: 'needs-native-build',
|
|
66
|
-
target: TARGET,
|
|
67
|
-
command: BUILD_COMMAND,
|
|
68
|
-
message: 'dsh-ops needs a Linux arm64 worker; build it and pass workerPath to compat_convert',
|
|
69
|
-
notes: [
|
|
70
|
-
'install a Rust toolchain and a musl aarch64 cross linker, or use cross/cargo-zigbuild',
|
|
71
|
-
'stage the result at runtime/linux-arm64/dsh-ops-worker',
|
|
72
|
-
],
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (!isAarch64Elf(workerBinary)) {
|
|
76
|
-
return { status: 'invalid-worker', message: 'worker is not an AArch64 ELF shared/executable image' }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const resolved = await resolveSpec(spec, { signal: options.signal })
|
|
80
|
-
const archive = await loadArchive(resolved, { signal: options.signal })
|
|
81
|
-
const { entries } = extractTar(archive)
|
|
82
|
-
const prefix = findRootPrefix(entries)
|
|
83
|
-
const top = entries
|
|
84
|
-
.filter(entry => entry.type === 'file')
|
|
85
|
-
.map(entry => ({ ...entry, path: prefix !== '' && entry.path.startsWith(prefix) ? entry.path.slice(prefix.length) : entry.path }))
|
|
86
|
-
const root = selectRoot(top)
|
|
87
|
-
const files = top
|
|
88
|
-
.filter(entry => entry.path.startsWith(root))
|
|
89
|
-
.map(entry => ({ ...entry, path: entry.path.slice(root.length) }))
|
|
90
|
-
const byPath = new Map(files.map(file => [file.path, file]))
|
|
91
|
-
const getText = path => byPath.get(path)?.data?.toString('utf8')
|
|
92
|
-
|
|
93
|
-
const runtime = getText('lib/ptc/runtime.js')
|
|
94
|
-
if (typeof runtime !== 'string' || !runtime.includes("export const PLATFORMS = Object.freeze(['win32-x64'])")) {
|
|
95
|
-
return { status: 'recipe-mismatch', message: 'lib/ptc/runtime.js does not match the 0.3.x recipe' }
|
|
96
|
-
}
|
|
97
|
-
let patchedRuntime = runtime.replace(
|
|
98
|
-
"export const PLATFORMS = Object.freeze(['win32-x64'])",
|
|
99
|
-
"export const PLATFORMS = Object.freeze(['win32-x64', 'linux-arm64'])",
|
|
100
|
-
)
|
|
101
|
-
patchedRuntime = patchedRuntime.replaceAll('${platform}-${arch}', 'target')
|
|
102
|
-
const signature = 'export function resolveWorker(config = {}, { packageRoot = PACKAGE_ROOT, env = process.env, platform = process.platform, arch = process.arch } = {}) {'
|
|
103
|
-
if (!patchedRuntime.includes(signature)) return { status: 'recipe-mismatch', message: 'resolveWorker signature changed' }
|
|
104
|
-
patchedRuntime = patchedRuntime.replace(
|
|
105
|
-
signature,
|
|
106
|
-
`${signature}\n const target = platform === 'openharmony' || (platform === 'linux' && arch === 'arm64') ? 'linux-arm64' : \`\${platform}-\${arch}\``,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
const fileWorker = getText('lib/file/worker.js')
|
|
110
|
-
if (typeof fileWorker !== 'string' || !fileWorker.includes('const argv = [file]')) {
|
|
111
|
-
return { status: 'recipe-mismatch', message: 'lib/file/worker.js spawn call changed' }
|
|
112
|
-
}
|
|
113
|
-
const patchedFileWorker = fileWorker.replace(
|
|
114
|
-
'const argv = [file]',
|
|
115
|
-
"const argv = ['bash', '-c', 'exec \"$0\" \"$@\"', file]",
|
|
116
|
-
)
|
|
117
|
-
const ptcWorker = getText('lib/ptc/worker.js')
|
|
118
|
-
let patchedPtcWorker = ptcWorker
|
|
119
|
-
if (typeof ptcWorker === 'string' && ptcWorker.includes('let argv = [file]')) {
|
|
120
|
-
patchedPtcWorker = ptcWorker.replace(
|
|
121
|
-
'let argv = [file]',
|
|
122
|
-
"let argv = ['bash', '-c', 'exec \"$0\" \"$@\"', file]",
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const pkgFile = byPath.get('package.json')
|
|
127
|
-
if (pkgFile?.data === undefined) return { status: 'invalid-package', message: 'package.json is missing' }
|
|
128
|
-
const pkg = JSON.parse(pkgFile.data.toString('utf8'))
|
|
129
|
-
delete pkg.os
|
|
130
|
-
delete pkg.cpu
|
|
131
|
-
if (pkg.optionalDependencies !== null && typeof pkg.optionalDependencies === 'object') {
|
|
132
|
-
delete pkg.optionalDependencies['@dsh-ops/bash-win32-x64']
|
|
133
|
-
delete pkg.optionalDependencies['@dsh-ops/pwsh-win32-x64']
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const pluginFile = byPath.get('dsh-plugin.json')
|
|
137
|
-
if (pluginFile?.data !== undefined) {
|
|
138
|
-
const manifest = JSON.parse(pluginFile.data.toString('utf8'))
|
|
139
|
-
const components = manifest?.contributes?.['x-components']
|
|
140
|
-
if (Array.isArray(components)) {
|
|
141
|
-
for (const component of components) {
|
|
142
|
-
if (component === null || typeof component !== 'object') continue
|
|
143
|
-
if (Array.isArray(component.platforms) && !component.platforms.includes('openharmony')) component.platforms.push('openharmony')
|
|
144
|
-
if (Array.isArray(component.targets) && !component.targets.includes('linux-arm64')) component.targets.push('linux-arm64')
|
|
145
|
-
if (Array.isArray(component.architectures) && !component.architectures.includes('arm64')) component.architectures.push('arm64')
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
pluginFile.data = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const runtimeDir = `runtime/linux-arm64`
|
|
152
|
-
const workerPath = `${runtimeDir}/dsh-ops-worker`
|
|
153
|
-
const provenancePath = `${runtimeDir}/provenance.json`
|
|
154
|
-
const sha256 = createHash('sha256').update(workerBinary).digest('hex')
|
|
155
|
-
const provenance = {
|
|
156
|
-
version: typeof pkg.version === 'string' ? pkg.version : '0.0.0',
|
|
157
|
-
target: 'linux-arm64',
|
|
158
|
-
executable: 'dsh-ops-worker',
|
|
159
|
-
sha256,
|
|
160
|
-
source: 'external aarch64-unknown-linux-musl build supplied by the caller',
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** @type {string[]} */
|
|
164
|
-
const changed = [
|
|
165
|
-
'added linux-arm64 to PLATFORMS and mapped openharmony/linux-arm64 to it',
|
|
166
|
-
'wrapped the file worker spawn in bash -c so dshm-vm-bash routes it to the guest',
|
|
167
|
-
'removed package.json os/cpu gates and win32 optional payloads',
|
|
168
|
-
'added dsh-plugin.json openharmony/linux-arm64 targets',
|
|
169
|
-
`staged ${workerPath}`,
|
|
170
|
-
]
|
|
171
|
-
if (patchedPtcWorker !== ptcWorker) changed.push('wrapped the PTC worker spawn in bash -c')
|
|
172
|
-
|
|
173
|
-
const output = files.map(file => {
|
|
174
|
-
if (file.path === 'lib/ptc/runtime.js') return { path: file.path, data: Buffer.from(patchedRuntime, 'utf8'), mode: file.mode }
|
|
175
|
-
if (file.path === 'lib/file/worker.js') return { path: file.path, data: Buffer.from(patchedFileWorker, 'utf8'), mode: file.mode }
|
|
176
|
-
if (file.path === 'lib/ptc/worker.js' && patchedPtcWorker !== ptcWorker) return { path: file.path, data: Buffer.from(patchedPtcWorker, 'utf8'), mode: file.mode }
|
|
177
|
-
if (file.path === 'package.json') return { path: file.path, data: Buffer.from(`${JSON.stringify(pkg, null, 2)}\n`, 'utf8'), mode: file.mode }
|
|
178
|
-
return { path: file.path, data: file.data, mode: file.mode }
|
|
179
|
-
})
|
|
180
|
-
output.push({ path: workerPath, data: workerBinary, mode: 0o755 })
|
|
181
|
-
output.push({ path: provenancePath, data: Buffer.from(`${JSON.stringify(provenance, null, 2)}\n`, 'utf8'), mode: 0o644 })
|
|
182
|
-
|
|
183
|
-
const packed = packTarGz(output)
|
|
184
|
-
const outputDir = join(compatDir(process.env), 'builds')
|
|
185
|
-
mkdirSync(outputDir, { recursive: true })
|
|
186
|
-
const tarball = join(outputDir, `dsh-ops-${pkg.version ?? '0.0.0'}-dshm.tgz`)
|
|
187
|
-
writeFileSync(tarball, packed)
|
|
188
|
-
const validation = validateTarball(tarball)
|
|
189
|
-
if (!validation.ok) return { status: 'invalid-output', tarball, errors: validation.errors }
|
|
190
|
-
return {
|
|
191
|
-
status: 'ok',
|
|
192
|
-
tarball,
|
|
193
|
-
bytes: packed.length,
|
|
194
|
-
sha256: createHash('sha256').update(packed).digest('hex'),
|
|
195
|
-
changed,
|
|
196
|
-
warnings: validation.warnings,
|
|
197
|
-
validation,
|
|
198
|
-
worker: { target: TARGET, sha256 },
|
|
199
|
-
}
|
|
200
|
-
}
|