diff options
author | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2022-11-11 13:03:08 +0100 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@yahooinc.com> | 2022-11-11 13:03:16 +0100 |
commit | 56b326a1257b5f44e2da3f283c3fbcfe5fa2663a (patch) | |
tree | 4d6d37457d2fc82ae17072f110c574a2fd2fbf4b | |
parent | 7fbe42d86205804e81fb290fdf21f5469c9f7414 (diff) |
Add dependency enforcer
Compares all dependencies (including transitive) for all modules in Maven project.
7 files changed, 584 insertions, 0 deletions
@@ -127,6 +127,7 @@ <module>vespaclient-container-plugin</module> <module>vespaclient-java</module> <module>vespa-athenz</module> + <module>vespa-dependencies-enforcer</module> <module>vespa-documentgen-plugin</module> <module>vespa-feed-client</module> <module>vespa-feed-client-api</module> diff --git a/vespa-dependencies-enforcer/OWNERS b/vespa-dependencies-enforcer/OWNERS new file mode 100644 index 00000000000..569bf1cc3a1 --- /dev/null +++ b/vespa-dependencies-enforcer/OWNERS @@ -0,0 +1 @@ +bjorncs diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt new file mode 100644 index 00000000000..b76b8c8d936 --- /dev/null +++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt @@ -0,0 +1,245 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +aopalliance:aopalliance:1.0 +backport-util-concurrent:backport-util-concurrent:3.1 +biz.aQute.bnd:biz.aQute.bnd.util:6.1.0 +biz.aQute.bnd:biz.aQute.bndlib:6.1.0 +ch.qos.logback:logback-classic:1.2.10 +ch.qos.logback:logback-core:1.2.10 +classworlds:classworlds:1.1-alpha-2 +com.amazonaws:aws-java-sdk-core:1.12.331 +com.amazonaws:aws-java-sdk-ssm:1.12.331 +com.amazonaws:aws-java-sdk-sts:1.12.331 +com.amazonaws:jmespath-java:1.12.331 +com.auth0:java-jwt:3.10.0 +com.fasterxml.jackson.core:jackson-annotations:2.13.4 +com.fasterxml.jackson.core:jackson-core:2.13.4 +com.fasterxml.jackson.core:jackson-databind:2.13.4.2 +com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.6 +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4 +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4 +com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:2.13.4 +com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.13.4 +com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.13.4 +com.github.spotbugs:spotbugs-annotations:3.1.9 +com.github.tomakehurst:wiremock-jre8-standalone:2.35.0 +com.google.code.findbugs:jsr305:3.0.2 +com.google.errorprone:error_prone_annotations:2.2.0 +com.google.guava:failureaccess:1.0.1 +com.google.guava:guava:27.1-jre +com.google.guava:guava-testlib:27.1-jre +com.google.inject:guice:4.2.3 +com.google.inject:guice:4.2.3:no_aop +com.google.j2objc:j2objc-annotations:1.1 +com.google.jimfs:jimfs:1.1 +com.google.jimfs:jimfs:1.2 +com.google.protobuf:protobuf-java:3.21.7 +com.ibm.icu:icu4j:70.1 +com.intellij:annotations:9.0.4 +com.microsoft.onnxruntime:onnxruntime:1.12.1 +com.sun.activation:javax.activation:1.2.0 +com.sun.istack:istack-commons-runtime:3.0.8 +com.sun.xml.bind:jaxb-core:2.3.0 +com.sun.xml.bind:jaxb-impl:2.3.0 +com.sun.xml.fastinfoset:FastInfoset:1.2.16 +com.thaiopensource:jing:20091111 +com.yahoo.athenz:athenz-auth-core:1.10.54 +com.yahoo.athenz:athenz-client-common:1.10.54 +com.yahoo.athenz:athenz-zms-core:1.10.54 +com.yahoo.athenz:athenz-zpe-java-client:1.10.54 +com.yahoo.athenz:athenz-zts-core:1.10.54 +com.yahoo.rdl:rdl-java:1.5.4 +commons-cli:commons-cli:1.4 +commons-codec:commons-codec:1.15 +commons-fileupload:commons-fileupload:1.4 +commons-io:commons-io:2.11.0 +commons-io:commons-io:2.2 +commons-io:commons-io:2.6 +commons-logging:commons-logging:1.2 +io.airlift:aircompressor:0.21 +io.airlift:airline:0.9 +io.dropwizard.metrics:metrics-core:3.2.5 +io.jsonwebtoken:jjwt-api:0.11.2 +io.jsonwebtoken:jjwt-impl:0.11.2 +io.jsonwebtoken:jjwt-jackson:0.11.2 +io.netty:netty-buffer:4.1.73.Final +io.netty:netty-codec:4.1.73.Final +io.netty:netty-common:4.1.73.Final +io.netty:netty-handler:4.1.73.Final +io.netty:netty-resolver:4.1.73.Final +io.netty:netty-tcnative:2.0.48.Final +io.netty:netty-tcnative-classes:2.0.48.Final +io.netty:netty-transport:4.1.73.Final +io.netty:netty-transport-classes-epoll:4.1.73.Final +io.netty:netty-transport-native-epoll:4.1.73.Final +io.netty:netty-transport-native-unix-common:4.1.73.Final +io.prometheus:simpleclient:0.6.0 +io.prometheus:simpleclient_common:0.6.0 +javax.annotation:javax.annotation-api:1.2 +javax.inject:javax.inject:1 +javax.servlet:javax.servlet-api:3.1.0 +javax.validation:validation-api:1.1.0.Final +javax.ws.rs:javax.ws.rs-api:2.0 +javax.ws.rs:javax.ws.rs-api:2.0.1 +javax.xml.bind:jaxb-api:2.3.0 +joda-time:joda-time:2.8.1 +junit:junit:4.13.2 +net.bytebuddy:byte-buddy:1.11.19 +net.bytebuddy:byte-buddy-agent:1.11.19 +net.java.dev.jna:jna:5.11.0 +org.antlr:antlr-runtime:3.5.2 +org.antlr:antlr4-runtime:4.9.3 +org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle:1.3.5 +org.apache.commons:commons-compress:1.21 +org.apache.commons:commons-csv:1.8 +org.apache.commons:commons-exec:1.3 +org.apache.commons:commons-lang3:3.11 +org.apache.commons:commons-lang3:3.8.1 +org.apache.commons:commons-math3:3.6.1 +org.apache.curator:curator-client:5.3.0 +org.apache.curator:curator-framework:5.3.0 +org.apache.curator:curator-recipes:5.3.0 +org.apache.curator:curator-test:5.3.0 +org.apache.felix:org.apache.felix.framework:7.0.1 +org.apache.felix:org.apache.felix.log:1.0.1 +org.apache.httpcomponents:httpclient:4.5.13 +org.apache.httpcomponents:httpcore:4.4.13 +org.apache.httpcomponents:httpmime:4.5.13 +org.apache.httpcomponents.client5:httpclient5:5.1.3 +org.apache.httpcomponents.core5:httpcore5:5.1.3 +org.apache.httpcomponents.core5:httpcore5-h2:5.1.3 +org.apache.maven:maven-archiver:3.5.2 +org.apache.maven:maven-artifact:3.8.6 +org.apache.maven:maven-artifact-manager:2.2.1 +org.apache.maven:maven-builder-support:3.8.6 +org.apache.maven:maven-compat:3.0 +org.apache.maven:maven-core:3.8.6 +org.apache.maven:maven-model:3.8.6 +org.apache.maven:maven-model-builder:3.8.6 +org.apache.maven:maven-plugin-api:3.8.5 +org.apache.maven:maven-plugin-registry:2.2.1 +org.apache.maven:maven-profile:2.2.1 +org.apache.maven:maven-project:2.2.1 +org.apache.maven:maven-repository-metadata:2.2.1 +org.apache.maven:maven-repository-metadata:3.8.6 +org.apache.maven:maven-resolver-provider:3.8.6 +org.apache.maven:maven-settings:2.2.1 +org.apache.maven:maven-settings:3.8.6 +org.apache.maven:maven-settings-builder:3.8.6 +org.apache.maven.plugin-tools:maven-plugin-annotations:3.6.4 +org.apache.maven.plugins:maven-jar-plugin:3.2.0 +org.apache.maven.resolver:maven-resolver-api:1.6.3 +org.apache.maven.resolver:maven-resolver-impl:1.6.3 +org.apache.maven.resolver:maven-resolver-spi:1.6.3 +org.apache.maven.resolver:maven-resolver-util:1.6.3 +org.apache.maven.shared:file-management:3.0.0 +org.apache.maven.shared:maven-shared-io:3.0.0 +org.apache.maven.shared:maven-shared-utils:3.2.1 +org.apache.maven.wagon:wagon-provider-api:1.0-beta-6 +org.apache.maven.wagon:wagon-provider-api:2.10 +org.apache.opennlp:opennlp-tools:1.9.3 +org.apache.velocity:velocity-engine-core:2.3 +org.apache.yetus:audience-annotations:0.12.0 +org.apache.zookeeper:zookeeper:3.8.0 +org.apache.zookeeper:zookeeper-jute:3.8.0 +org.apiguardian:apiguardian-api:1.1.2 +org.assertj:assertj-core:3.11.1 +org.bouncycastle:bcpkix-jdk18on:1.72 +org.bouncycastle:bcprov-jdk18on:1.72 +org.bouncycastle:bcutil-jdk18on:1.72 +org.checkerframework:checker-qual:2.5.2 +org.codehaus.plexus:plexus-archiver:4.2.1 +org.codehaus.plexus:plexus-cipher:2.0 +org.codehaus.plexus:plexus-classworlds:2.6.0 +org.codehaus.plexus:plexus-component-annotations:1.5.5 +org.codehaus.plexus:plexus-container-default:1.0-alpha-9-stable-1 +org.codehaus.plexus:plexus-interpolation:1.11 +org.codehaus.plexus:plexus-interpolation:1.26 +org.codehaus.plexus:plexus-io:3.2.0 +org.codehaus.plexus:plexus-sec-dispatcher:2.0 +org.codehaus.plexus:plexus-utils:3.3.0 +org.codehaus.plexus:plexus-utils:3.3.1 +org.cthul:cthul-matchers:1.0 +org.eclipse.collections:eclipse-collections:11.0.0 +org.eclipse.collections:eclipse-collections-api:11.0.0 +org.eclipse.jetty:jetty-alpn-java-server:9.4.49.v20220914 +org.eclipse.jetty:jetty-alpn-server:9.4.49.v20220914 +org.eclipse.jetty:jetty-client:9.4.49.v20220914 +org.eclipse.jetty:jetty-continuation:9.4.49.v20220914 +org.eclipse.jetty:jetty-http:9.4.49.v20220914 +org.eclipse.jetty:jetty-io:9.4.49.v20220914 +org.eclipse.jetty:jetty-jmx:9.4.49.v20220914 +org.eclipse.jetty:jetty-security:9.4.49.v20220914 +org.eclipse.jetty:jetty-server:9.4.49.v20220914 +org.eclipse.jetty:jetty-servlet:9.4.49.v20220914 +org.eclipse.jetty:jetty-servlets:9.4.49.v20220914 +org.eclipse.jetty:jetty-util:9.4.49.v20220914 +org.eclipse.jetty:jetty-util-ajax:9.4.49.v20220914 +org.eclipse.jetty.alpn:alpn-api:1.1.3.v20160715 +org.eclipse.jetty.http2:http2-common:9.4.49.v20220914 +org.eclipse.jetty.http2:http2-hpack:9.4.49.v20220914 +org.eclipse.jetty.http2:http2-server:9.4.49.v20220914 +org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5 +org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5 +org.fusesource.jansi:jansi:1.18 +org.glassfish.hk2:hk2-api:2.5.0-b30 +org.glassfish.hk2:hk2-locator:2.5.0-b30 +org.glassfish.hk2:hk2-utils:2.5.0-b30 +org.glassfish.hk2:osgi-resource-locator:1.0.1 +org.glassfish.hk2.external:aopalliance-repackaged:2.5.0-b30 +org.glassfish.hk2.external:javax.inject:2.5.0-b30 +org.glassfish.jaxb:jaxb-runtime:2.3.2 +org.glassfish.jaxb:txw2:2.3.2 +org.glassfish.jersey.bundles.repackaged:jersey-guava:2.25 +org.glassfish.jersey.core:jersey-client:2.25 +org.glassfish.jersey.core:jersey-common:2.25 +org.glassfish.jersey.core:jersey-server:2.25 +org.glassfish.jersey.ext:jersey-entity-filtering:2.25 +org.glassfish.jersey.ext:jersey-proxy-client:2.25 +org.glassfish.jersey.media:jersey-media-json-jackson:2.25 +org.glassfish.jersey.media:jersey-media-multipart:2.25 +org.hamcrest:hamcrest-all:1.3 +org.hamcrest:hamcrest-core:1.3 +org.hamcrest:hamcrest-library:1.3 +org.hdrhistogram:HdrHistogram:2.1.12 +org.iq80.snappy:snappy:0.4 +org.javassist:javassist:3.20.0-GA +org.json:json:20220320 +org.junit.jupiter:junit-jupiter:5.8.1 +org.junit.jupiter:junit-jupiter-api:5.8.1 +org.junit.jupiter:junit-jupiter-engine:5.8.1 +org.junit.jupiter:junit-jupiter-params:5.8.1 +org.junit.platform:junit-platform-commons:1.8.1 +org.junit.platform:junit-platform-engine:1.8.1 +org.junit.platform:junit-platform-launcher:1.8.1 +org.junit.vintage:junit-vintage-engine:5.8.1 +org.jvnet.mimepull:mimepull:1.9.6 +org.jvnet.staxex:stax-ex:1.8.1 +org.kohsuke:libpam4j:1.11 +org.lz4:lz4-java:1.8.0 +org.mockito:mockito-core:4.0.0 +org.mockito:mockito-junit-jupiter:4.0.0 +org.objenesis:objenesis:3.2 +org.opentest4j:opentest4j:1.2.0 +org.osgi:org.osgi.compendium:4.1.0 +org.osgi:org.osgi.core:4.1.0 +org.ow2.asm:asm:9.3 +org.ow2.asm:asm-analysis:9.3 +org.ow2.asm:asm-commons:9.3 +org.ow2.asm:asm-tree:9.3 +org.ow2.asm:asm-util:9.3 +org.questdb:questdb:6.2 +org.slf4j:jcl-over-slf4j:1.7.32 +org.slf4j:log4j-over-slf4j:1.7.32 +org.slf4j:slf4j-api:1.7.32 +org.slf4j:slf4j-jdk14:1.7.32 +org.slf4j:slf4j-nop:1.7.32 +org.slf4j:slf4j-simple:1.7.32 +org.sonatype.sisu:sisu-guice:2.1.7:noaop +org.sonatype.sisu:sisu-inject-bean:1.4.2 +org.sonatype.sisu:sisu-inject-plexus:1.4.2 +org.tukaani:xz:1.8 +org.xerial.snappy:snappy-java:1.1.7 +software.amazon.ion:ion-java:1.0.2 +xerces:xercesImpl:2.12.2 +xml-apis:xml-apis:1.4.01 +xmlunit:xmlunit:1.5 diff --git a/vespa-dependencies-enforcer/pom.xml b/vespa-dependencies-enforcer/pom.xml new file mode 100644 index 00000000000..1735964ec62 --- /dev/null +++ b/vespa-dependencies-enforcer/pom.xml @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>8-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + + <artifactId>vespa-dependencies-enforcer</artifactId> + <version>8-SNAPSHOT</version> + <packaging>pom</packaging> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <inherited>false</inherited> + <dependencies> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vespa-enforcer-extensions</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + <executions> + <execution> + <id>default-cli</id> + <goals> + <!-- To allow running 'mvn enforcer:enforce' from the command line --> + <goal>enforce</goal> + </goals> + <configuration> + <rules> + <enforceDependencies implementation="com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects"> + <specFile>allowed-maven-dependencies.txt</specFile> + <ignored> + <i>com.yahoo.vespa:*:*</i> + <i>ai.vespa:*:*</i> + </ignored> + </enforceDependencies> + </rules> + <fail>true</fail> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <properties> + <maven.javadoc.skip>true</maven.javadoc.skip> + <maven.deploy.skip>true</maven.deploy.skip> + <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo> + </properties> + +</project> diff --git a/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java b/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java new file mode 100644 index 00000000000..ec39bd23e3f --- /dev/null +++ b/vespa-enforcer-extensions/src/main/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjects.java @@ -0,0 +1,187 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.maven.plugin.enforcer; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.enforcer.rule.api.EnforcerRule; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.DefaultProjectBuildingRequest; +import org.apache.maven.project.MavenProject; +import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; +import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException; +import org.apache.maven.shared.dependency.graph.DependencyNode; +import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author bjorncs + */ +public class EnforceDependenciesAllProjects implements EnforcerRule { + + private static final String WRITE_SPEC_PROP = "dependencyEnforcer.writeSpec"; + + private String specFile; + private List<String> ignored; + + @Override + public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException { + Log log = helper.getLog(); + SortedSet<Dependency> dependencies = getDependenciesOfAllProjects(helper, ignored); + log.info("Found %d unique dependencies".formatted(dependencies.size())); + Path specFile = resolveSpecFile(helper, this.specFile); + if (System.getProperties().containsKey(WRITE_SPEC_PROP)) { + writeDependencySpec(specFile, dependencies); + log.info("Updated spec file '%s'".formatted(specFile.toString())); + } else { + validateDependencies(dependencies, specFile); + } + log.info("The dependency enforcer completed successfully"); + } + + // Config injection for rule configuration. Method names must match config XML elements. + @SuppressWarnings("unused") public void setSpecFile(String f) { this.specFile = f; } + @SuppressWarnings("unused") public String getSpecFile() { return specFile; } + @SuppressWarnings("unused") public void setIgnored(List<String> l) { this.ignored = l; } + @SuppressWarnings("unused") public List<String> getIgnored() { return ignored; } + + record Dependency(String groupId, String artifactId, String version, Optional<String> classifier) + implements Comparable<Dependency> { + static Dependency fromArtifact(Artifact a) { + return new Dependency( + a.getGroupId(), a.getArtifactId(), a.getVersion(), Optional.ofNullable(a.getClassifier())); + } + + static Dependency fromString(String s) { + String[] splits = s.split(":"); + return splits.length == 3 + ? new Dependency(splits[0], splits[1], splits[2], Optional.empty()) + : new Dependency(splits[0], splits[1], splits[2], Optional.of(splits[3])); + } + + String asString() { + var b = new StringBuilder(groupId).append(':').append(artifactId).append(':').append(version); + classifier.ifPresent(c -> b.append(':').append(c)); + return b.toString(); + } + + static final Comparator<Dependency> COMPARATOR = Comparator.comparing(Dependency::groupId) + .thenComparing(Dependency::artifactId).thenComparing(Dependency::version) + .thenComparing(d -> d.classifier().orElse("")); + @Override public int compareTo(Dependency o) { return COMPARATOR.compare(this, o); } + } + + static void validateDependencies(SortedSet<Dependency> dependencies, Path specFile) + throws EnforcerRuleException { + SortedSet<Dependency> allowedDependencies = loadDependencySpec(specFile); + SortedSet<Dependency> forbiddenDependencies = new TreeSet<>(dependencies); + forbiddenDependencies.removeAll(allowedDependencies); + SortedSet<Dependency> removeDependencies = new TreeSet<>(allowedDependencies); + removeDependencies.removeAll(dependencies); + if (!forbiddenDependencies.isEmpty() || !removeDependencies.isEmpty()) { + StringBuilder errorMsg = new StringBuilder("The dependency enforcer failed:\n"); + if (!forbiddenDependencies.isEmpty()) { + errorMsg.append("Forbidden dependencies:\n"); + forbiddenDependencies.forEach(d -> errorMsg.append(" - ").append(d.asString()).append('\n')); + } + if (!removeDependencies.isEmpty()) { + errorMsg.append("Removed dependencies:\n"); + removeDependencies.forEach(d -> errorMsg.append(" - ").append(d.asString()).append('\n')); + } + throw new EnforcerRuleException( + errorMsg.append("Maven dependency validation failed. To update dependency spec run " + + "'mvn enforcer:enforce -D") + .append(WRITE_SPEC_PROP).append("'") + .toString()); + } + } + + private static SortedSet<Dependency> getDependenciesOfAllProjects(EnforcerRuleHelper helper, List<String> ignored) + throws EnforcerRuleException { + try { + Pattern ignorePattern = Pattern.compile( + ignored.stream() + .map(s -> s.replace(".", "\\.").replace("*", ".*").replace(":", "\\:").replace('?', '.')) + .collect(Collectors.joining(")|(", "^(", ")$"))); + SortedSet<Dependency> dependencies = new TreeSet<>(); + MavenSession session = (MavenSession) helper.evaluate("${session}"); + var graphBuilder = helper.getComponent(DependencyGraphBuilder.class); + for (MavenProject project : session.getAllProjects()) { + var req = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); + req.setProject(project); + DependencyNode root = graphBuilder.buildDependencyGraph(req, null); + addDependenciesRecursive(root, dependencies, ignorePattern); + } + return dependencies; + } catch (ExpressionEvaluationException | DependencyGraphBuilderException | ComponentLookupException e) { + throw new EnforcerRuleException(e.getMessage(), e); + } + } + + private static void addDependenciesRecursive(DependencyNode node, Set<Dependency> dependencies, Pattern ignored) { + if (node.getChildren() != null) { + for (DependencyNode dep : node.getChildren()) { + Dependency dependency = Dependency.fromArtifact(dep.getArtifact()); + if (!dependency.version().endsWith("SNAPSHOT") && !ignored.matcher(dependency.asString()).matches()) { + dependencies.add(dependency); + } + addDependenciesRecursive(dep, dependencies, ignored); + } + } + } + + private static Path resolveSpecFile(EnforcerRuleHelper helper, String specFile) throws EnforcerRuleException { + try { + MavenProject project = (MavenProject) helper.evaluate("${project}"); + return Paths.get(project.getBasedir() + File.separator + specFile).normalize(); + } catch (ExpressionEvaluationException e) { + throw new EnforcerRuleException(e.getMessage(), e); + } + } + + static void writeDependencySpec(Path specFile, SortedSet<Dependency> dependencies) + throws EnforcerRuleException { + try (var out = Files.newBufferedWriter(specFile)) { + out.write("# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.\n"); + for (Dependency d : dependencies) { + out.write(d.asString()); + out.write('\n'); + } + } catch (IOException e) { + throw new EnforcerRuleException(e.getMessage(), e); + } + } + + private static SortedSet<Dependency> loadDependencySpec(Path specFile) throws EnforcerRuleException { + try { + try (Stream<String> s = Files.lines(specFile)) { + return s.map(String::trim).filter(l -> !l.isEmpty() && !l.startsWith("#")).map(Dependency::fromString) + .collect(Collectors.toCollection(TreeSet::new)); + } + } catch (IOException e) { + throw new EnforcerRuleException(e.getMessage(), e); + } + } + + // Mark rule as not cachable + @Override public boolean isCacheable() { return false; } + @Override public boolean isResultValid(EnforcerRule r) { return false; } + @Override public String getCacheId() { return ""; } + +} diff --git a/vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java b/vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java new file mode 100644 index 00000000000..a6d6661071e --- /dev/null +++ b/vespa-enforcer-extensions/src/test/java/com/yahoo/vespa/maven/plugin/enforcer/EnforceDependenciesAllProjectsTest.java @@ -0,0 +1,84 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.maven.plugin.enforcer; + +import com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.Dependency; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import static com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.validateDependencies; +import static com.yahoo.vespa.maven.plugin.enforcer.EnforceDependenciesAllProjects.writeDependencySpec; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author bjorncs + */ +class EnforceDependenciesAllProjectsTest { + + @Test + void succeeds_dependencies_matches_spec() { + SortedSet<Dependency> dependencies = new TreeSet<>(Set.of( + Dependency.fromString("com.example:foo:1.2.3"), + Dependency.fromString("com.example:bar:2.3.4"))); + Path specFile = Paths.get("src/test/resources/allowed-dependencies.txt"); + assertDoesNotThrow(() -> validateDependencies(dependencies, specFile)); + } + + @Test + void fails_on_forbidden_dependency() { + SortedSet<Dependency> dependencies = new TreeSet<>(Set.of( + Dependency.fromString("com.example:foo:1.2.3"), + Dependency.fromString("com.example:bar:2.3.4"), + Dependency.fromString("com.example:foobar:3.4.5"))); + Path specFile = Paths.get("src/test/resources/allowed-dependencies.txt"); + var exception = assertThrows(EnforcerRuleException.class, + () -> validateDependencies(dependencies, specFile)); + String expectedErrorMessage = + """ + The dependency enforcer failed: + Forbidden dependencies: + - com.example:foobar:3.4.5 + Maven dependency validation failed. To update dependency spec run 'mvn enforcer:enforce -DdependencyEnforcer.writeSpec'"""; + assertEquals(expectedErrorMessage, exception.getMessage()); + } + + @Test + void fails_on_missing_dependency() { + SortedSet<Dependency> dependencies = new TreeSet<>(Set.of( + Dependency.fromString("com.example:foo:1.2.3"))); + Path specFile = Paths.get("src/test/resources/allowed-dependencies.txt"); + var exception = assertThrows(EnforcerRuleException.class, + () -> validateDependencies(dependencies, specFile)); + String expectedErrorMessage = + """ + The dependency enforcer failed: + Removed dependencies: + - com.example:bar:2.3.4 + Maven dependency validation failed. To update dependency spec run 'mvn enforcer:enforce -DdependencyEnforcer.writeSpec'"""; + assertEquals(expectedErrorMessage, exception.getMessage()); + } + + @Test + void writes_valid_spec_file(@TempDir Path tempDir) throws EnforcerRuleException, IOException { + SortedSet<Dependency> dependencies = new TreeSet<>(Set.of( + Dependency.fromString("com.example:foo:1.2.3"), + Dependency.fromString("com.example:bar:2.3.4"))); + Path outputFile = tempDir.resolve("allowed-dependencies.txt"); + writeDependencySpec(outputFile, dependencies); + assertEquals( + Files.readString(Paths.get("src/test/resources/allowed-dependencies.txt")), + Files.readString(outputFile)); + + } + +}
\ No newline at end of file diff --git a/vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt b/vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt new file mode 100644 index 00000000000..dc6ab2e9be0 --- /dev/null +++ b/vespa-enforcer-extensions/src/test/resources/allowed-dependencies.txt @@ -0,0 +1,3 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +com.example:bar:2.3.4 +com.example:foo:1.2.3 |