UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

207 lines (179 loc) 6.71 kB
import type { SolcConfig, SolidityBuildProfileConfig, } from "../../../../types/config.js"; import type { CompilationJobCreationError } from "../../../../types/solidity/build-system.js"; import type { ResolvedFile } from "../../../../types/solidity/resolved-file.js"; import type { DependencyGraph } from "../../../../types/solidity.js"; import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; import { shortenPath } from "@nomicfoundation/hardhat-utils/path"; import { intersects, maxSatisfying, satisfies } from "semver"; import { CompilationJobCreationErrorReason } from "../../../../types/solidity/build-system.js"; export class SolcConfigSelector { readonly #buildProfileName: string; readonly #buildProfile: SolidityBuildProfileConfig; /** * Creates a new SolcConfigSelector that can be used to select the best solc * configuration for subgraphs of the given dependency graph. * * All the queries are done in the context of the given dependency graph, and * using the same build profile. * * @param buildProfileName The name of the build profile to use. * @param buildProfile The build profile config. * @param _dependencyGraph The entire dependency graph of the project. */ constructor( buildProfileName: string, buildProfile: SolidityBuildProfileConfig, _dependencyGraph: DependencyGraph, ) { this.#buildProfileName = buildProfileName; this.#buildProfile = buildProfile; } /** * Selects the best solc configuration for a subgraph of the dependency graph * with which this selector was created. * * @param subgraph A single-root subgraph of the dependency graph. * @returns The best solc configuration for the subgraph, or a * CompilationJobCreationError if no compatible solc version could be found. */ public selectBestSolcConfigForSingleRootGraph( subgraph: DependencyGraph, ): SolcConfig | CompilationJobCreationError { const roots = subgraph.getRoots(); assertHardhatInvariant( roots.size === 1, "This method only works for single root graphs", ); const [userSourceName, root] = [...roots.entries()][0]; const allVersionPragamas = [...subgraph.getAllFiles()] .map(({ content }) => content.versionPragmas) .flat(1); const versionRange = Array.from(new Set(allVersionPragamas)).join(" "); const overriddenCompiler = this.#buildProfile.overrides[userSourceName]; // if there's an override, we only check that if (overriddenCompiler !== undefined) { if (!satisfies(overriddenCompiler.version, versionRange)) { return this.#getCompilationJobCreationError( root, subgraph, [overriddenCompiler.version], true, ); } return overriddenCompiler; } // if there's no override, we find a compiler that matches the version range const compilerVersions = this.#buildProfile.compilers.map((x) => x.version); const matchingVersion = maxSatisfying(compilerVersions, versionRange); if (matchingVersion === null) { return this.#getCompilationJobCreationError( root, subgraph, compilerVersions, false, ); } const matchingConfig = this.#buildProfile.compilers.find( (x) => x.version === matchingVersion, ); assertHardhatInvariant( matchingConfig !== undefined, `Matching config not found for version '${matchingVersion.toString()}'`, ); return matchingConfig; } #getCompilationJobCreationError( root: ResolvedFile, dependencyGraph: DependencyGraph, compilerVersions: string[], overridden: boolean, ): CompilationJobCreationError { const rootVersionRange = root.content.versionPragmas.join(" "); if (maxSatisfying(compilerVersions, rootVersionRange) === null) { let reason: CompilationJobCreationErrorReason; let formattedReason: string; if (overridden) { reason = CompilationJobCreationErrorReason.INCOMPATIBLE_OVERRIDDEN_SOLC_VERSION; formattedReason = `An override with incompatible solc version was found for this file.`; } else { reason = CompilationJobCreationErrorReason.NO_COMPATIBLE_SOLC_VERSION_WITH_ROOT; formattedReason = `No solc version enabled in this profile is compatible with this file.`; } return { reason, rootFilePath: root.fsPath, buildProfile: this.#buildProfileName, formattedReason, }; } for (const transitiveDependency of this.#getTransitiveDependencies( root, dependencyGraph, )) { const transitiveDependencyVersionRange = transitiveDependency.versionPragmasPath .map((pragmas) => pragmas.join(" ")) .join(" "); if (!intersects(rootVersionRange, transitiveDependencyVersionRange)) { return { reason: CompilationJobCreationErrorReason.IMPORT_OF_INCOMPATIBLE_FILE, rootFilePath: root.fsPath, buildProfile: this.#buildProfileName, incompatibleImportPath: transitiveDependency.fsPath, formattedReason: `Following these imports leads to an incompatible solc version pragma that no version can satisfy: * ${shortenPath(root.fsPath)} * ${transitiveDependency.fsPath.map((s) => shortenPath(s)).join("\n * ")} `, }; } } return { reason: CompilationJobCreationErrorReason.NO_COMPATIBLE_SOLC_VERSION_FOUND, rootFilePath: root.fsPath, buildProfile: this.#buildProfileName, formattedReason: `No solc version enabled in this profile is compatible with this file and all of its dependencies.`, }; } *#getTransitiveDependencies( root: ResolvedFile, dependencyGraph: DependencyGraph, visited = new Set<ResolvedFile>([root]), ): Generator<{ fsPath: string[]; versionPragmasPath: string[][]; dependency: ResolvedFile; }> { for (const dependency of dependencyGraph.getDependencies(root)) { const file = dependency.file; if (visited.has(file)) { continue; } visited.add(file); yield { fsPath: [file.fsPath], versionPragmasPath: [file.content.versionPragmas], dependency: file, }; for (const transitive of this.#getTransitiveDependencies( file, dependencyGraph, visited, )) { yield { fsPath: [file.fsPath, ...transitive.fsPath], versionPragmasPath: [ file.content.versionPragmas, ...transitive.versionPragmasPath, ], dependency: transitive.dependency, }; } } } }