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": [
"dapps/templates/*",
"dapps/tests/*",
"packages/core/*",
"packages/plugins/*",
"packages/stack/*",
"packages/embarkjs/*",
"packages/*",
"packages/cockpit/*",
"packages/*"
"packages/core/*",
"packages/embarkjs/*",
"packages/plugins/*",
"packages/stack/*"
],
"nohoist": [
"embark/embark-test-contract-0",
"embark/embark-test-contract-1",
"embark-dapp-template-demo/bootstrap",
"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 {
dappGenerators: any;
}
@ -7,6 +9,7 @@ export interface Plugins {
loadInternalPlugin(name: string, options: any): void;
getPluginsProperty(pluginType: string, property: string, sub_property?: string): any[];
plugins: Plugin[];
runActionsForEvent(event: string, args: any, cb: Callback<any>): void;
}
export interface Plugin {

View File

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

View File

@ -41,9 +41,9 @@
"watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch"
},
"dependencies": {
"@babel/runtime-corejs2": "7.3.1",
"embark-async-wrapper": "^4.1.1",
"embark-i18n": "^4.1.1"
"@babel/runtime-corejs2": "7.6.0",
"embark-i18n": "^4.1.1",
"embark-utils": "^4.1.1"
},
"devDependencies": {
"@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 * 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 {
private fs: any;
private logger: any;
private plugins: Plugins;
private isCoverage: boolean;
constructor(embark: Embark, options: any) {
this.fs = embark.fs;
this.logger = embark.logger;
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.compile_contracts.bind(this));
embark.events.setCommandHandler("compiler:contracts:compile", this.compileContracts.bind(this));
}
private compile_contracts(contractFiles: any[], cb: any) {
if (contractFiles.length === 0) {
private async runAfterActions(compiledObject: any): Promise<any> {
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, {});
}
const compiledObject: {[index: string]: any} = {};
const compiledObject: { [index: string]: any } = {};
const compilerOptions = {};
const compilerOptions = {
isCoverage: this.isCoverage,
};
try {
contractFiles = this.checkContractFiles(await this.runBeforeActions(contractFiles));
async.eachObject(this.getAvailableCompilers(),
(extension: string, compilers: any, next: any) => {
const matchingFiles = contractFiles.filter(this.filesMatchingExtension(extension));
if (matchingFiles.length === 0) {
return next();
}
await Promise.all(
Object.entries(this.getAvailableCompilers()).map(async ([extension, compilers]: [string, any]) => {
const matchingFiles = contractFiles.filter(this.filesMatchingExtension(extension));
if (!matchingFiles.length) {
return;
}
async.someLimit(compilers, 1, (compiler: any, someCb: Callback<boolean>) => {
compiler.call(compiler, matchingFiles, compilerOptions, (err: any, compileResult: any) => {
if (err) {
return someCb(err);
}
for await (const compileResult of this.callCompilers(compilers, matchingFiles, compilerOptions)) {
if (compileResult === false) {
// Compiler not compatible, trying the next one
return someCb(null, false);
continue;
}
Object.assign(compiledObject, compileResult);
someCb(null, true);
});
}, (err: Error, result: boolean) => {
if (err) {
return next(err);
return;
}
if (!result) {
// No compiler was compatible
return next(new Error(__("No installed compiler was compatible with your version of %s files", extension)));
}
next();
});
},
(err: any) => {
contractFiles.filter((f: any) => !f.compiled).forEach((file: any) => {
throw new Error(__("No installed compiler was compatible with your version of %s files", extension));
}),
);
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));
});
cb(err, compiledObject);
},
);
cb(null, await this.runAfterActions(compiledObject));
} catch (err) {
cb(err);
}
}
private getAvailableCompilers() {
const available_compilers: { [index: string]: any } = {};
const availableCompilers: { [index: string]: any } = {};
this.plugins.getPluginsProperty("compilers", "compilers").forEach((compilerObject: CompilerPluginObject) => {
if (!available_compilers[compilerObject.extension]) {
available_compilers[compilerObject.extension] = [];
if (!availableCompilers[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) {
return (file: any) => {
const fileMatch = file.path.match(/\.[0-9a-z]+$/);
if (fileMatch && (fileMatch[0] === extension)) {
if (fileMatch && fileMatch[0] === extension) {
file.compiled = true;
return true;
}

View File

@ -1047,6 +1047,14 @@
core-js "^2.5.7"
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":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.5.5.tgz#c3214c08ef20341af4187f1c9fbdc357fbec96b2"