Skip to content

Commit 3afde3e

Browse files
committed
Update javy plugin generation to reduce duplication
1 parent 7fa942e commit 3afde3e

File tree

9 files changed

+330
-147
lines changed

9 files changed

+330
-147
lines changed
Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
1+
# syntax=docker/dockerfile:1
12
# Deterministic build: wasm32-wasip1 cdylib, then javy init-plugin --deterministic.
23
# Exports to dist: initialized .plugin.wasm only (uninit cdylib stays in builder target/ only).
34
#
45
# Usage:
56
# ./scripts/build-plugin-docker.sh
67

7-
# --- Javy CLI via ensureJavy (identical to SKIP_DOCKER_IMAGE / build-plugin-local.sh) ---
8+
# --- Javy CLI + host crate generation (same code path as user builds) ---
89
FROM oven/bun:slim AS javy-cli
910

1011
ARG CRE_JAVY_VERSION=v8.1.0
1112
ENV CRE_JAVY_VERSION=${CRE_JAVY_VERSION}
1213
ENV CRE_SDK_JAVY_LOG_STDERR=1
1314

1415
WORKDIR /w
15-
COPY scripts/ensure-javy.ts scripts/print-javy-path-for-build.ts scripts/
16+
COPY scripts/ensure-javy.ts scripts/print-javy-path-for-build.ts scripts/generate-host-crate.ts scripts/
1617

1718
RUN set -eu; \
1819
JAVY_BIN="$(bun scripts/print-javy-path-for-build.ts | tr -d '\r\n')"; \
1920
test -f "$JAVY_BIN"; \
2021
cp "$JAVY_BIN" /w/javy; \
2122
chmod +x /w/javy
2223

23-
# --- Chainlink plugin (Rust) + cre_wasm_exports path dep + init-plugin ---
24+
# Generate host crate via generateHostCrate (zero extensions).
25+
# Layout at /build/src/... mirrors the Rust builder so absolute paths in the
26+
# generated Cargo.toml resolve in both stages.
27+
COPY src/javy_chainlink_sdk/rust-toolchain.toml /build/src/javy_chainlink_sdk/rust-toolchain.toml
28+
RUN bun -e "import{generateHostCrate}from'./scripts/generate-host-crate.ts';generateHostCrate('/build/cre_generated_host','/build',[])"
29+
30+
# --- Chainlink plugin (Rust) via generated host crate + init-plugin ---
2431
FROM rust:1.85.0-slim-bookworm AS plugin-builder
2532

2633
COPY --from=javy-cli /w/javy /usr/local/bin/javy
@@ -31,30 +38,31 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
3138

3239
WORKDIR /build
3340

34-
# Dependency warm-up: both crates (path ../cre_wasm_exports from javy_chainlink_sdk)
35-
COPY src/cre_wasm_exports/Cargo.toml /build/cre_wasm_exports/
36-
RUN mkdir -p /build/cre_wasm_exports/src && echo '' > /build/cre_wasm_exports/src/lib.rs
41+
# Dependency warm-up: copy manifests + stub sources, then build to cache deps
42+
COPY src/cre_wasm_exports/Cargo.toml /build/src/cre_wasm_exports/
43+
RUN mkdir -p /build/src/cre_wasm_exports/src && echo '' > /build/src/cre_wasm_exports/src/lib.rs
44+
45+
COPY src/javy_chainlink_sdk/rust-toolchain.toml src/javy_chainlink_sdk/Cargo.toml src/javy_chainlink_sdk/Cargo.lock /build/src/javy_chainlink_sdk/
46+
RUN mkdir -p /build/src/javy_chainlink_sdk/src && echo '' > /build/src/javy_chainlink_sdk/src/lib.rs
3747

38-
COPY src/javy_chainlink_sdk/rust-toolchain.toml src/javy_chainlink_sdk/Cargo.toml src/javy_chainlink_sdk/Cargo.lock /build/javy_chainlink_sdk/
39-
RUN mkdir -p /build/javy_chainlink_sdk/src && echo '' > /build/javy_chainlink_sdk/src/lib.rs
48+
COPY --from=javy-cli /build/cre_generated_host /build/cre_generated_host
4049

