1 | import chalk from "chalk";
|
2 | import fsExtra from "fs-extra";
|
3 | import path from "path";
|
4 |
|
5 | import {
|
6 | getArtifactFromContractOutput,
|
7 | saveArtifact,
|
8 | } from "../internal/artifacts";
|
9 | import {
|
10 | SOLC_INPUT_FILENAME,
|
11 | SOLC_OUTPUT_FILENAME,
|
12 | } from "../internal/constants";
|
13 | import { internalTask, task, types } from "../internal/core/config/config-env";
|
14 | import { BuidlerError } from "../internal/core/errors";
|
15 | import { ERRORS } from "../internal/core/errors-list";
|
16 | import { Compiler } from "../internal/solidity/compiler";
|
17 | import { getInputFromDependencyGraph } from "../internal/solidity/compiler/compiler-input";
|
18 | import { DependencyGraph } from "../internal/solidity/dependencyGraph";
|
19 | import { Resolver } from "../internal/solidity/resolver";
|
20 | import { glob } from "../internal/util/glob";
|
21 | import { getCompilersDir } from "../internal/util/global-dir";
|
22 | import { pluralize } from "../internal/util/strings";
|
23 | import { ResolvedBuidlerConfig, SolcInput } from "../types";
|
24 |
|
25 | import {
|
26 | TASK_BUILD_ARTIFACTS,
|
27 | TASK_COMPILE,
|
28 | TASK_COMPILE_CHECK_CACHE,
|
29 | TASK_COMPILE_COMPILE,
|
30 | TASK_COMPILE_GET_COMPILER_INPUT,
|
31 | TASK_COMPILE_GET_DEPENDENCY_GRAPH,
|
32 | TASK_COMPILE_GET_RESOLVED_SOURCES,
|
33 | TASK_COMPILE_GET_SOURCE_PATHS,
|
34 | TASK_COMPILE_RUN_COMPILER,
|
35 | } from "./task-names";
|
36 | import { areArtifactsCached, cacheBuidlerConfig } from "./utils/cache";
|
37 |
|
38 | async function cacheSolcJsonFiles(
|
39 | config: ResolvedBuidlerConfig,
|
40 | input: any,
|
41 | output: any
|
42 | ) {
|
43 | await fsExtra.ensureDir(config.paths.cache);
|
44 |
|
45 |
|
46 | await fsExtra.writeFile(
|
47 | path.join(config.paths.cache, SOLC_INPUT_FILENAME),
|
48 | JSON.stringify(input, undefined, 2),
|
49 | {
|
50 | encoding: "utf8",
|
51 | }
|
52 | );
|
53 |
|
54 | await fsExtra.writeFile(
|
55 | path.join(config.paths.cache, SOLC_OUTPUT_FILENAME),
|
56 | JSON.stringify(output, undefined, 2),
|
57 | {
|
58 | encoding: "utf8",
|
59 | }
|
60 | );
|
61 | }
|
62 |
|
63 | function isConsoleLogError(error: any): boolean {
|
64 | return (
|
65 | error.type === "TypeError" &&
|
66 | typeof error.message === "string" &&
|
67 | error.message.includes("log") &&
|
68 | error.message.includes("type(library console)")
|
69 | );
|
70 | }
|
71 |
|
72 | export default function () {
|
73 | internalTask(TASK_COMPILE_GET_SOURCE_PATHS, async (_, { config }) => {
|
74 | return glob(path.join(config.paths.sources, "**/*.sol"));
|
75 | });
|
76 |
|
77 | internalTask(
|
78 | TASK_COMPILE_GET_RESOLVED_SOURCES,
|
79 | async (_, { config, run }) => {
|
80 | const resolver = new Resolver(config.paths.root);
|
81 | const paths = await run(TASK_COMPILE_GET_SOURCE_PATHS);
|
82 | return Promise.all(
|
83 | paths.map((p: string) => resolver.resolveProjectSourceFile(p))
|
84 | );
|
85 | }
|
86 | );
|
87 |
|
88 | internalTask(
|
89 | TASK_COMPILE_GET_DEPENDENCY_GRAPH,
|
90 | async (_, { config, run }) => {
|
91 | const resolver = new Resolver(config.paths.root);
|
92 | const localFiles = await run(TASK_COMPILE_GET_RESOLVED_SOURCES);
|
93 |
|
94 | return DependencyGraph.createFromResolvedFiles(resolver, localFiles);
|
95 | }
|
96 | );
|
97 |
|
98 | internalTask(TASK_COMPILE_GET_COMPILER_INPUT, async (_, { config, run }) => {
|
99 | const dependencyGraph: DependencyGraph = await run(
|
100 | TASK_COMPILE_GET_DEPENDENCY_GRAPH
|
101 | );
|
102 |
|
103 | return getInputFromDependencyGraph(
|
104 | dependencyGraph,
|
105 | config.solc.optimizer,
|
106 | config.solc.evmVersion
|
107 | );
|
108 | });
|
109 |
|
110 | internalTask(TASK_COMPILE_RUN_COMPILER)
|
111 | .addParam(
|
112 | "input",
|
113 | "The compiler standard JSON input",
|
114 | undefined,
|
115 | types.json
|
116 | )
|
117 | .setAction(async ({ input }: { input: SolcInput }, { config }) => {
|
118 | const compilersCache = await getCompilersDir();
|
119 | const compiler = new Compiler(config.solc.version, compilersCache);
|
120 |
|
121 | return compiler.compile(input);
|
122 | });
|
123 |
|
124 | internalTask(TASK_COMPILE_COMPILE, async (_, { config, run }) => {
|
125 | const input = await run(TASK_COMPILE_GET_COMPILER_INPUT);
|
126 |
|
127 | console.log("Compiling...");
|
128 | const output = await run(TASK_COMPILE_RUN_COMPILER, { input });
|
129 |
|
130 | let hasErrors = false;
|
131 | let hasConsoleLogErrors = false;
|
132 | if (output.errors) {
|
133 | for (const error of output.errors) {
|
134 | hasErrors = hasErrors || error.severity === "error";
|
135 | if (error.severity === "error") {
|
136 | hasErrors = true;
|
137 |
|
138 | if (isConsoleLogError(error)) {
|
139 | hasConsoleLogErrors = true;
|
140 | }
|
141 |
|
142 | console.error(chalk.red(error.formattedMessage));
|
143 | } else {
|
144 | console.log("\n");
|
145 | console.warn(chalk.yellow(error.formattedMessage));
|
146 | }
|
147 | }
|
148 | }
|
149 |
|
150 | if (hasConsoleLogErrors) {
|
151 | console.error(
|
152 | chalk.red(
|
153 | `The console.log call you made isn’t supported. See https://buidler.dev/console-log for the list of supported methods.`
|
154 | )
|
155 | );
|
156 | console.log();
|
157 | }
|
158 |
|
159 | if (hasErrors || !output.contracts) {
|
160 | throw new BuidlerError(ERRORS.BUILTIN_TASKS.COMPILE_FAILURE);
|
161 | }
|
162 |
|
163 | await cacheSolcJsonFiles(config, input, output);
|
164 |
|
165 | await cacheBuidlerConfig(config.paths, config.solc);
|
166 |
|
167 | return output;
|
168 | });
|
169 |
|
170 | internalTask(TASK_COMPILE_CHECK_CACHE, async ({ force }, { config, run }) => {
|
171 | if (force) {
|
172 | return false;
|
173 | }
|
174 |
|
175 | const dependencyGraph: DependencyGraph = await run(
|
176 | TASK_COMPILE_GET_DEPENDENCY_GRAPH
|
177 | );
|
178 |
|
179 | const sourceTimestamps = dependencyGraph
|
180 | .getResolvedFiles()
|
181 | .map((file) => file.lastModificationDate.getTime());
|
182 |
|
183 | return areArtifactsCached(sourceTimestamps, config.solc, config.paths);
|
184 | });
|
185 |
|
186 | internalTask(TASK_BUILD_ARTIFACTS, async ({ force }, { config, run }) => {
|
187 | const sources = await run(TASK_COMPILE_GET_SOURCE_PATHS);
|
188 |
|
189 | if (sources.length === 0) {
|
190 | console.log("No Solidity source file available.");
|
191 | return;
|
192 | }
|
193 |
|
194 | const isCached: boolean = await run(TASK_COMPILE_CHECK_CACHE, { force });
|
195 |
|
196 | if (isCached) {
|
197 | console.log(
|
198 | "All contracts have already been compiled, skipping compilation."
|
199 | );
|
200 | return;
|
201 | }
|
202 |
|
203 | const compilationOutput = await run(TASK_COMPILE_COMPILE);
|
204 |
|
205 | if (compilationOutput === undefined) {
|
206 | return;
|
207 | }
|
208 |
|
209 | await fsExtra.ensureDir(config.paths.artifacts);
|
210 | let numberOfContracts = 0;
|
211 |
|
212 | for (const file of Object.values<any>(compilationOutput.contracts)) {
|
213 | for (const [contractName, contractOutput] of Object.entries(file)) {
|
214 | const artifact = getArtifactFromContractOutput(
|
215 | contractName,
|
216 | contractOutput
|
217 | );
|
218 | numberOfContracts += 1;
|
219 |
|
220 | await saveArtifact(config.paths.artifacts, artifact);
|
221 | }
|
222 | }
|
223 |
|
224 | console.log(
|
225 | "Compiled",
|
226 | numberOfContracts,
|
227 | pluralize(numberOfContracts, "contract"),
|
228 | "successfully"
|
229 | );
|
230 | });
|
231 |
|
232 | task(TASK_COMPILE, "Compiles the entire project, building all artifacts")
|
233 | .addFlag("force", "Force compilation ignoring cache")
|
234 | .setAction(async ({ force: force }: { force: boolean }, { run }) =>
|
235 | run(TASK_BUILD_ARTIFACTS, { force })
|
236 | );
|
237 | }
|