feat(@embark/compiler): support :before and :after hooks on event compiler:contracts:compile (#1878)

* feat(@embark/compiler): support :before and :after hooks on event compiler:contracts:compile

* style: keep root package.json sorted cleanly

* style: formatting with prettier where print-width is 200

Per the suggestion of @iurimatias

* WIP: PR review

* WIP: PR review

* WIP: refactor to check if file.path is within dappPath() or os.tmpdir()

* WIP: PR review

* WIP: use native async functions (and related JS lang constructs) instead of the async library

* WIP: async.eachObject is no longer a dependency
This commit is contained in:
Michael Bradley 2019-09-11 12:08:52 -05:00 committed by Iuri Matias
parent 5faa07cc9f
commit 043ccc0a24
6 changed files with 112 additions and 61 deletions

View File

@ -75,19 +75,19 @@
"packages": [ "packages": [
"dapps/templates/*", "dapps/templates/*",
"dapps/tests/*", "dapps/tests/*",
"packages/core/*", "packages/*",
"packages/plugins/*",
"packages/stack/*",
"packages/embarkjs/*",
"packages/cockpit/*", "packages/cockpit/*",
"packages/*" "packages/core/*",
"packages/embarkjs/*",
"packages/plugins/*",
"packages/stack/*"
], ],
"nohoist": [ "nohoist": [
"embark/embark-test-contract-0",
"embark/embark-test-contract-1",
"embark-dapp-template-demo/bootstrap", "embark-dapp-template-demo/bootstrap",
"embark-dapp-test-app/embark-dapp-test-service", "embark-dapp-test-app/embark-dapp-test-service",
"embark-dapp-test-app/zeppelin-solidity" "embark-dapp-test-app/zeppelin-solidity",
"embark/embark-test-contract-0",
"embark/embark-test-contract-1"
] ]
} }
} }

View File

