-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathconsistent-versions-plugin.ts
More file actions
218 lines (202 loc) · 7.85 KB
/
consistent-versions-plugin.ts
File metadata and controls
218 lines (202 loc) · 7.85 KB
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import { logger } from '../../../../logger/index.ts';
import * as fs from '../../../../util/fs/index.ts';
import { newlineRegex, regEx } from '../../../../util/regex.ts';
import { coerceString } from '../../../../util/string.ts';
import type { PackageDependency } from '../../types.ts';
import type { GradleManagerData } from '../types.ts';
import { isDependencyString, versionLikeSubstring } from '../utils.ts';
export const VERSIONS_PROPS = 'versions.props';
export const VERSIONS_LOCK = 'versions.lock';
export const LOCKFIlE_HEADER_TEXT = regEx(
/^# Run \.\/gradlew (?:--write-locks|writeVersionsLock|writeVersionsLocks) to regenerate this file/,
);
/**
* Determines if Palantir gradle-consistent-versions is in use, https://github.com/palantir/gradle-consistent-versions.
* Both `versions.props` and `versions.lock` must exist and the special header line of lock file must match
*
* @param versionsPropsFilename is the full file name path of `versions.props`
* @param fileContents map with file contents of all files
*/
export function usesGcv(
versionsPropsFilename: string,
fileContents: Record<string, string | null>,
): boolean {
const versionsLockFile: string = fs.getSiblingFileName(
versionsPropsFilename,
VERSIONS_LOCK,
);
return !!fileContents[versionsLockFile]?.match(LOCKFIlE_HEADER_TEXT);
}
/**
* Confirms whether the provided file name is the props file
*/
export function isGcvPropsFile(fileName: string): boolean {
return fileName === VERSIONS_PROPS || fileName.endsWith(`/${VERSIONS_PROPS}`);
}
/**
* Confirms whether the provided file name is the lock file
*/
export function isGcvLockFile(fileName: string): boolean {
return fileName === VERSIONS_LOCK || fileName.endsWith(`/${VERSIONS_LOCK}`);
}
/**
* Parses Gradle-Consistent-Versions files to figure out what dependencies, versions
* and groups they contain. The parsing goes like this:
* - Parse `versions.props` into deps (or groups) and versions, remembering file offsets
* - Parse `versions.lock` into deps and lock-versions
* - For each exact dep in props file, lookup the lock-version from lock file
* - For each group/regex dep in props file, lookup the set of exact deps and versions in lock file
*
* @param propsFileName name and path of the props file
* @param fileContents text content of all files
*/
export function parseGcv(
propsFileName: string,
fileContents: Record<string, string | null>,
): PackageDependency<GradleManagerData>[] {
const propsFileContent = coerceString(fileContents[propsFileName]);
const lockFileName = fs.getSiblingFileName(propsFileName, VERSIONS_LOCK);
const lockFileContent = coerceString(fileContents[lockFileName]);
const lockFileMap = parseLockFile(lockFileContent);
const [propsFileExactMap, propsFileRegexMap] =
parsePropsFile(propsFileContent);
const extractedDeps: PackageDependency<GradleManagerData>[] = [];
// For each exact dep in props file
for (const [propDep, versionAndPosition] of propsFileExactMap) {
if (lockFileMap.has(propDep)) {
const newDep: Record<string, any> = {
managerData: {
packageFile: propsFileName,
fileReplacePosition: versionAndPosition.filePos,
},
depName: propDep,
currentValue: versionAndPosition.version,
lockedVersion: lockFileMap.get(propDep)?.version,
depType: lockFileMap.get(propDep)?.depType,
} satisfies PackageDependency<GradleManagerData>;
extractedDeps.push(newDep);
// Remove from the lockfile map so the same exact lib will not be included in globbing
lockFileMap.delete(propDep);
}
}
// For each regular expression dep in props file (starting with the longest glob string)...
for (const [propDepGlob, propVerAndPos] of propsFileRegexMap) {
const globRegex = globToRegex(propDepGlob);
for (const [exactDep, lockVersionAndDepType] of lockFileMap) {
if (globRegex.test(exactDep)) {
const newDep: Record<string, any> = {
managerData: {
packageFile: propsFileName,
fileReplacePosition: propVerAndPos.filePos,
},
depName: exactDep,
currentValue: propVerAndPos.version,
lockedVersion: lockVersionAndDepType.version,
depType: lockVersionAndDepType.depType,
sharedVariableName: propDepGlob,
} satisfies PackageDependency<GradleManagerData>;
extractedDeps.push(newDep);
// Remove from the lockfile map so the same lib will not be included in more generic globs later
lockFileMap.delete(exactDep);
}
}
}
return extractedDeps;
}
// Translate glob syntax to a regex that does the same. Note that we cannot use replaceAll as it does not exist in Node14
// Loosely borrowed mapping to regex from https://github.com/palantir/gradle-consistent-versions/blob/develop/src/main/java/com/palantir/gradle/versions/FuzzyPatternResolver.java
function globToRegex(depName: string): RegExp {
return regEx(
depName
.replace(/\*/g, '_WC_CHAR_')
.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
.replace(/_WC_CHAR_/g, '.*?'),
);
}
interface VersionWithPosition {
version: string;
filePos: number;
}
interface VersionWithDepType {
version: string;
depType: string;
}
/**
* Parses `versions.lock`
*/
export function parseLockFile(input: string): Map<string, VersionWithDepType> {
const lockLineRegex = regEx(
`^(?<depName>[^:]+:[^:]+):(?<lockVersion>[^ ]+) \\(\\d+ constraints: [0-9a-f]+\\)$`,
);
const depVerMap = new Map<string, VersionWithDepType>();
let isTestDepType = false;
for (const line of input.split(newlineRegex)) {
const lineMatch = lockLineRegex.exec(line);
if (lineMatch?.groups) {
const { depName, lockVersion } = lineMatch.groups;
if (isDependencyString(`${depName}:${lockVersion}`)) {
depVerMap.set(depName, {
version: lockVersion,
depType: isTestDepType ? 'test' : 'dependencies',
} as VersionWithDepType);
}
} else if (line === '[Test dependencies]') {
isTestDepType = true; // We know that all lines below this header are test dependencies
}
}
logger.trace(
`Found ${depVerMap.size} locked dependencies in ${VERSIONS_LOCK}.`,
);
return depVerMap;
}
/**
* Parses `versions.props`, this is CR/LF safe
* @param input the entire property file from file system
* @return two maps, first being exact matches, second regex matches
*/
export function parsePropsFile(
input: string,
): [Map<string, VersionWithPosition>, Map<string, VersionWithPosition>] {
const propsLineRegex = regEx(
`^(?<depName>[^:]+:[^=]+?) *= *(?<propsVersion>.*)$`,
);
const depVerExactMap = new Map<string, VersionWithPosition>();
const depVerRegexMap = new Map<string, VersionWithPosition>();
let startOfLineIdx = 0;
const isCrLf = input.indexOf('\r\n') > 0;
const validGlob = /^[a-zA-Z][-_a-zA-Z0-9.:*]+$/;
for (const line of input.split(newlineRegex)) {
const lineMatch = propsLineRegex.exec(line);
if (lineMatch?.groups) {
const { depName, propsVersion } = lineMatch.groups;
if (
validGlob.test(depName) &&
versionLikeSubstring(propsVersion) !== null
) {
const startPosInLine = line.lastIndexOf(propsVersion);
const propVersionPos = startOfLineIdx + startPosInLine;
if (depName.includes('*')) {
depVerRegexMap.set(depName, {
version: propsVersion,
filePos: propVersionPos,
});
} else {
depVerExactMap.set(depName, {
version: propsVersion,
filePos: propVersionPos,
});
}
}
}
startOfLineIdx += line.length + (isCrLf ? 2 : 1);
}
logger.trace(
`Found ${depVerExactMap.size} dependencies and ${depVerRegexMap.size} wildcard dependencies in ${VERSIONS_PROPS}.`,
);
return [
depVerExactMap,
new Map(
[...depVerRegexMap].sort(([ka], [kb]) => ka.localeCompare(kb)).reverse(),
),
];
}