Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Gradle related provider add/list/upgrade #3080

Merged
merged 3 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 21 additions & 44 deletions packages/@cdktf/cli-core/src/lib/dependencies/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
Errors,
logger,
isGradleProject,
getGradleDependencies,
getDependencyInformationFromLine,
} from "@cdktf/commons";
import { existsSync } from "fs-extra";
import path from "path";
Expand Down Expand Up @@ -649,67 +651,42 @@ class GradlePackageManager extends JavaPackageManager {
packageName: string,
packageVersion = "latest.release"
): Promise<void> {
const dependencyRegex =
/dependencies {(\s*(implementation|api) ('|")(\S)*('|")\s*\n)*}/gm;

const buildGradlePath = path.join(this.workingDirectory, "build.gradle");
const buildGradle = await fs.readFile(buildGradlePath, "utf8");
const buildGradleLines = buildGradle.split(/\r?\n/);

const dependencies = dependencyRegex.exec(buildGradle);
if (!dependencies || !dependencies.index) {
const dependenciesRegex = /dependencies\s+\{/i;
const dependencyBlockStart = buildGradleLines.findIndex((line) =>
dependenciesRegex.test(line)
);
if (dependencyBlockStart === -1) {
throw Errors.Usage(
"Could not find dependencies section in the build.gradle"
);
}

// Find the index of the opening bracket of the dependencies section
const dependenciesStartIndex = buildGradle.indexOf("{", dependencies.index);
const newBuildGradle =
buildGradle.slice(0, dependenciesStartIndex + 1) +
`\n implementation '${packageName}:${packageVersion}'` +
buildGradle.slice(dependenciesStartIndex + 1);
await fs.writeFile(buildGradlePath, newBuildGradle);
const newPackageDependency = `\timplementation '${packageName}:${packageVersion}'`;
buildGradleLines.splice(dependencyBlockStart + 1, 0, newPackageDependency);
await fs.writeFile(buildGradlePath, buildGradleLines.join("\n"));
}

public async listProviderPackages(): Promise<
{ name: string; version: string }[]
> {
const dependencyRegex =
/dependencies {(\s*(implementation|api) ('|")(\S)*('|")\s*\n)*}/gm;

const buildGradlePath = path.join(this.workingDirectory, "build.gradle");
const buildGradle = await fs.readFile(buildGradlePath, "utf8");
const dependencies = buildGradle.match(dependencyRegex);
const dependencies = await getGradleDependencies();
if (!dependencies) {
throw Errors.Usage(
"Could not find dependencies section in the build.gradle"
);
}
const dependencyBody = dependencies[0];
// e.g. implementation 'com.hashicorp:cdktf-provider-google:8.0.8'
// or api 'com.hashicorp:cdktf-provider-google:8.0.8'
// We need to get the name and version from this
const dependencyRegex2 = /(\S)*('|")/gm;
const dependencyLines = dependencyBody.match(dependencyRegex2);
if (!dependencyLines) {
throw Errors.Usage(
"Could not find dependencies section in the build.gradle"
);
throw Errors.Usage("Could not find any dependencies");
}

function removeQuotes(str: string): string {
return str.replace(/['"]+/g, "");
}

const dependencyList = dependencyLines
.map((line) => {
const dependency = line.split(":");
return {
name: `${removeQuotes(dependency[0])}.${dependency[1]}`,
version: removeQuotes(dependency[2]),
};
const dependencyList = dependencies
.map((line) => getDependencyInformationFromLine(line))
.filter((dep) => {
if (!dep) {
return false;
}
return dep.name.includes("cdktf-provider-");
})
.filter(({ name }) => name.startsWith("com.hashicorp.cdktf-provider-"));
.map((dep) => ({ name: dep!.name, version: dep!.version }));

return dependencyList;
}
Expand Down
63 changes: 1 addition & 62 deletions packages/@cdktf/commons/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { exec } from "./util";
import { terraformVersion } from "./terraform";
import { DISPLAY_VERSION } from "./version";
import { pathExists } from "fs-extra";
import { isGradleProject } from "./gradle";
import { getGradlePackageVersion, isGradleProject } from "./gradle";

export function getLanguage(projectPath = process.cwd()): string | undefined {
try {
Expand Down Expand Up @@ -336,67 +336,6 @@ async function getMavenPackageVersion(packageName: string) {
return versionLine.substring(versionStart, versionEnd);
}

/*
* Example output:
implementation - Implementation dependencies for the 'main' feature. (n)
+--- com.hashicorp:cdktf:0.18.0 (n)
+--- software.constructs:constructs:10.0.25 (n)
+--- junit:junit:4.13.2 (n)
\--- org.junit.jupiter:junit-jupiter:5.8.0 (n)
*/
async function getGradlePackageVersion(packageName: string) {
const translationMap: Record<string, string> = {
jsii: "jsii-runtime",
};
const gradlePackageName = translationMap[packageName] || packageName;

let output;
try {
output = await exec("gradle", ["dependencies", "--console=plain"], {
env: process.env,
});
} catch (e) {
logger.debug(`Unable to run 'gradle dependencies': ${e}`);
return undefined;
}

const lines = output.split(/\r\n|\r|\n/);

// find the implementation section
const implementationSection = lines.findIndex((line) =>
line.includes("implementation - ")
);
if (implementationSection === -1) {
logger.debug(
`Unable to find implementation section in output of 'gradle dependencies': ${output}`
);
return undefined;
}

// loop through the subsequent lines to find the one starting with package name
for (let i = implementationSection + 1; i < lines.length; i++) {
const line = lines[i];
if (line.includes(`:${gradlePackageName}:`)) {
const packageNameRegex = /^.*\s+[^:]+:[^:]+:([^\s]+)/;
const matches = line.match(packageNameRegex);
if (!matches) {
logger.debug(
"Unexpected format for gradle build. Please file an issue at https://cdk.tf/bug"
);
return undefined;
}

return matches[1];
}

if (line.trim() === "") {
break;
}
}

return undefined;
}

async function getJavaPackageVersion(packageName: string) {
if (isGradleProject("./")) {
return getGradlePackageVersion(packageName);
Expand Down
127 changes: 127 additions & 0 deletions packages/@cdktf/commons/src/gradle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import path from "path";
import fs from "fs-extra";
import { logger } from "./logging";
import { exec } from "./util";

export function isGradleProject(workingDirectory: string): boolean {
const buildGradlePath = path.join(workingDirectory, "build.gradle");
Expand All @@ -13,6 +15,131 @@ export function isGradleProject(workingDirectory: string): boolean {
fs.accessSync(buildGradlePath, fs.constants.R_OK | fs.constants.W_OK);
return true;
} catch {
logger.debug(`No build.gradle found at ${buildGradlePath}`);
return false;
}
}

export async function getGradleDependencies() {
let output;
try {
output = await exec("gradle", ["dependencies", "--console=plain"], {
env: process.env,
});
} catch (e) {
logger.debug(`Unable to run 'gradle dependencies': ${e}`);
return undefined;
}

const lines = output.split(/\r\n|\r|\n/);

// find the implementation section
const implementationSection = lines.findIndex((line) =>
line.includes("implementation - ")
);
if (implementationSection === -1) {
logger.debug(
`Unable to find implementation section in output of 'gradle dependencies': ${output}`
);
return undefined;
}

const emptyLineRegex = /^\s*$/;
const implementationSectionLines = lines.slice(implementationSection + 1);
const sectionEnd = implementationSectionLines.findIndex((line) =>
emptyLineRegex.test(line)
);
const implementationLines = implementationSectionLines.slice(0, sectionEnd);

// find the api section
const apiSection = lines.findIndex((line) => line.includes("api - "));
if (apiSection === -1) {
logger.debug(
`Unable to find api section in output of 'gradle dependencies': ${output}`
);
return undefined;
}

const apiSectionLines = lines.slice(apiSection + 1);
const apiSectionEnd = apiSectionLines.findIndex((line) =>
emptyLineRegex.test(line)
);

const apiLines = apiSectionLines.slice(0, apiSectionEnd);

const prefixRegex = /^\s*[+\\-]+\s*/;
const suffixRegex = /\s+\([nc*]\)\s*$/;

return [...implementationLines, ...apiLines]
.filter((line) => line !== "No dependencies")
.map((line) => line.replace(prefixRegex, "").replace(suffixRegex, ""));
}

export type DependencyInformation = {
group: string;
name: string;
version: string;
};
export function getDependencyInformationFromLine(
line: string
): DependencyInformation | undefined {
const packageNameRegex = /^\s*([^:]+):([^:]+)(?::([^\s]+))?/;
const matches = line.match(packageNameRegex);
if (!matches) {
logger.debug(
"Unexpected format for gradle build. Please file an issue at https://cdk.tf/bug"
);
return undefined;
}

if (matches[3] === undefined) {
return {
group: "",
name: matches[1],
version: matches[2],
};
}

return {
group: matches[1],
name: matches[2],
version: matches[3],
};
}

/*
* Example output:
implementation - Implementation dependencies for the 'main' feature. (n)
+--- com.hashicorp:cdktf:0.18.0 (n)
+--- software.constructs:constructs:10.0.25 (n)
+--- junit:junit:4.13.2 (n)
\--- org.junit.jupiter:junit-jupiter:5.8.0 (n)
*/
export async function getGradlePackageVersion(packageName: string) {
const translationMap: Record<string, string> = {
jsii: "jsii-runtime",
};
const gradlePackageName = translationMap[packageName] || packageName;
logger.debug(
"Running 'gradle dependencies' to find package version",
gradlePackageName
);

const lines = await getGradleDependencies();
if (!lines || lines.length === 0) {
return undefined;
}

// loop through the subsequent lines to find the one starting with package name
for (const line of lines) {
if (line.includes(`:${gradlePackageName}:`)) {
const dep = getDependencyInformationFromLine(line);
if (dep) {
return dep.version;
}
return undefined;
}
}

return undefined;
}