/ lib / Compiler.js
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  };