vasku
Version:
TVM-Solidity contract development framework
272 lines (271 loc) • 8.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readMetadata = exports.readContracts = exports.compile = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const cache_1 = require("./cache");
const glob_1 = require("glob");
const make_1 = require("./make");
const artifacts_1 = require("./artifacts");
const generateIndex_1 = require("./generators/generateIndex");
async function compile(config, all = false) {
const cache = all ? {} : (0, cache_1.readCache)(config);
const contracts = readContracts(config.paths);
const metadata = readMetadata(config.paths, cache, contracts);
const newOrChangedContracts = getNewOrChangedContracts(cache, metadata);
const importers = reverseImports(metadata);
const candidates = getCandidates(importers, newOrChangedContracts);
const includes = readIncludes(config);
const excludes = readExcludes(config);
const sources = readSources(includes, excludes);
const contractsToCompile = getContractToCompile(config.paths, sources, candidates);
if (contractsToCompile.length === 0)
return;
await (0, make_1.make)(config, contractsToCompile);
(0, generateIndex_1.generateIndex)(config.paths, sources);
(0, artifacts_1.removeOldArtifacts)(config.paths, sources);
(0, cache_1.writeCache)(config, metadata);
}
exports.compile = compile;
/**
* Read all files from contracts directory
* @param config
* @return
* ['A.tsol', 'B.tsol', 'x/C.tsol']
*/
function readContracts(config) {
const directory = path_1.default.resolve(process.cwd(), config.contracts);
return (0, glob_1.globSync)(path_1.default.resolve(directory, '**'), { nodir: true })
.map(value => path_1.default.relative(directory, value));
}
exports.readContracts = readContracts;
/**
* If the contract has not changed, read the metadata from the cache. If changed, read from file
* @param config
* @param cache
* {
* 'A.tsol': {
* modificationTime: 1666666666,
* imports: ['IA.tsol']
* }
* }
* @param contracts
* ['A.tsol', 'B.tsol']
* @return
* {
* 'A.tsol': {
* modificationTime: 1666666666,
* imports: ['IA.tsol']
* },
* 'B.tsol': {
* modificationTime: 1666666666,
* imports: ['IB.tsol']
* }
* }
*/
function readMetadata(config, cache, contracts) {
return contracts.reduce((meta, contract) => {
const file = path_1.default.resolve(process.cwd(), config.contracts, contract);
const modificationTime = fs_extra_1.default.statSync(file).mtime.getTime();
const cacheContract = cache[contract];
meta[contract] = (cacheContract !== undefined && cacheContract.modificationTime === modificationTime)
? cacheContract
: {
modificationTime,
imports: readImports(config, file)
};
return meta;
}, {});
}
exports.readMetadata = readMetadata;
/**
* Read imports from contract file
* @param config
* @param file
* 'A.tsol'
* @return
* ['B.tsol', 'IA.tsol']
*/
function readImports(config, file) {
const result = [];
const relativeDirectory = path_1.default.relative(path_1.default.resolve(process.cwd(), config.contracts), path_1.default.dirname(file));
const content = fs_extra_1.default.readFileSync(file, { encoding: 'utf8' });
const regexp = /(?<=import ").+(?=";)|(?<=import ').+(?=';)/g;
const maths = content.matchAll(regexp);
for (const [importRelative] of maths)
result.push(path_1.default.relative(relativeDirectory, importRelative));
return result;
}
/**
* Returns contracts that are not in the cache, or are in the cache and the modification time has been changed
* @param cache
* {
* 'A.tsol': {
* modificationTime: 1666666666,
* imports: ['IA.tsol']
* },
* 'B.tsol': {
* modificationTime: 1666666666,
* imports: ['IA.tsol']
* }
* }
* @param metadata
* {
* 'A.tsol': {
* modificationTime: 1666666666,
* imports: ['IA.tsol']
* },
* 'B.tsol': {
* modificationTime: 1666666666,
* imports: ['IB.tsol']
* }
* }
* @return
* ['B.tsol']
*/
function getNewOrChangedContracts(cache, metadata) {
const result = [];
for (const contract in metadata) {
const cacheContract = cache[contract];
const metaContract = metadata[contract];
if (cacheContract === undefined || cacheContract.modificationTime !== metaContract.modificationTime)
result.push(contract);
}
return result;
}
/**
* Return map (contract) => (contracts that depend on it)
* @param metadata
* {
* 'A.tsol': {
* modificationTime: 1666666666,
* imports: ['B.tsol', 'C.tsol']
* },
* 'B.tsol': {
* modificationTime: 1666666666,
* imports: []
* },
* 'C.tsol': {
* modificationTime: 1666666666,
* imports: ['D.tsol']
* },
* 'D.tsol': {
* modificationTime: 1666666666,
* imports: []
* }
* }
* @return
* {
* 'B.tsol': ['A.tsol'],
* 'C.tsol': ['A.tsol'],
* 'D.tsol': ['C.tsol']
* }
*/
function reverseImports(metadata) {
const result = {};
for (const contract in metadata) {
metadata[contract].imports.forEach(value => {
if (result[value] === undefined)
result[value] = [contract];
else
result[value].push(contract);
});
}
return result;
}
/**
* Return a set of candidate contracts to compilation
* @param importers
* {
* 'B.tsol': ['A.tsol'],
* 'C.tsol': ['A.tsol'],
* 'D.tsol': ['C.tsol']
* }
* @param contracts
* ['B.tsol']
* @return
* Set {'A.tsol', 'B.tsol'}
*/
function getCandidates(importers, contracts) {
const set = new Set();
let list = [...contracts];
for (let i = 0; i < list.length; ++i) {
const contract = list[i];
if (set.has(contract))
continue;
set.add(contract);
const contractImporters = importers[contract];
if (contractImporters !== undefined)
list = list.concat(contractImporters);
}
return set;
}
/**
* Return a set of contracts that included to compilation
* @param config
* @return
* Set {'A.tsol', 'B.tsol'}
*/
function readIncludes(config) {
return readFiles(config.paths, config.compile.include);
}
/**
* Return a set of contracts that excluded from compilation
* @param config
* @return
* Set {'A.tsol', 'B.tsol'}
*/
function readExcludes(config) {
return readFiles(config.paths, config.compile.exclude);
}
/**
* Return a set of contracts using glob
* @param config
* @param globs
* ['*.tsol', '*.sol']
* @return
* Set {'A.tsol', 'B.tsol'}
*/
function readFiles(config, globs) {
const directory = path_1.default.resolve(process.cwd(), config.contracts);
return globs.reduce((set, value) => (0, glob_1.globSync)(path_1.default.resolve(directory, value), { nodir: true })
.map(value => path_1.default.relative(directory, value))
.reduce((set, value) => set.add(value), set), new Set());
}
/**
* Exclude contracts from include
* @param includes
* Set {'A.tsol', 'B.tsol'}
* @param excludes
* Set {'A.tsol'}
* @return
* Set {'B.tsol'}
*/
function readSources(includes, excludes) {
return [...includes].reduce((set, value) => excludes.has(value) ? set : set.add(value), new Set());
}
/**
* Return contract array to compilation
* @param config
* @param sources
* Set(3) {'Counter.tsol', 'd/B.tsol', 'd/A.tsol'}
* @param candidates
* Set(4) {
* 'Counter.tsol',
* 'interface/ICounter.tsol',
* 'd/B.tsol',
* 'd/A.tsol'
* }
* @return
* ['Counter.tsol', 'd/B.tsol', 'd/A.tsol']
*/
function getContractToCompile(config, sources, candidates) {
return [...sources].reduce((result, value) => {
if (candidates.has(value) || !(0, artifacts_1.buildsArtifactsExists)(config, value))
result.push(value);
return result;
}, []);
}