Skip to content

Commit

Permalink
Merge pull request #191 from snyk/feat/improve-accuracy
Browse files Browse the repository at this point in the history
feat: improve gradle plugin accuracy
  • Loading branch information
muscar authored Oct 29, 2021
2 parents d545c85 + 3043f0b commit 9989b21
Show file tree
Hide file tree
Showing 17 changed files with 770 additions and 186 deletions.
113 changes: 75 additions & 38 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ export interface GradleInspectOptions {
reachableVulns?: boolean;
callGraphBuilderTimeout?: number;
initScript?: string;

// Best practices for recent version of Gradle are to avoid configs that are
// both resolvable _and_ consumable, i.e. canBeResolved == true &&
// canBeConsumed == true. Legacy projects will have these, so CLI scan might
// report less dependencies than the users expect. This flag allows the users
// to relax the configuration filtering strategy. This runs the risk that
// there will be configuration variant conflicts, but the user should know
// what they get themselves into.
// See: https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:resolvable-consumable-configs
gradleAcceptLegacyConfigRoles?: boolean;
}

type Options = api.InspectOptions & GradleInspectOptions;
Expand Down Expand Up @@ -404,8 +414,14 @@ async function printIfEcho(line: string) {
}
}

async function getInjectedScriptPath(): Promise<{
injectedScripPath: string;
function getPluginFileName(options: Options): string {
return options.gradleAcceptLegacyConfigRoles
? 'init.gradle'
: 'legacy-init.gradle';
}

async function injectedPlugin(scriptName: string): Promise<{
injectedPluginFilePath: string;
cleanupCallback?: () => void;
}> {
let initGradleAsset: string | null = null;
Expand All @@ -414,22 +430,22 @@ async function getInjectedScriptPath(): Promise<{
// running from ./dist
// path.join call has to be exactly in this format, needed by "pkg" to build a standalone Snyk CLI binary:
// https://www.npmjs.com/package/pkg#detecting-assets-in-source-code
initGradleAsset = path.join(__dirname, '../lib/init.gradle');
initGradleAsset = path.join(__dirname, '../lib', scriptName);
} else if (/index.ts$/.test(__filename)) {
// running from ./lib
initGradleAsset = path.join(__dirname, 'init.gradle');
initGradleAsset = path.join(__dirname, scriptName);
} else {
throw new Error('Cannot locate Snyk init.gradle script');
throw new Error(`Cannot locate Snyk ${scriptName} script`);
}

// We could be running from a bundled CLI generated by `pkg`.
// The Node filesystem in that case is not real: https://github.com/zeit/pkg#snapshot-filesystem
// Copying the injectable script into a temp file.
try {
const tmpInitGradle = tmp.fileSync({ postfix: '-init.gradle' });
const tmpInitGradle = tmp.fileSync({ postfix: `-${scriptName}` });
fs.writeSync(tmpInitGradle.fd, fs.readFileSync(initGradleAsset));
return {
injectedScripPath: tmpInitGradle.name,
injectedPluginFilePath: tmpInitGradle.name,
cleanupCallback: tmpInitGradle.removeCallback,
};
} catch (error) {
Expand Down Expand Up @@ -488,12 +504,10 @@ function getVersionBuildInfo(
}
}

async function getAllDeps(
async function getGradleVersion(
root: string,
targetFile: string,
options: Options,
): Promise<JsonDepsScriptResult> {
const command = getCommand(root, targetFile);
command: string,
): Promise<string> {
debugLog('`gradle -v` command run: ' + command);
let gradleVersionOutput = '[COULD NOT RUN gradle -v]';
try {
Expand All @@ -503,39 +517,62 @@ async function getAllDeps(
} catch (_) {
// intentionally empty
}
return gradleVersionOutput;
}

if (gradleVersionOutput.match(/Gradle 1/)) {
throw new Error('Gradle 1.x is not supported');
}

const { injectedScripPath, cleanupCallback } = await getInjectedScriptPath();
const args = buildArgs(root, targetFile, injectedScripPath, options);
async function getAllDepsWithPlugin(
root: string,
targetFile: string,
options: Options,
): Promise<JsonDepsScriptResult> {
const command = getCommand(root, targetFile);
const pluginFileName = getPluginFileName(options);
const { injectedPluginFilePath, cleanupCallback } = await injectedPlugin(
pluginFileName,
);
const args = buildArgs(root, targetFile, injectedPluginFilePath, options);

const fullCommandText = 'gradle command: ' + command + ' ' + args.join(' ');
debugLog('Executing ' + fullCommandText);

const stdoutText = await subProcess.execute(
command,
args,
{ cwd: root },
printIfEcho,
);
if (cleanupCallback) {
cleanupCallback();
}
const extractedJSON = extractJsonFromScriptOutput(stdoutText);
const jsonAttrsPretty = getGradleAttributesPretty(stdoutText);
logger(
`The following attributes and their possible values were found in your configurations: ${jsonAttrsPretty}`,
);

return extractedJSON;
}

async function getAllDeps(
root: string,
targetFile: string,
options: Options,
): Promise<JsonDepsScriptResult> {
const command = getCommand(root, targetFile);
const gradleVersion = await getGradleVersion(root, command);
if (gradleVersion.match(/Gradle 1/)) {
throw new Error('Gradle 1.x is not supported');
}

try {
const stdoutText = await subProcess.execute(
command,
args,
{ cwd: root },
printIfEcho,
);
if (cleanupCallback) {
cleanupCallback();
}
const extractedJSON = extractJsonFromScriptOutput(stdoutText);
const jsonAttrsPretty = getGradleAttributesPretty(stdoutText);
logger(
`The following attributes and their possible values were found in your configurations: ${jsonAttrsPretty}`,
);
const versionBuildInfo = getVersionBuildInfo(gradleVersionOutput);
const extractedJSON = await getAllDepsWithPlugin(root, targetFile, options);
const versionBuildInfo = getVersionBuildInfo(gradleVersion);
if (versionBuildInfo) {
extractedJSON.versionBuildInfo = versionBuildInfo;
}

return await processProjectsInExtractedJSON(root, extractedJSON);
} catch (error0) {
const error: Error = error0;
} catch (err) {
const error: Error = err;
const gradleErrorMarkers = /^\s*>\s.*$/;
const gradleErrorEssence = error.message
.split('\n')
Expand All @@ -544,7 +581,7 @@ async function getAllDeps(

const orange = chalk.rgb(255, 128, 0);
const blackOnYellow = chalk.bgYellowBright.black;
gradleVersionOutput = orange(gradleVersionOutput);
const gradleVersionOutput = orange(gradleVersion);
let mainErrorMessage = `Error running Gradle dependency analysis.
Please ensure you are calling the \`snyk\` command with correct arguments.
Expand Down Expand Up @@ -607,7 +644,6 @@ to
)}
${blackOnYellow('===== DEBUG INFORMATION START =====')}
${orange(fullCommandText)}
${orange(gradleVersionOutput)}
${orange(error.message)}
${blackOnYellow('===== DEBUG INFORMATION END =====')}
Expand Down Expand Up @@ -803,4 +839,5 @@ export const exportsForTests = {
getVersionBuildInfo,
toCamelCase,
getGradleAttributesPretty,
getPluginFileName,
};
Loading

0 comments on commit 9989b21

Please sign in to comment.