| #!/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; |
| } |