Skip to content

Commit 995fd55

Browse files
committed
CDS extractor cleanup checkpoint 2
1 parent c41597c commit 995fd55

File tree

10 files changed

+619
-109
lines changed

10 files changed

+619
-109
lines changed

extractors/cds/tools/src/cds/index.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

extractors/cds/tools/src/cds/parser/functions.ts

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { basename, dirname, join, relative, sep } from 'path';
33

44
import { sync } from 'glob';
55

6-
import { CdsImport, PackageJson } from './types';
6+
import { CdsFilesToCompile, CdsImport, PackageJson } from './types';
77
import { cdsExtractorLog } from '../../logging';
88

99
/**
@@ -390,13 +390,19 @@ export function readPackageJsonFile(filePath: string): PackageJson | undefined {
390390
}
391391

392392
/**
393-
* Determines which CDS files in a project should be compiled to JSON.
394-
* For CAP projects with typical directory structure (db/, srv/), we should use project-aware compilation.
395-
* For other projects, we fall back to the previous approach of identifying root files.
393+
* Determines which CDS files should be compiled for a given project and what output files to expect.
394+
* This function analyzes the project structure and dependencies to decide
395+
* whether to use project-level compilation or individual file compilation.
396+
*
397+
* For CAP projects (identified by either having @sap/cds dependencies or
398+
* typical CAP directory structure), it returns a special marker indicating
399+
* project-level compilation should be used. For other projects, it attempts
400+
* to identify root files (files that are not imported by others) and returns
401+
* those for individual compilation.
396402
*
397403
* @param sourceRootDir - The source root directory
398-
* @param project - The CDS project to analyze
399-
* @returns Array of CDS file paths (relative to source root) that should be compiled
404+
* @param project - The project to analyze, containing cdsFiles, imports, and projectDir
405+
* @returns Object containing files to compile and expected output files
400406
*/
401407
export function determineCdsFilesToCompile(
402408
sourceRootDir: string,
@@ -405,14 +411,21 @@ export function determineCdsFilesToCompile(
405411
imports?: Map<string, CdsImport[]>;
406412
projectDir: string;
407413
},
408-
): string[] {
414+
): CdsFilesToCompile {
409415
if (!project.cdsFiles || project.cdsFiles.length === 0) {
410-
return [];
416+
return {
417+
filesToCompile: [],
418+
expectedOutputFiles: [],
419+
};
411420
}
412421

413422
// If there's only one CDS file, it should be compiled individually.
414423
if (project.cdsFiles.length === 1) {
415-
return [...project.cdsFiles];
424+
const filesToCompile = [...project.cdsFiles];
425+
return {
426+
filesToCompile,
427+
expectedOutputFiles: computeExpectedOutputFiles(filesToCompile, project.projectDir),
428+
};
416429
}
417430

418431
const absoluteProjectDir = join(sourceRootDir, project.projectDir);
@@ -425,13 +438,21 @@ export function determineCdsFilesToCompile(
425438
if (project.cdsFiles.length > 1 && (hasCapStructure || hasCapDeps)) {
426439
// For CAP projects, we should use project-level compilation
427440
// Return a special marker that indicates the entire project should be compiled together
428-
return ['__PROJECT_LEVEL_COMPILATION__'];
441+
const filesToCompile = ['__PROJECT_LEVEL_COMPILATION__'];
442+
return {
443+
filesToCompile,
444+
expectedOutputFiles: computeExpectedOutputFiles(filesToCompile, project.projectDir),
445+
};
429446
}
430447

431448
// For non-CAP projects or when we can't determine project type,
432449
// fall back to the original logic of identifying root files
433450
if (!project.imports || project.imports.size === 0) {
434-
return [...project.cdsFiles];
451+
const filesToCompile = [...project.cdsFiles];
452+
return {
453+
filesToCompile,
454+
expectedOutputFiles: computeExpectedOutputFiles(filesToCompile, project.projectDir),
455+
};
435456
}
436457

437458
try {
@@ -475,18 +496,67 @@ export function determineCdsFilesToCompile(
475496
'warn',
476497
`No root CDS files identified in project ${project.projectDir}, will compile all files`,
477498
);
478-
return [...project.cdsFiles];
499+
const filesToCompile = [...project.cdsFiles];
500+
return {
501+
filesToCompile,
502+
expectedOutputFiles: computeExpectedOutputFiles(filesToCompile, project.projectDir),
503+
};
479504
}
480505

481-
return rootFiles;
506+
return {
507+
filesToCompile: rootFiles,
508+
expectedOutputFiles: computeExpectedOutputFiles(rootFiles, project.projectDir),
509+
};
482510
} catch (error) {
483511
cdsExtractorLog(
484512
'warn',
485513
`Error determining files to compile for project ${project.projectDir}: ${String(error)}`,
486514
);
487515
// Fall back to compiling all files on error
488-
return [...project.cdsFiles];
516+
const filesToCompile = [...project.cdsFiles];
517+
return {
518+
filesToCompile,
519+
expectedOutputFiles: computeExpectedOutputFiles(filesToCompile, project.projectDir),
520+
};
521+
}
522+
}
523+
524+
/**
525+
* Computes the expected output files for a given set of files to compile.
526+
* This function predicts what .cds.json files will be generated during compilation.
527+
*
528+
* @param filesToCompile - Array of files to compile (may include special markers)
529+
* @param projectDir - The project directory
530+
* @returns Array of expected output file paths (relative to source root)
531+
*/
532+
function computeExpectedOutputFiles(filesToCompile: string[], projectDir: string): string[] {
533+
const expectedFiles: string[] = [];
534+
535+
// Check if this project uses project-level compilation.
536+
const usesProjectLevelCompilation = filesToCompile.includes('__PROJECT_LEVEL_COMPILATION__');
537+
538+
// Validate that __PROJECT_LEVEL_COMPILATION__ element does not coexist with other
539+
// files. We either expect a single project-level compilation marker or a list of
540+
// individual files to compile, not both.
541+
if (usesProjectLevelCompilation && filesToCompile.length !== 1) {
542+
throw new Error(
543+
`Invalid compilation configuration: '__PROJECT_LEVEL_COMPILATION__' must be the only element in filesToCompile array, but found ${filesToCompile.length} elements: ${filesToCompile.join(', ')}`,
544+
);
489545
}
546+
547+
if (usesProjectLevelCompilation) {
548+
// For project-level compilation, expect a single model.cds.json file in the project
549+
// root directory.
550+
const projectModelFile = join(projectDir, 'model.cds.json');
551+
expectedFiles.push(projectModelFile);
552+
} else {
553+
// For individual file compilation, expect a .cds.json file for each .cds file compiled.
554+
for (const cdsFile of filesToCompile) {
555+
expectedFiles.push(`${cdsFile}.json`);
556+
}
557+
}
558+
559+
return expectedFiles;
490560
}
491561

