diff --git a/README.md b/README.md
index db2c52c..0039ac6 100644
--- a/README.md
+++ b/README.md
@@ -341,6 +341,11 @@ following keys for setting custom paths for the said executables.
EXHORT_MVN_PATH |
+Maven |
+maven |
+EXHORT_PREFER_MVNW |
+
+
NPM |
npm |
EXHORT_NPM_PATH |
@@ -385,6 +390,11 @@ following keys for setting custom paths for the said executables.
gradle |
EXHORT_GRADLE_PATH |
+
+Gradle |
+gradle |
+EXHORT_PREFER_GRADLEW |
+
#### Match Manifest Versions Feature
@@ -463,7 +473,7 @@ Need to set environment variable/option - `EXHORT_PIP_USE_DEP_TREE` to true.
### Known Issues
- For pip requirements.txt - It's been observed that for python versions 3.11.x, there might be slowness for invoking the analysis.
- If you encounter a performance issue with version >= 3.11.x, kindly try to set environment variable/option `EXHORT_PIP_USE_DEP_TREE`=true, before calling the analysis.
+ If you encounter a performance issue with version >= 3.11.x, kindly try to set environment variable/option `EXHORT_PIP_USE_DEP_TREE=true`, before calling the analysis.
- For maven pom.xml, it has been noticed that using java 17 might cause stack analysis to hang forever.
diff --git a/src/providers/base_java.js b/src/providers/base_java.js
index c9cbde8..c626104 100644
--- a/src/providers/base_java.js
+++ b/src/providers/base_java.js
@@ -1,6 +1,7 @@
import { PackageURL } from 'packageurl-js'
-
-import { invokeCommand } from "../tools.js"
+import { getCustomPath, getGitRootDir, getWrapperPreference, invokeCommand } from "../tools.js"
+import fs from 'node:fs'
+import path from 'node:path'
/** @typedef {import('../provider').Provider} */
@@ -21,6 +22,19 @@ export default class Base_Java {
DEP_REGEX = /(([-a-zA-Z0-9._]{2,})|[0-9])/g
CONFLICT_REGEX = /.*omitted for conflict with (\S+)\)/
+ globalBinary
+ localWrapper
+
+ /**
+ *
+ * @param {string} globalBinary name of the global binary
+ * @param {string} localWrapper name of the local wrapper filename
+ */
+ constructor(globalBinary, localWrapper) {
+ this.globalBinary = globalBinary
+ this.localWrapper = localWrapper
+ }
+
/**
* Recursively populates the SBOM instance with the parsed graph
* @param {string} src - Source dependency to start the calculations from
@@ -107,10 +121,72 @@ export default class Base_Java {
return new PackageURL('maven', group, artifact, version, undefined, undefined);
}
- /** this method invokes command string in a process in a synchronous way.
+ /** This method invokes command string in a process in a synchronous way.
+ * Exists for stubbing in tests.
* @param bin - the command to be invoked
* @param args - the args to pass to the binary
* @protected
*/
_invokeCommand(bin, args, opts={}) { return invokeCommand(bin, args, opts) }
+
+ /**
+ *
+ * @param {string} manifestPath
+ * @param {{}} opts
+ * @returns string
+ */
+ selectToolBinary(manifestPath, opts) {
+ const toolPath = getCustomPath(this.globalBinary, opts)
+
+ const useWrapper = getWrapperPreference(toolPath, opts)
+ if (useWrapper) {
+ const wrapper = this.traverseForWrapper(manifestPath)
+ if (wrapper !== undefined) {
+ try {
+ this._invokeCommand(wrapper, ['--version'])
+ } catch (error) {
+ throw new Error(`failed to check for ${this.localWrapper}`, {cause: error})
+ }
+ return wrapper
+ }
+ }
+ // verify tool is accessible, if wrapper was not requested or not found
+ try {
+ this._invokeCommand(toolPath, ['--version'])
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ throw new Error((useWrapper ? `${this.localWrapper} not found and ` : '') + `${this.globalBinary === 'mvn' ? 'maven' : 'gradle'} not found at ${toolPath}`)
+ } else {
+ throw new Error(`failed to check for ${this.globalBinary === 'mvn' ? 'maven' : 'gradle'}`, {cause: error})
+ }
+ }
+ return toolPath
+ }
+
+ /**
+ *
+ * @param {string} startingManifest - the path of the manifest from which to start searching for the wrapper
+ * @param {string} repoRoot - the root of the repository at which point to stop searching for mvnw, derived via git if unset and then fallsback
+ * to the root of the drive the manifest is on (assumes absolute path is given)
+ * @returns {string|undefined}
+ */
+ traverseForWrapper(startingManifest, repoRoot = undefined) {
+ repoRoot = repoRoot || getGitRootDir(path.resolve(path.dirname(startingManifest))) || path.parse(path.resolve(startingManifest)).root
+
+ const wrapperName = this.localWrapper;
+ const wrapperPath = path.join(path.resolve(path.dirname(startingManifest)), wrapperName);
+
+ try {
+ fs.accessSync(wrapperPath, fs.constants.X_OK)
+ } catch(error) {
+ if (error.code === 'ENOENT') {
+ if (path.resolve(path.dirname(startingManifest)) === repoRoot) {
+ return undefined
+ }
+ return this.traverseForWrapper(path.resolve(path.dirname(startingManifest)), repoRoot)
+ }
+ throw new Error(`failure searching for ${this.localWrapper}`, {cause: error})
+ }
+ return wrapperPath
+ }
}
diff --git a/src/providers/java_gradle.js b/src/providers/java_gradle.js
index 5b8e4a6..4b01fc1 100644
--- a/src/providers/java_gradle.js
+++ b/src/providers/java_gradle.js
@@ -1,5 +1,4 @@
import fs from 'node:fs'
-import {getCustomPath} from "../tools.js";
import path from 'node:path'
import Sbom from '../sbom.js'
import { EOL } from 'os'
@@ -88,6 +87,9 @@ const stackAnalysisConfigs = ["runtimeClasspath","compileClasspath"];
* This class provides common functionality for Groovy and Kotlin DSL files.
*/
export default class Java_gradle extends Base_java {
+ constructor() {
+ super('gradle', 'gradlew' + (process.platform === 'win32' ? '.bat' : ''))
+ }
_getManifestName() {
throw new Error('implement getManifestName method')
@@ -154,7 +156,7 @@ export default class Java_gradle extends Base_java {
* @private
*/
#createSbomStackAnalysis(manifest, opts = {}) {
- let content = this.#getDependencies(manifest)
+ let content = this.#getDependencies(manifest, opts)
let properties = this.#extractProperties(manifest, opts)
// read dependency tree from temp file
if (process.env["EXHORT_DEBUG"] === "true") {
@@ -192,7 +194,7 @@ export default class Java_gradle extends Base_java {
* @return {string} string content of the properties
*/
#getProperties(manifestPath, opts) {
- let gradle = getCustomPath("gradle", opts);
+ let gradle = this.selectToolBinary(manifestPath, opts)
try {
let properties = this._invokeCommand(gradle, ['properties'], {cwd: path.dirname(manifestPath)})
return properties.toString()
@@ -208,7 +210,7 @@ export default class Java_gradle extends Base_java {
* @private
*/
#getSbomForComponentAnalysis(manifestPath, opts = {}) {
- let content = this.#getDependencies(manifestPath)
+ let content = this.#getDependencies(manifestPath, opts)
let properties = this.#extractProperties(manifestPath, opts)
let configurationNames = componentAnalysisConfigs
@@ -234,8 +236,8 @@ export default class Java_gradle extends Base_java {
* @private
*/
- #getDependencies(manifest) {
- const gradle = getCustomPath("gradle")
+ #getDependencies(manifest, opts={}) {
+ const gradle = this.selectToolBinary(manifest, opts)
try {
const commandResult = this._invokeCommand(gradle, ['dependencies'], {cwd: path.dirname(manifest)})
return commandResult.toString()
diff --git a/src/providers/java_maven.js b/src/providers/java_maven.js
index 9665d14..33809a3 100644
--- a/src/providers/java_maven.js
+++ b/src/providers/java_maven.js
@@ -1,6 +1,5 @@
import { XMLParser } from 'fast-xml-parser'
import fs from 'node:fs'
-import { getCustomPath, getGitRootDir, getWrapperPreference } from "../tools.js";
import os from 'node:os'
import path from 'node:path'
import Sbom from '../sbom.js'
@@ -17,6 +16,9 @@ import Base_java, { ecosystem_maven } from "./base_java.js";
/** @typedef {{groupId: string, artifactId: string, version: string, scope: string, ignore: boolean}} Dependency */
export default class Java_maven extends Base_java {
+ constructor() {
+ super('mvn', 'mvnw' + (process.platform === 'win32' ? '.cmd' : ''))
+ }
/**
* @param {string} manifestName - the subject manifest name-type
@@ -71,7 +73,7 @@ export default class Java_maven extends Base_java {
* @private
*/
#createSbomStackAnalysis(manifest, opts = {}) {
- const mvn = this.#selectMvnRuntime(manifest, opts)
+ const mvn = this.selectToolBinary(manifest, opts)
// clean maven target
try {
@@ -136,7 +138,7 @@ export default class Java_maven extends Base_java {
* @private
*/
#getSbomForComponentAnalysis(manifestPath, opts = {}) {
- const mvn = this.#selectMvnRuntime(manifestPath, opts)
+ const mvn = this.selectToolBinary(manifestPath, opts)
const tmpEffectivePom = path.resolve(path.join(path.dirname(manifestPath), 'effective-pom.xml'))
const targetPom = manifestPath
@@ -201,63 +203,6 @@ export default class Java_maven extends Base_java {
return rootDependency
}
- #selectMvnRuntime(manifestPath, opts) {
- // get custom maven path
- let mvn = getCustomPath('mvn', opts)
-
- // check if mvnw is preferred and available
- let useMvnw = getWrapperPreference('mvn', opts)
- if (useMvnw) {
- const mvnw = this.#traverseForMvnw(manifestPath)
- if (mvnw !== undefined) {
- try {
- this._invokeCommand(mvnw, ['--version'])
- } catch (error) {
- throw new Error(`failed to check for mvnw`, {cause: error})
- }
- return mvnw
- }
- }
- // verify maven is accessible, if mvnw was not requested or not found
- try {
- this._invokeCommand(mvn, ['--version'])
- } catch (error) {
- if (error.code === 'ENOENT') {
- throw new Error((useMvnw ? 'mvnw not found and ' : '') + `maven not accessible at "${mvn}"`)
- } else {
- throw new Error(`failed to check for maven`, {cause: error})
- }
- }
- return mvn
- }
-
- /**
- *
- * @param {string} startingManifest - the path of the manifest from which to start searching for mvnw
- * @param {string} repoRoot - the root of the repository at which point to stop searching for mvnw, derived via git if unset and then fallsback
- * to the root of the drive the manifest is on (assumes absolute path is given)
- * @returns
- */
- #traverseForMvnw(startingManifest, repoRoot = undefined) {
- repoRoot = repoRoot || getGitRootDir(path.resolve(path.dirname(startingManifest))) || path.parse(path.resolve(startingManifest)).root
-
- const wrapperName = 'mvnw' + (process.platform === 'win32' ? '.cmd' : '');
- const wrapperPath = path.join(path.resolve(path.dirname(startingManifest)), wrapperName);
-
- try {
- fs.accessSync(wrapperPath, fs.constants.X_OK)
- } catch(error) {
- if (error.code === 'ENOENT') {
- if (path.resolve(path.dirname(startingManifest)) === repoRoot) {
- return undefined
- }
- return this.#traverseForMvnw(path.resolve(path.dirname(startingManifest)), repoRoot)
- }
- throw new Error(`failure searching for mvnw`, {cause: error})
- }
- return wrapperPath
- }
-
/**
* Get a list of dependencies with marking of dependencies commented with .
* @param {string} manifest - path for pom.xml
diff --git a/test/it/end-to-end.js b/test/it/end-to-end.js
index a5a34a8..12aa915 100644
--- a/test/it/end-to-end.js
+++ b/test/it/end-to-end.js
@@ -52,7 +52,7 @@ suite('Integration Tests', () => {
let pomPath = `test/it/test_manifests/${packageManager}/${manifestName}`
let providedDataForStack = await index.stackAnalysis(pomPath)
console.log(JSON.stringify(providedDataForStack,null , 4))
- let providers = ["osv"]
+ let providers = ["tpa"]
providers.forEach(provider => expect(extractTotalsGeneralOrFromProvider(providedDataForStack, provider)).greaterThan(0))
// TODO: if sources doesn't exist, add "scanned" instead
// python transitive count for stack analysis is awaiting fix in exhort backend
@@ -84,7 +84,7 @@ suite('Integration Tests', () => {
reportParsedFromHtml = JSON.parse("{" + startOfJson.substring(0,startOfJson.indexOf("};") + 1))
reportParsedFromHtml = reportParsedFromHtml.report
} finally {
- parsedStatusFromHtmlOsvNvd = reportParsedFromHtml.providers["osv"].status
+ parsedStatusFromHtmlOsvNvd = reportParsedFromHtml.providers["tpa"].status
expect(parsedStatusFromHtmlOsvNvd.code).equals(200)
parsedScannedFromHtml = reportParsedFromHtml.scanned
expect( typeof html).equals("string")
@@ -101,7 +101,7 @@ suite('Integration Tests', () => {
expect(analysisReport.scanned.total).greaterThan(0)
expect(analysisReport.scanned.transitive).equal(0)
- let providers = ["osv"]
+ let providers = ["tpa"]
providers.forEach(provider => expect(extractTotalsGeneralOrFromProvider(analysisReport, provider)).greaterThan(0))
providers.forEach(provider => expect(analysisReport.providers[provider].status.code).equals(200))
}).timeout(20000);