@@ -3,6 +3,7 @@ import * as fs from "fs";
33import os from "node:os" ;
44import path from "node:path" ;
55import * as semver from "semver" ;
6+ import { ExecFileOptions } from "node:child_process" ;
67import { CompilerInput , CompilerOutput } from "../../../types" ;
78import { HardhatError } from "../../core/errors" ;
89import { ERRORS } from "../../core/errors-list" ;
@@ -17,32 +18,21 @@ export class Compiler implements ICompiler {
1718 public async compile ( input : CompilerInput ) {
1819 const scriptPath = path . join ( __dirname , "./solcjs-runner.js" ) ;
1920
20- const output : string = await new Promise ( ( resolve , reject ) => {
21- try {
22- const subprocess = execFile (
23- process . execPath ,
24- [ scriptPath , this . _pathToSolcJs ] ,
25- {
26- maxBuffer : 1024 * 1024 * 500 ,
27- } ,
28- ( err , stdout ) => {
29- if ( err !== null ) {
30- return reject ( err ) ;
31- }
32- resolve ( stdout ) ;
33- }
34- ) ;
35-
36- subprocess . stdin ! . write ( JSON . stringify ( input ) ) ;
37- subprocess . stdin ! . end ( ) ;
38- } catch ( e : any ) {
39- throw new HardhatError (
40- ERRORS . SOLC . SOLCJS_ERROR ,
41- { error : e . message } ,
42- e
43- ) ;
44- }
45- } ) ;
21+ let output : string ;
22+ try {
23+ const { stdout } = await execFileWithInput (
24+ process . execPath ,
25+ [ scriptPath , this . _pathToSolcJs ] ,
26+ JSON . stringify ( input ) ,
27+ {
28+ maxBuffer : 1024 * 1024 * 500 ,
29+ }
30+ ) ;
31+
32+ output = stdout ;
33+ } catch ( e : any ) {
34+ throw new HardhatError ( ERRORS . SOLC . SOLCJS_ERROR , { } , e ) ;
35+ }
4636
4737 return JSON . parse ( output ) ;
4838 }
@@ -69,29 +59,78 @@ export class NativeCompiler implements ICompiler {
6959 }
7060 }
7161
72- const output : string = await new Promise ( ( resolve , reject ) => {
73- try {
74- const process = execFile (
75- this . _pathToSolc ,
76- args ,
77- {
78- maxBuffer : 1024 * 1024 * 500 ,
79- } ,
80- ( err , stdout ) => {
81- if ( err !== null ) {
82- return reject ( err ) ;
83- }
84- resolve ( stdout ) ;
85- }
86- ) ;
62+ let output : string ;
63+ try {
64+ const { stdout } = await execFileWithInput (
65+ this . _pathToSolc ,
66+ args ,
67+ JSON . stringify ( input ) ,
68+ {
69+ maxBuffer : 1024 * 1024 * 500 ,
70+ }
71+ ) ;
8772
88- process . stdin ! . write ( JSON . stringify ( input ) ) ;
89- process . stdin ! . end ( ) ;
90- } catch ( e : any ) {
91- throw new HardhatError ( ERRORS . SOLC . CANT_RUN_NATIVE_COMPILER , { } , e ) ;
92- }
93- } ) ;
73+ output = stdout ;
74+ } catch ( e : any ) {
75+ throw new HardhatError ( ERRORS . SOLC . CANT_RUN_NATIVE_COMPILER , { } , e ) ;
76+ }
9477
9578 return JSON . parse ( output ) ;
9679 }
9780}
81+
82+ /**
83+ * Executes a command using execFile, writes provided input to stdin,
84+ * and returns a Promise that resolves with stdout and stderr.
85+ *
86+ * @param {string } file - The file to execute.
87+ * @param {readonly string[] } args - The arguments to pass to the file.
88+ * @param {ExecFileOptions } options - The options to pass to the exec function.
89+ * @returns {Promise<{stdout: string, stderr: string}> }
90+ */
91+ export async function execFileWithInput (
92+ file : string ,
93+ args : readonly string [ ] ,
94+ input : string ,
95+ options : ExecFileOptions = { }
96+ ) : Promise < { stdout : string ; stderr : string } > {
97+ return new Promise ( ( resolve , reject ) => {
98+ const child = execFile ( file , args , options , ( error , stdout , stderr ) => {
99+ // `error` is any execution error. e.g. command not found, non-zero exit code, etc.
100+ if ( error !== null ) {
101+ reject ( error ) ;
102+ } else {
103+ resolve ( { stdout, stderr } ) ;
104+ }
105+ } ) ;
106+
107+ // This could be triggered if node fails to spawn the child process
108+ child . on ( "error" , ( err ) => {
109+ reject ( err ) ;
110+ } ) ;
111+
112+ const stdin = child . stdin ;
113+
114+ if ( stdin !== null ) {
115+ stdin . on ( "error" , ( err ) => {
116+ // This captures EPIPE error
117+ reject ( err ) ;
118+ } ) ;
119+
120+ child . once ( "spawn" , ( ) => {
121+ if ( ! stdin . writable || child . killed ) {
122+ return reject ( new Error ( "Failed to write to unwritable stdin" ) ) ;
123+ }
124+
125+ stdin . write ( input , ( error ) => {
126+ if ( error !== null && error !== undefined ) {
127+ reject ( error ) ;
128+ }
129+ stdin . end ( ) ;
130+ } ) ;
131+ } ) ;
132+ } else {
133+ reject ( new Error ( "No stdin on child process" ) ) ;
134+ }
135+ } ) ;
136+ }
0 commit comments