Skip to content

Improve multi-compiler support in the build system by adding a SolidityCompilerConfig base interface#8008

Merged
marianfe merged 11 commits intomainfrom
feat/solx-step1-core-multi-compiler
Mar 23, 2026
Merged

Improve multi-compiler support in the build system by adding a SolidityCompilerConfig base interface#8008
marianfe merged 11 commits intomainfrom
feat/solx-step1-core-multi-compiler

Conversation

@marianfe
Copy link
Copy Markdown
Contributor

@marianfe marianfe commented Feb 25, 2026

Summary

This change introduces compiler-agnostic base interfaces: SolidityCompilerConfig, SolidityCompilerUserConfig and SolidityCompilerType, with a new type property to distinguish between compilers e.g. when downloading/injecting settings/getting the compiler/invoking compilation.

Note: This PR also introduces a new pattern to add post-validation hooks after a config has been resolved. We need this because we need to validate that all compiler entries in the config have been 'claimed' by an existing plugin (either the built-in solc, or other plugins) and that no remaining compiler types are 'orphaned' (e.g. due to typos).

This is the first change in a series of PRs to introduce support for solx in Hardhat 3. In later PRs, the type property will be used to split handling of downloads, settings/extra args, and compilation invocation between the current built-in solc pipeline, and a new solx compilation path (which will be made available via a plugin).

Details

  • SolcUserConfig/SolcConfig extend the base types, preserving backward compatibility.
  • Export spawnCompile() and isSolcConfig() for plugin reuse (will be used in a later PR)
  • Widen invokeSolc hook parameter from SolcConfig to SolidityCompilerConfig
  • Add zod schema type field for runtime validation

This is a backward compatible change for Hardhat users, and a mostly backward compatible change for plugin authors - it is strictly backward compatible at runtime, although at build time there is a less likely minor type-level breaking change: invokeSolc hook parameter widens from SolcConfig to SolidityCompilerConfig. Existing handlers compile and run without changes. Plugins accessing preferWasm should use the isSolcConfig() type guard.

  • Currently no plugins seem to actually use preferWasm, this property is only used in hardhat core for the current solc pipeline.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 25, 2026

🦋 Changeset detected

Latest commit: f344087

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
hardhat Patch
@nomicfoundation/hardhat-errors Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@marianfe marianfe changed the title Introduce multi-compiler abstraction with SolidityCompilerConfig base interface Introduce better multi-compiler support in the build system by adding SolidityCompilerConfig base interface Feb 25, 2026
@marianfe marianfe changed the title Introduce better multi-compiler support in the build system by adding SolidityCompilerConfig base interface Introduce better multi-compiler support in the build system by adding a SolidityCompilerConfig base interface Feb 25, 2026
@marianfe marianfe requested a review from Copilot February 25, 2026 23:10
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch from 9ecf615 to 43bb131 Compare February 25, 2026 23:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces compiler-agnostic Solidity compiler config base interfaces (SolidityCompilerConfig / SolidityCompilerUserConfig) with a type discriminator to support multiple compiler implementations (e.g. future solx integration) while keeping SolcConfig backwards compatible.

Changes:

  • Add SolidityCompilerConfig/SolidityCompilerUserConfig base interfaces with optional type, and widen relevant hook/config types to use the base interface.
  • Export isSolcConfig() type guard and spawnCompile() for plugin reuse via a new hardhat/internal/solidity export path.
  • Relax custom compiler version parsing to accept plain semver, add tests, and update config resolution tests to cover type behavior.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
