diff --git a/docs/usage/java.md b/docs/usage/java.md
index c0c58ea5e64555..372936a1590081 100644
--- a/docs/usage/java.md
+++ b/docs/usage/java.md
@@ -77,6 +77,14 @@ Renovate can update dependency versions found in Maven `pom.xml` files.
Renovate will search repositories for all `pom.xml` files and processes them independently.
+Renovate will also parse `settings.xml` files in the following locations:
+
+- `.mvn/settings.xml`
+- `.m2/settings.xml`
+- `settings.xml`
+
+Any repository URLs found within will be added as `registryUrls` to extracted dependencies.
+
## Custom registry support, and authentication
Unless using `deepExtract`, Renovate does not make use of authentication credentials available to Gradle.
diff --git a/lib/manager/maven/__fixtures__/complex.settings.xml b/lib/manager/maven/__fixtures__/complex.settings.xml
new file mode 100644
index 00000000000000..9017cbb6d5c6ca
--- /dev/null
+++ b/lib/manager/maven/__fixtures__/complex.settings.xml
@@ -0,0 +1,74 @@
+
+
+
+ my-maven-repo
+ https://artifactory.company.com/artifactory/my-maven-repo
+ *
+
+
+ my-maven-repo-v2
+ https://repo.adobe.com/nexus/content/groups/public
+ custom-repo
+
+
+
+
+ adobe-public
+
+
+ adobe-public-releases
+ Adobe Public Repository
+ https://repo.adobe.com/nexus/content/groups/public
+
+ true
+ never
+
+
+ false
+
+
+
+ adobe-public-releases-v2
+ Adobe Public Repository v2
+ https://repo.adobe.com/v2/nexus/content/groups/public
+
+ true
+ never
+
+
+ false
+
+
+
+
+
+ adobe-public-v2
+
+
+ adobe-public-releases-v3
+ Adobe Public Repository
+ https://repo.adobe.com/v3/nexus/content/groups/public
+
+ true
+ never
+
+
+ false
+
+
+
+ adobe-public-releases-v4
+ Adobe Public Repository v2
+ https://repo.adobe.com/v4/nexus/content/groups/public
+
+ true
+ never
+
+
+ false
+
+
+
+
+
+
diff --git a/lib/manager/maven/__fixtures__/mirror.settings.xml b/lib/manager/maven/__fixtures__/mirror.settings.xml
new file mode 100644
index 00000000000000..8b024b07d096f5
--- /dev/null
+++ b/lib/manager/maven/__fixtures__/mirror.settings.xml
@@ -0,0 +1,9 @@
+
+
+
+ my-maven-repo
+ https://artifactory.company.com/artifactory/my-maven-repo
+ *
+
+
+
diff --git a/lib/manager/maven/__fixtures__/profile.settings.xml b/lib/manager/maven/__fixtures__/profile.settings.xml
new file mode 100644
index 00000000000000..eb0143dd2933f2
--- /dev/null
+++ b/lib/manager/maven/__fixtures__/profile.settings.xml
@@ -0,0 +1,21 @@
+
+
+
+ adobe-public
+
+
+ adobe-public-releases
+ Adobe Public Repository
+ https://repo.adobe.com/nexus/content/groups/public
+
+ true
+ never
+
+
+ false
+
+
+
+
+
+
diff --git a/lib/manager/maven/extract.spec.ts b/lib/manager/maven/extract.spec.ts
index e850cd1808c6b2..ac2d3adad94fb1 100644
--- a/lib/manager/maven/extract.spec.ts
+++ b/lib/manager/maven/extract.spec.ts
@@ -1,9 +1,13 @@
import { loadFixture } from '../../../test/util';
-import { extractPackage } from './extract';
+import { extractPackage, extractRegistries } from './extract';
const minimumContent = loadFixture(`minimum.pom.xml`);
const simpleContent = loadFixture(`simple.pom.xml`);
+const mirrorSettingsContent = loadFixture(`mirror.settings.xml`);
+const profileSettingsContent = loadFixture(`profile.settings.xml`);
+const complexSettingsContent = loadFixture(`complex.settings.xml`);
+
describe('manager/maven/extract', () => {
describe('extractDependencies', () => {
it('returns null for invalid XML', () => {
@@ -81,4 +85,37 @@ describe('manager/maven/extract', () => {
});
});
});
+ describe('extractRegistries', () => {
+ it('returns null for invalid XML', () => {
+ expect(extractRegistries(undefined)).toBeEmptyArray();
+ expect(extractRegistries('invalid xml content')).toBeEmptyArray();
+ expect(extractRegistries('')).toBeEmptyArray();
+ expect(extractRegistries('')).toBeEmptyArray();
+ });
+
+ it('extract registries from a simple mirror settings file', () => {
+ const res = extractRegistries(mirrorSettingsContent);
+ expect(res).toStrictEqual([
+ 'https://artifactory.company.com/artifactory/my-maven-repo',
+ ]);
+ });
+
+ it('extract registries from a simple profile settings file', () => {
+ const res = extractRegistries(profileSettingsContent);
+ expect(res).toStrictEqual([
+ 'https://repo.adobe.com/nexus/content/groups/public',
+ ]);
+ });
+
+ it('extract registries from a complex profile settings file', () => {
+ const res = extractRegistries(complexSettingsContent);
+ expect(res).toStrictEqual([
+ 'https://artifactory.company.com/artifactory/my-maven-repo',
+ 'https://repo.adobe.com/nexus/content/groups/public',
+ 'https://repo.adobe.com/v2/nexus/content/groups/public',
+ 'https://repo.adobe.com/v3/nexus/content/groups/public',
+ 'https://repo.adobe.com/v4/nexus/content/groups/public',
+ ]);
+ });
+ });
});
diff --git a/lib/manager/maven/extract.ts b/lib/manager/maven/extract.ts
index 74e91ff3da3c62..059f3826ced608 100644
--- a/lib/manager/maven/extract.ts
+++ b/lib/manager/maven/extract.ts
@@ -223,6 +223,61 @@ export function extractPackage(
return result;
}
+export function extractRegistries(rawContent: string): string[] {
+ if (!rawContent) {
+ return [];
+ }
+
+ const settings = parseSettings(rawContent);
+ if (!settings) {
+ return [];
+ }
+
+ const urls = [];
+
+ const mirrorUrls = parseUrls(settings, 'mirrors');
+ urls.push(...mirrorUrls);
+
+ settings.childNamed('profiles')?.eachChild((profile) => {
+ const repositoryUrls = parseUrls(profile, 'repositories');
+ urls.push(...repositoryUrls);
+ });
+
+ // filter out duplicates
+ return [...new Set(urls)];
+}
+
+function parseUrls(xmlNode: XmlElement, path: string): string[] {
+ const children = xmlNode.descendantWithPath(path);
+ const urls = [];
+ if (children?.children) {
+ children.eachChild((child) => {
+ const url = child.valueWithPath('url');
+ if (url) {
+ urls.push(url);
+ }
+ });
+ }
+ return urls;
+}
+
+export function parseSettings(raw: string): XmlDocument | null {
+ let settings: XmlDocument;
+ try {
+ settings = new XmlDocument(raw);
+ } catch (e) {
+ return null;
+ }
+ const { name, attr } = settings;
+ if (name !== 'settings') {
+ return null;
+ }
+ if (attr.xmlns === 'http://maven.apache.org/SETTINGS/1.0.0') {
+ return settings;
+ }
+ return null;
+}
+
export function resolveParents(packages: PackageFile[]): PackageFile[] {
const packageFileNames: string[] = [];
const extractedPackages: Record = {};
@@ -310,17 +365,42 @@ export async function extractAllPackageFiles(
packageFiles: string[]
): Promise {
const packages: PackageFile[] = [];
+ const additionalRegistryUrls = [];
+
for (const packageFile of packageFiles) {
const content = await readLocalFile(packageFile, 'utf8');
- if (content) {
+ if (!content) {
+ logger.trace({ packageFile }, 'packageFile has no content');
+ continue;
+ }
+ if (packageFile.endsWith('settings.xml')) {
+ const registries = extractRegistries(content);
+ if (registries) {
+ logger.debug(
+ { registries, packageFile },
+ 'Found registryUrls in settings.xml'
+ );
+ additionalRegistryUrls.push(...registries);
+ }
+ } else {
const pkg = extractPackage(content, packageFile);
if (pkg) {
packages.push(pkg);
} else {
- logger.debug({ packageFile }, 'can not read dependencies');
+ logger.trace({ packageFile }, 'can not read dependencies');
+ }
+ }
+ }
+ if (additionalRegistryUrls) {
+ for (const pkgFile of packages) {
+ for (const dep of pkgFile.deps) {
+ /* istanbul ignore else */
+ if (dep.registryUrls) {
+ dep.registryUrls.push(...additionalRegistryUrls);
+ } else {
+ dep.registryUrls = [...additionalRegistryUrls];
+ }
}
- } else {
- logger.debug({ packageFile }, 'packageFile has no content');
}
}
return cleanResult(resolveParents(packages));
diff --git a/lib/manager/maven/index.spec.ts b/lib/manager/maven/index.spec.ts
index e9027061dc60a2..c3ef2b1a801b67 100644
--- a/lib/manager/maven/index.spec.ts
+++ b/lib/manager/maven/index.spec.ts
@@ -9,6 +9,7 @@ const pomContent = loadFixture('simple.pom.xml');
const pomParent = loadFixture('parent.pom.xml');
const pomChild = loadFixture('child.pom.xml');
const origContent = loadFixture('grouping.pom.xml');
+const settingsContent = loadFixture('mirror.settings.xml');
function selectDep(deps: PackageDependency[], name = 'org.example:quuz') {
return deps.find((dep) => dep.depName === name);
@@ -28,6 +29,27 @@ describe('manager/maven/index', () => {
expect(res).toBeEmptyArray();
});
+ it('should return packages with urls from a settings file', async () => {
+ fs.readLocalFile
+ .mockResolvedValueOnce(settingsContent)
+ .mockResolvedValueOnce(pomContent);
+ const packages = await extractAllPackageFiles({}, [
+ 'settings.xml',
+ 'simple.pom.xml',
+ ]);
+ const urls = [
+ 'https://repo.maven.apache.org/maven2',
+ 'https://maven.atlassian.com/content/repositories/atlassian-public/',
+ 'https://artifactory.company.com/artifactory/my-maven-repo',
+ ];
+ for (const pkg of packages) {
+ for (const dep of pkg.deps) {
+ const depUrls = [...dep.registryUrls];
+ expect(depUrls).toEqual(urls);
+ }
+ }
+ });
+
it('should return package files info', async () => {
fs.readLocalFile.mockResolvedValueOnce(pomContent);
const packages = await extractAllPackageFiles({}, ['random.pom.xml']);
diff --git a/lib/manager/maven/index.ts b/lib/manager/maven/index.ts
index b53ff3213328a2..7b285561f4e513 100644
--- a/lib/manager/maven/index.ts
+++ b/lib/manager/maven/index.ts
@@ -8,7 +8,7 @@ export { updateDependency } from './update';
export const language = ProgrammingLanguage.Java;
export const defaultConfig = {
- fileMatch: ['\\.pom\\.xml$', '(^|/)pom\\.xml$'],
+ fileMatch: ['(^|/|\\.)pom\\.xml$', '^(((\\.mvn)|(\\.m2))/)?settings\\.xml$'],
versioning: mavenVersioning.id,
};