hardhat
Version:
Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
137 lines (118 loc) • 3.93 kB
text/typescript
import { execFile } from "child_process";
import * as fs from "fs";
import os from "node:os";
import path from "node:path";
import * as semver from "semver";
import { ExecFileOptions } from "node:child_process";
import { CompilerInput, CompilerOutput } from "../../../types";
import { HardhatError } from "../../core/errors";
import { ERRORS } from "../../core/errors-list";
export interface ICompiler {
compile(input: CompilerInput): Promise<CompilerOutput>;
}
export class Compiler implements ICompiler {
constructor(private _pathToSolcJs: string) {}
public async compile(input: CompilerInput) {
const scriptPath = path.join(__dirname, "./solcjs-runner.js");
let output: string;
try {
const { stdout } = await execFileWithInput(
process.execPath,
[scriptPath, this._pathToSolcJs],
JSON.stringify(input),
{
maxBuffer: 1024 * 1024 * 500,
}
);
output = stdout;
} catch (e: any) {
throw new HardhatError(ERRORS.SOLC.SOLCJS_ERROR, {}, e);
}
return JSON.parse(output);
}
}
export class NativeCompiler implements ICompiler {
constructor(private _pathToSolc: string, private _solcVersion?: string) {}
public async compile(input: CompilerInput) {
const args = ["--standard-json"];
// Logic to make sure that solc default import callback is not being used.
// If solcVersion is not defined or <= 0.6.8, do not add extra args.
if (this._solcVersion !== undefined) {
if (semver.gte(this._solcVersion, "0.8.22")) {
// version >= 0.8.22
args.push("--no-import-callback");
} else if (semver.gte(this._solcVersion, "0.6.9")) {
// version >= 0.6.9
const tmpFolder = path.join(os.tmpdir(), "hardhat-solc");
fs.mkdirSync(tmpFolder, { recursive: true });
args.push(`--base-path`);
args.push(tmpFolder);
}
}
let output: string;
try {
const { stdout } = await execFileWithInput(
this._pathToSolc,
args,
JSON.stringify(input),
{
maxBuffer: 1024 * 1024 * 500,
}
);
output = stdout;
} catch (e: any) {
throw new HardhatError(ERRORS.SOLC.CANT_RUN_NATIVE_COMPILER, {}, e);
}
return JSON.parse(output);
}
}
/**
* Executes a command using execFile, writes provided input to stdin,
* and returns a Promise that resolves with stdout and stderr.
*
* @param {string} file - The file to execute.
* @param {readonly string[]} args - The arguments to pass to the file.
* @param {ExecFileOptions} options - The options to pass to the exec function.
* @returns {Promise<{stdout: string, stderr: string}>}
*/
export async function execFileWithInput(
file: string,
args: readonly string[],
input: string,
options: ExecFileOptions = {}
): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
const child = execFile(file, args, options, (error, stdout, stderr) => {
// `error` is any execution error. e.g. command not found, non-zero exit code, etc.
if (error !== null) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
// This could be triggered if node fails to spawn the child process
child.on("error", (err) => {
reject(err);
});
const stdin = child.stdin;
if (stdin !== null) {
stdin.on("error", (err) => {
// This captures EPIPE error
reject(err);
});
child.once("spawn", () => {
if (!stdin.writable || child.killed) {
return reject(new Error("Failed to write to unwritable stdin"));
}
stdin.write(input, (error) => {
if (error !== null && error !== undefined) {
reject(error);
}
stdin.end();
});
});
} else {
reject(new Error("No stdin on child process"));
}
});
}