Skip to content

Commit 99923e0

Browse files
committed
Common project graph for CDS parse and compile
This commit: - Fixes project-aware `cds compile` commands for projects with multiple directories and/or `.cds` files, including fixes for generating the `.cds.json` files that had been missing for such projects. - Attempts to make the CDS extractor more consistent, robust, and simple by using the project-graph-based approach as more of a common data reference (type) for all CDS extractor "run modes". - Attempts to make the CDS extractor more robust in terms of prioritizing the compilation / generation of `.cds.json` files. - Addresses some comments from initial peer review of PR #195 for the "advanced-security/codeql-sap-js" repository.
1 parent e4bbc1f commit 99923e0

29 files changed

+4053
-1185
lines changed

extractors/cds/tools/cds-extractor.ts

Lines changed: 121 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import { join } from 'path';
2+
3+
import { sync as globSync } from 'glob';
4+
5+
import { determineCdsCommand } from './src/cds';
6+
import { orchestrateCompilation } from './src/cds/compiler/graph';
17
import {
2-
buildCdsProjectDependencyGraph,
3-
compileCdsToJson,
4-
determineCdsCommand,
5-
findProjectForCdsFile,
6-
} from './src/cds';
7-
import { CdsProjectMapWithDebugSignals } from './src/cds/parser/types';
8+
handleDebugParserMode,
9+
handleDebugCompilerMode,
10+
isDebugMode,
11+
isDebugParserMode,
12+
isDebugCompilerMode,
13+
} from './src/cds/parser/debugUtils';
14+
import { buildEnhancedCdsProjectDependencyGraph } from './src/cds/parser/graph';
815
import { runJavaScriptExtractor } from './src/codeql';
916
import { addCompilationDiagnostic } from './src/diagnostics';
1017
import { configureLgtmIndexFilters, setupAndValidateEnvironment } from './src/environment';
@@ -66,103 +73,143 @@ cdsExtractorLog(
6673
`CodeQL CDS extractor using run mode '${runMode}' for scan of project source root directory '${sourceRoot}'.`,
6774
);
6875

69-
// Using the new project-aware approach to find CDS projects and their dependencies
70-
cdsExtractorLog('info', 'Detecting CDS projects and analyzing their structure...');
76+
// Using the enhanced project-aware approach to find CDS projects and their dependencies
77+
cdsExtractorLog('info', 'Building enhanced CDS project dependency graph...');
78+
79+
// Build the enhanced dependency graph using the new enhanced parser
80+
let dependencyGraph;
7181

