From 657b698e1eba7fde2322bb81547eba26491c0af4 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:11:08 +0100 Subject: Add sol-compiler watch mode --- packages/sol-compiler/CHANGELOG.json | 13 +++++++ packages/sol-compiler/package.json | 4 ++ packages/sol-compiler/src/cli.ts | 10 ++++- packages/sol-compiler/src/compiler.ts | 46 +++++++++++++++++++++++ packages/sol-compiler/src/utils/compiler.ts | 7 ++-- packages/sol-compiler/src/utils/types.ts | 9 +++++ packages/sol-compiler/test/compiler_utils_test.ts | 6 +-- 7 files changed, 88 insertions(+), 7 deletions(-) diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index 0a757f519..17b3f9edd 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,4 +1,17 @@ [ + { + "version": "2.0.0", + "changes": [ + { + "note": "Add sol-compiler watch mode with -w flag", + "pr": "TODO" + }, + { + "note": "Make error and warning colouring more visually pleasant and consistent with other compilers", + "pr": "TODO" + } + ] + }, { "version": "1.1.16", "changes": [ diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index 0ad620b1f..86167a603 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -44,7 +44,9 @@ "devDependencies": { "@0x/dev-utils": "^1.0.21", "@0x/tslint-config": "^2.0.0", + "@types/chokidar": "^1.7.5", "@types/mkdirp": "^0.5.2", + "@types/pluralize": "^0.0.29", "@types/require-from-string": "^1.2.0", "@types/semver": "^5.5.0", "chai": "^4.0.1", @@ -74,10 +76,12 @@ "@0x/web3-wrapper": "^3.2.1", "@types/yargs": "^11.0.0", "chalk": "^2.3.0", + "chokidar": "^2.0.4", "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", "mkdirp": "^0.5.1", + "pluralize": "^7.0.0", "require-from-string": "^2.0.1", "semver": "5.5.0", "solc": "^0.4.23", diff --git a/packages/sol-compiler/src/cli.ts b/packages/sol-compiler/src/cli.ts index 0a9db6e05..18cc68aaf 100644 --- a/packages/sol-compiler/src/cli.ts +++ b/packages/sol-compiler/src/cli.ts @@ -25,6 +25,10 @@ const SEPARATOR = ','; type: 'string', description: 'comma separated list of contracts to compile', }) + .option('watch', { + alias: 'w', + default: false, + }) .help().argv; const contracts = _.isUndefined(argv.contracts) ? undefined @@ -37,7 +41,11 @@ const SEPARATOR = ','; contracts, }; const compiler = new Compiler(opts); - await compiler.compileAsync(); + if (argv.watch) { + await compiler.watchAsync(); + } else { + await compiler.compileAsync(); + } })().catch(err => { logUtils.log(err); process.exit(1); diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 45cbf527b..17a1ce563 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -6,13 +6,17 @@ import { NPMResolver, RelativeFSResolver, Resolver, + SpyResolver, URLResolver, } from '@0x/sol-resolver'; import { logUtils } from '@0x/utils'; +import chalk from 'chalk'; +import * as chokidar from 'chokidar'; import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types'; import * as fs from 'fs'; import * as _ from 'lodash'; import * as path from 'path'; +import * as pluralize from 'pluralize'; import * as semver from 'semver'; import solc = require('solc'); @@ -30,6 +34,7 @@ import { } from './utils/compiler'; import { constants } from './utils/constants'; import { fsWrapper } from './utils/fs_wrapper'; +import { CompilationError } from './utils/types'; import { utils } from './utils/utils'; type TYPE_ALL_FILES_IDENTIFIER = '*'; @@ -129,6 +134,43 @@ export class Compiler { const promisedOutputs = this._compileContractsAsync(this._getContractNamesToCompile(), false); return promisedOutputs; } + public async watchAsync(): Promise { + console.clear(); // tslint:disable-line:no-console + logWithTime('Starting compilation in watch mode...'); + const watcher = chokidar.watch('^$', { ignored: /(^|[\/\\])\../ }); + const onFileChangedAsync = async () => { + watcher.unwatch('*'); // Stop watching + try { + await this.compileAsync(); + logWithTime('Found 0 errors. Watching for file changes.'); + } catch (err) { + if (err.typeName === 'CompilationError') { + logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); + } else { + logWithTime('Found errors. Watching for file changes.'); + } + } + + const pathsToWatch = this._getPathsToWatch(); + watcher.add(pathsToWatch); + }; + await onFileChangedAsync(); + watcher.on('change', (changedFilePath: string) => { + console.clear(); // tslint:disable-line:no-console + logWithTime('File change detected. Starting incremental compilation...'); + onFileChangedAsync(); + }); + } + private _getPathsToWatch(): string[] { + const contractNames = this._getContractNamesToCompile(); + const spyResolver = new SpyResolver(this._resolver); + for (const contractName of contractNames) { + const contractSource = spyResolver.resolve(contractName); + getSourceTreeHash(spyResolver, contractSource.path); + } + const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath)); + return pathsToWatch; + } private _getContractNamesToCompile(): string[] { let contractNamesToCompile; if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) { @@ -298,3 +340,7 @@ export class Compiler { logUtils.warn(`${contractName} artifact saved!`); } } + +function logWithTime(arg: string): void { + logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`); +} diff --git a/packages/sol-compiler/src/utils/compiler.ts b/packages/sol-compiler/src/utils/compiler.ts index 034f72f8d..486d8bedd 100644 --- a/packages/sol-compiler/src/utils/compiler.ts +++ b/packages/sol-compiler/src/utils/compiler.ts @@ -12,6 +12,7 @@ import { binPaths } from '../solc/bin_paths'; import { constants } from './constants'; import { fsWrapper } from './fs_wrapper'; +import { CompilationError } from './types'; /** * Gets contract data on network or returns if an artifact does not exist. @@ -147,13 +148,13 @@ function printCompilationErrorsAndWarnings(solcErrors: solc.SolcError[]): void { if (!_.isEmpty(errors)) { errors.forEach(error => { const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message); - logUtils.warn(chalk.red(normalizedErrMsg)); + logUtils.log(chalk.red('error'), normalizedErrMsg); }); - throw new Error('Compilation errors encountered'); + throw new CompilationError(errors.length); } else { warnings.forEach(warning => { const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message); - logUtils.warn(chalk.yellow(normalizedWarningMsg)); + logUtils.log(chalk.yellow('warning'), normalizedWarningMsg); }); } } diff --git a/packages/sol-compiler/src/utils/types.ts b/packages/sol-compiler/src/utils/types.ts index b211cfcbc..64328899d 100644 --- a/packages/sol-compiler/src/utils/types.ts +++ b/packages/sol-compiler/src/utils/types.ts @@ -29,3 +29,12 @@ export interface Token { } export type DoneCallback = (err?: Error) => void; + +export class CompilationError extends Error { + public errorsCount: number; + public typeName = 'CompilationError'; + constructor(errorsCount: number) { + super('Compilation errors encountered'); + this.errorsCount = errorsCount; + } +} diff --git a/packages/sol-compiler/test/compiler_utils_test.ts b/packages/sol-compiler/test/compiler_utils_test.ts index 4fe7b994e..b8c18110c 100644 --- a/packages/sol-compiler/test/compiler_utils_test.ts +++ b/packages/sol-compiler/test/compiler_utils_test.ts @@ -52,7 +52,7 @@ describe('Compiler utils', () => { const source = await fsWrapper.readFileAsync(path, { encoding: 'utf8', }); - const dependencies = parseDependencies({ source, path }); + const dependencies = parseDependencies({ source, path, absolutePath: path }); const expectedDependencies = [ 'zeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'packages/sol-compiler/lib/test/fixtures/contracts/TokenTransferProxy.sol', @@ -68,7 +68,7 @@ describe('Compiler utils', () => { const source = await fsWrapper.readFileAsync(path, { encoding: 'utf8', }); - expect(parseDependencies({ source, path })).to.be.deep.equal([ + expect(parseDependencies({ source, path, absolutePath: path })).to.be.deep.equal([ 'zeppelin-solidity/contracts/ownership/Ownable.sol', 'zeppelin-solidity/contracts/token/ERC20/ERC20.sol', ]); @@ -77,7 +77,7 @@ describe('Compiler utils', () => { it.skip('correctly parses commented out dependencies', async () => { const path = ''; const source = `// import "./TokenTransferProxy.sol";`; - expect(parseDependencies({ path, source })).to.be.deep.equal([]); + expect(parseDependencies({ path, source, absolutePath: path })).to.be.deep.equal([]); }); }); }); -- cgit v1.2.3