[tests] Fix up output paths, lsp-cli invocation

This commit is contained in:
Mario Zechner 2025-07-15 16:22:45 +02:00
parent 0c74907da2
commit 4d45abbcf3
9 changed files with 48 additions and 45 deletions

View File

@ -1,21 +1,22 @@
#!/bin/bash
set -e
set -o pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
pushd "$SCRIPT_DIR" > /dev/null
echo "Analyzing Java API..."
npx tsx src/analyze-java-api.ts
npx tsx src/analyze-java-api.ts || { echo "Failed to analyze Java API"; exit 1; }
echo "Generating serializer IR..."
npx tsx src/generate-serializer-ir.ts
npx tsx src/generate-serializer-ir.ts || { echo "Failed to generate IR"; exit 1; }
echo "Generating Java SkeletonSerializer..."
npx tsx src/generate-java-serializer.ts
npx tsx src/generate-java-serializer.ts || { echo "Failed to generate Java serializer"; exit 1; }
echo "Generating C++ SkeletonSerializer..."
npx tsx src/generate-cpp-serializer.ts
npx tsx src/generate-cpp-serializer.ts || { echo "Failed to generate C++ serializer"; exit 1; }
echo "Done."
popd > /dev/null

View File

@ -6,7 +6,7 @@
"": {
"name": "spine-tests",
"dependencies": {
"@mariozechner/lsp-cli": "^0.1.1"
"@mariozechner/lsp-cli": "^0.1.3"
},
"devDependencies": {
"@types/node": "^20.0.0",
@ -456,9 +456,9 @@
}
},
"node_modules/@mariozechner/lsp-cli": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@mariozechner/lsp-cli/-/lsp-cli-0.1.1.tgz",
"integrity": "sha512-/5HF/PoYhQKFMhXLQiH1ZHTBJfs7rB8xcXa4YXSq6aTR5G6tWOrE6fwFbK7pwtkry6ZFeCTE2HI4BRWv5La9Qg==",
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@mariozechner/lsp-cli/-/lsp-cli-0.1.3.tgz",
"integrity": "sha512-5kyYiI4k7bf48ineGLdHluHx9ZjYLIEknsgi3vpxBjUCf6y6pDmXsqDezlvZsWkittz9PMUJzCJD9cze6/d/Ug==",
"dependencies": {
"chalk": "^5.4.1",
"commander": "^11.0.0",
@ -468,7 +468,7 @@
"vscode-languageserver-protocol": "^3.17.0"
},
"bin": {
"lsp-cli": "dist/index.js"
"lsp-cli": "dist/cli.js"
}
},
"node_modules/@types/node": {

View File

@ -10,6 +10,6 @@
"tsx": "^4.0.0"
},
"dependencies": {
"@mariozechner/lsp-cli": "^0.1.1"
"@mariozechner/lsp-cli": "^0.1.3"
}
}

View File

