blob: 9692b98db355a5ec451317ad4cfa3693811d93e7 [file]
#!/usr/bin/env node
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
function parseArguments(args) {
const options = {
metadata: path.resolve(__dirname, "../target/codegraph-index-metadata.tsv"),
root: path.resolve(__dirname, "../projects"),
output: path.resolve(__dirname, "../target/codegraphs")
};
for (let index = 0; index < args.length; index++) {
const argument = args[index];
if (argument === "--metadata" || argument === "--root" || argument === "--output") {
options[argument.substring(2)] = path.resolve(args[++index]);
} else {
throw new Error(`Unknown argument: ${argument}`);
}
}
return options;
}
function readMetadata(file) {
const modules = new Map();
const lines = fs.readFileSync(file, "utf8").trim().split("\n");
lines.forEach((line, lineIndex) => {
const fields = line.split("\t");
const kind = fields.shift();
const moduleName = fields.shift();
if (kind === "MODULE") {
modules.set(moduleName, {
name: moduleName,
coordinates: {
groupId: fields[0], artifactId: fields[1], version: fields[2], type: "swc"
},
dependencies: { js: [], swf: [] },
namespaces: []
});
return;
}
const module = modules.get(moduleName);
if (!module) {
throw new Error(`${file}:${lineIndex + 1}: ${kind} appears before MODULE ${moduleName}`);
}
if (kind === "DEPENDENCY") {
const classifier = fields[0];
if (classifier === "js" || classifier === "swf") {
module.dependencies[classifier].push({
groupId: fields[1], artifactId: fields[2], version: fields[3],
type: "swc", classifier
});
}
} else if (kind === "NAMESPACE") {
module.namespaces.push({ type: fields[0], uri: fields[1], manifest: fields[2], tags: [] });
} else if (kind === "TAG") {
const namespace = module.namespaces.find(candidate =>
candidate.type === fields[0] && candidate.uri === fields[1]);
if (!namespace) {
throw new Error(`${file}:${lineIndex + 1}: TAG has no matching namespace`);
}
namespace.tags.push({
name: fields[2], class: fields[3], lookupOnly: fields[4] === "true"
});
} else {
throw new Error(`${file}:${lineIndex + 1}: unknown record ${kind}`);
}
});
return Array.from(modules.values());
}
function graphPath(root, module, target) {
return path.join(root, module, "target", "codegraph", target, `${module}.json`);
}
function namespaceSupportsTarget(namespace, target) {
return !namespace.type || namespace.type === (target === "js" ? "js" : "as");
}
function writeJson(file, value) {
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
}
function main() {
const options = parseArguments(process.argv.slice(2));
const modules = readMetadata(options.metadata);
const versions = new Set(modules.map(module => module.coordinates.version));
if (versions.size !== 1 || versions.has("")) {
throw new Error(`Expected one non-empty framework version, found: ${Array.from(versions).join(", ")}`);
}
const version = Array.from(versions)[0];
const versionRoot = path.join(options.output, version);
fs.rmSync(options.output, { recursive: true, force: true });
fs.mkdirSync(versionRoot, { recursive: true });
const symbolsByTarget = { js: new Map(), swf: new Map() };
const indexModules = modules.map(module => {
const targets = {};
["js", "swf"].forEach(target => {
const source = graphPath(options.root, module.name, target);
const contents = fs.readFileSync(source);
const graph = JSON.parse(contents.toString("utf8"));
const relativePath = path.join(version, module.name, target, `${module.name}.json`);
const destination = path.join(options.output, relativePath);
fs.mkdirSync(path.dirname(destination), { recursive: true });
fs.copyFileSync(source, destination);
graph.symbols.forEach(symbol => symbolsByTarget[target].set(symbol.qualifiedName, symbol.id));
targets[target] = {
path: relativePath.split(path.sep).join("/"),
sha256: crypto.createHash("sha256").update(contents).digest("hex"),
symbolCount: graph.symbols.length,
classCount: graph.symbols.filter(symbol => symbol.kind === "class").length
};
});
return {
name: module.name,
coordinates: module.coordinates,
dependencies: module.dependencies,
targets
};
});
const mxmlTargets = {};
let unavailableTags = 0;
["js", "swf"].forEach(target => {
mxmlTargets[target] = [];
modules.forEach(module => module.namespaces
.filter(namespace => namespaceSupportsTarget(namespace, target))
.forEach(namespace => {
const tags = namespace.tags.map(tag => {
const id = symbolsByTarget[target].get(tag.class);
if (!id) {
unavailableTags++;
}
const mapping = {
name: tag.name,
class: tag.class,
lookupOnly: tag.lookupOnly,
available: Boolean(id)
};
if (id) {
mapping.id = id;
}
return mapping;
});
mxmlTargets[target].push({
module: module.name,
uri: namespace.uri,
manifest: namespace.manifest,
tags
});
}));
});
writeJson(path.join(options.output, "index.json"), {
schemaVersion: "1.0", version, modules: indexModules
});
writeJson(path.join(options.output, "mxml.json"), {
schemaVersion: "1.0", version, targets: mxmlTargets
});
console.log(`Generated codegraph index for ${modules.length} module(s) at ${options.output}.`);
if (unavailableTags > 0) {
console.log(`Preserved ${unavailableTags} MXML tag mapping(s) unavailable in their target graph.`);
}
}
try {
main();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}