72-
// Build the project dependency graph using the project-aware parser
73-
// Pass the script directory (__dirname) to support debug-parser mode internally
74-
const projectMap = buildCdsProjectDependencyGraph(sourceRoot, runMode, __dirname);
82+
try {
83+
dependencyGraph = buildEnhancedCdsProjectDependencyGraph(sourceRoot, runMode, __dirname);
84+
85+
cdsExtractorLog(
86+
'info',
87+
`Enhanced dependency graph created with ${dependencyGraph.projects.size} projects and ${dependencyGraph.statusSummary.totalCdsFiles} CDS files`,
88+
);
7589

76-
// Cast to the interface with debug signals to properly handle debug mode
77-
const typedProjectMap = projectMap as CdsProjectMapWithDebugSignals;
90+
// Handle debug modes early - these modes should exit after completing their specific tasks
91+
if (isDebugParserMode(runMode)) {
92+
const debugSuccess = handleDebugParserMode(dependencyGraph, sourceRoot, __dirname);
93+
process.exit(debugSuccess ? 0 : 1);
94+
}
7895

79-
// Check if we're in debug-parser mode and should exit (based on signals from buildCdsProjectDependencyGraph)
80-
if (typedProjectMap.__debugParserSuccess) {
81-
cdsExtractorLog('info', 'Debug parser mode completed successfully.');
82-
process.exit(0);
83-
} else if (typedProjectMap.__debugParserFailure) {
84-
cdsExtractorLog('warn', 'No CDS projects found. Cannot generate debug information.');
96+
// Log details about discovered projects for debugging
97+
if (dependencyGraph.projects.size > 0) {
98+
for (const [projectDir, project] of dependencyGraph.projects.entries()) {
99+
cdsExtractorLog(
100+
'info',
101+
`Enhanced Project: ${projectDir}, Status: ${project.status}, CDS files: ${project.cdsFiles.length}, Files to compile: ${project.cdsFilesToCompile.length}`,
102+
);
103+
}
104+
} else {
105+
cdsExtractorLog(
106+
'warn',
107+
'No CDS projects were detected. This may indicate an issue with project detection logic.',
108+
);
109+
// Let's also try to find CDS files directly as a backup check
110+
try {
111+
const allCdsFiles = Array.from(
112+
new Set([
113+
...globSync(join(sourceRoot, '**/*.cds'), {
114+
ignore: ['**/node_modules/**', '**/.git/**'],
115+
}),
116+
]),
117+
);
118+
cdsExtractorLog(
119+
'info',
120+
`Direct search found ${allCdsFiles.length} CDS files in the source tree.`,
121+
);
122+
if (allCdsFiles.length > 0) {
123+
cdsExtractorLog(
124+
'info',
125+
`Sample CDS files: ${allCdsFiles.slice(0, 5).join(', ')}${allCdsFiles.length > 5 ? ', ...' : ''}`,
126+
);
127+
}
128+
} catch (globError) {
129+
cdsExtractorLog('warn', `Could not perform direct CDS file search: ${String(globError)}`);
130+
}
131+
}
132+
} catch (error) {
133+
cdsExtractorLog('error', `Failed to build enhanced dependency graph: ${String(error)}`);
134+
// Exit with error since we can't continue without a proper dependency graph
85135
process.exit(1);
86136
}
87137

88138
// Install dependencies of discovered CAP/CDS projects
89-
cdsExtractorLog(
90-
'info',
91-
'Ensuring dependencies are installed in cache for required CDS compiler versions...',
92-
);
93-
const projectCacheDirMap = installDependencies(projectMap, sourceRoot, codeqlExePath);
139+
cdsExtractorLog('info', 'Installing dependencies for discovered CDS projects...');
140+
141+
const projectCacheDirMap = installDependencies(dependencyGraph, sourceRoot, codeqlExePath);
94142

95143
const cdsFilePathsToProcess: string[] = [];
96144

97145
cdsExtractorLog('info', 'Extracting CDS files from discovered projects...');
98146

99-
// Use the project map to collect all `.cds` files from each project.
147+
// Use the enhanced dependency graph to collect all `.cds` files from each project.
100148
// We want to "extract" all `.cds` files from all projects so that we have a copy
101149
// of each `.cds` source file in the CodeQL database.
102-
for (const [, project] of projectMap.entries()) {
150+
for (const project of dependencyGraph.projects.values()) {
103151
cdsFilePathsToProcess.push(...project.cdsFiles);
104152
}
105153

106-
cdsExtractorLog('info', 'Processing CDS files to JSON ...');
154+
cdsExtractorLog('info', 'Processing CDS files to JSON using enhanced compilation orchestration...');
107155

108-
// Collect files that need compilation, handling project-level compilation
109-
const cdsFilesToCompile: string[] = [];
110-
const projectsForProjectLevelCompilation = new Set<string>();
156+
// Check if we're running in debug mode
157+
if (isDebugMode(runMode)) {
158+
cdsExtractorLog(
159+
'info',
160+
`Running in ${runMode} mode - enhanced debug information will be collected...`,
161+
);
162+
}
111163

112-
for (const [projectDir, project] of projectMap.entries()) {
113-
if (project.cdsFilesToCompile.includes('__PROJECT_LEVEL_COMPILATION__')) {
114-
// This project needs project-level compilation
115-
projectsForProjectLevelCompilation.add(projectDir);
116-
// We'll only compile one file per project to trigger project-level compilation
117-
// Use the first CDS file as a representative
118-
if (project.cdsFiles.length > 0) {
119-
cdsFilesToCompile.push(project.cdsFiles[0]);
120-
}
121-
} else {
122-
// Normal individual file compilation
123-
cdsFilesToCompile.push(...project.cdsFilesToCompile);
124-
}
164+
// Initialize CDS command cache early to avoid repeated testing during compilation
165+
// This is a critical optimization that avoids testing commands for every single file
166+
try {
167+
determineCdsCommand(undefined, sourceRoot);
168+
cdsExtractorLog('info', 'CDS command cache initialized successfully');
169+
} catch (error) {
170+
cdsExtractorLog('warn', `CDS command cache initialization failed: ${String(error)}`);
171+
// Continue anyway - individual calls will handle fallbacks
125172
}
126173

127174
cdsExtractorLog(
128175
'info',
129-
`Found ${cdsFilePathsToProcess.length} total CDS files, ${cdsFilesToCompile.length} files to compile (${projectsForProjectLevelCompilation.size} project-level compilations)`,
176+
`Found ${cdsFilePathsToProcess.length} total CDS files, ${dependencyGraph.statusSummary.totalCdsFiles} CDS files in dependency graph`,
130177
);
131178

132-
// Evaluate each `.cds` source file that should be compiled to JSON.
133-
for (const rawCdsFilePath of cdsFilesToCompile) {
134-
try {
135-
// Find which project this CDS file belongs to, to use the correct cache directory
136-
const projectDir = findProjectForCdsFile(rawCdsFilePath, sourceRoot, projectMap);
137-
const cacheDir = projectDir ? projectCacheDirMap.get(projectDir) : undefined;
138-
139-
// Determine the CDS command to use based on the cache directory for this specific file
140-
const cdsCommand = determineCdsCommand(cacheDir);
141-
142-
// Use resolved path directly instead of passing through getArg
143-
// Pass the project dependency information to enable project-aware compilation
144-
const compilationResult = compileCdsToJson(
145-
rawCdsFilePath,
146-
sourceRoot,
147-
cdsCommand,
148-
cacheDir,
149-
projectMap,
150-
projectDir,
151-
);
179+
try {
180+
// Use the new orchestrated compilation approach with debug awareness
181+
orchestrateCompilation(dependencyGraph, projectCacheDirMap, codeqlExePath, isDebugMode(runMode));
152182

153-
if (!compilationResult.success && compilationResult.message) {
154-
cdsExtractorLog(
155-
'error',
156-
`adding diagnostic for source file=${rawCdsFilePath} : ${compilationResult.message} ...`,
157-
);
158-
addCompilationDiagnostic(rawCdsFilePath, compilationResult.message, codeqlExePath);
159-
}
160-
} catch (errorMessage) {
183+
// Check if we should exit for debug modes after successful compilation
184+
if (isDebugCompilerMode(runMode)) {
185+
const debugSuccess = handleDebugCompilerMode(dependencyGraph, runMode);
186+
process.exit(debugSuccess ? 0 : 1);
187+
}
188+
189+
// Handle compilation failures for normal mode
190+
if (!dependencyGraph.statusSummary.overallSuccess) {
161191
cdsExtractorLog(
162192
'error',
163-
`adding diagnostic for source file=${rawCdsFilePath} : ${String(errorMessage)} ...`,
193+
`Compilation completed with failures: ${dependencyGraph.statusSummary.failedCompilations} failed out of ${dependencyGraph.statusSummary.totalCompilationTasks} total tasks`,
194+
);
195+
196+
// Add diagnostics for critical errors
197+
for (const error of dependencyGraph.errors.critical) {
198+
cdsExtractorLog('error', `Critical error in ${error.phase}: ${error.message}`);
199+
}
200+
201+
// Don't exit with error - let the JavaScript extractor run on whatever was compiled
202+
}
203+
} catch (error) {
204+
cdsExtractorLog('error', `Compilation orchestration failed: ${String(error)}`);
205+
206+
// Add diagnostic for the overall failure
207+
if (cdsFilePathsToProcess.length > 0) {
208+
addCompilationDiagnostic(
209+
cdsFilePathsToProcess[0], // Use first file as representative
210+
`Compilation orchestration failed: ${String(error)}`,
211+
codeqlExePath,
164212
);
165-
addCompilationDiagnostic(rawCdsFilePath, String(errorMessage), codeqlExePath);
166213
}
167214
}
168215

0 commit comments

Comments
 (0)