-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add splitTestsCompilation solidity setting (11): closing PR
#8138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
alcuadrado
merged 15 commits into
dont-split-compilations
from
dont-split-compilations-phase-11
Apr 16, 2026
Merged
Changes from 7 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
4f5a3c5
Add PLUGIN_MIGRATION_GUIDE.md
alcuadrado 3d2cb10
Add empty file to trigger the full CI
alcuadrado 1efe5e6
Remove code duplication in the SolidityBuildSystemImplementation
alcuadrado ce19625
Small type-safety improvement in build task
alcuadrado 3602f5c
Improve error descriptor
alcuadrado c1ef662
Update plugin migration guide
alcuadrado c90a604
Remove the spec
alcuadrado b7ed426
Add comment
alcuadrado 2163afb
Improve type
alcuadrado 97a5215
Improve doc comment
alcuadrado 4409680
Rename function
alcuadrado 55586b7
Use the right type
alcuadrado c67029d
Add a comment
alcuadrado 32c5c1c
Update PLUGIN_MIGRATION_GUIDE.md
alcuadrado 2931710
Update PLUGIN_MIGRATION_GUIDE.md
alcuadrado File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| # Plugin Migration Guide: `splitTestsCompilation` | ||
|
|
||
| ## Overview | ||
|
|
||
| This version of Hardhat introduces a `splitTestsCompilation` Solidity config field that controls whether Solidity test files are compiled in a separate pass from contract files. Previous to this version, they were always compiled separately. | ||
|
|
||
| ```typescript | ||
| export default { | ||
| solidity: { | ||
| version: "0.8.28", | ||
| splitTestsCompilation: true, // opt in to the previous two-pass behavior | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| - **Default: `false`** — contracts and tests are compiled together in a single pass under `scope: "contracts"`. | ||
| - **`true`** — contracts and tests are compiled in separate passes (the previous default behavior). | ||
|
|
||
| This guide covers what changes for plugin authors and how to adapt. | ||
|
|
||
| ## Configuration | ||
|
|
||
| The field is accepted in all object-typed Solidity user configs (`SingleVersionSolidityUserConfig`, `MultiVersionSolidityUserConfig`, `BuildProfilesSolidityUserConfig`). String and string-array configs always resolve to `false`. | ||
|
|
||
| The resolved value is available at `hre.config.solidity.splitTestsCompilation`. | ||
|
|
||
| ## Build System (`hre.solidity`) | ||
|
|
||
| ### `scope: "tests"` is rejected when `splitTestsCompilation` is `false` | ||
|
|
||
| When `splitTestsCompilation` is `false`, tests are compiled together with contracts under `scope: "contracts"`. Using `scope: "tests"` is a logic error and throws a `HardhatError` with descriptor `SOLIDITY.SPLIT_TESTS_COMPILATION_DISABLED` in the following APIs: | ||
|
|
||
| - `hre.solidity.getRootFilePaths({ scope: "tests" })` | ||
| - `hre.solidity.build(rootFiles, { scope: "tests" })` | ||
| - `hre.solidity.getCompilationJobs(rootFiles, { scope: "tests" })` | ||
| - `hre.solidity.emitArtifacts(compilationJob, compilerOutput, { scope: "tests" })` | ||
| - `hre.solidity.cleanupArtifacts(rootFiles, { scope: "tests" })` | ||
|
|
||
| **Exception:** `hre.solidity.getArtifactsDirectory("tests")` does **not** throw. It returns the main artifacts path (same as `"contracts"`), since it is a read-only query with no side effects. | ||
|
|
||
| ### `getRootFilePaths({ scope: "contracts" })` | ||
|
|
||
| When `splitTestsCompilation` is `false`, this returns **all** build roots — contract roots, test roots, and npm roots — together. | ||
|
|
||
| When `splitTestsCompilation` is `true`, it returns contract roots only (unchanged). | ||
|
|
||
| ### `getArtifactsDirectory(scope)` | ||
|
|
||
| | `splitTestsCompilation` | `scope: "contracts"` | `scope: "tests"` | | ||
| | ----------------------- | -------------------- | -------------------------- | | ||
| | `false` | `artifactsPath` | `artifactsPath` | | ||
| | `true` | `artifactsPath` | `cachePath/test-artifacts` | | ||
|
|
||
| ### File classification is unchanged | ||
|
|
||
| `hre.solidity.getScope(fsPath)` continues to classify files as `"contracts"` or `"tests"` based on path and suffix rules. Use this API to distinguish contract files from test files when processing mixed sets. | ||
|
|
||
| ### `cleanupArtifacts()` | ||
|
|
||
| When `splitTestsCompilation` is `false`: | ||
|
|
||
| - Cleanup operates on the main artifacts directory. | ||
| - Duplicate contract-name detection runs across the mixed contract/test artifact set. | ||
| - `onCleanUpArtifacts` receives the mixed contract/test artifact set, so if you are hooking into it, you may need to adapt your Hook Handler. See below. | ||
|
|
||
| ## Artifacts | ||
|
|
||
| When `splitTestsCompilation` is `false`, both contract and test artifacts live under the same `paths.artifacts` directory. This means: | ||
|
|
||
| - `getAllArtifactPaths()` includes test artifacts. | ||
| - `getAllFullyQualifiedNames()` includes test artifacts. | ||
| - Bare-name lookup can become **ambiguous** if a test contract and a source contract share the same name. Ambiguous names type to `never` in the generated `artifacts.d.ts`. Users hitting a collision should switch to fully qualified names (e.g. `"contracts/Foo.sol:Foo"` instead of `"Foo"`); this affects APIs like `hardhat-ethers`'s `getContractFactory` / `getContractAt` / `deployContract`, `hardhat-viem`'s `deployContract` / `getContractAt`, `hardhat-verify`'s automatic contract inference, and `hardhat-ignition`'s artifact resolution. | ||
| - Fully qualified name lookup continues to work without ambiguity. | ||
| - **Test roots do not get per-source `artifacts.d.ts` files.** Only contract roots emit TypeScript declarations. They are not part of the `ArtifactMap` interface from `hardhat/types/artifacts`. | ||
| - This means that test contracts aren't part of the autocompletion in the `ethers` and `viem` plugins. | ||
|
|
||
| Plugins using `hre.artifacts` must no longer assume that "artifacts path" means "contracts only." | ||
|
|
||
| ### Filtering test artifacts | ||
|
|
||
| You can take a look at the `hardhat-typechain` plugin to understand how to filter out the test artifacts. | ||
|
|
||
| ## Build Task (`hardhat build` / `hardhat compile`) | ||
|
|
||
| ### When `splitTestsCompilation` is `false` | ||
|
|
||
| The build task uses a **single compilation pass** under `scope: "contracts"`. | ||
|
|
||
| <!-- prettier-ignore --> | ||
| | Scenario | Behavior | | ||
| | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | ||
| | Full build (no flags, no files) | Compiles all contracts and tests. Runs cleanup. Regenerates top-level `artifacts.d.ts`. Fires `onCleanUpArtifacts`.| | ||
| | Explicit `files` | Partial build of exactly those files. No cleanup. No `artifacts.d.ts` regeneration. No `onCleanupArtifacts`. | | ||
| | `--no-tests` (no files) | Partial build of contract roots only. No cleanup. No `artifacts.d.ts` regeneration. No `onCleanupArtifacts`. | | ||
| | `--no-contracts` (no files) | Partial build of test roots only. No cleanup. No `artifacts.d.ts` regeneration. No `onCleanupArtifacts`. | | ||
| | `files` + compatible flag (e.g. contract files + `--no-tests`) | Partial build of the provided files. No cleanup. No `artifacts.d.ts` regeneration. No `onCleanupArtifacts`. | | ||
| | `files` + incompatible flag (e.g. test files + `--no-tests`) | Throws `HardhatError` with descriptor `SOLIDITY.INCOMPATIBLE_FILES_WITH_BUILD_FLAGS`. | | ||
|
|
||
| **Important:** `--no-tests` and `--no-contracts` behave as synthetic partial builds when `splitTestsCompilation` is `false`. This is different from `splitTestsCompilation: true`, where `--no-tests` runs a full contracts build with cleanup. Plugins that depend on cleanup running after `--no-tests` should account for this. | ||
|
|
||
| ### When `splitTestsCompilation` is `true` | ||
|
|
||
| Current two-pass behavior is preserved. `--no-tests` and `--no-contracts` each skip one full pass with cleanup. | ||
|
|
||
| ### Return value | ||
|
|
||
| Both modes return: | ||
|
|
||
| ```typescript | ||
| { | ||
| contractRootPaths: string[]; | ||
| testRootPaths: string[]; | ||
| } | ||
| ``` | ||
|
|
||
| The arrays reflect the roots actually built, partitioned using `getScope()`. | ||
|
|
||
| ### Plugin pattern for calling `build` | ||
|
|
||
| If your plugin calls `build` and previously passed `noTests: true`, update it to branch on the config. When `splitTestsCompilation` is `false` there is no cheap "contracts-only" pass to fall back to — skipping tests would require an extra partial build — so the flag is only meaningful in split mode: | ||
|
alcuadrado marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```typescript | ||
| // Before | ||
| await hre.tasks.getTask("build").run({ noTests: true }); | ||
|
|
||
| // After: only skip tests when they live in a separate compilation pass. | ||
| const noTests = hre.config.solidity.splitTestsCompilation; | ||
| await hre.tasks.getTask("build").run({ noTests }); | ||
| ``` | ||
|
|
||
| ## Solidity Hooks | ||
|
|
||
| ### `build` hook | ||
|
|
||
| When `splitTestsCompilation` is `false`, full builds call the hook **once** with `scope: "contracts"` and a mixed set of contract and test roots. Plugins that need contract-only behavior must filter per file with `getScope()`. | ||
|
|
||
| ### `preprocessProjectFileBeforeBuilding` | ||
|
|
||
| The same compilation may include both contract and test files. Plugins can distinguish with `context.solidity.getScope(fsPath)`. | ||
|
|
||
| ### `preprocessSolcInputBeforeBuilding` | ||
|
|
||
| `solcInput.sources` may contain both contract and test sources together when `splitTestsCompilation` is `false`. | ||
|
|
||
| ### `onCleanUpArtifacts` | ||
|
|
||
| When `splitTestsCompilation` is `false`, this hook only fires during full builds (not partial builds from `--no-tests`, `--no-contracts`, or explicit files). It receives mixed contract/test artifact paths. Take a look at the `hardhat-typechain` plugin for an example of how to filter out test artifacts. | ||
|
|
||
| ### Unchanged hooks | ||
|
|
||
| `downloadCompilers`, `getCompiler`, `invokeSolc`, `readSourceFile`, and `readNpmPackageRemappings` are not affected. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.