@ -1,3 +1,5 @@
import {Callback} from "./callbacks";
export interface Plugin { export interface Plugin {
dappGenerators: any; dappGenerators: any;
} }
@ -7,6 +9,7 @@ export interface Plugins {
loadInternalPlugin(name: string, options: any): void; loadInternalPlugin(name: string, options: any): void;
getPluginsProperty(pluginType: string, property: string, sub_property?: string): any[]; getPluginsProperty(pluginType: string, property: string, sub_property?: string): any[];
plugins: Plugin[]; plugins: Plugin[];
runActionsForEvent(event: string, args: any, cb: Callback<any>): void;
} }
export interface Plugin { export interface Plugin {

View File

@ -62,5 +62,3 @@ arguments:
* `callback(error, compiledObject)` * `callback(error, compiledObject)`
## Dependencies ## Dependencies
* async.eachObject

View File

@ -41,9 +41,9 @@
"watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch" "watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch"
}, },
"dependencies": { "dependencies": {
"@babel/runtime-corejs2": "7.3.1", "@babel/runtime-corejs2": "7.6.0",
"embark-async-wrapper": "^4.1.1", "embark-i18n": "^4.1.1",
"embark-i18n": "^4.1.1" "embark-utils": "^4.1.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.2.3", "@babel/cli": "7.2.3",

View File

@ -1,88 +1,130 @@
import {Callback, CompilerPluginObject, Embark, Plugins} /* supplied by @types/embark in packages/embark-typings */ from "embark"; import { Callback, CompilerPluginObject, Embark, Plugins /* supplied by @types/embark in packages/embark-typings */ } from "embark";
import { __ } from "embark-i18n"; import { __ } from "embark-i18n";
import * as os from "os";
import * as path from "path";
import { promisify } from "util";
const async = require("embark-async-wrapper"); const { File, Types, dappPath } = require("embark-utils");
class Compiler { class Compiler {
private fs: any;
private logger: any; private logger: any;
private plugins: Plugins; private plugins: Plugins;
private isCoverage: boolean;
constructor(embark: Embark, options: any) { constructor(embark: Embark, options: any) {
this.fs = embark.fs;
this.logger = embark.logger; this.logger = embark.logger;
this.plugins = options.plugins; this.plugins = options.plugins;
this.isCoverage = options.isCoverage;
// embark.events.setCommandHandler("compiler:contracts", this.compile_contracts.bind(this)); embark.events.setCommandHandler("compiler:contracts:compile", this.compileContracts.bind(this));
embark.events.setCommandHandler("compiler:contracts:compile", this.compile_contracts.bind(this));
} }
private compile_contracts(contractFiles: any[], cb: any) { private async runAfterActions(compiledObject: any): Promise<any> {
if (contractFiles.length === 0) { return (((await promisify(this.plugins.runActionsForEvent.bind(this.plugins, "compiler:contracts:compile:after"))(compiledObject)) as unknown) as any) || {};
}
private async runBeforeActions(contractFiles: any[]): Promise<any[]> {
return (((await promisify(this.plugins.runActionsForEvent.bind(this.plugins, "compiler:contracts:compile:before"))(contractFiles)) as unknown) as any[]) || [];
}
private *callCompilers(compilers: any, matchingFiles: any[], compilerOptions: object) {
for (const compiler of compilers) {
yield promisify(compiler)(matchingFiles, compilerOptions);
}
}
private checkContractFiles(contractFiles: any[]) {
const _dappPath = dappPath();
const osTmpDir = os.tmpdir();
return contractFiles.map((file) => {
if (file instanceof File) {
return file;
}
if (!file.path) {
throw new TypeError("path property was missing on contract file object");
}
let filePath: string;
if (!path.isAbsolute(file.path)) {
filePath = dappPath(file.path);
} else {
filePath = path.normalize(file.path);
}
if (![_dappPath, osTmpDir].some((dir) => filePath.startsWith(dir))) {
throw new Error("path must be within the DApp project or the OS temp directory");
}
return new File({
originalPath: file.path,
path: file.path,
resolver: (callback: Callback<any>) => {
this.fs.readFile(file.path, { encoding: "utf-8" }, callback);
},
type: Types.dappFile,
});
});
}
private async compileContracts(contractFiles: any[], cb: Callback<any>) {
if (!contractFiles.length) {
return cb(null, {}); return cb(null, {});
} }
const compiledObject: {[index: string]: any} = {}; const compiledObject: { [index: string]: any } = {};
const compilerOptions = {};
const compilerOptions = { try {
isCoverage: this.isCoverage, contractFiles = this.checkContractFiles(await this.runBeforeActions(contractFiles));
};
async.eachObject(this.getAvailableCompilers(), await Promise.all(
(extension: string, compilers: any, next: any) => { Object.entries(this.getAvailableCompilers()).map(async ([extension, compilers]: [string, any]) => {
const matchingFiles = contractFiles.filter(this.filesMatchingExtension(extension)); const matchingFiles = contractFiles.filter(this.filesMatchingExtension(extension));
if (matchingFiles.length === 0) { if (!matchingFiles.length) {
return next(); return;
} }
async.someLimit(compilers, 1, (compiler: any, someCb: Callback<boolean>) => { for await (const compileResult of this.callCompilers(compilers, matchingFiles, compilerOptions)) {
compiler.call(compiler, matchingFiles, compilerOptions, (err: any, compileResult: any) => {
if (err) {
return someCb(err);
}
if (compileResult === false) { if (compileResult === false) {
// Compiler not compatible, trying the next one continue;
return someCb(null, false);
} }
Object.assign(compiledObject, compileResult); Object.assign(compiledObject, compileResult);
someCb(null, true); return;
});
}, (err: Error, result: boolean) => {
if (err) {
return next(err);
} }
if (!result) {
// No compiler was compatible throw new Error(__("No installed compiler was compatible with your version of %s files", extension));
return next(new Error(__("No installed compiler was compatible with your version of %s files", extension))); }),
} );
next();
}); contractFiles
}, .filter((f: any) => !f.compiled)
(err: any) => { .forEach((file: any) => {
contractFiles.filter((f: any) => !f.compiled).forEach((file: any) => {
this.logger.warn(__("%s doesn't have a compatible contract compiler. Maybe a plugin exists for it.", file.path)); this.logger.warn(__("%s doesn't have a compatible contract compiler. Maybe a plugin exists for it.", file.path));
}); });
cb(err, compiledObject); cb(null, await this.runAfterActions(compiledObject));
}, } catch (err) {
); cb(err);
}
} }
private getAvailableCompilers() { private getAvailableCompilers() {
const available_compilers: { [index: string]: any } = {}; const availableCompilers: { [index: string]: any } = {};
this.plugins.getPluginsProperty("compilers", "compilers").forEach((compilerObject: CompilerPluginObject) => { this.plugins.getPluginsProperty("compilers", "compilers").forEach((compilerObject: CompilerPluginObject) => {
if (!available_compilers[compilerObject.extension]) { if (!availableCompilers[compilerObject.extension]) {
available_compilers[compilerObject.extension] = []; availableCompilers[compilerObject.extension] = [];
} }
available_compilers[compilerObject.extension].unshift(compilerObject.cb); availableCompilers[compilerObject.extension].unshift(compilerObject.cb);
}); });
return available_compilers; return availableCompilers;
} }
private filesMatchingExtension(extension: string) { private filesMatchingExtension(extension: string) {
return (file: any) => { return (file: any) => {
const fileMatch = file.path.match(/\.[0-9a-z]+$/); const fileMatch = file.path.match(/\.[0-9a-z]+$/);
if (fileMatch && (fileMatch[0] === extension)) { if (fileMatch && fileMatch[0] === extension) {
file.compiled = true; file.compiled = true;
return true; return true;
} }

View File

@ -1047,6 +1047,14 @@
core-js "^2.5.7" core-js "^2.5.7"
regenerator-runtime "^0.12.0" regenerator-runtime "^0.12.0"
"@babel/runtime-corejs2@7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.6.0.tgz#6fcd37c2580070817d62f219db97f67e26f50f9c"
integrity sha512-zbPQzlbyJab2xCYb6VaESn8Tk/XiVpQJU7WvIKiQCwlFyc2NSCzKjqtBXCvpZBbiTOHCx10s2656REVnySwb+A==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/runtime-corejs2@^7.0.0": "@babel/runtime-corejs2@^7.0.0":
version "7.5.5" version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.5.5.tgz#c3214c08ef20341af4187f1c9fbdc357fbec96b2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.5.5.tgz#c3214c08ef20341af4187f1c9fbdc357fbec96b2"