41-
WORKDIR /build/javy_chainlink_sdk
50+
WORKDIR /build/cre_generated_host
4251
RUN --mount=type=cache,target=/usr/local/cargo/registry \
4352
cargo build --target wasm32-wasip1 --release 2>/dev/null || true
4453

45-
COPY src/cre_wasm_exports/src /build/cre_wasm_exports/src
46-
RUN touch /build/cre_wasm_exports/src/lib.rs
54+
COPY src/cre_wasm_exports/src /build/src/cre_wasm_exports/src
55+
RUN touch /build/src/cre_wasm_exports/src/lib.rs
4756

48-
COPY src/javy_chainlink_sdk/src /build/javy_chainlink_sdk/src
49-
RUN touch /build/javy_chainlink_sdk/src/lib.rs
57+
COPY src/javy_chainlink_sdk/src /build/src/javy_chainlink_sdk/src
58+
RUN touch /build/src/javy_chainlink_sdk/src/lib.rs
5059

5160
RUN --mount=type=cache,target=/usr/local/cargo/registry \
5261
cargo build --target wasm32-wasip1 --release
5362

5463
RUN javy init-plugin --deterministic \
55-
target/wasm32-wasip1/release/javy_chainlink_sdk.wasm \
64+
target/wasm32-wasip1/release/cre_generated_host.wasm \
5665
-o /javy-chainlink-sdk.plugin.wasm
5766

5867
FROM scratch
59-
# Uninitialized javy_chainlink_sdk.wasm stays in the builder image only (input to init-plugin above).
6068
COPY --from=plugin-builder /javy-chainlink-sdk.plugin.wasm /javy-chainlink-sdk.plugin.wasm
-266 Bytes
Binary file not shown.
Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,57 @@
11
#!/usr/bin/env bun
22

3-
import { copyFileSync, mkdirSync } from 'node:fs'
4-
import { join } from 'node:path'
3+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'
4+
import { tmpdir } from 'node:os'
5+
import { join, resolve } from 'node:path'
56
import { ensureJavy } from './ensure-javy.ts'
7+
import { generateHostCrate } from './generate-host-crate.ts'
68
import { JAVY_VERSION, run } from './shared.ts'
79

810
const pluginDir = join(import.meta.dir, '..')
9-
const crateDir = join(pluginDir, 'src', 'javy_chainlink_sdk')
10-
/** Uninitialized cdylib; only passed to `javy init-plugin` (not copied to dist — see .gitignore). */
11-
const builtWasmPath = join(
12-
crateDir,
13-
'target',
14-
'wasm32-wasip1',
15-
'release',
16-
'javy_chainlink_sdk.wasm',
17-
)
1811
const distPluginWasmPath = join(pluginDir, 'dist', 'javy-chainlink-sdk.plugin.wasm')
1912
const witFilePath = join(pluginDir, 'src', 'workflow.wit')
2013
const distWitFilePath = join(pluginDir, 'dist', 'workflow.wit')
2114

15+
function findBuiltWasm(targetDir: string): string {
16+
const releaseDir = resolve(targetDir, 'wasm32-wasip1', 'release')
17+
for (const name of ['cre_generated_host.wasm', 'libcre_generated_host.wasm']) {
18+
const candidate = resolve(releaseDir, name)
19+
if (existsSync(candidate)) return candidate
20+
}
21+
throw new Error(`Build succeeded but WASM not found in ${releaseDir}`)
22+
}
23+
2224
export const main = async () => {
2325
console.info('\n\n---> Compiling Chainlink SDK Javy plugin (Rust) \n\n')
2426

25-
const [, javyPath] = await Promise.all([
26-
run('cargo', ['build', '--target', 'wasm32-wasip1', '--release'], crateDir),
27-
ensureJavy({ version: JAVY_VERSION }),
28-
])
29-
30-
const distDir = join(pluginDir, 'dist')
31-
mkdirSync(distDir, { recursive: true })
32-
copyFileSync(witFilePath, distWitFilePath)
33-
34-
await run(
35-
javyPath,
36-
['init-plugin', '--deterministic', builtWasmPath, '-o', distPluginWasmPath],
37-
pluginDir,
38-
)
39-
40-
console.info('✅ Done!')
27+
const tmpDir = mkdtempSync(join(tmpdir(), 'cre-plugin-'))
28+
const sharedTargetDir = resolve(pluginDir, '.cargo-target')
29+
30+
try {
31+
generateHostCrate(tmpDir, pluginDir, [])
32+
33+
const [, javyPath] = await Promise.all([
34+
run('cargo', ['build', '--target', 'wasm32-wasip1', '--release'], tmpDir, {
35+
CARGO_TARGET_DIR: sharedTargetDir,
36+
}),
37+
ensureJavy({ version: JAVY_VERSION }),
38+
])
39+
40+
const builtWasm = findBuiltWasm(sharedTargetDir)
41+
const distDir = join(pluginDir, 'dist')
42+
mkdirSync(distDir, { recursive: true })
43+
copyFileSync(witFilePath, distWitFilePath)
44+
45+
await run(
46+
javyPath,
47+
['init-plugin', '--deterministic', builtWasm, '-o', distPluginWasmPath],
48+
tmpDir,
49+
)
50+
51+
console.info('✅ Done!')
52+
} finally {
53+
rmSync(tmpDir, { recursive: true, force: true })
54+
}
4155
}
4256

