diff --git a/cli.js b/cli.js index c2da212..cf0e383 100755 --- a/cli.js +++ b/cli.js @@ -30,6 +30,8 @@ const compiler = require("./src/compiler"); const version = require("./package").version; +const parsePathMap = require("./utils/parse_path_map"); + const argv = require("yargs") .version(version) .usage("circom [input source circuit file] -r [output r1cs file] -c [output c file] -w [output wasm file] -t [output wat file] -s [output sym file]") @@ -43,6 +45,11 @@ const argv = require("yargs") .alias("n", "newThreadTemplates") .help("h") .alias("h", "help") + .option("path-map", { + alias: "a", + type: "array", + description: "Allow a given path for imports. A list of paths can be supplied by separating them with a comma." + }) .option("verbose", { alias: "v", type: "boolean", @@ -80,10 +87,12 @@ async function run() { const r1csName = typeof(argv.r1cs) === "string" ? argv.r1cs : fileName + ".r1cs"; const symName = typeof(argv.sym) === "string" ? argv.sym : fileName + ".sym"; + const options = {}; options.reduceConstraints = !argv.fast; options.verbose = argv.verbose || false; options.sanityCheck = argv.sanitycheck; + options.pathMap = {}; if (argv.csource) { options.cSourceFileName = cSourceName; @@ -115,6 +124,9 @@ async function run() { options.prime = Scalar.fromString(argv.prime); } + if (argv.pathMap) { + options.pathMap = parsePathMap(argv.pathMap); + } await compiler(fullFileName, options); diff --git a/src/compiler.js b/src/compiler.js index 5618ac6..9649f2b 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -66,6 +66,7 @@ async function compile(srcFile, options) { ctx.verbose= options.verbose || false; ctx.mainComponent = options.mainComponent || "main"; ctx.newThreadTemplates = options.newThreadTemplates; + ctx.pathMap = options.pathMap; measures.constructionPhase = -performance.now(); constructionPhase(ctx, srcFile); diff --git a/src/construction_phase.js b/src/construction_phase.js index af590e9..32dac5e 100644 --- a/src/construction_phase.js +++ b/src/construction_phase.js @@ -1067,7 +1067,19 @@ function createRefs(ctx, ast, scope) { } else if (ast.type == "FUNCTIONDEF") { ast.refId = define(ast.name, {t: "F"}); } else if (ast.type == "INCLUDE") { - const incFileName = path.resolve(ctx.filePath, ast.file); + const match = ast.file.match(/@(.+)\//); + let incFileName; + if (match) { + const key = match[1]; + if (ctx.pathMap[key] == undefined) { + return ctx.throwError(ast, "Included path-mapped file not found:" + ast.file); + } + const val = ctx.pathMap[key]; + incFileName = path.resolve(ast.file.replace("@" + key, val)); + } else { + incFileName = path.resolve(ctx.filePath, ast.file); + } + const incFilePath = path.dirname(incFileName); ctx.includedFiles = ctx.includedFiles || []; diff --git a/test/circuits/pathMap.circom b/test/circuits/pathMap.circom new file mode 100644 index 0000000..7734418 --- /dev/null +++ b/test/circuits/pathMap.circom @@ -0,0 +1,10 @@ +include "@customlib/pathMapDep.circom"; + +template Foo() { + signal input in; + component bar = Bar(); + + bar.in <== in; +} + +component main = Foo(); diff --git a/test/circuits/pathMapDep.circom b/test/circuits/pathMapDep.circom new file mode 100644 index 0000000..0e5fa70 --- /dev/null +++ b/test/circuits/pathMapDep.circom @@ -0,0 +1,6 @@ +template Bar() { + signal input in; + signal output out; + + out <== in; +} diff --git a/test/compiler.js b/test/compiler.js new file mode 100644 index 0000000..5c0ab45 --- /dev/null +++ b/test/compiler.js @@ -0,0 +1,21 @@ +const path = require("path"); +const fs = require("fs"); +const shelljs = require("shelljs"); +const expect = require("chai").expect; + +describe("compiler tests", function () { + this.timeout(100000); + + it('should import a path-mapped dependency', function() { + const r1csFile = "test." + Date.now().toString() + ".r1cs"; + if (fs.existsSync(r1csFile)) { + console.error("Please delete " + r1csFile + " before running this test"); + return; + } + const cliPath = path.join(__dirname, "..", "cli.js"); + const cmd = "node " + cliPath + " ./test/circuits/pathMap.circom -a @customlib=./test/circuits/ -r " + r1csFile; + shelljs.exec(cmd); + expect(fs.existsSync(r1csFile)).to.equal(true); + fs.unlinkSync(r1csFile); + }); +}); diff --git a/utils/parse_path_map.js b/utils/parse_path_map.js new file mode 100644 index 0000000..895d86f --- /dev/null +++ b/utils/parse_path_map.js @@ -0,0 +1,23 @@ +module.exports = parsePathMap; + +function parsePathMap(pathMap) { + const result = {}; + const regex = /@(.+)=(.+)/ + for (let i = 0; i < pathMap.length; i ++) { + const match = pathMap[i].match(regex); + if (!match) { + console.log("Invalid --path-map / -a value. Its format should be @alias=path."); + process.exit(1); + } + const alias = match[1]; + const path = match[2]; + + if (Object.keys(result).indexOf(alias) !== -1) { + console.log("Duplicate --path-map / -a value."); + process.exit(1); + } + + result[alias] = path; + } + return result; +}