Do not aggregate manifest files in multi-module projects

Also remove snapshot-dependency-file-name and snapshot-dependency-file-name options, since they no longer make sense
This commit is contained in:
Justin Holguín
2025-05-14 22:46:16 +00:00
committed by GitHub
parent 4bf8a28b00
commit 06796b833d
8 changed files with 215 additions and 202 deletions
-10
View File
@@ -25,16 +25,6 @@ inputs:
type: string type: string
default: '' default: ''
snapshot-include-file-name:
description: Optionally include the file name in the dependency snapshot report to GitHub. This is required to be true if you want the results in the dependency tree to have a working link.
type: boolean
default: true
snapshot-dependency-file-name:
description: An optional override to specify the path to the file in the repository that the snapshot should be associated with.
type: string
required: false
token: token:
description: The GitHub token to use to submit the depedency snapshot to the repository description: The GitHub token to use to submit the depedency snapshot to the repository
type: string type: string
+75 -89
View File
@@ -7,10 +7,11 @@ require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.artifactToPackageURL = exports.parseDependencyJson = exports.MavenDependencyGraph = void 0; exports.artifactToPackageURL = exports.parseDependencyJson = exports.MavenDependencyGraph = exports.depgraphfilename = void 0;
const packageurl_js_1 = __nccwpck_require__(8915); const packageurl_js_1 = __nccwpck_require__(8915);
const dependency_submission_toolkit_1 = __nccwpck_require__(3415); const dependency_submission_toolkit_1 = __nccwpck_require__(3415);
const file_utils_1 = __nccwpck_require__(799); const file_utils_1 = __nccwpck_require__(799);
exports.depgraphfilename = 'maven-dependency-submission-action-depgraph.json';
class MavenDependencyGraph { class MavenDependencyGraph {
constructor(graph) { constructor(graph) {
this.depGraph = graph; this.depGraph = graph;
@@ -119,20 +120,20 @@ class MavenDependencyGraph {
} }
} }
exports.MavenDependencyGraph = MavenDependencyGraph; exports.MavenDependencyGraph = MavenDependencyGraph;
function parseDependencyJson(file, isMultiModule = false) { function parseDependencyJson(file) {
const data = (0, file_utils_1.loadFileContents)(file); const data = (0, file_utils_1.loadFileContents)(file);
const pomXmlFilepath = file.replace(`target/${exports.depgraphfilename}`, 'pom.xml');
if (!data) { if (!data) {
return { return {
filePath: pomXmlFilepath,
graphName: 'empty', graphName: 'empty',
artifacts: [], artifacts: [],
dependencies: [], dependencies: [],
isMultiModule: isMultiModule
}; };
} }
try { try {
const depGraph = JSON.parse(data); const depGraph = JSON.parse(data);
depGraph.isMultiModule = isMultiModule; return Object.assign(Object.assign({}, depGraph), { filePath: pomXmlFilepath });
return depGraph;
} }
catch (err) { catch (err) {
throw new Error(`Failed to parse JSON dependency data: ${err.message}`); throw new Error(`Failed to parse JSON dependency data: ${err.message}`);
@@ -252,8 +253,6 @@ function run() {
mavenArgs: core.getInput('maven-args') || '', mavenArgs: core.getInput('maven-args') || '',
}; };
const snapshotConfig = { const snapshotConfig = {
includeManifestFile: core.getBooleanInput('snapshot-include-file-name'),
manifestFile: core.getInput('snapshot-dependency-file-name'),
sha: core.getInput('snapshot-sha'), sha: core.getInput('snapshot-sha'),
ref: core.getInput('snapshot-ref'), ref: core.getInput('snapshot-ref'),
}; };
@@ -482,56 +481,45 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.generateDependencyGraph = exports.generateSnapshot = void 0; exports.generateDependencyGraphs = exports.generateSnapshot = void 0;
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const path = __importStar(__nccwpck_require__(1017)); const path = __importStar(__nccwpck_require__(1017));
const dependency_submission_toolkit_1 = __nccwpck_require__(3415); const dependency_submission_toolkit_1 = __nccwpck_require__(3415);
const depgraph_1 = __nccwpck_require__(8047); const depgraph_1 = __nccwpck_require__(8047);
const maven_runner_1 = __nccwpck_require__(7433); const maven_runner_1 = __nccwpck_require__(7433);
const file_utils_1 = __nccwpck_require__(799); const fs_1 = __nccwpck_require__(7147);
const packageData = __nccwpck_require__(2876); const packageData = __nccwpck_require__(2876);
const DEPGRAPH_MAVEN_PLUGIN_VERSION = '4.0.3'; const DEPGRAPH_MAVEN_PLUGIN_VERSION = '4.0.3';
function generateSnapshot(directory, mvnConfig, snapshotConfig) { function generateSnapshot(directory, mvnConfig, snapshotConfig) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
var _a, _b; var _a, _b;
const depgraph = yield generateDependencyGraph(directory, mvnConfig); const depgraphs = yield generateDependencyGraphs(directory, mvnConfig);
const detector = (_a = snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.detector) !== null && _a !== void 0 ? _a : getDetector();
let snapshot = new dependency_submission_toolkit_1.Snapshot(detector, snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.context, snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.job);
snapshot.job.correlator = (snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.correlator)
? snapshotConfig.correlator
: (_b = snapshot.job) === null || _b === void 0 ? void 0 : _b.correlator;
const specifiedRef = getNonEmptyValue(snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.ref);
if (specifiedRef) {
snapshot.ref = specifiedRef;
}
const specifiedSha = getNonEmptyValue(snapshot === null || snapshot === void 0 ? void 0 : snapshot.sha);
if (specifiedSha) {
snapshot.sha = specifiedSha;
}
try { try {
const mavenDependencies = new depgraph_1.MavenDependencyGraph(depgraph); for (const depgraph of depgraphs) {
let manifest; const mavenDependencies = new depgraph_1.MavenDependencyGraph(depgraph);
if (snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.includeManifestFile) { const pomFile = getRepositoryRelativePath(depgraph.filePath);
let pomFile; const manifest = mavenDependencies.createManifest(pomFile);
if (snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.manifestFile) { snapshot.addManifest(manifest);
pomFile = snapshotConfig.manifestFile;
}
else {
// The filepath to the POM needs to be relative to the root of the GitHub repository for the links to work once uploaded
pomFile = getRepositoryRelativePath(path.join(directory, 'pom.xml'));
}
manifest = mavenDependencies.createManifest(pomFile);
} }
else {
manifest = mavenDependencies.createManifest();
}
const detector = (_a = snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.detector) !== null && _a !== void 0 ? _a : getDetector();
const snapshot = new dependency_submission_toolkit_1.Snapshot(detector, snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.context, snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.job);
snapshot.addManifest(manifest);
snapshot.job.correlator = (snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.correlator)
? snapshotConfig.correlator
: (_b = snapshot.job) === null || _b === void 0 ? void 0 : _b.correlator;
const specifiedRef = getNonEmptyValue(snapshotConfig === null || snapshotConfig === void 0 ? void 0 : snapshotConfig.ref);
if (specifiedRef) {
snapshot.ref = specifiedRef;
}
const specifiedSha = getNonEmptyValue(snapshot === null || snapshot === void 0 ? void 0 : snapshot.sha);
if (specifiedSha) {
snapshot.sha = specifiedSha;
}
return snapshot;
} }
catch (err) { catch (err) {
core.error(err); core.error(err);
throw new Error(`Could not generate a snapshot of the dependencies; ${err.message}`); throw new Error(`Could not generate a snapshot of the dependencies; ${err.message}`);
} }
return snapshot;
}); });
} }
exports.generateSnapshot = generateSnapshot; exports.generateSnapshot = generateSnapshot;
@@ -542,71 +530,44 @@ function getDetector() {
version: packageData.version version: packageData.version
}; };
} }
function generateDependencyGraph(directory, config) { function generateDependencyGraphs(directory, config) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
const mvn = new maven_runner_1.MavenRunner(directory, config === null || config === void 0 ? void 0 : config.settingsFile, config === null || config === void 0 ? void 0 : config.ignoreMavenWrapper, config === null || config === void 0 ? void 0 : config.mavenArgs); const mvn = new maven_runner_1.MavenRunner(directory, config === null || config === void 0 ? void 0 : config.settingsFile, config === null || config === void 0 ? void 0 : config.ignoreMavenWrapper, config === null || config === void 0 ? void 0 : config.mavenArgs);
core.startGroup('depgraph-maven-plugin:reactor');
const mavenReactorArguments = [
`com.github.ferstl:depgraph-maven-plugin:${DEPGRAPH_MAVEN_PLUGIN_VERSION}:reactor`,
'-DgraphFormat=json',
'-DoutputFileName=reactor.json'
];
const reactorResults = yield mvn.exec(directory, mavenReactorArguments);
core.info(reactorResults.stdout);
core.info(reactorResults.stderr);
core.endGroup();
if (reactorResults.exitCode !== 0) {
throw new Error(`Failed to successfully generate reactor results with Maven, exit code: ${reactorResults.exitCode}`);
}
core.startGroup('depgraph-maven-plugin:aggregate'); core.startGroup('depgraph-maven-plugin:aggregate');
const mavenAggregateArguments = [ const mavenGraphArguments = [
`com.github.ferstl:depgraph-maven-plugin:${DEPGRAPH_MAVEN_PLUGIN_VERSION}:aggregate`, `com.github.ferstl:depgraph-maven-plugin:${DEPGRAPH_MAVEN_PLUGIN_VERSION}:graph`,
'-DgraphFormat=json', '-DgraphFormat=json',
'-DoutputDirectory=target', `-DoutputFileName=${depgraph_1.depgraphfilename}`,
'-DoutputFileName=aggregate-depgraph.json'
]; ];
const aggregateResults = yield mvn.exec(directory, mavenAggregateArguments); const graphResults = yield mvn.exec(directory, mavenGraphArguments);
core.info(aggregateResults.stdout); core.info(graphResults.stdout);
core.info(aggregateResults.stderr); core.info(graphResults.stderr);
core.endGroup(); core.endGroup();
if (aggregateResults.exitCode !== 0) { if (graphResults.exitCode !== 0) {
throw new Error(`Failed to successfully dependency results with Maven, exit code: ${aggregateResults.exitCode}`); throw new Error(`Failed to successfully dependency results with Maven, exit code: ${graphResults.exitCode}`);
} }
} }
catch (err) { catch (err) {
core.error(err); core.error(err);
throw new Error(`A problem was encountered generating dependency files, please check execution logs for details; ${err.message}`); throw new Error(`A problem was encountered generating dependency files, please check execution logs for details; ${err.message}`);
} }
const targetPath = path.join(directory, 'target'); const graphFiles = getDepgraphFiles(directory, depgraph_1.depgraphfilename);
const isMultiModule = checkForMultiModule(path.join(targetPath, 'reactor.json')); let results = [];
// Now we have the aggregate dependency graph file to process for (const graphFile of graphFiles) {
const aggregateGraphFile = path.join(targetPath, 'aggregate-depgraph.json'); core.debug(`Found depgraph file: ${graphFile}`);
try { try {
return (0, depgraph_1.parseDependencyJson)(aggregateGraphFile, isMultiModule); const depgraph = (0, depgraph_1.parseDependencyJson)(graphFile);
} results.push(depgraph);
catch (err) { }
core.error(err); catch (err) {
throw new Error(`Could not parse maven dependency file, '${aggregateGraphFile}': ${err.message}`); core.error(`Could not parse depgraph file, '${graphFile}': ${err.message}`);
}
} }
return results;
}); });
} }
exports.generateDependencyGraph = generateDependencyGraph; exports.generateDependencyGraphs = generateDependencyGraphs;
function checkForMultiModule(reactorJsonFile) {
const data = (0, file_utils_1.loadFileContents)(reactorJsonFile);
if (data) {
try {
const reactor = JSON.parse(data);
// The reactor file will have an array of artifacts making up the parent and child modules if it is a multi module project
return reactor.artifacts && reactor.artifacts.length > 0;
}
catch (err) {
throw new Error(`Failed to parse reactor JSON payload: ${err.message}`);
}
}
// If no data report that it is not a multi module project
return false;
}
// TODO this is assuming the checkout was made into the base path of the workspace... // TODO this is assuming the checkout was made into the base path of the workspace...
function getRepositoryRelativePath(file) { function getRepositoryRelativePath(file) {
const workspaceDirectory = path.resolve(process.env.GITHUB_WORKSPACE || '.'); const workspaceDirectory = path.resolve(process.env.GITHUB_WORKSPACE || '.');
@@ -631,6 +592,31 @@ function getNonEmptyValue(str) {
} }
return undefined; return undefined;
} }
// getDepgraphFiles should *recursively* find all files that match the filename within the directory
function getDepgraphFiles(directory, filename) {
let files = [];
// debug only
files = (0, fs_1.readdirSync)(directory);
try {
files = (0, fs_1.readdirSync)(directory)
.filter((f) => f === filename)
.map((f) => path.join(directory, f));
}
catch (err) {
core.error(`Could not read depgraphs directory: ${err.message}`);
return [];
}
// recursively find all files that match the filename within the directory
const subdirs = (0, fs_1.readdirSync)(directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const subdir of subdirs) {
const subdirPath = path.join(directory, subdir);
const subdirFiles = getDepgraphFiles(subdirPath, filename);
files = files.concat(subdirFiles);
}
return files;
}
//# sourceMappingURL=snapshot-generator.js.map //# sourceMappingURL=snapshot-generator.js.map
/***/ }), /***/ }),
+1 -1
View File
File diff suppressed because one or more lines are too long
+10 -5
View File
@@ -4,10 +4,10 @@ import { DependencyScope } from '@github/dependency-submission-toolkit';
import { loadFileContents } from './utils/file-utils'; import { loadFileContents } from './utils/file-utils';
export type Depgraph = { export type Depgraph = {
filePath: string,
graphName: string, graphName: string,
artifacts: DepgraphArtifact[], artifacts: DepgraphArtifact[],
dependencies: DepgraphDependency[], dependencies: DepgraphDependency[],
isMultiModule: boolean,
} }
export type DepgraphArtifact = { export type DepgraphArtifact = {
@@ -30,6 +30,8 @@ export type DepgraphDependency = {
resolution: string, resolution: string,
} }
export const depgraphfilename = 'maven-dependency-submission-action-depgraph.json';
export class MavenDependencyGraph { export class MavenDependencyGraph {
private depGraph: Depgraph; private depGraph: Depgraph;
@@ -171,22 +173,25 @@ export class MavenDependencyGraph {
} }
} }
export function parseDependencyJson(file: string, isMultiModule: boolean = false): Depgraph { export function parseDependencyJson(file: string): Depgraph {
const data = loadFileContents(file); const data = loadFileContents(file);
const pomXmlFilepath = file.replace(`target/${depgraphfilename}`, 'pom.xml');
if (!data) { if (!data) {
return { return {
filePath: pomXmlFilepath,
graphName: 'empty', graphName: 'empty',
artifacts: [], artifacts: [],
dependencies: [], dependencies: [],
isMultiModule: isMultiModule
}; };
} }
try { try {
const depGraph: Depgraph = JSON.parse(data); const depGraph: Depgraph = JSON.parse(data);
depGraph.isMultiModule = isMultiModule; return {
return depGraph; ...depGraph,
filePath: pomXmlFilepath,
};
} catch (err: any) { } catch (err: any) {
throw new Error(`Failed to parse JSON dependency data: ${err.message}`); throw new Error(`Failed to parse JSON dependency data: ${err.message}`);
} }
-3
View File
@@ -18,7 +18,6 @@ program.option('-j --job-name <jobName>', 'Optional name for the activity creati
program.option('-i --run-id <jobName>', 'Optional Run ID number for the activity that is providing the graph'); program.option('-i --run-id <jobName>', 'Optional Run ID number for the activity that is providing the graph');
program.option('--snapshot-exclude-file-name', 'exclude the file name in the dependency snapshot report. If false the name of the artifactor from the POM will be used, but any links in GitHub will not work.'); program.option('--snapshot-exclude-file-name', 'exclude the file name in the dependency snapshot report. If false the name of the artifactor from the POM will be used, but any links in GitHub will not work.');
program.option('--snapshot-dependency-file-name <fileName>', 'optional override to specificy the path to the file that the snapshot will be associated with in the repository');
program.option('--detector-name <detectorName>', 'optional name of the detector that generated the snapshot'); program.option('--detector-name <detectorName>', 'optional name of the detector that generated the snapshot');
program.option('--detector-url <detectorUrl>', 'optional URL of the detector that generated the snapshot, but not optional if you specify an detector-name'); program.option('--detector-url <detectorUrl>', 'optional URL of the detector that generated the snapshot, but not optional if you specify an detector-name');
@@ -92,8 +91,6 @@ async function execute() {
sha: opts.sha, sha: opts.sha,
ref: opts.branchRef, ref: opts.branchRef,
manifestFile: opts.snapshotDependencyFileName,
includeManifestFile: !opts.snapshotExcludeFileName,
detector: detector detector: detector
} }
-2
View File
@@ -13,8 +13,6 @@ async function run() {
mavenArgs: core.getInput('maven-args') || '', mavenArgs: core.getInput('maven-args') || '',
} }
const snapshotConfig: SnapshotConfig = { const snapshotConfig: SnapshotConfig = {
includeManifestFile: core.getBooleanInput('snapshot-include-file-name'),
manifestFile: core.getInput('snapshot-dependency-file-name'),
sha: core.getInput('snapshot-sha'), sha: core.getInput('snapshot-sha'),
ref: core.getInput('snapshot-ref'), ref: core.getInput('snapshot-ref'),
} }
+59 -5
View File
@@ -1,6 +1,7 @@
import { getMavenProjectDirectory } from './utils/test-util'; import { getMavenProjectDirectory } from './utils/test-util';
import { generateDependencyGraph, generateSnapshot } from './snapshot-generator'; import { generateDependencyGraphs, generateSnapshot } from './snapshot-generator';
import {describe, it, expect} from 'vitest'; import {describe, it, expect} from 'vitest';
import { Manifest } from '@github/dependency-submission-toolkit';
describe('snapshot-generator', () => { describe('snapshot-generator', () => {
@@ -8,7 +9,11 @@ describe('snapshot-generator', () => {
it('should generate a snapshot for a simple project', async () => { it('should generate a snapshot for a simple project', async () => {
const projectDir = getMavenProjectDirectory('simple'); const projectDir = getMavenProjectDirectory('simple');
const depGraph = await generateDependencyGraph(projectDir); const depGraphs = await generateDependencyGraphs(projectDir);
expect(depGraphs).toBeDefined();
expect(depGraphs.length).toBe(1);
const depGraph = depGraphs[0];
expect(depGraph.dependencies.length).toBe(20); expect(depGraph.dependencies.length).toBe(20);
}, 20000); }, 20000);
}); });
@@ -37,9 +42,50 @@ describe('snapshot-generator', () => {
const projectDir = getMavenProjectDirectory('multi-module-multi-branch'); const projectDir = getMavenProjectDirectory('multi-module-multi-branch');
const snapshot = await generateSnapshot(projectDir); const snapshot = await generateSnapshot(projectDir);
expect(snapshot.manifests['bs-parent']).toBeDefined();
expect(snapshot.detector.version).toBe(version); expect(snapshot.detector.version).toBe(version);
expect(snapshot.manifests['bs-parent'].countDependencies()).toBe(20);
const bsParentManifest = snapshot.manifests['bs-parent'];
expect(bsParentManifest).toBeDefined();
expect(getDirectDependencyPurls(bsParentManifest)).toEqual([
'pkg:maven/junit/junit@4.13?type=jar']);
const bsApplicationManifest = snapshot.manifests['bs-application'];
expect(bsApplicationManifest).toBeDefined();
expect(getDirectDependencyPurls(bsApplicationManifest)).toEqual([
'pkg:maven/com.github.octodemo/bs-library-web@1.0.0-SNAPSHOT?type=jar',
'pkg:maven/junit/junit@4.13?type=jar',
'pkg:maven/org.eclipse.jetty/jetty-server@10.0.10?type=jar',
]);
const bsLibrariesManifest = snapshot.manifests['bs-libraries'];
expect(bsLibrariesManifest).toBeDefined();
expect(getDirectDependencyPurls(bsLibrariesManifest)).toEqual([
'pkg:maven/junit/junit@4.13?type=jar',
'pkg:maven/org.apache.logging.log4j/log4j-api@2.19.0?type=jar',
]);
const bsOtherManifest = snapshot.manifests['bs-other'];
expect(bsOtherManifest).toBeDefined();
expect(getDirectDependencyPurls(bsOtherManifest)).toEqual([
'pkg:maven/junit/junit@4.13?type=jar',
]);
const bsLibraryDatabaseManifest = snapshot.manifests['bs-library-database'];
expect(bsLibraryDatabaseManifest).toBeDefined();
expect(getDirectDependencyPurls(bsLibraryDatabaseManifest)).toEqual([
'pkg:maven/junit/junit@4.13?type=jar',
'pkg:maven/org.apache.logging.log4j/log4j-api@2.19.0?type=jar',
'pkg:maven/org.postgresql/postgresql@42.5.0?type=jar',
'pkg:maven/org.xerial/sqlite-jdbc@3.36.0.3?type=jar',
]);
const bsLibraryWebManifest = snapshot.manifests['bs-library-web'];
expect(bsLibraryWebManifest).toBeDefined();
expect(getDirectDependencyPurls(bsLibraryWebManifest)).toEqual([
'pkg:maven/junit/junit@4.13?type=jar',
'pkg:maven/org.apache.logging.log4j/log4j-api@2.19.0?type=jar',
'pkg:maven/org.eclipse.jetty.http2/http2-http-client-transport@10.0.10?type=jar',
]);
}, 20000); }, 20000);
it('should generate a snapshot for a maven-wrapper project', async () => { it('should generate a snapshot for a maven-wrapper project', async () => {
@@ -94,4 +140,12 @@ describe('snapshot-generator', () => {
expect(snapshot.job.correlator).toBe('jobCorrelator'); expect(snapshot.job.correlator).toBe('jobCorrelator');
}, 20000); }, 20000);
}); });
}); });
function getDirectDependencyPurls(manifest: Manifest): string[] {
return Object.values(manifest.resolved).filter(dep => dep.relationship === 'direct').map(dep => dep.depPackage.packageURL.toString()).sort();
}
function getOtherDependencyPurls(manifest: Manifest): string[] {
return Object.values(manifest.resolved).filter(dep => dep.relationship !== 'direct').map(dep => dep.depPackage.packageURL.toString()).sort();
}
+70 -87
View File
@@ -2,9 +2,10 @@ import * as core from '@actions/core';
import * as path from 'path'; import * as path from 'path';
import { Manifest, Snapshot } from '@github/dependency-submission-toolkit'; import { Manifest, Snapshot } from '@github/dependency-submission-toolkit';
import { Depgraph, MavenDependencyGraph, parseDependencyJson } from './depgraph'; import { Depgraph, MavenDependencyGraph, parseDependencyJson, depgraphfilename } from './depgraph';
import { MavenRunner } from './maven-runner'; import { MavenRunner } from './maven-runner';
import { loadFileContents } from './utils/file-utils'; import { loadFileContents } from './utils/file-utils';
import { readdirSync } from 'fs';
const packageData = require('../package.json'); const packageData = require('../package.json');
const DEPGRAPH_MAVEN_PLUGIN_VERSION = '4.0.3'; const DEPGRAPH_MAVEN_PLUGIN_VERSION = '4.0.3';
@@ -16,8 +17,6 @@ export type MavenConfiguration = {
} }
export type SnapshotConfig = { export type SnapshotConfig = {
includeManifestFile?: boolean;
manifestFile?: string;
context?: any; context?: any;
job?: any; job?: any;
sha?: any; sha?: any;
@@ -31,48 +30,37 @@ export type SnapshotConfig = {
}; };
export async function generateSnapshot(directory: string, mvnConfig?: MavenConfiguration, snapshotConfig?: SnapshotConfig) { export async function generateSnapshot(directory: string, mvnConfig?: MavenConfiguration, snapshotConfig?: SnapshotConfig) {
const depgraph = await generateDependencyGraph(directory, mvnConfig); const depgraphs = await generateDependencyGraphs(directory, mvnConfig);
const detector = snapshotConfig?.detector ?? getDetector();
let snapshot = new Snapshot(detector, snapshotConfig?.context, snapshotConfig?.job);
snapshot.job.correlator = snapshotConfig?.correlator
? snapshotConfig.correlator
: snapshot.job?.correlator;
const specifiedRef = getNonEmptyValue(snapshotConfig?.ref);
if (specifiedRef) {
snapshot.ref = specifiedRef;
}
const specifiedSha = getNonEmptyValue(snapshot?.sha);
if (specifiedSha) {
snapshot.sha = specifiedSha;
}
try { try {
const mavenDependencies = new MavenDependencyGraph(depgraph); for (const depgraph of depgraphs) {
const mavenDependencies = new MavenDependencyGraph(depgraph);
const pomFile = getRepositoryRelativePath(depgraph.filePath);
const manifest = mavenDependencies.createManifest(pomFile);
let manifest: Manifest; snapshot.addManifest(manifest);
if (snapshotConfig?.includeManifestFile) {
let pomFile;
if (snapshotConfig?.manifestFile) {
pomFile = snapshotConfig.manifestFile;
} else {
// The filepath to the POM needs to be relative to the root of the GitHub repository for the links to work once uploaded
pomFile = getRepositoryRelativePath(path.join(directory, 'pom.xml'));
}
manifest = mavenDependencies.createManifest(pomFile);
} else {
manifest = mavenDependencies.createManifest();
} }
const detector = snapshotConfig?.detector ?? getDetector();
const snapshot = new Snapshot(detector, snapshotConfig?.context, snapshotConfig?.job);
snapshot.addManifest(manifest);
snapshot.job.correlator = snapshotConfig?.correlator
? snapshotConfig.correlator
: snapshot.job?.correlator;
const specifiedRef = getNonEmptyValue(snapshotConfig?.ref);
if (specifiedRef) {
snapshot.ref = specifiedRef;
}
const specifiedSha = getNonEmptyValue(snapshot?.sha);
if (specifiedSha) {
snapshot.sha = specifiedSha;
}
return snapshot;
} catch (err: any) { } catch (err: any) {
core.error(err); core.error(err);
throw new Error(`Could not generate a snapshot of the dependencies; ${err.message}`); throw new Error(`Could not generate a snapshot of the dependencies; ${err.message}`);
} }
return snapshot;
} }
function getDetector() { function getDetector() {
@@ -83,75 +71,42 @@ function getDetector() {
}; };
} }
export async function generateDependencyGraph(directory: string, config?: MavenConfiguration): Promise<Depgraph> { export async function generateDependencyGraphs(directory: string, config?: MavenConfiguration): Promise<Depgraph[]> {
try { try {
const mvn = new MavenRunner(directory, config?.settingsFile, config?.ignoreMavenWrapper, config?.mavenArgs); const mvn = new MavenRunner(directory, config?.settingsFile, config?.ignoreMavenWrapper, config?.mavenArgs);
core.startGroup('depgraph-maven-plugin:reactor');
const mavenReactorArguments = [
`com.github.ferstl:depgraph-maven-plugin:${DEPGRAPH_MAVEN_PLUGIN_VERSION}:reactor`,
'-DgraphFormat=json',
'-DoutputFileName=reactor.json'
];
const reactorResults = await mvn.exec(directory, mavenReactorArguments);
core.info(reactorResults.stdout);
core.info(reactorResults.stderr);
core.endGroup();
if (reactorResults.exitCode !== 0) {
throw new Error(`Failed to successfully generate reactor results with Maven, exit code: ${reactorResults.exitCode}`);
}
core.startGroup('depgraph-maven-plugin:aggregate'); core.startGroup('depgraph-maven-plugin:aggregate');
const mavenAggregateArguments = [ const mavenGraphArguments = [
`com.github.ferstl:depgraph-maven-plugin:${DEPGRAPH_MAVEN_PLUGIN_VERSION}:aggregate`, `com.github.ferstl:depgraph-maven-plugin:${DEPGRAPH_MAVEN_PLUGIN_VERSION}:graph`,
'-DgraphFormat=json', '-DgraphFormat=json',
'-DoutputDirectory=target', `-DoutputFileName=${depgraphfilename}`,
'-DoutputFileName=aggregate-depgraph.json'
]; ];
const aggregateResults = await mvn.exec(directory, mavenAggregateArguments); const graphResults = await mvn.exec(directory, mavenGraphArguments);
core.info(aggregateResults.stdout); core.info(graphResults.stdout);
core.info(aggregateResults.stderr); core.info(graphResults.stderr);
core.endGroup(); core.endGroup();
if (aggregateResults.exitCode !== 0) { if (graphResults.exitCode !== 0) {
throw new Error(`Failed to successfully dependency results with Maven, exit code: ${aggregateResults.exitCode}`); throw new Error(`Failed to successfully dependency results with Maven, exit code: ${graphResults.exitCode}`);
} }
} catch (err: any) { } catch (err: any) {
core.error(err); core.error(err);
throw new Error(`A problem was encountered generating dependency files, please check execution logs for details; ${err.message}`); throw new Error(`A problem was encountered generating dependency files, please check execution logs for details; ${err.message}`);
} }
const targetPath = path.join(directory, 'target'); const graphFiles = getDepgraphFiles(directory, depgraphfilename);
const isMultiModule = checkForMultiModule(path.join(targetPath, 'reactor.json')); let results: Depgraph[] = [];
for (const graphFile of graphFiles) {
// Now we have the aggregate dependency graph file to process core.debug(`Found depgraph file: ${graphFile}`);
const aggregateGraphFile = path.join(targetPath, 'aggregate-depgraph.json');
try {
return parseDependencyJson(aggregateGraphFile, isMultiModule);
} catch (err: any) {
core.error(err);
throw new Error(`Could not parse maven dependency file, '${aggregateGraphFile}': ${err.message}`);
}
}
function checkForMultiModule(reactorJsonFile): boolean {
const data = loadFileContents(reactorJsonFile);
if (data) {
try { try {
const reactor = JSON.parse(data); const depgraph = parseDependencyJson(graphFile);
// The reactor file will have an array of artifacts making up the parent and child modules if it is a multi module project results.push(depgraph);
return reactor.artifacts && reactor.artifacts.length > 0;
} catch (err: any) { } catch (err: any) {
throw new Error(`Failed to parse reactor JSON payload: ${err.message}`); core.error(`Could not parse depgraph file, '${graphFile}': ${err.message}`);
} }
} }
return results;
// If no data report that it is not a multi module project
return false;
} }
// TODO this is assuming the checkout was made into the base path of the workspace... // TODO this is assuming the checkout was made into the base path of the workspace...
@@ -182,3 +137,31 @@ function getNonEmptyValue(str?: string) {
} }
return undefined; return undefined;
} }
// getDepgraphFiles should *recursively* find all files that match the filename within the directory
function getDepgraphFiles(directory: string, filename: string): string[] {
let files: string[] = [];
// debug only
files = readdirSync(directory);
try {
files = readdirSync(directory)
.filter((f: string) => f === filename)
.map((f: string) => path.join(directory, f));
} catch (err: any) {
core.error(`Could not read depgraphs directory: ${err.message}`);
return [];
}
// recursively find all files that match the filename within the directory
const subdirs = readdirSync(directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const subdir of subdirs) {
const subdirPath = path.join(directory, subdir);
const subdirFiles = getDepgraphFiles(subdirPath, filename);
files = files.concat(subdirFiles);
}
return files;
}