@ -4,12 +4,13 @@ import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import type { Symbol, LspOutput, ClassInfo, PropertyInfo, AnalysisResult } from './types';
import type { ClassInfo, PropertyInfo, AnalysisResult } from './types';
import type { LspCliResult, SymbolInfo } from '@mariozechner/lsp-cli';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function ensureOutputDir(): string {
const outputDir = path.resolve(__dirname, '../../output');
const outputDir = path.resolve(__dirname, '../output');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
@ -18,7 +19,7 @@ function ensureOutputDir(): string {
function generateLspData(outputDir: string): string {
const outputFile = path.join(outputDir, 'spine-libgdx-symbols.json');
const projectDir = '/Users/badlogic/workspaces/spine-runtimes/spine-libgdx';
const projectDir = path.resolve(__dirname, '../../spine-libgdx');
const srcDir = path.join(projectDir, 'spine-libgdx/src');
// Check if we need to regenerate
@ -42,9 +43,8 @@ function generateLspData(outputDir: string): string {
if (needsRegeneration) {
console.error('Generating LSP data for spine-libgdx...');
try {
execSync(`npx lsp-cli "${projectDir}" java "${outputFile}"`, {
encoding: 'utf8',
stdio: ['ignore', 'ignore', 'pipe'] // Hide stdout but show stderr
execSync(`node ${path.join(__dirname, '../node_modules/@mariozechner/lsp-cli/dist/index.js')} "${projectDir}" java "${outputFile}"`, {
stdio: 'inherit' // Show all output
});
console.error('LSP data generated successfully');
} catch (error: any) {
@ -58,11 +58,11 @@ function generateLspData(outputDir: string): string {
return outputFile;
}
function analyzeClasses(symbols: Symbol[]): Map<string, ClassInfo> {
function analyzeClasses(symbols: SymbolInfo[]): Map<string, ClassInfo> {
const classMap = new Map<string, ClassInfo>();
const srcPath = '/Users/badlogic/workspaces/spine-runtimes/spine-libgdx/spine-libgdx/src/';
const srcPath = path.resolve(__dirname, '../../spine-libgdx/spine-libgdx/src/');
function processSymbol(symbol: Symbol, parentName?: string) {
function processSymbol(symbol: SymbolInfo, parentName?: string) {
if (symbol.kind !== 'class' && symbol.kind !== 'enum' && symbol.kind !== 'interface') return;
// Filter: only process symbols in spine-libgdx/src, excluding SkeletonSerializer
@ -256,6 +256,9 @@ function findAccessibleTypes(
const typeMatches = returnType.match(/\b([A-Z]\w+(?:\.[A-Z]\w+)*)\b/g);
if (typeMatches) {
for (const match of typeMatches) {
if (classMap.has(match) && !visited.has(match)) {
toVisit.push(match);
}
// For non-qualified names, also try as inner class
if (!match.includes('.')) {
// Try as inner class of current type and its parents
@ -279,7 +282,7 @@ function findAccessibleTypes(
}
function loadExclusions(): { types: Set<string>, methods: Map<string, Set<string>>, fields: Map<string, Set<string>> } {
const exclusionsPath = path.resolve(__dirname, 'java-exclusions.txt');
const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt');
const types = new Set<string>();
const methods = new Map<string, Set<string>>();
const fields = new Map<string, Set<string>>();
@ -688,7 +691,7 @@ async function main() {
// Read and parse the JSON
const jsonContent = fs.readFileSync(jsonFile, 'utf8');
const lspData: LspOutput = JSON.parse(jsonContent);
const lspData: LspCliResult = JSON.parse(jsonContent);
console.error(`Analyzing ${lspData.symbols.length} symbols...`);

View File

@ -420,7 +420,7 @@ function generateCppFromIR(ir: SerializerIR): string {
async function main() {
try {
// Read the IR file
const irFile = path.resolve(__dirname, '../../output/serializer-ir.json');
const irFile = path.resolve(__dirname, '../output/serializer-ir.json');
if (!fs.existsSync(irFile)) {
console.error('Serializer IR not found. Run generate-serializer-ir.ts first.');
process.exit(1);
@ -434,7 +434,7 @@ async function main() {
// Write the C++ file
const cppFile = path.resolve(
__dirname,
'../../../spine-cpp/tests/SkeletonSerializer.h'
'../../spine-cpp/tests/SkeletonSerializer.h'
);
fs.mkdirSync(path.dirname(cppFile), { recursive: true });

View File

@ -290,7 +290,7 @@ function generateJavaFromIR(ir: SerializerIR): string {
async function main() {
try {
// Read the IR file
const irFile = path.resolve(__dirname, '../../output/serializer-ir.json');
const irFile = path.resolve(__dirname, '../output/serializer-ir.json');
if (!fs.existsSync(irFile)) {
console.error('Serializer IR not found. Run generate-serializer-ir.ts first.');
process.exit(1);
@ -304,7 +304,7 @@ async function main() {
// Write the Java file
const javaFile = path.resolve(
__dirname,
'../../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java'
'../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java'
);
fs.mkdirSync(path.dirname(javaFile), { recursive: true });

View File

@ -88,27 +88,27 @@ interface SerializedAnalysisResult {
}
function loadExclusions(): { types: Set<string>, methods: Map<string, Set<string>>, fields: Map<string, Set<string>> } {
const exclusionsPath = path.resolve(__dirname, 'java-exclusions.txt');
const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt');
const types = new Set<string>();
const methods = new Map<string, Set<string>>();
const fields = new Map<string, Set<string>>();
if (!fs.existsSync(exclusionsPath)) {
return { types, methods, fields };
}
const content = fs.readFileSync(exclusionsPath, 'utf-8');
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const parts = trimmed.split(/\s+/);
if (parts.length < 2) continue;
const [type, className, property] = parts;
switch (type) {
case 'type':
types.add(className);
@ -131,7 +131,7 @@ function loadExclusions(): { types: Set<string>, methods: Map<string, Set<string
break;
}
}
return { types, methods, fields };
}
@ -182,7 +182,7 @@ function analyzePropertyType(propType: string, classMap: Map<string, ClassInfo>)
if (propType.startsWith('Array<')) {
const innerType = propType.match(/Array<(.+?)>/)![1].trim();
const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(innerType) ? "primitive" : "object";
return {
kind: "array",
name,
@ -257,14 +257,14 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize
if (classInfo.isEnum && classInfo.enumValues) {
const shortName = className.split('.').pop()!;
const valueMap: { [javaValue: string]: string } = {};
for (const javaValue of classInfo.enumValues) {
// Convert Java enum value to C++ enum value
// e.g. "setup" -> "MixBlend_Setup", "first" -> "MixBlend_First"
const cppValue = `${shortName}_${javaValue.charAt(0).toUpperCase() + javaValue.slice(1)}`;
valueMap[javaValue] = cppValue;
}
enumMappings[shortName] = valueMap;
}
}
@ -356,7 +356,7 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize
for (const typeName of Array.from(typesNeedingMethods).sort()) {
const classInfo = classMap.get(typeName);
if (!classInfo) continue;
// Skip enums - they are handled inline with .name() calls
if (classInfo.isEnum) continue;
@ -376,12 +376,12 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize
if (classInfo.isAbstract || classInfo.isInterface) {
// Handle abstract types with instanceof chain
const implementations = classInfo.concreteImplementations || [];
// Filter out excluded types from implementations
const filteredImplementations = implementations.filter(impl => {
return !exclusions.types.has(impl);
});
if (filteredImplementations.length > 0) {
writeMethod.subtypeChecks = filteredImplementations.map(impl => {
const implShortName = impl.split('.').pop()!;
@ -399,7 +399,7 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize
if (prop.excluded) {
continue; // Skip excluded properties
}
const propName = prop.isGetter ?
prop.name.replace('get', '').replace('()', '').charAt(0).toLowerCase() +
prop.name.replace('get', '').replace('()', '').slice(1) :
@ -474,7 +474,7 @@ function analyzePropertyWithDetails(prop: PropertyInfo, propName: string, getter
if (propType.startsWith('Array<')) {
const innerType = propType.match(/Array<(.+?)>/)![1].trim();
const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(innerType) ? "primitive" : "object";
return {
kind: "array",
name: propName,
@ -539,7 +539,7 @@ function analyzePropertyWithDetails(prop: PropertyInfo, propName: string, getter
async function main() {
try {
// Read analysis result
const analysisFile = path.resolve(__dirname, '../../output/analysis-result.json');
const analysisFile = path.resolve(__dirname, '../output/analysis-result.json');
if (!fs.existsSync(analysisFile)) {
console.error('Analysis result not found. Run analyze-java-api.ts first.');
process.exit(1);
@ -551,7 +551,7 @@ async function main() {
const ir = generateSerializerIR(analysisData);
// Write the IR file
const irFile = path.resolve(__dirname, '../../output/serializer-ir.json');
const irFile = path.resolve(__dirname, '../output/serializer-ir.json');
fs.mkdirSync(path.dirname(irFile), { recursive: true });
fs.writeFileSync(irFile, JSON.stringify(ir, null, 2));

View File

@ -1,4 +1,4 @@
import { Symbol, Supertype, LspOutput } from '@mariozechner/lsp-cli';
import { Supertype } from '@mariozechner/lsp-cli';
// Shared types for the Spine serializer generator
export interface ClassInfo {

View File

@ -1,4 +1,3 @@
- lsp-cli should export its types, so we can pull them in as a dependency instead of redefining them ourselves in types.ts
- clean up logging in spine-c/codegen, use chalk to do colored warnings/errors and make logging look very nice and informative (no emojis)
- spine-c/codegen type extractor should also report typedefs like typedef long long PropertyId; so primitive type to some name, and we need to handle that in the codegen
- Generate language bindings in spine-c/codegen