v-next/hardhat/src/internal/builtin-plugins/solidity/type-extensions.ts Introduces base compiler config/user config interfaces and widens hook/config types to compiler-agnostic ones.
v-next/hardhat/src/internal/builtin-plugins/solidity/config.ts Adds type runtime validation and resolves configs via the new base compiler config shape.
v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/build-system.ts Adds isSolcConfig guard and adjusts preferWasm/version validation logic for generalized compiler configs.
v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/solc-config-selection.ts Widens selector return type from SolcConfig to SolidityCompilerConfig.
v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/index.ts Extends custom compiler --version parsing to accept plain semver and synthesize a long version.
v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/compiler.ts Exports spawnCompile for reuse.
v-next/hardhat/src/internal/builtin-plugins/solidity/exports.ts Adds internal re-exports (isSolcConfig, spawnCompile) for plugin consumption.
v-next/hardhat/src/types/solidity/compilation-job.ts Updates compilation job typing/docs to use the compiler-agnostic config type.
v-next/hardhat/package.json Adds the ./internal/solidity package export.
v-next/hardhat/test/internal/builtin-plugins/solidity/config.ts Adds tests for type validation/resolution and isSolcConfig.
v-next/hardhat/test/internal/builtin-plugins/solidity/hooks.ts Updates hook typing in tests to accept SolidityCompilerConfig.
v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compilation-job.ts Updates test typing to SolidityCompilerConfig.
v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/version-parsing.ts Adds unit tests for the updated version parsing behavior.
.changeset/cold-deer-buy.md Adds a patch changeset entry for the new abstraction/export surface.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +352 to +356
// Resolve solc-specific preferWasm if this is a SolcUserConfig
if (isSolcUserConfig(compilerConfig)) {
// Resolve per-compiler preferWasm:
// If explicitly set, use that value.
// Otherwise, for ARM64 Linux in production, default to true only for
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveSolidityCompilerConfig drops solc-only fields like preferWasm when type is non-solc, but the current zod schema still allows preferWasm on any compiler entry. This can silently ignore user config mistakes (e.g. type: "solx" with preferWasm: true). Consider making the per-compiler schema conditional: only allow preferWasm when type is undefined/"solc", and reject it for other type values.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

@marianfe marianfe added no docs needed This PR doesn't require links to documentation no peer bump needed labels Feb 26, 2026
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch 5 times, most recently from 62da1a7 to 1cd6812 Compare February 26, 2026 09:01
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch from 1cd6812 to 0f7deb7 Compare February 26, 2026 12:03
@marianfe marianfe changed the title Introduce better multi-compiler support in the build system by adding a SolidityCompilerConfig base interface Introduce improved multi-compiler support in the build system by adding a SolidityCompilerConfig base interface Feb 26, 2026
@marianfe marianfe changed the title Introduce improved multi-compiler support in the build system by adding a SolidityCompilerConfig base interface Improve multi-compiler support in the build system by adding a SolidityCompilerConfig base interface Feb 26, 2026
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch 2 times, most recently from d004fe7 to 7cade07 Compare March 4, 2026 12:27
Copilot AI review requested due to automatic review settings March 5, 2026 15:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +40 to +42
"./internal/coverage": "./dist/src/internal/builtin-plugins/coverage/exports.js",
"./internal/gas-analytics": "./dist/src/internal/builtin-plugins/gas-analytics/exports.js",
"./internal/solidity": "./dist/src/internal/builtin-plugins/solidity/exports.js",
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The package.json exports map now includes "./internal/coverage" and "./internal/gas-analytics" pointing at dist/src/internal/builtin-plugins/{coverage,gas-analytics}/exports.js, but there are no corresponding source entrypoints (exports.ts) in those directories. This will produce broken package exports at publish/build time unless those files are added or the export targets are updated to existing built artifacts (e.g. index.js).

Suggested change
"./internal/coverage": "./dist/src/internal/builtin-plugins/coverage/exports.js",
"./internal/gas-analytics": "./dist/src/internal/builtin-plugins/gas-analytics/exports.js",
"./internal/solidity": "./dist/src/internal/builtin-plugins/solidity/exports.js",
"./internal/coverage": "./dist/src/internal/builtin-plugins/coverage/index.js",
"./internal/gas-analytics": "./dist/src/internal/builtin-plugins/gas-analytics/index.js",
"./internal/solidity": "./dist/src/internal/builtin-plugins/solidity/index.js",

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't pay attention to this comment, LGTM

Comment on lines +417 to +420
type: compilerConfig.type,
version: compilerConfig.version,
settings: resolvedSettings,
path: compilerConfig.path,
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For non-"solc" compiler types the zod schema allows arbitrary extra fields via .passthrough(), but resolveSolidityCompilerConfig reconstructs a new object with only { type, version, settings, path }, dropping any plugin-defined fields from the resolved config. If plugins are expected to add compiler-specific options (as implied by passthrough), those options won’t be available later in the build pipeline. Consider preserving unknown fields (e.g. by spreading the original config and overriding settings with the merged value) for non-solc types.

Suggested change
type: compilerConfig.type,
version: compilerConfig.version,
settings: resolvedSettings,
path: compilerConfig.path,
...compilerConfig,
settings: resolvedSettings,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is irrelevant, because we have our own resolution. This change looks good to me.

Comment on lines 45 to +49
public selectBestSolcConfigForSingleRootGraph(
subgraph: DependencyGraph,
): { success: true; config: SolcConfig } | CompilationJobCreationError {
):
| { success: true; config: SolidityCompilerConfig }
| CompilationJobCreationError {
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SolcConfigSelector now returns a generic SolidityCompilerConfig, but selection is still based only on version and doesn’t consider type. If a build profile contains multiple compiler types (or two configs share the same version but different types), the selected config can be ambiguous/non-deterministic. Consider either validating that each build profile is single-compiler-type for now, or including type as part of the selection/identity logic (and any tie-breaking).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting point. But the whole selector class works with a single build profile. It will return the right SolidityCompilerConfig based either on the version matching logic, or overrides.

Copilot isn't aware that we only have a single compiler config per version, so the situation of matching the same version but different types is not possible.

However, we should make sure that we are considering the type when merging compilation jobs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, we should make sure that we are considering the type when merging compilation jobs.

Self-answer: we merge by SolidityCompilerConfig identity, not even value, so the type is taken into account.

Comment on lines 457 to 459
let subgraphsWithConfig: Array<
[SolcConfig, DependencyGraphImplementation]
[SolidityCompilerConfig, DependencyGraphImplementation]
> = [];
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subgraphsWithConfig now carries SolidityCompilerConfig, but the build-system logic downstream still treats compiler configs as solc-only (e.g. compiler metadata caches keyed by version, and getCompiler(version, { preferWasm, compilerPath })). With multiple compiler types this can conflate different compilers that share a version string and makes it easy to accidentally run the solc pipeline for non-solc configs. Consider incorporating type into cache keys / compiler selection, or rejecting mixed/non-solc compiler types until the build-system paths are properly split by type.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good point tbh

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we would need to add type to CompileCacheEntry.

Copy link
Copy Markdown
Member

@alcuadrado alcuadrado Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch 2 times, most recently from 5a48ee9 to 2fe6cb9 Compare March 5, 2026 15:44
Copilot AI review requested due to automatic review settings March 6, 2026 09:24
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch from 2fe6cb9 to 5b313d0 Compare March 6, 2026 09:24
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch from 5544e68 to 5d68d88 Compare March 16, 2026 18:11
Copilot AI review requested due to automatic review settings March 16, 2026 18:23
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch from 5d68d88 to 1163e75 Compare March 16, 2026 18:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

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

  • compilerType is required in CompileCacheEntry, but loadCache() can return entries from older cache files where this field is missing. Consider making compilerType optional in the type (and/or normalizing/migrating loaded entries) so the TypeScript type matches the runtime data shape and avoids unsafe assumptions elsewhere.
export interface CompileCacheEntry {
  jobHash: string;
  isolated: boolean;
  compilerType: string;
  buildInfoPath: string;
  buildInfoOutputPath: string;
  artifactPaths: string[];
  typeFilePath?: string;
  wasm: boolean;
}

You can also share your feedback on Copilot code review. Take the survey.

...incompatibleCompilerFields,
});