4357
main()

packages/cre-sdk-javy-plugin/scripts/generate-host-crate.test.ts

Lines changed: 156 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { describe, expect, test } from 'bun:test'
22
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
33
import { tmpdir } from 'node:os'
44
import { join } from 'node:path'
5-
import { generateHostCrate, readCrateName, resolveExtensions } from './generate-host-crate'
5+
import {
6+
generateHostCrate,
7+
parseToolchainVersion,
8+
readCrateName,
9+
resolveExtensions,
10+
resolveToolchain,
11+
} from './generate-host-crate'
612

713
describe('generate-host-crate', () => {
814
describe('readCrateName', () => {
@@ -49,12 +55,119 @@ describe('generate-host-crate', () => {
4955
})
5056
})
5157

58+
describe('parseToolchainVersion', () => {
59+
test('parses semver channel from rust-toolchain.toml', () => {
60+
const tmp = mkdtempSync(join(tmpdir(), 'cre-tc-'))
61+
try {
62+
writeFileSync(
63+
join(tmp, 'rust-toolchain.toml'),
64+
'[toolchain]\nchannel = "1.85.0"\ntargets = ["wasm32-wasip1"]\n',
65+
)
66+
expect(parseToolchainVersion(join(tmp, 'rust-toolchain.toml'))).toEqual([1, 85, 0])
67+
} finally {
68+
rmSync(tmp, { recursive: true })
69+
}
70+
})
71+
72+
test('returns null for non-semver channel (nightly)', () => {
73+
const tmp = mkdtempSync(join(tmpdir(), 'cre-tc-'))
74+
try {
75+
writeFileSync(
76+
join(tmp, 'rust-toolchain.toml'),
77+
'[toolchain]\nchannel = "nightly-2024-01-01"\n',
78+
)
79+
expect(parseToolchainVersion(join(tmp, 'rust-toolchain.toml'))).toBeNull()
80+
} finally {
81+
rmSync(tmp, { recursive: true })
82+
}
83+
})
84+
85+
test('returns null for missing file', () => {
86+
expect(parseToolchainVersion('/nonexistent/rust-toolchain.toml')).toBeNull()
87+
})
88+
})
89+
90+
describe('resolveToolchain', () => {
91+
test('uses SDK version when no extensions have toolchain files', () => {
92+
const sdkDir = mkdtempSync(join(tmpdir(), 'cre-sdk-'))
93+
try {
94+
writeFileSync(
95+
join(sdkDir, 'rust-toolchain.toml'),
96+
'[toolchain]\nchannel = "1.85.0"\ntargets = ["wasm32-wasip1"]\n',
97+
)
98+
const result = resolveToolchain(join(sdkDir, 'rust-toolchain.toml'), [])
99+
expect(result).toContain('channel = "1.85.0"')
100+
expect(result).toContain('wasm32-wasip1')
101+
} finally {
102+
rmSync(sdkDir, { recursive: true })
103+
}
104+
})
105+
106+
test('picks higher extension version over SDK', () => {
107+
const sdkDir = mkdtempSync(join(tmpdir(), 'cre-sdk-'))
108+
const extDir = mkdtempSync(join(tmpdir(), 'cre-ext-'))
109+
try {
110+
writeFileSync(join(sdkDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.85.0"\n')
111+
writeFileSync(join(extDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.87.0"\n')
112+
const result = resolveToolchain(join(sdkDir, 'rust-toolchain.toml'), [
113+
{ crateName: 'ext', path: extDir },
114+
])
115+
expect(result).toContain('channel = "1.87.0"')
116+
} finally {
117+
rmSync(sdkDir, { recursive: true })
118+
rmSync(extDir, { recursive: true })
119+
}
120+
})
121+
122+
test('keeps SDK version when extension is older', () => {
123+
const sdkDir = mkdtempSync(join(tmpdir(), 'cre-sdk-'))
124+
const extDir = mkdtempSync(join(tmpdir(), 'cre-ext-'))
125+
try {
126+
writeFileSync(join(sdkDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.85.0"\n')
127+
writeFileSync(join(extDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.80.0"\n')
128+
const result = resolveToolchain(join(sdkDir, 'rust-toolchain.toml'), [
129+
{ crateName: 'ext', path: extDir },
130+
])
131+
expect(result).toContain('channel = "1.85.0"')
132+
} finally {
133+
rmSync(sdkDir, { recursive: true })
134+
rmSync(extDir, { recursive: true })
135+
}
136+
})
137+
138+
test('ignores extensions with nightly channel', () => {
139+
const sdkDir = mkdtempSync(join(tmpdir(), 'cre-sdk-'))
140+
const extDir = mkdtempSync(join(tmpdir(), 'cre-ext-'))
141+
try {
142+
writeFileSync(join(sdkDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.85.0"\n')
143+
writeFileSync(join(extDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "nightly"\n')
144+
const result = resolveToolchain(join(sdkDir, 'rust-toolchain.toml'), [
145+
{ crateName: 'ext', path: extDir },
146+
])
147+
expect(result).toContain('channel = "1.85.0"')
148+
} finally {
149+
rmSync(sdkDir, { recursive: true })
150+
rmSync(extDir, { recursive: true })
151+
}
152+
})
153+
})
154+
52155
describe('generateHostCrate', () => {
53-
test('requires at least one extension', () => {
156+
test('generates host crate with zero extensions (standalone)', () => {
54157
const outDir = mkdtempSync(join(tmpdir(), 'cre-host-'))
55158
const pluginDir = join(import.meta.dir, '..')
56159
try {
57-
expect(() => generateHostCrate(outDir, pluginDir, [])).toThrow(/at least one --cre-exports/)
160+
generateHostCrate(outDir, pluginDir, [])
161+
const cargo = readFileSync(join(outDir, 'Cargo.toml'), 'utf8')
162+
expect(cargo).toContain('name = "cre_generated_host"')
163+
expect(cargo).toContain('crate-type = ["cdylib"]')
164+
expect(cargo).toContain('javy_chainlink_sdk = { path')
165+
expect(cargo).not.toContain('cre_wasm_exports')
166+
const libRs = readFileSync(join(outDir, 'src', 'lib.rs'), 'utf8')
167+
expect(libRs).toContain('javy_chainlink_sdk::config')
168+
expect(libRs).toContain('javy_chainlink_sdk::modify_runtime')
169+
expect(libRs).not.toContain('register')
170+
expect(libRs).not.toContain('context().with')
58171
} finally {
59172
rmSync(outDir, { recursive: true })
60173
}
@@ -94,7 +207,7 @@ describe('generate-host-crate', () => {
94207
}
95208
})
96209

97-
test('Cargo.toml uses path deps for javy_chainlink_sdk, cre_wasm_exports, and extensions', () => {
210+
test('Cargo.toml uses path deps for javy_chainlink_sdk and extensions', () => {
98211
const outDir = mkdtempSync(join(tmpdir(), 'cre-host-'))
99212
const pluginDir = join(import.meta.dir, '..')
100213
const examplesDir = join(pluginDir, '..', 'cre-sdk-examples', 'rust-inject')
@@ -110,38 +223,67 @@ describe('generate-host-crate', () => {
110223
expect(cargo).toContain('javy-plugin-api = "6.0.0"')
111224
expect(cargo).toContain('javy = "7.0.0"')
112225
expect(cargo).toContain('javy_chainlink_sdk = { path')
113-
expect(cargo).toContain('cre_wasm_exports = { path')
114226
expect(cargo).toContain('alpha = { path')
115227
expect(cargo).toContain('lib_beta = { path')
116228
const libRs = readFileSync(join(outDir, 'src', 'lib.rs'), 'utf8')
117229
expect(libRs).toContain('alpha::register')
118230
expect(libRs).toContain('lib_beta::register')
119-
expect(libRs).toContain('cre_wasm_exports::reset_registry')
120-
expect(libRs).toContain('cre_wasm_exports::check_duplicates')
121231
expect(libRs).toContain('javy_chainlink_sdk::config')
122232
expect(libRs).toContain('javy_chainlink_sdk::modify_runtime')
123233
} finally {
124234
rmSync(outDir, { recursive: true })
125235
}
126236
})
127237

128-
test('copies rust-toolchain.toml into generated crate', () => {
238+
test('writes rust-toolchain.toml with resolved version', () => {
239+
const outDir = mkdtempSync(join(tmpdir(), 'cre-host-'))
240+
const pluginDir = join(import.meta.dir, '..')
241+
try {
242+
generateHostCrate(outDir, pluginDir, [])
243+
const toolchainPath = join(outDir, 'rust-toolchain.toml')
244+
expect(existsSync(toolchainPath)).toBe(true)
245+
const content = readFileSync(toolchainPath, 'utf8')
246+
expect(content).toContain('wasm32-wasip1')
247+
expect(content).toMatch(/channel = "\d+\.\d+\.\d+"/)
248+
} finally {
249+
rmSync(outDir, { recursive: true })
250+
}
251+
})
252+
253+
test('rust-toolchain.toml picks higher extension version', () => {
129254
const outDir = mkdtempSync(join(tmpdir(), 'cre-host-'))
130255
const extDir = mkdtempSync(join(tmpdir(), 'cre-ext-'))
131256
const pluginDir = join(import.meta.dir, '..')
132257
try {
133258
writeFileSync(
134259
join(extDir, 'Cargo.toml'),
135-
'[package]\nname = "toolchain_ext"\nversion = "0.1.0"',
260+
'[package]\nname = "newer_ext"\nversion = "0.1.0"',
136261
)
137262
mkdirSync(join(extDir, 'src'))
138263
writeFileSync(join(extDir, 'src', 'lib.rs'), 'pub fn register(_ctx: &()) {}')
139-
const extensions = [{ crateName: 'toolchain_ext', path: extDir }]
264+
writeFileSync(join(extDir, 'rust-toolchain.toml'), '[toolchain]\nchannel = "1.99.0"\n')
265+
const extensions = [{ crateName: 'newer_ext', path: extDir }]
140266
generateHostCrate(outDir, pluginDir, extensions)
141-
const toolchainPath = join(outDir, 'rust-toolchain.toml')
142-
expect(existsSync(toolchainPath)).toBe(true)
143-
const content = readFileSync(toolchainPath, 'utf8')
144-
expect(content).toContain('wasm32-wasip1')
267+
const content = readFileSync(join(outDir, 'rust-toolchain.toml'), 'utf8')
268+
expect(content).toContain('channel = "1.99.0"')
269+
} finally {
270+
rmSync(outDir, { recursive: true })
271+
rmSync(extDir, { recursive: true })
272+
}
273+
})
274+
275+
test('Cargo.toml does not use default-features = false', () => {
276+
const outDir = mkdtempSync(join(tmpdir(), 'cre-host-'))
277+
const extDir = mkdtempSync(join(tmpdir(), 'cre-ext-'))
278+
const pluginDir = join(import.meta.dir, '..')
279+
try {
280+
writeFileSync(join(extDir, 'Cargo.toml'), '[package]\nname = "test_ext"\nversion = "0.1.0"')
281+
mkdirSync(join(extDir, 'src'))
282+
writeFileSync(join(extDir, 'src', 'lib.rs'), 'pub fn register(_ctx: &()) {}')
283+
const extensions = [{ crateName: 'test_ext', path: extDir }]
284+
generateHostCrate(outDir, pluginDir, extensions)
285+
const cargo = readFileSync(join(outDir, 'Cargo.toml'), 'utf8')
286+
expect(cargo).not.toContain('default-features')
145287
} finally {
146288
rmSync(outDir, { recursive: true })
147289
rmSync(extDir, { recursive: true })

0 commit comments

Comments
 (0)