Compiler.js
1 const async = require('async'); 2 const shelljs = require('shelljs'); 3 const fs = require('fs'); 4 const path = require('path'); 5 6 function compileSolcContract(logger, compileSettings, allowedDirectories, callback) { 7 const command = `solc --standard-json --allow-paths ${allowedDirectories.join(',')}`; 8 9 shelljs.ShellString(JSON.stringify(compileSettings)).exec(command, {silent: true}, (code, stdout, stderr) => { 10 if (stderr) { 11 logger.warn(stderr); 12 } 13 14 if (code !== 0) { 15 return callback(`solc exited with error code ${code}`); 16 } 17 18 if (!stdout) { 19 return callback('solc execution returned nothing'); 20 } 21 22 callback(null, stdout.replace(/\n/g, '')); 23 }); 24 } 25 26 function getSolcVersion(logger, callback) { 27 shelljs.exec('solc --version', {silent: true}, (code, stdout, stderr) => { 28 if (stderr) { 29 logger.warn(stderr); 30 } 31 32 if (code !== 0) { 33 return callback(`solc exited with error code ${code}`); 34 } 35 36 if (!stdout) { 37 return callback('solc execution returned nothing'); 38 } 39 40 const result = stdout.match(/(\d+.\d+.\d+)/); 41 callback(null, result[1]); 42 }); 43 } 44 45 function compileSolc(embark, contractFiles, contractDirectories, options, callback) { 46 if (!contractFiles || !contractFiles.length) { 47 return callback(); 48 } 49 50 const logger = embark.logger; 51 const outputBinary = embark.pluginConfig.outputBinary; 52 const outputDir = embark.config.buildDir + embark.config.contractDirectories[0]; 53 const solcConfig = embark.config.embarkConfig.options.solc; 54 55 let allowedDirectories = []; 56 const remappings = []; 57 const compilationSettings = { 58 language: 'Solidity', 59 sources: {}, 60 settings: { 61 optimizer: { 62 enabled: solcConfig['optimize'], 63 runs: solcConfig['optimize-runs'] 64 }, 65 remappings, 66 outputSelection: { 67 '*': { 68 '': ['ast'], 69 '*': [ 70 'abi', 71 'devdoc', 72 'evm.bytecode', 73 'evm.deployedBytecode', 74 'evm.gasEstimates', 75 'evm.legacyAssembly', 76 'evm.methodIdentifiers', 77 'metadata', 78 'userdoc' 79 ] 80 } 81 } 82 } 83 }; 84 85 async.waterfall([ 86 function checkSolc(next) { 87 const solc = shelljs.which('solc'); 88 if (!solc) { 89 logger.error('solc is not installed on your machine'); 90 logger.info('You can install it by following the instructions on: http://solidity.readthedocs.io/en/latest/installing-solidity.html'); 91 return next('Compiler not installed'); 92 } 93 logger.info("compiling solidity contracts with command line solc..."); 94 next(); 95 }, 96 97 function getContentAndRemappings(next) { 98 async.each(contractFiles, (file, eachCb) => { 99 file.prepareForCompilation(options.isCoverage).then((content) => { 100 // add contract directory and all it's recusrive import direcotries to allowed directories 101 let dir = path.dirname(file.path); 102 if (!allowedDirectories.includes(dir)) allowedDirectories.push(dir); 103 104 file.importRemappings.forEach((importRemapping) => { 105 dir = path.dirname(importRemapping.target); 106 if (!allowedDirectories.includes(dir)) allowedDirectories.push(dir); 107 108 const remapping = `${importRemapping.prefix}=${importRemapping.target}`; 109 if (!remappings.includes(remapping)) { 110 remappings.push(remapping); 111 } 112 }); 113 114 // TODO change this to Embark's utils function once embark-solc is in the mono-repo 115 compilationSettings.sources[file.path.replace(/\\/g, '/')] = { 116 content: content.replace(/\r\n/g, '\n') 117 }; 118 119 eachCb(); 120 }).catch(eachCb); 121 }, next); 122 }, 123 124 function compile(next) { 125 compileSolcContract(logger, compilationSettings, allowedDirectories, (err, compileString) => { 126 if (err) { 127 return next(err); 128 } 129 let json; 130 try { 131 json = JSON.parse(compileString); 132 } catch (e) { 133 logger.error(e.message || e); 134 return callback(`Compiling returned an unreadable result`); 135 } 136 137 embark.events.emit('contracts:compiled:solc', json); 138 139 const contracts = json.contracts; 140 141 // Check for errors 142 if (json.errors) { 143 let isError = false; 144 json.errors.forEach(error => { 145 if (error.severity === 'error') { 146 isError = true; 147 logger.error(error.formattedMessage); 148 } else { 149 logger.warn(error.formattedMessage); 150 } 151 logger.debug(error.message); // Print more error information in debug 152 }); 153 if (isError) { 154 return next(`Error while compiling`); 155 } 156 } 157 next(null, contracts); 158 }); 159 }, 160 161 function populateCompiledObject(contracts, next) { 162 const compiledObject = {}; 163 for (let contractFile in contracts) { 164 for (let contractName in contracts[contractFile]) { 165 let contract = contracts[contractFile][contractName]; 166 let filename = contractFile; 167 for (let directory of contractDirectories) { 168 let match = new RegExp("^" + directory); 169 filename = filename.replace(match, ''); 170 } 171 172 let className = contractName; 173 const contractFileMatch = className.match(/.sol:(.*)/); 174 if (contractFileMatch) { 175 className = contractFileMatch[1]; 176 } 177 178 compiledObject[className] = {}; 179 compiledObject[className].code = contract.evm.bytecode.object; 180 compiledObject[className].linkReferences = contract.evm.bytecode.linkReferences; 181 compiledObject[className].runtimeBytecode = contract.evm.deployedBytecode.object; 182 compiledObject[className].realRuntimeBytecode = contract.evm.deployedBytecode.object.slice(0, -68); 183 compiledObject[className].swarmHash = contract.evm.deployedBytecode.object.slice(-68).slice(0, 64); 184 compiledObject[className].gasEstimates = contract.evm.gasEstimates; 185 compiledObject[className].functionHashes = contract.evm.methodIdentifiers; 186 compiledObject[className].abiDefinition = contract.abi; 187 compiledObject[className].userdoc = contract.userdoc; 188 compiledObject[className].filename = filename; 189 const normalized = path.normalize(filename); 190 const origContract = contractFiles.find(contractFile => normalized.includes(path.normalize(contractFile.originalPath))); 191 if (origContract) { 192 compiledObject[className].originalFilename = path.normalize(origContract.originalPath); 193 } 194 } 195 } 196 197 next(null, compiledObject); 198 } 199 200 ], (err, compiledObject) => { 201 callback(err, compiledObject); 202 203 if (outputBinary) { 204 embark.events.once("outputDone", () => { 205 async.eachOf(compiledObject, (contract, className, eachCb) => { 206 fs.writeFile(path.join(outputDir, className + ".bin"), compiledObject[className].code, eachCb); 207 }, (err) => { 208 if (err) { 209 logger.error("Error writing binary file", err.message || err); 210 } 211 }); 212 }); 213 } 214 }); 215 } 216 217 module.exports = { 218 compileSolc, 219 compileSolcContract, 220 getSolcVersion 221 };