This is an automated email from the ASF dual-hosted git repository. Harbs pushed a commit to branch codegraph in repository https://gitbox.apache.org/repos/asf/royale-asjs.git
commit cc94d840b773a817559dd56bf9da4f82afee4495 Author: Harbs <[email protected]> AuthorDate: Sat Aug 1 22:09:03 2026 +0300 Add code graph generation and validation targets, scripts, and configuration --- frameworks/build.xml | 24 +++ frameworks/codegraph-index-metadata.xsl | 89 ++++++++++ frameworks/codegraphs.xml | 167 ++++++++++++++++++ frameworks/codegraphs.xsl | 113 ++++++++++++ .../src/main/config/compile-js-config.xml | 1 + frameworks/projects/pom.xml | 23 +++ frameworks/scripts/generate-codegraph-index.js | 189 +++++++++++++++++++++ frameworks/scripts/validate-codegraphs.js | 155 +++++++++++++++++ 8 files changed, 761 insertions(+) diff --git a/frameworks/build.xml b/frameworks/build.xml index bd3c46c027..133acaa08a 100644 --- a/frameworks/build.xml +++ b/frameworks/build.xml @@ -160,6 +160,30 @@ <!-- <antcall target="ExternsJS"/> --> </target> + <target name="codegraphs" description="Generates public API code graphs for all framework modules"> + <ant antfile="${basedir}/codegraphs.xml" target="codegraphs" inheritAll="true"/> + </target> + + <target name="codegraph" description="Generates public API code graphs for one framework module"> + <ant antfile="${basedir}/codegraphs.xml" target="codegraph" inheritAll="true"/> + </target> + + <target name="validate-codegraphs" description="Validates generated public API code graphs"> + <ant antfile="${basedir}/codegraphs.xml" target="validate-codegraphs" inheritAll="true"/> + </target> + + <target name="validate-codegraph" description="Generates and validates one framework module code graph"> + <ant antfile="${basedir}/codegraphs.xml" target="validate-codegraph" inheritAll="true"/> + </target> + + <target name="validate-codegraph-determinism" description="Regenerates and byte-compares one framework module code graph"> + <ant antfile="${basedir}/codegraphs.xml" target="validate-codegraph-determinism" inheritAll="true"/> + </target> + + <target name="codegraph-index" description="Stages code graph shards and generates aggregate indexes"> + <ant antfile="${basedir}/codegraphs.xml" target="codegraph-index" inheritAll="true"/> + </target> + <target name="test" description="Tests all SWCs"> <antcall target="LanguageTest"/> <antcall target="CoreTest"/> diff --git a/frameworks/codegraph-index-metadata.xsl b/frameworks/codegraph-index-metadata.xsl new file mode 100644 index 0000000000..8937b102bc --- /dev/null +++ b/frameworks/codegraph-index-metadata.xsl @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:m="http://maven.apache.org/POM/4.0.0" + exclude-result-prefixes="m"> + + <xsl:output method="text" encoding="UTF-8"/> + + <xsl:template match="/m:project"> + <xsl:for-each select="m:modules/m:module"> + <xsl:variable name="module" select="string(.)"/> + <xsl:variable name="project" select="document(concat($module, '/pom.xml'), /)/m:project"/> + <xsl:text>MODULE	</xsl:text> + <xsl:value-of select="$module"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$project/m:parent/m:groupId"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$project/m:artifactId"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$project/m:version"/> + <xsl:text> </xsl:text> + + <xsl:for-each select="$project/m:dependencies/m:dependency | $project/m:profiles/m:profile/m:dependencies/m:dependency"> + <xsl:text>DEPENDENCY	</xsl:text> + <xsl:value-of select="$module"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="m:classifier"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="m:groupId"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="m:artifactId"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="m:version"/> + <xsl:text> </xsl:text> + </xsl:for-each> + + <xsl:for-each select="$project/m:build/m:plugins/m:plugin[m:artifactId = 'royale-maven-plugin']/m:configuration/m:namespaces/m:namespace"> + <xsl:variable name="namespaceType" select="m:type"/> + <xsl:variable name="uri" select="m:uri"/> + <xsl:variable name="manifestPath" select="substring-after(m:manifest, '${project.basedir}/')"/> + <xsl:text>NAMESPACE	</xsl:text> + <xsl:value-of select="$module"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$namespaceType"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$uri"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$manifestPath"/> + <xsl:text> </xsl:text> + <xsl:for-each select="document($manifestPath, $project)/*/component"> + <xsl:text>TAG	</xsl:text> + <xsl:value-of select="$module"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$namespaceType"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="$uri"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="@class"/> + <xsl:text>	</xsl:text> + <xsl:value-of select="@lookupOnly"/> + <xsl:text> </xsl:text> + </xsl:for-each> + </xsl:for-each> + </xsl:for-each> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file diff --git a/frameworks/codegraphs.xml b/frameworks/codegraphs.xml new file mode 100644 index 0000000000..e8ae15dac2 --- /dev/null +++ b/frameworks/codegraphs.xml @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project name="codegraphs" default="codegraphs" basedir="."> + + <property name="ROYALE_HOME" location=".."/> + <property file="${ROYALE_HOME}/env.properties"/> + <property environment="env"/> + <property file="${ROYALE_HOME}/local.properties"/> + <property file="${ROYALE_HOME}/build.properties"/> + + <condition property="ROYALE_COMPILER_HOME" value="${ROYALE_HOME}/js"> + <available file="${ROYALE_HOME}/js/lib/codegraph.jar"/> + </condition> + <condition property="ROYALE_COMPILER_HOME" value="${env.ROYALE_COMPILER_HOME}"> + <available file="${env.ROYALE_COMPILER_HOME}/lib/codegraph.jar"/> + </condition> + <condition property="AIR_HOME" value="${env.AIR_HOME}"> + <isset property="env.AIR_HOME"/> + </condition> + <property name="AIR_HOME" value="${ROYALE_HOME}"/> + + <macrodef name="codegraph-js"> + <attribute name="module"/> + <attribute name="config" default="js-config.xml"/> + <element name="extra-args" optional="true"/> + <sequential> + <echo message="Generating JS code graph for @{module}"/> + <mkdir dir="${basedir}/projects/@{module}/target/codegraph/js"/> + <java jar="${ROYALE_COMPILER_HOME}/lib/codegraph.jar" + dir="${basedir}/js/projects/@{module}JS" + fork="true" failonerror="true"> + <jvmarg value="-Xmx384m"/> + <jvmarg value="-Dsun.io.useCanonCaches=false"/> + <jvmarg value="-Droyalecompiler=${ROYALE_COMPILER_HOME}"/> + <jvmarg value="-Droyalelib=${ROYALE_HOME}/frameworks"/> + <arg value="-compiler.strict-xml=true"/> + <arg value="-output=${basedir}/projects/@{module}/target/codegraph/js/@{module}.json"/> + <arg value="-load-config=${basedir}/@{config}"/> + <arg value="-load-config+=${basedir}/js/projects/@{module}JS/src/main/config/compile-js-config.xml"/> + <arg value="-keep-asdoc=true"/> + <extra-args/> + </java> + </sequential> + </macrodef> + + <macrodef name="codegraph-swf"> + <attribute name="module"/> + <element name="extra-args" optional="true"/> + <sequential> + <echo message="Generating SWF code graph for @{module}"/> + <mkdir dir="${basedir}/projects/@{module}/target/codegraph/swf"/> + <java jar="${ROYALE_COMPILER_HOME}/lib/codegraph.jar" + dir="${basedir}/projects/@{module}" + fork="true" failonerror="true"> + <jvmarg value="-Xmx384m"/> + <jvmarg value="-Dsun.io.useCanonCaches=false"/> + <jvmarg value="-Droyalecompiler=${ROYALE_COMPILER_HOME}"/> + <jvmarg value="-Droyalelib=${ROYALE_HOME}/frameworks"/> + <arg value="+playerglobal.version=${playerglobal.version}"/> + <arg value="+env.AIR_HOME=${AIR_HOME}"/> + <arg value="-compiler.strict-xml=true"/> + <arg value="-output=${basedir}/projects/@{module}/target/codegraph/swf/@{module}.json"/> + <arg value="-load-config=${basedir}/projects/@{module}/src/main/config/compile-swf-config.xml"/> + <arg value="-keep-asdoc=true"/> + <extra-args/> + </java> + </sequential> + </macrodef> + + <target name="check"> + <fail message="ROYALE_COMPILER_HOME must point to a compiler with lib/codegraph.jar" + unless="ROYALE_COMPILER_HOME"/> + </target> + + <target name="generate-driver"> + <mkdir dir="${basedir}/target"/> + <xslt in="${basedir}/projects/pom.xml" + out="${basedir}/target/codegraph-modules.xml" + style="${basedir}/codegraphs.xsl"/> + </target> + + <target name="generate-index-metadata"> + <mkdir dir="${basedir}/target"/> + <xslt in="${basedir}/projects/pom.xml" + out="${basedir}/target/codegraph-index-metadata.tsv" + style="${basedir}/codegraph-index-metadata.xsl"/> + </target> + + <target name="codegraph-index" depends="validate-codegraphs,generate-index-metadata"> + <exec executable="node" failonerror="true"> + <arg value="${basedir}/scripts/generate-codegraph-index.js"/> + <arg value="--metadata"/> + <arg value="${basedir}/target/codegraph-index-metadata.tsv"/> + <arg value="--root"/> + <arg value="${basedir}/projects"/> + <arg value="--output"/> + <arg value="${basedir}/target/codegraphs"/> + </exec> + </target> + + <target name="codegraphs" depends="check,generate-driver"> + <ant antfile="${basedir}/target/codegraph-modules.xml" target="codegraphs" inheritAll="true"/> + </target> + + <target name="codegraph" depends="check,generate-driver"> + <fail message="Set codegraph.module to a framework module name, for example -Dcodegraph.module=Basic" + unless="codegraph.module"/> + <ant antfile="${basedir}/target/codegraph-modules.xml" + target="codegraph-${codegraph.module}" inheritAll="true"/> + </target> + + <target name="validate-codegraphs"> + <exec executable="node" failonerror="true"> + <arg value="${basedir}/scripts/validate-codegraphs.js"/> + <arg value="--root"/> + <arg value="${basedir}/projects"/> + </exec> + </target> + + <target name="validate-codegraph" depends="codegraph"> + <exec executable="node" failonerror="true"> + <arg value="${basedir}/scripts/validate-codegraphs.js"/> + <arg value="--root"/> + <arg value="${basedir}/projects"/> + <arg value="--module"/> + <arg value="${codegraph.module}"/> + </exec> + </target> + + <target name="validate-codegraph-determinism"> + <fail message="Set codegraph.module to a framework module name, for example -Dcodegraph.module=Basic" + unless="codegraph.module"/> + <delete dir="${basedir}/target/codegraph-baseline"/> + <copy todir="${basedir}/target/codegraph-baseline/${codegraph.module}"> + <fileset dir="${basedir}/projects/${codegraph.module}" includes="target/codegraph/**/*.json"/> + </copy> + <antcall target="codegraph"/> + <exec executable="node" failonerror="true"> + <arg value="${basedir}/scripts/validate-codegraphs.js"/> + <arg value="--root"/> + <arg value="${basedir}/projects"/> + <arg value="--module"/> + <arg value="${codegraph.module}"/> + <arg value="--compare-root"/> + <arg value="${basedir}/target/codegraph-baseline"/> + </exec> + <delete dir="${basedir}/target/codegraph-baseline"/> + </target> + +</project> \ No newline at end of file diff --git a/frameworks/codegraphs.xsl b/frameworks/codegraphs.xsl new file mode 100644 index 0000000000..7e2e2776a2 --- /dev/null +++ b/frameworks/codegraphs.xsl @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:m="http://maven.apache.org/POM/4.0.0" + exclude-result-prefixes="m"> + + <xsl:output method="xml" indent="yes"/> + + <xsl:template match="/"> + <project name="codegraph-modules" default="codegraphs" basedir=".."> + <import file="../codegraphs.xml"/> + <target name="codegraphs" depends="codegraphs-js,codegraphs-swf"/> + <target name="codegraphs-js"> + <xsl:attribute name="depends"> + <xsl:for-each select="m:project/m:modules/m:module"> + <xsl:if test="position() != 1">,</xsl:if> + <xsl:text>codegraph-</xsl:text><xsl:value-of select="."/><xsl:text>-js</xsl:text> + </xsl:for-each> + </xsl:attribute> + </target> + <target name="codegraphs-swf" if="env.AIR_HOME"> + <xsl:attribute name="depends"> + <xsl:for-each select="m:project/m:modules/m:module"> + <xsl:if test="position() != 1">,</xsl:if> + <xsl:text>codegraph-</xsl:text><xsl:value-of select="."/><xsl:text>-swf</xsl:text> + </xsl:for-each> + </xsl:attribute> + </target> + <xsl:for-each select="m:project/m:modules/m:module"> + <target name="codegraph-{.}" depends="codegraph-{.}-js,codegraph-{.}-swf"/> + <target name="codegraph-{.}-js"> + <xsl:choose> + <xsl:when test=". = 'Ace'"> + <codegraph-js module="{.}" config="ace-config.xml"/> + </xsl:when> + <xsl:when test=". = 'CreateJS'"> + <codegraph-js module="{.}" config="createjs-config.xml"/> + </xsl:when> + <xsl:when test=". = 'JQuery'"> + <codegraph-js module="{.}" config="jquery-config.xml"/> + </xsl:when> + <xsl:when test=". = 'MXRoyaleBase' or . = 'MXRoyale'"> + <codegraph-js module="{.}"> + <extra-args> + <arg value="-compiler.define+=ROYALE::DISPLAYOBJECT,IUIComponent"/> + <arg value="-compiler.define+=GOOG::DEBUG,goog.DEBUG"/> + </extra-args> + </codegraph-js> + </xsl:when> + <xsl:when test=". = 'SparkRoyale'"> + <codegraph-js module="{.}"> + <extra-args> + <arg value="-compiler.define+=GOOG::DEBUG,goog.DEBUG"/> + </extra-args> + </codegraph-js> + </xsl:when> + <xsl:otherwise> + <codegraph-js module="{.}"/> + </xsl:otherwise> + </xsl:choose> + </target> + <target name="codegraph-{.}-swf" if="env.AIR_HOME"> + <xsl:choose> + <xsl:when test=". = 'Ace'"> + <codegraph-swf module="{.}"> + <extra-args> + <arg value="-compiler.allow-subclass-overrides=true"/> + </extra-args> + </codegraph-swf> + </xsl:when> + <xsl:when test=". = 'MXRoyaleBase' or . = 'MXRoyale'"> + <codegraph-swf module="{.}"> + <extra-args> + <arg value="-compiler.define+=GOOG::DEBUG,true"/> + <arg value="-compiler.define+=ROYALE::DISPLAYOBJECT,DisplayObject"/> + </extra-args> + </codegraph-swf> + </xsl:when> + <xsl:when test=". = 'SparkRoyale'"> + <codegraph-swf module="{.}"> + <extra-args> + <arg value="-compiler.define+=GOOG::DEBUG,true"/> + </extra-args> + </codegraph-swf> + </xsl:when> + <xsl:otherwise> + <codegraph-swf module="{.}"/> + </xsl:otherwise> + </xsl:choose> + </target> + </xsl:for-each> + </project> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file diff --git a/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml b/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml index ab405d525c..c850783664 100644 --- a/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml +++ b/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml @@ -65,6 +65,7 @@ <path-element>../../../../../libs/EffectsJS.swc</path-element> <path-element>../../../../../libs/ExpressJS.swc</path-element> <path-element>../../../../../libs/GraphicsJS.swc</path-element> + <path-element>../../../../../libs/CollectionsJS.swc</path-element> <path-element>../../../../../libs/DragDropJS.swc</path-element> <path-element>../../../../../libs/HTMLJS.swc</path-element> <path-element>../../../../../libs/NetworkJS.swc</path-element> diff --git a/frameworks/projects/pom.xml b/frameworks/projects/pom.xml index 2091f83386..2b0f370b54 100644 --- a/frameworks/projects/pom.xml +++ b/frameworks/projects/pom.xml @@ -122,4 +122,27 @@ </dependency> </dependencies> + <profiles> + <profile> + <id>codegraphs</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.royale.compiler</groupId> + <artifactId>royale-maven-plugin</artifactId> + <executions> + <execution> + <id>codegraphs</id> + <phase>prepare-package</phase> + <goals> + <goal>compile-codegraph</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + </project> diff --git a/frameworks/scripts/generate-codegraph-index.js b/frameworks/scripts/generate-codegraph-index.js new file mode 100644 index 0000000000..9692b98db3 --- /dev/null +++ b/frameworks/scripts/generate-codegraph-index.js @@ -0,0 +1,189 @@ +#!/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; +} \ No newline at end of file diff --git a/frameworks/scripts/validate-codegraphs.js b/frameworks/scripts/validate-codegraphs.js new file mode 100644 index 0000000000..8c14e876b2 --- /dev/null +++ b/frameworks/scripts/validate-codegraphs.js @@ -0,0 +1,155 @@ +#!/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 fs = require("fs"); +const path = require("path"); + +function parseArguments(args) { + const options = { + root: path.resolve(__dirname, "../projects"), + targets: ["js", "swf"] + }; + + for (let index = 0; index < args.length; index++) { + const argument = args[index]; + if (argument === "--root") { + options.root = path.resolve(args[++index]); + } else if (argument === "--module") { + options.module = args[++index]; + } else if (argument === "--targets") { + options.targets = args[++index].split(","); + } else if (argument === "--compare-root") { + options.compareRoot = path.resolve(args[++index]); + } else { + throw new Error(`Unknown argument: ${argument}`); + } + } + + return options; +} + +function collectModules(options) { + if (options.module) { + return [options.module]; + } + + return fs.readdirSync(options.root, { withFileTypes: true }) + .filter(entry => entry.isDirectory()) + .map(entry => entry.name) + .filter(module => options.targets.some(target => + fs.existsSync(graphPath(options.root, module, target)))) + .sort(); +} + +function graphPath(root, module, target) { + return path.join(root, module, "target", "codegraph", target, `${module}.json`); +} + +function findUnresolved(value, location, unresolved) { + if (!value || typeof value !== "object") { + return; + } + if (value.unresolved === true) { + unresolved.push(location); + } + if (Array.isArray(value)) { + value.forEach((item, index) => findUnresolved(item, `${location}[${index}]`, unresolved)); + return; + } + Object.keys(value).forEach(key => findUnresolved(value[key], `${location}.${key}`, unresolved)); +} + +function validateDeclarations(graph, file) { + if (!Array.isArray(graph.symbols)) { + throw new Error(`${file}: symbols must be an array`); + } + + const ids = new Set(); + graph.symbols.forEach((symbol, symbolIndex) => { + const declarations = [symbol].concat(Array.isArray(symbol.members) ? symbol.members : []); + declarations.forEach((declaration, declarationIndex) => { + if (typeof declaration.id !== "string" || declaration.id.length === 0) { + throw new Error(`${file}: declaration ${symbolIndex}:${declarationIndex} has no id`); + } + if (ids.has(declaration.id)) { + throw new Error(`${file}: duplicate declaration id ${declaration.id}`); + } + ids.add(declaration.id); + }); + }); +} + +function validateGraph(options, module, target) { + const file = graphPath(options.root, module, target); + if (!fs.existsSync(file)) { + throw new Error(`${file}: expected graph does not exist`); + } + + const contents = fs.readFileSync(file); + const graph = JSON.parse(contents.toString("utf8")); + if (graph.schemaVersion !== "1.0") { + throw new Error(`${file}: expected schemaVersion 1.0, found ${graph.schemaVersion}`); + } + if (graph.module !== module) { + throw new Error(`${file}: expected module ${module}, found ${graph.module}`); + } + if (graph.target !== target) { + throw new Error(`${file}: expected target ${target}, found ${graph.target}`); + } + + validateDeclarations(graph, file); + const unresolved = []; + findUnresolved(graph, "$", unresolved); + const unresolvedApi = unresolved.filter(location => location.indexOf(".metadata[") === -1); + if (unresolvedApi.length > 0) { + throw new Error(`${file}: ${unresolvedApi.length} unresolved API reference(s), first at ${unresolvedApi[0]}`); + } + + if (options.compareRoot) { + const comparisonFile = graphPath(options.compareRoot, module, target); + if (!fs.existsSync(comparisonFile) || !contents.equals(fs.readFileSync(comparisonFile))) { + throw new Error(`${file}: differs from ${comparisonFile}`); + } + } + return unresolved.length - unresolvedApi.length; +} + +function main() { + const options = parseArguments(process.argv.slice(2)); + const modules = collectModules(options); + if (modules.length === 0) { + throw new Error(`No codegraph modules found under ${options.root}`); + } + + let unresolvedMetadata = 0; + modules.forEach(module => options.targets.forEach(target => { + unresolvedMetadata += validateGraph(options, module, target); + })); + console.log(`Validated ${modules.length * options.targets.length} codegraph shard(s) for ${modules.length} module(s).`); + if (unresolvedMetadata > 0) { + console.log(`Preserved ${unresolvedMetadata} unresolved metadata reference name(s).`); + } +} + +try { + main(); +} catch (error) { + console.error(error.message); + process.exitCode = 1; +} \ No newline at end of file