// This defintion needs to be aligned with solidityCompilerUserConfigType.
Comment on lines +264 to +274
const versionPart = this.solcConfig.version.replaceAll(".", "_");

// For non-solc compiler types, include the compiler type in the build ID.
// We keep the `solc-` prefix for all types to avoid breaking codepaths
// that look for it.
if (compilerType !== undefined && compilerType !== "solc") {
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions --
compilerType is `never` in the base type system (only "solc" is registered),
but plugins can extend SolidityCompilerConfigPerType to add new compiler types. */
return `solc-${versionPart}-${compilerType}-${jobHash}`;
}
const BUILD_INFO_FORMAT =
/^solc-(?<major>\d+)_(?<minor>\d+)_(?<patch>\d+)-[0-9a-fA-F]*$/;
export const BUILD_INFO_FORMAT: RegExp =
/^solc-(?<major>\d+)_(?<minor>\d+)_(?<patch>\d+)(?:-(?<compilerType>[a-zA-Z][a-zA-Z0-9]*))?-[0-9a-fA-F]*$/;
"hardhat": patch
---

Add an compilerType field to the `SolidityBuildInfo` and their ids. Where undefined/not-present means "solc". ([#8008](https://github.com/NomicFoundation/hardhat/pull/8008))
"Expected a string or an array of strings",
);
/**
* The top-level type SolidityUserConfig is a unition type too complex for
* `compilers` field.
*/
const incompatibleCompilerFields = {
type: incompatibleFieldType("This field is incompatible with `profiles`"),
Comment on lines +84 to +89
const commonSolidityCompilerUserConfigFields = {
type: z.string().optional(),
version: z.string(),
settings: z.any().optional(),
path: z.string().optional(),
preferWasm: z.boolean().optional(),
compilers: incompatibleFieldType("This field is incompatible with `version`"),
overrides: incompatibleFieldType("This field is incompatible with `version`"),
profiles: incompatibleFieldType("This field is incompatible with `version`"),
});
};
Copy link
Copy Markdown
Member

