Skip to content

Commit 83b412e

Browse files
committed
Add downloadCompilers parallel hook for extensible compiler downloads
1 parent f344087 commit 83b412e

File tree

7 files changed

+277
-11
lines changed

7 files changed

+277
-11
lines changed

.changeset/good-eels-buy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hardhat": patch
3+
---
4+
5+
Add downloadCompilers parallel hook for extensible compiler downloads

v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,20 +1107,17 @@ export class SolidityBuildSystemImplementation implements SolidityBuildSystem {
11071107
return;
11081108
}
11091109

1110-
await downloadSolcCompilers(this.#getAllCompilerVersions(), quiet);
1110+
const allSolidityCompilerConfigs = this.#getAllSolidityCompilerConfigs();
1111+
await this.#hooks.runParallelHandlers("solidity", "downloadCompilers", [
1112+
allSolidityCompilerConfigs,
1113+
quiet,
1114+
]);
11111115
this.#configuredCompilersDownloaded = true;
11121116
}
11131117

1114-
#getAllCompilerVersions(): Set<string> {
1115-
return new Set(
1116-
Object.values(this.#options.solidityConfig.profiles)
1117-
.map((profile) => [
1118-
...profile.compilers.map((compiler) => compiler.version),
1119-
...Object.values(profile.overrides).map(
1120-
(override) => override.version,
1121-
),
1122-
])
1123-
.flat(1),
1118+
#getAllSolidityCompilerConfigs(): SolidityCompilerConfig[] {
1119+
return Object.values(this.#options.solidityConfig.profiles).flatMap(
1120+
(profile) => [...profile.compilers, ...Object.values(profile.overrides)],
11241121
);
11251122
}
11261123

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { SolidityHooks } from "../../../../types/hooks.js";
2+
3+
import { isSolcConfig } from "../build-system/build-system.js";
4+
import { downloadSolcCompilers } from "../build-system/compiler/index.js";
5+
6+
export default async (): Promise<Partial<SolidityHooks>> => ({
7+
downloadCompilers: async (_context, compilerConfigs, quiet) => {
8+
const solcVersions = new Set(
9+
compilerConfigs.filter(isSolcConfig).map((c) => c.version),
10+
);
11+
12+
if (solcVersions.size > 0) {
13+
await downloadSolcCompilers(solcVersions, quiet);
14+
}
15+
},
16+
});

v-next/hardhat/src/internal/builtin-plugins/solidity/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const hardhatPlugin: HardhatPlugin = {
4141
hookHandlers: {
4242
config: () => import("./hook-handlers/config.js"),
4343
hre: () => import("./hook-handlers/hre.js"),
44+
solidity: () => import("./hook-handlers/solidity.js"),
4445
},
4546
tasks: [
4647
{

v-next/hardhat/src/internal/builtin-plugins/solidity/type-extensions.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,21 @@ declare module "../../../types/hooks.js" {
309309
}
310310

311311
export interface SolidityHooks {
312+
/**
313+
* Hook triggered to download compilers needed for compilation.
314+
* Each handler should download compilers it is responsible for.
315+
* Runs in parallel — all registered handlers execute concurrently.
316+
*
317+
* @param context The hook context.
318+
* @param compilerConfigs All compiler configurations from all build profiles.
319+
* @param quiet Whether to suppress download progress output.
320+
*/
321+
downloadCompilers: (
322+
context: HookContext,
323+
compilerConfigs: SolidityCompilerConfig[],
324+
quiet: boolean,
325+
) => Promise<void>;
326+
312327
/**
313328
* Hook triggered during the cleanup process of Solidity compilation artifacts.
314329
* This hook runs after unused artifacts and build-info files have been removed.

v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/build-system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from "@nomicfoundation/hardhat-utils/fs";
2525

2626
import { SolidityBuildSystemImplementation } from "../../../../../src/internal/builtin-plugins/solidity/build-system/build-system.js";
27+
import createSolidityHookHandlers from "../../../../../src/internal/builtin-plugins/solidity/hook-handlers/solidity.js";
2728
import { HookManagerImplementation } from "../../../../../src/internal/core/hook-manager.js";
2829

2930
async function emitArtifacts(solidity: SolidityBuildSystem): Promise<void> {
@@ -107,6 +108,7 @@ describe(
107108
const hooks = new HookManagerImplementation(process.cwd(), []);
108109
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We don't care about hooks in this context
109110
hooks.setContext({} as HookContext);
111+
hooks.registerHandlers("solidity", await createSolidityHookHandlers());
110112
solidity = new SolidityBuildSystemImplementation(hooks, {
111113
solidityConfig,
112114
projectRoot: process.cwd(),
@@ -130,6 +132,7 @@ describe(
130132
const hooks = new HookManagerImplementation(process.cwd(), []);
131133
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We don't care about hooks in this context
132134
hooks.setContext({} as HookContext);
135+
hooks.registerHandlers("solidity", await createSolidityHookHandlers());
133136
solidity = new SolidityBuildSystemImplementation(hooks, {
134137
solidityConfig,
135138
projectRoot: process.cwd(),

v-next/hardhat/test/internal/builtin-plugins/solidity/hooks.ts

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,233 @@ describe("solidity - hooks", () => {
245245
});
246246
});
247247
});
248+
249+
describe("downloadCompilers", () => {
250+
useFixtureProject("solidity/simple-project");
251+
252+
it("should invoke downloadCompilers hook with compiler configs during build", async () => {
253+
let capturedConfigs: SolidityCompilerConfig[] = [];
254+
let downloadHookCalled = false;
255+
256+
const downloadPlugin: HardhatPlugin = {
257+
id: "test-download-compilers-plugin",
258+
hookHandlers: {
259+
solidity: async () => ({
260+
default: async () => {
261+
const handlers: Partial<SolidityHooks> = {
262+
downloadCompilers: async (
263+
_context: HookContext,
264+
compilerConfigs: SolidityCompilerConfig[],
265+
_quiet: boolean,
266+
) => {
267+
downloadHookCalled = true;
268+
capturedConfigs = compilerConfigs;
269+
},
270+
};
271+
272+
return handlers;
273+
},
274+
}),
275+
},
276+
};
277+
278+
const hre = await createHardhatRuntimeEnvironment({
279+
plugins: [downloadPlugin],
280+
solidity: "0.8.23",
281+
});
282+
283+
const roots = await hre.solidity.getRootFilePaths();
284+
await hre.solidity.build(roots, {
285+
force: true,
286+
quiet: true,
287+
});
288+
289+
assert.ok(
290+
downloadHookCalled,
291+
"The downloadCompilers hook should be triggered during build",
292+
);
293+
assert.ok(
294+
capturedConfigs.length > 0,
295+
"The downloadCompilers hook should receive compiler configs",
296+
);
297+
assert.equal(
298+
capturedConfigs[0].version,
299+
"0.8.23",
300+
"The compiler config should have the expected version",
301+
);
302+
});
303+
304+
it("should pass all compiler configs from all profiles", async () => {
305+
let capturedConfigs: SolidityCompilerConfig[] = [];
306+
307+
const downloadPlugin: HardhatPlugin = {
308+
id: "test-download-all-configs-plugin",
309+
hookHandlers: {
310+
solidity: async () => ({
311+
default: async () => {
312+
const handlers: Partial<SolidityHooks> = {
313+
downloadCompilers: async (
314+
_context: HookContext,
315+
compilerConfigs: SolidityCompilerConfig[],
316+
_quiet: boolean,
317+
) => {
318+
capturedConfigs = compilerConfigs;
319+
},
320+
};
321+
322+
return handlers;
323+
},
324+
}),
325+
},
326+
};
327+
328+
const hre = await createHardhatRuntimeEnvironment({
329+
plugins: [downloadPlugin],
330+
solidity: {
331+
compilers: [{ version: "0.8.23" }, { version: "0.8.24" }],
332+
},
333+
});
334+
335+
const roots = await hre.solidity.getRootFilePaths();
336+
await hre.solidity.build(roots, {
337+
force: true,
338+
quiet: true,
339+
});
340+
341+
const versions = capturedConfigs.map((c) => c.version);
342+
// Both default and production profiles have these compilers
343+
assert.ok(versions.includes("0.8.23"), "Should include version 0.8.23");
344+
assert.ok(versions.includes("0.8.24"), "Should include version 0.8.24");
345+
});
346+
347+
it("should include overrides in compiler configs", async () => {
348+
let capturedConfigs: SolidityCompilerConfig[] = [];
349+
350+
const downloadPlugin: HardhatPlugin = {
351+
id: "test-download-overrides-plugin",
352+
hookHandlers: {
353+
solidity: async () => ({
354+
default: async () => {
355+
const handlers: Partial<SolidityHooks> = {
356+
downloadCompilers: async (
357+
_context: HookContext,
358+
compilerConfigs: SolidityCompilerConfig[],
359+
_quiet: boolean,
360+
) => {
361+
capturedConfigs = compilerConfigs;
362+
},
363+
};
364+
365+
return handlers;
366+
},
367+
}),
368+
},
369+
};
370+
371+
const hre = await createHardhatRuntimeEnvironment({
372+
plugins: [downloadPlugin],
373+
solidity: {
374+
compilers: [{ version: "0.8.23" }],
375+
overrides: {
376+
"contracts/Special.sol": { version: "0.8.24" },
377+
},
378+
},
379+
});
380+
381+
const roots = await hre.solidity.getRootFilePaths();
382+
await hre.solidity.build(roots, {
383+
force: true,
384+
quiet: true,
385+
});
386+
387+
const versions = capturedConfigs.map((c) => c.version);
388+
assert.ok(
389+
versions.includes("0.8.24"),
390+
"Should include override version 0.8.24",
391+
);
392+
});
393+
394+
it("should filter non-solc configs in built-in handler", async () => {
395+
// The built-in handler should only download solc compilers.
396+
// A non-solc config (type: "solx") should not cause a download failure.
397+
// We verify this by building with a mixed config — if the built-in
398+
// handler tried to download "solx" type, it would fail since there's
399+
// no solx binary to download via the solc downloader.
400+
let capturedConfigs: SolidityCompilerConfig[] = [];
401+
402+
const downloadPlugin: HardhatPlugin = {
403+
id: "test-filter-non-solc-plugin",
404+
hookHandlers: {
405+
solidity: async () => ({
406+
default: async () => {
407+
const handlers: Partial<SolidityHooks> = {
408+
downloadCompilers: async (
409+
_context: HookContext,
410+
compilerConfigs: SolidityCompilerConfig[],
411+
_quiet: boolean,
412+
) => {
413+
capturedConfigs = compilerConfigs;
414+
// Don't call next — we handle all downloads here
415+
// to prevent the built-in handler from running
416+
},
417+
};
418+
419+
return handlers;
420+
},
421+
}),
422+
},
423+
};
424+
425+
const hre = await createHardhatRuntimeEnvironment({
426+
plugins: [downloadPlugin],
427+
solidity: {
428+
compilers: [
429+
{ version: "0.8.23" },
430+
{ type: "solx", version: "0.8.23" },
431+
],
432+
},
433+
});
434+
435+
const roots = await hre.solidity.getRootFilePaths();
436+
await hre.solidity.build(roots, {
437+
force: true,
438+
quiet: true,
439+
});
440+
441+
// Verify both configs are passed (the hook receives ALL configs)
442+
assert.equal(
443+
capturedConfigs.filter((c) => c.type === undefined).length > 0,
444+
true,
445+
"Should include solc configs (type undefined)",
446+
);
447+
assert.equal(
448+
capturedConfigs.filter((c) => c.type === "solx").length > 0,
449+
true,
450+
"Should include non-solc configs (type solx)",
451+
);
452+
});
453+
454+
it("should only download solc compilers in built-in handler", async () => {
455+
// Build with both solc and non-solc configs.
456+
// The built-in handler uses isSolcConfig() to filter — only solc
457+
// versions should be downloaded. If it tried to download a non-existent
458+
// "solx" version via the solc downloader, the build would fail.
459+
const hre = await createHardhatRuntimeEnvironment({
460+
solidity: {
461+
compilers: [
462+
{ version: "0.8.23" },
463+
{ type: "solx", version: "0.8.23", path: "/mock/path/to/solx" },
464+
],
465+
},
466+
});
467+
468+
const roots = await hre.solidity.getRootFilePaths();
469+
// This should NOT throw — the built-in handler should skip the solx
470+
// config and only download solc 0.8.23 (which is cached)
471+
await hre.solidity.build(roots, {
472+
force: true,
473+
quiet: true,
474+
});
475+
});
476+
});
248477
});

0 commit comments

Comments
 (0)