492562
/**
@@ -497,27 +567,33 @@ export function determineCdsFilesToCompile(
497567
* @returns Array of expected output file paths (relative to source root)
498568
*/
499569
export function determineExpectedOutputFiles(project: {
500-
cdsFiles: string[];
501570
cdsFilesToCompile: string[];
502571
projectDir: string;
503572
}): string[] {
504573
const expectedFiles: string[] = [];
505574

506-
// Check if this project uses project-level compilation
575+
// Check if this project uses project-level compilation.
507576
const usesProjectLevelCompilation = project.cdsFilesToCompile.includes(
508577
'__PROJECT_LEVEL_COMPILATION__',
509578
);
579+
// Validate that __PROJECT_LEVEL_COMPILATION__ element does not coexist with other
580+
// files. We either expect a single project-level compilation marker or a list of
581+
// individual files to compile, not both.
582+
if (usesProjectLevelCompilation && project.cdsFilesToCompile.length !== 1) {
583+
throw new Error(
584+
`Invalid compilation configuration: '__PROJECT_LEVEL_COMPILATION__' must be the only element in cdsFilesToCompile array, but found ${project.cdsFilesToCompile.length} elements: ${project.cdsFilesToCompile.join(', ')}`,
585+
);
586+
}
510587

511588
if (usesProjectLevelCompilation) {
512-
// For project-level compilation, expect a single model.cds.json file in the project root
589+
// For project-level compilation, expect a single model.cds.json file in the project
590+
// root directory.
513591
const projectModelFile = join(project.projectDir, 'model.cds.json');
514592
expectedFiles.push(projectModelFile);
515593
} else {
516-
// For individual file compilation, expect a .cds.json file for each file to compile
594+
// For individual file compilation, expect a .cds.json file for each .cds file compiled.
517595
for (const cdsFile of project.cdsFilesToCompile) {
518-
if (cdsFile !== '__PROJECT_LEVEL_COMPILATION__') {
519-
expectedFiles.push(`${cdsFile}.json`);
520-
}
596+
expectedFiles.push(`${cdsFile}.json`);
521597
}
522598
}
523599

extractors/cds/tools/src/cds/parser/graph.ts

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
determineCdsFilesForProjectDir,
55
determineCdsFilesToCompile,
66
determineCdsProjectsUnderSourceDir,
7-
determineExpectedOutputFiles,
87
extractCdsImports,
98
readPackageJsonFile,
109
} from './functions';
@@ -156,42 +155,42 @@ function buildBasicCdsProjectDependencyGraph(
156155
}
157156
}
158157