@alcuadrado alcuadrado left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

Copilot AI review requested due to automatic review settings March 23, 2026 10:01
@marianfe marianfe force-pushed the feat/solx-step1-core-multi-compiler branch from 127601c to 4238ea7 Compare March 23, 2026 10:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated 2 comments.

Comment on lines +270 to 278
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions --
compilerType is `never` in the base type system (only "solc" is registered),
but plugins can extend SolidityCompilerConfigPerType to add new compiler types. */
return `solc-${versionPart}-${compilerType}-${jobHash}`;
}

return `solc-${versionPart}-${jobHash}`;
}

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compilerType is interpolated verbatim into the build id (solc-<version>-<compilerType>-<hash>). Because build ids are later used as filenames (e.g. artifacts/build-info/<buildId>.json), a compiler type containing path separators or extra dashes can create invalid/ambiguous ids and potentially unsafe paths. Consider validating/sanitizing compilerType before using it in the id (e.g. enforce an allowed pattern and throw a config validation error if it doesn’t match), or encode it so that it can’t contain /, .., or --delimiters.

Suggested change
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions --
compilerType is `never` in the base type system (only "solc" is registered),
but plugins can extend SolidityCompilerConfigPerType to add new compiler types. */
return `solc-${versionPart}-${compilerType}-${jobHash}`;
}
return `solc-${versionPart}-${jobHash}`;
}
const compilerTypeForId = this.#sanitizeCompilerTypeForBuildId(compilerType);
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions --
compilerType is `never` in the base type system (only "solc" is registered),
but plugins can extend SolidityCompilerConfigPerType to add new compiler types. */
return `solc-${versionPart}-${compilerTypeForId}-${jobHash}`;
}
return `solc-${versionPart}-${jobHash}`;
}
#sanitizeCompilerTypeForBuildId(compilerType: string): string {
// Restrict the compiler type used in build IDs to a safe subset of characters
// to avoid introducing path separators or ambiguous delimiters into filenames.
// This keeps behavior unchanged for "normal" identifiers while safely
// normalizing any unexpected input.
return compilerType.replace(/[^A-Za-z0-9_]/g, "_");
}

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +90
const commonSolidityCompilerUserConfigFields = {
type: z.string().optional(),
version: z.string(),
settings: z.any().optional(),
path: z.string().optional(),
preferWasm: z.boolean().optional(),
compilers: incompatibleFieldType("This field is incompatible with `version`"),
overrides: incompatibleFieldType("This field is incompatible with `version`"),
profiles: incompatibleFieldType("This field is incompatible with `version`"),
});
};
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type discriminator is currently validated as an arbitrary string (type: z.string().optional()), but downstream the build id format and EDR parsing regex assume a restricted token-like compiler type. Without restricting/validating this field, users/plugins can create compiler types that yield build ids that can’t be parsed and/or aren’t safe as filenames (e.g. containing /, .., or -). Consider tightening the schema (and/or adding a resolved-config validation) to enforce a safe pattern for compiler types that matches the build id format.

Copilot uses AI. Check for mistakes.
@marianfe marianfe added this pull request to the merge queue Mar 23, 2026
Merged via the queue into main with commit 2e25edb Mar 23, 2026
249 checks passed
@marianfe marianfe deleted the feat/solx-step1-core-multi-compiler branch March 23, 2026 11:23
@github-actions github-actions bot mentioned this pull request Mar 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no docs needed This PR doesn't require links to documentation no peer bump needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants