/
artifacts.ts
192 lines (167 loc) · 5.81 KB
/
artifacts.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import is from '@sindresorhus/is';
import { quote } from 'shlex';
import { dirname, join } from 'upath';
import { TEMPORARY_ERROR } from '../../../constants/error-messages';
import { logger } from '../../../logger';
import { exec } from '../../../util/exec';
import type { ExecOptions } from '../../../util/exec/types';
import { findUpLocal, readLocalFile, writeLocalFile } from '../../../util/fs';
import { getFiles, getRepoStatus } from '../../../util/git';
import { regEx } from '../../../util/regex';
import { scm } from '../../platform/scm';
import {
extraEnv,
extractGradleVersion,
getJavaConstraint,
gradleWrapperFileName,
nullRedirectionCommand,
prepareGradleCommand,
} from '../gradle-wrapper/utils';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
import {
isGcvLockFile,
isGcvPropsFile,
} from './extract/consistent-versions-plugin';
import { isGradleBuildFile } from './utils';
// .lockfile is gradle default lockfile, /versions.lock is gradle-consistent-versions plugin lockfile
function isLockFile(fileName: string): boolean {
return fileName.endsWith('.lockfile') || isGcvLockFile(fileName);
}
async function getUpdatedLockfiles(
oldLockFileContentMap: Record<string, string | null>
): Promise<UpdateArtifactsResult[]> {
const res: UpdateArtifactsResult[] = [];
const status = await getRepoStatus();
for (const modifiedFile of status.modified) {
if (isLockFile(modifiedFile)) {
const newContent = await readLocalFile(modifiedFile, 'utf8');
if (oldLockFileContentMap[modifiedFile] !== newContent) {
res.push({
file: {
type: 'addition',
path: modifiedFile,
contents: newContent,
},
});
}
}
}
return res;
}
async function getSubProjectList(
cmd: string,
execOptions: ExecOptions
): Promise<string[]> {
const subprojects = ['']; // = root project
const subprojectsRegex = regEx(/^[ \t]*subprojects: \[(?<subprojects>.+)\]/m);
const gradleProperties = await exec(`${cmd} properties`, execOptions);
const subprojectsMatch = gradleProperties.stdout.match(subprojectsRegex);
if (subprojectsMatch?.groups?.subprojects) {
const projectRegex = regEx(/project '(?<name>.+?)'/g);
const matches = subprojectsMatch.groups.subprojects.matchAll(projectRegex);
for (const match of matches) {
if (match?.groups?.name) {
subprojects.push(match.groups.name);
}
}
}
return subprojects;
}
async function getGradleVersion(gradlewFile: string): Promise<string | null> {
const propertiesFile = join(
dirname(gradlewFile),
'gradle/wrapper/gradle-wrapper.properties'
);
const properties = await readLocalFile(propertiesFile, 'utf8');
const extractResult = extractGradleVersion(properties ?? '');
return extractResult ? extractResult.version : null;
}
export async function updateArtifacts({
packageFileName,
updatedDeps,
newPackageFileContent,
config,
}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
logger.debug(`gradle.updateArtifacts(${packageFileName})`);
const fileList = await scm.getFileList();
const lockFiles = fileList.filter((file) => isLockFile(file));
if (!lockFiles.length) {
logger.debug('No Gradle dependency lockfiles found - skipping update');
return null;
}
const gradlewName = gradleWrapperFileName();
const gradlewFile = await findUpLocal(gradlewName, dirname(packageFileName));
if (!gradlewFile) {
logger.debug(
'Found Gradle dependency lockfiles but no gradlew - aborting update'
);
return null;
}
if (
config.isLockFileMaintenance &&
(!isGradleBuildFile(packageFileName) ||
dirname(packageFileName) !== dirname(gradlewFile))
) {
logger.trace(
'No build.gradle(.kts) file or not in root project - skipping lock file maintenance'
);
return null;
}
logger.debug('Updating found Gradle dependency lockfiles');
try {
const oldLockFileContentMap = await getFiles(lockFiles);
await prepareGradleCommand(gradlewFile);
let cmd = `${gradlewName} --console=plain -q`;
const execOptions: ExecOptions = {
cwdFile: gradlewFile,
docker: {},
extraEnv,
toolConstraints: [
{
toolName: 'java',
constraint:
config.constraints?.java ??
getJavaConstraint(await getGradleVersion(gradlewFile)),
},
],
};
const subprojects = await getSubProjectList(cmd, execOptions);
cmd += ` ${subprojects
.map((project) => project + ':dependencies')
.map(quote)
.join(' ')}`;
if (config.isLockFileMaintenance || isGcvPropsFile(packageFileName)) {
cmd += ' --write-locks';
} else {
const updatedDepNames = updatedDeps
.map(({ depName, packageName }) => packageName ?? depName)
.filter(is.nonEmptyStringAndNotWhitespace);
cmd += ` --update-locks ${updatedDepNames.map(quote).join(',')}`;
}
// `./gradlew :dependencies` command can output huge text due to `:dependencies`
// that renders dependency graphs. Given the output can exceed `ExecOptions.maxBuffer` size,
// drop stdout from the command.
//
// Note: Windows without docker doesn't supported this yet
const nullRedirection = nullRedirectionCommand();
cmd += nullRedirection;
await writeLocalFile(packageFileName, newPackageFileContent);
await exec(cmd, execOptions);
const res = await getUpdatedLockfiles(oldLockFileContentMap);
logger.debug('Returning updated Gradle dependency lockfiles');
return res.length > 0 ? res : null;
} catch (err) {
if (err.message === TEMPORARY_ERROR) {
throw err;
}
logger.debug({ err }, 'Error while updating Gradle dependency lockfiles');
return [
{
artifactError: {
lockFile: packageFileName,
stderr: err.message,
},
},
];
}
}