159-
// Third pass: determine which CDS files should be compiled for each project
160-
cdsExtractorLog('info', 'Determining CDS files to compile for each project...');
158+
// Third pass: determine CDS files to compile and expected output files for each project
159+
cdsExtractorLog(
160+
'info',
161+
'Determining CDS files to compile and expected output files for each project...',
162+
);
161163
for (const [, project] of projectMap.entries()) {
162164
try {
163-
const filesToCompile = determineCdsFilesToCompile(sourceRootDir, project);
164-
project.cdsFilesToCompile = filesToCompile;
165-
cdsExtractorLog(
166-
'info',
167-
`Project ${project.projectDir}: ${filesToCompile.length} files to compile out of ${project.cdsFiles.length} total CDS files`,
165+
const projectPlan = determineCdsFilesToCompile(sourceRootDir, project);
166+
167+
// Assign the calculated values back to the project
168+
project.cdsFilesToCompile = projectPlan.filesToCompile;
169+
project.expectedOutputFiles = projectPlan.expectedOutputFiles;
170+
171+
// Check if using project-level compilation
172+
const usesProjectLevelCompilation = projectPlan.filesToCompile.includes(
173+
'__PROJECT_LEVEL_COMPILATION__',
168174
);
175+
176+
if (usesProjectLevelCompilation) {
177+
cdsExtractorLog(
178+
'info',
179+
`Project ${project.projectDir}: using project-level compilation for all ${project.cdsFiles.length} CDS files, expecting ${projectPlan.expectedOutputFiles.length} output files`,
180+
);
181+
} else {
182+
cdsExtractorLog(
183+
'info',
184+
`Project ${project.projectDir}: ${projectPlan.filesToCompile.length} files to compile out of ${project.cdsFiles.length} total CDS files, expecting ${projectPlan.expectedOutputFiles.length} output files`,
185+
);
186+
}
169187
} catch (error) {
170188
cdsExtractorLog(
171189
'warn',
172190
`Error determining files to compile for project ${project.projectDir}: ${String(error)}`,
173191
);
174192
// Fall back to compiling all files on error
175193
project.cdsFilesToCompile = [...project.cdsFiles];
176-
}
177-
}
178-
179-
// Fourth pass: determine expected output files for each project
180-
cdsExtractorLog('info', 'Determining expected output files for each project...');
181-
for (const [, project] of projectMap.entries()) {
182-
try {
183-
const expectedOutputFiles = determineExpectedOutputFiles(project);
184-
project.expectedOutputFiles = expectedOutputFiles;
185-
cdsExtractorLog(
186-
'info',
187-
`Project ${project.projectDir}: expecting ${expectedOutputFiles.length} output files`,
188-
);
189-
} catch (error) {
190-
cdsExtractorLog(
191-
'warn',
192-
`Error determining expected output files for project ${project.projectDir}: ${String(error)}`,
193-
);
194-
// Fall back to empty array on error
195194
project.expectedOutputFiles = [];
196195
}
197196
}

extractors/cds/tools/src/cds/parser/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
/** Types for CDS parsing. */
22

3+
/** Result of determining CDS files to compile and their expected outputs */
4+
export interface CdsFilesToCompile {
5+
/** CDS files that should be compiled (or special markers like __PROJECT_LEVEL_COMPILATION__) */
6+
filesToCompile: string[];
7+
8+
/** Expected JSON output files that will be generated (relative to source root) */
9+
expectedOutputFiles: string[];
10+
}
11+
312
/** Represents an import reference in a CDS file. */
413
export interface CdsImport {
514
/** Whether the import is from a module (node_modules). */

extractors/cds/tools/src/packageManager/installer.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ function extractUniqueDependencyCombinations(
7474
const resolvedVersions = resolveCdsVersions(cdsVersion, cdsDkVersion);
7575
const { resolvedCdsVersion, resolvedCdsDkVersion, ...rest } = resolvedVersions;
7676

77+
// Log the resolved CDS dependency versions for the project
78+
if (resolvedCdsVersion && resolvedCdsDkVersion) {
79+
let statusMsg: string;
80+
if (resolvedVersions.cdsExactMatch && resolvedVersions.cdsDkExactMatch) {
81+
statusMsg = ' (exact match)';
82+
} else if (!resolvedVersions.isFallback) {
83+
statusMsg = ' (compatible versions)';
84+
} else {
85+
statusMsg = ' (using fallback versions)';
86+
}
87+
cdsExtractorLog(
88+
'info',
89+
`Resolved to: @sap/cds@${resolvedCdsVersion}, @sap/cds-dk@${resolvedCdsDkVersion}${statusMsg}`,
90+
);
91+
} else {
92+
cdsExtractorLog(
93+
'error',
94+
`Failed to resolve CDS dependencies: @sap/cds@${cdsVersion}, @sap/cds-dk@${cdsDkVersion}`,
95+
);
96+
}
97+
7798
// Calculate hash based on resolved versions to ensure proper cache reuse
7899
const actualCdsVersion = resolvedCdsVersion ?? cdsVersion;
79100
const actualCdsDkVersion = resolvedCdsDkVersion ?? cdsDkVersion;

0 commit comments

Comments
 (0)