forked from advanced-security/codeql-sap-js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcds-extractor.ts
More file actions
270 lines (234 loc) · 10.2 KB
/
cds-extractor.ts
File metadata and controls
270 lines (234 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import { join } from 'path';
import { sync as globSync } from 'glob';
import { orchestrateCompilation } from './src/cds/compiler';
import { buildCdsProjectDependencyGraph } from './src/cds/parser';
import { runJavaScriptExtractor } from './src/codeql';
import { addCompilationDiagnostic } from './src/diagnostics';
import { configureLgtmIndexFilters, setupAndValidateEnvironment } from './src/environment';
import {
cdsExtractorLog,
generateStatusReport,
logExtractorStart,
logExtractorStop,
logPerformanceMilestone,
logPerformanceTrackingStart,
logPerformanceTrackingStop,
setSourceRootDirectory,
} from './src/logging';
import { cacheInstallDependencies } from './src/packageManager';
import { validateArguments } from './src/utils';
// Validate the script arguments.
const validationResult = validateArguments(process.argv);
if (!validationResult.isValid) {
console.warn(validationResult.usageMessage);
// Exit with an error code on invalid use of this script.
process.exit(1);
}
// Get the validated and sanitized arguments.
const { sourceRoot } = validationResult.args!;
// Initialize the unified logging system with the source root directory.
setSourceRootDirectory(sourceRoot);
// Log the start of the CDS extractor session as a whole.
logExtractorStart(sourceRoot);
// Setup the environment and validate all requirements first, before changing
// directory back to the "sourceRoot" directory. This ensures we can properly locate
// the CodeQL tools.
logPerformanceTrackingStart('Environment Setup');
const {
success: envSetupSuccess,
errorMessages,
codeqlExePath,
autobuildScriptPath,
platformInfo,
} = setupAndValidateEnvironment(sourceRoot);
logPerformanceTrackingStop('Environment Setup');
if (!envSetupSuccess) {
const codeqlExe = platformInfo.isWindows ? 'codeql.exe' : 'codeql';
cdsExtractorLog(
'warn',
`'${codeqlExe} database index-files --language cds' terminated early due to: ${errorMessages.join(
', ',
)}.`,
);
// Exit with an error code when environment setup fails.
logExtractorStop(false, 'Terminated: Environment setup failed');
process.exit(1);
}
// Force this script, and any process it spawns, to use the project (source) root
// directory as the current working directory.
process.chdir(sourceRoot);
cdsExtractorLog(
'info',
`CodeQL CDS extractor using autobuild mode for scan of project source root directory '${sourceRoot}'.`,
);
cdsExtractorLog('info', 'Building CDS project dependency graph...');
// Build the CDS project `dependencyGraph` as the foundation for the extraction process.
// This graph will contain all discovered CDS projects, their dependencies, the `.cds`
// files discovered within each project, the expected `.cds.json` files for each project
// and the compilation status of such `.cds.json` files.
//
// The `dependencyGraph` will be updated as CDS extractor phases progress, allowing for
// a single data structure to be used for planning, execution, retries (i.e. error handling),
// debugging, and final reporting.
let dependencyGraph;
try {
logPerformanceTrackingStart('Dependency Graph Build');
dependencyGraph = buildCdsProjectDependencyGraph(sourceRoot);
logPerformanceTrackingStop('Dependency Graph Build');
logPerformanceMilestone(
'Dependency graph created',
`${dependencyGraph.projects.size} projects, ${dependencyGraph.statusSummary.totalCdsFiles} CDS files`,
);
// Log details about discovered projects for debugging
if (dependencyGraph.projects.size > 0) {
for (const [projectDir, project] of dependencyGraph.projects.entries()) {
cdsExtractorLog(
'info',
`Project: ${projectDir}, Status: ${project.status}, CDS files: ${project.cdsFiles.length}, Compilation targets: ${project.compilationTargets.length}`,
);
}
} else {
cdsExtractorLog(
'error',
'No CDS projects were detected. This is an unrecoverable error as there is nothing to scan.',
);
// Let's also try to find CDS files directly as a backup check
try {
const allCdsFiles = Array.from(
new Set([
...globSync(join(sourceRoot, '**/*.cds'), {
ignore: ['**/node_modules/**', '**/.git/**'],
}),
]),
);
cdsExtractorLog(
'info',
`Direct search found ${allCdsFiles.length} CDS files in the source tree.`,
);
if (allCdsFiles.length > 0) {
cdsExtractorLog(
'info',
`Sample CDS files: ${allCdsFiles.slice(0, 5).join(', ')}${allCdsFiles.length > 5 ? ', ...' : ''}`,
);
cdsExtractorLog(
'error',
'CDS files were found but no projects were detected. This indicates a problem with project detection logic.',
);
} else {
cdsExtractorLog(
'info',
'No CDS files found in the source tree. This may be expected if the source does not contain CAP/CDS projects.',
);
}
} catch (globError) {
cdsExtractorLog('warn', `Could not perform direct CDS file search: ${String(globError)}`);
}
// Exit early since we have no CDS projects to process
logExtractorStop(false, 'Terminated: No CDS projects detected');
process.exit(1);
}
} catch (error) {
cdsExtractorLog('error', `Failed to build CDS dependency graph: ${String(error)}`);
// Exit with error since we can't continue without a proper dependency graph
logExtractorStop(false, 'Terminated: Dependency graph build failed');
process.exit(1);
}
logPerformanceTrackingStart('Dependency Installation');
const projectCacheDirMap = cacheInstallDependencies(dependencyGraph, sourceRoot, codeqlExePath);
logPerformanceTrackingStop('Dependency Installation');
// Check if dependency installation resulted in any usable project mappings
if (projectCacheDirMap.size === 0) {
cdsExtractorLog(
'error',
'No project cache directory mappings were created. This indicates that dependency installation failed for all discovered projects.',
);
// This is a critical error if we have projects but no cache mappings
if (dependencyGraph.projects.size > 0) {
cdsExtractorLog(
'error',
`Found ${dependencyGraph.projects.size} CDS projects but failed to install dependencies for any of them. Cannot proceed with compilation.`,
);
logExtractorStop(false, 'Terminated: Dependency installation failed for all projects');
process.exit(1);
}
// If we have no projects and no cache mappings, this should have been caught earlier
cdsExtractorLog(
'warn',
'No projects and no cache mappings - this should have been detected earlier.',
);
}
const cdsFilePathsToProcess: string[] = [];
// Use the dependency graph to collect all `.cds` files from each project.
// We want to "extract" all `.cds` files from all projects so that we have a copy
// of each `.cds` source file in the CodeQL database.
for (const project of dependencyGraph.projects.values()) {
cdsFilePathsToProcess.push(...project.cdsFiles);
}
// TODO : Improve logging / debugging of dependencyGraph.statusSummary. Just log the JSON?
cdsExtractorLog(
'info',
`Found ${cdsFilePathsToProcess.length} total CDS files, ${dependencyGraph.statusSummary.totalCdsFiles} CDS files in dependency graph`,
);
logPerformanceTrackingStart('CDS Compilation');
try {
// Use the new orchestrated compilation approach (autobuild mode, no debug)
orchestrateCompilation(dependencyGraph, projectCacheDirMap, codeqlExePath);
// Handle compilation failures for normal mode
if (!dependencyGraph.statusSummary.overallSuccess) {
cdsExtractorLog(
'error',
`Compilation completed with failures: ${dependencyGraph.statusSummary.failedCompilations} failed out of ${dependencyGraph.statusSummary.totalCompilationTasks} total tasks`,
);
// Add diagnostics for critical errors
for (const error of dependencyGraph.errors.critical) {
cdsExtractorLog('error', `Critical error in ${error.phase}: ${error.message}`);
}
// Don't exit with error - let the JavaScript extractor run on whatever was compiled
}
logPerformanceTrackingStop('CDS Compilation');
logPerformanceMilestone('CDS compilation completed');
} catch (error) {
logPerformanceTrackingStop('CDS Compilation');
cdsExtractorLog('error', `Compilation orchestration failed: ${String(error)}`);
// Add diagnostic for the overall failure
if (cdsFilePathsToProcess.length > 0) {
addCompilationDiagnostic(
cdsFilePathsToProcess[0], // Use first file as representative
`Compilation orchestration failed: ${String(error)}`,
codeqlExePath,
);
}
}
// Configure the "LGTM" index filters for proper extraction.
configureLgtmIndexFilters();
// Run CodeQL's JavaScript extractor to process the .cds source files and
// the compiled .cds.json files.
logPerformanceTrackingStart('JavaScript Extraction');
const extractionStartTime = Date.now();
const extractorResult = runJavaScriptExtractor(sourceRoot, autobuildScriptPath, codeqlExePath);
const extractionEndTime = Date.now();
logPerformanceTrackingStop('JavaScript Extraction');
// Update the dependency graph's performance metrics with the extraction duration
dependencyGraph.statusSummary.performance.extractionDurationMs =
extractionEndTime - extractionStartTime;
// Calculate total duration by summing all phases
const totalDuration =
dependencyGraph.statusSummary.performance.parsingDurationMs +
dependencyGraph.statusSummary.performance.compilationDurationMs +
dependencyGraph.statusSummary.performance.extractionDurationMs;
dependencyGraph.statusSummary.performance.totalDurationMs = totalDuration;
if (!extractorResult.success && extractorResult.error) {
cdsExtractorLog('error', `Error running JavaScript extractor: ${extractorResult.error}`);
logExtractorStop(false, 'JavaScript extractor failed');
} else {
logExtractorStop(true, 'CDS extraction completed successfully');
}
cdsExtractorLog(
'info',
'CDS Extractor Status Report : Final...\n' + generateStatusReport(dependencyGraph),
);
// Use the `cds-extractor.js` name in the log message as that is the name of the script
// that is actually run by the `codeql database index-files` command. This TypeScript
// file is where the code/logic is edited/implemented, but the runnable script is
// generated by the TypeScript compiler and is named `cds-extractor.js`.
console.log(`Completed run of the cds-extractor.js script for the CDS extractor.`);