Parallel Download

Wer bereits einen lokalen Maven Proxy im Einsatzt hat (siehe Nexus OSS Proxy), der kann Maven beschleunigen. Wenn zum Beispiel das .m2/repository Verzeichnis geleert wurde, dann kann man mit dem Parameter -Dmaven.artifact.threads=30 den Vorgang bei Artefakten aus unterschiedlichen Gruppen IDs verbessern.

Alternativ kann man auch die Umgebungsvariable MAVEN_OPTS setzen:

export MAVEN_OPTS=-Dmaven.artifact.threads=3

Ein echter paralleler Download ist das noch nicht. Es besteht aber ein Ticket in https://issues.apache.org/jira/browse/MRESOLVER-7, aber gelöst ist das Problem aktuell noch nicht.

Maven Caching

Warum sollte man einen zentralen Cache verwenden? Nun, wenn man in einem Betrieb mit mehreren Mitarbeitern arbeitet, dann kann es unter Umständen schon zu Engpässen in der Bandbreite führen. Dieses gilt auch heute bei Modernen DSL Anschlüssen, da diese oftmals auch Telefonie bereitstellen und daher wegen QoS diese zusätzlich von der Verfügung stehenden Bandbreite abgezogen werden müssen.

Es gibt aber auch noch weitere Gründe warum man den Zugang zu dem Maven Repository zentralisieren sollte. Es kann sein, dass bestimmte Projekte sehr strenge Vorgaben an die zu verwendenden Dependencies machen. Dieses können zum Beispiel verwendete Lizenzen oder CVEs sein.

Nexus

Zunächst muss man in Nexus ein Maven 2 Proxy anlegen. Ich gehe hier davon aus, dass eine laufende Instanz von dem Nexus Server im internen Netz vorhanden ist. Der Nexus Server ist auch als Docker Image vorhanden und lässt sich in ein paar Minuten aufsetzen.

Proxy Einrichten

Wie bereits beschrieben muss zunächst ein Maven Proxy angelegt werden. Dazu meldet man sich als Administrator an und geht in den Einstellungen in die Repositories. Dort kann man mit create ein neues Repository anlegen. Wählen sie den Type Maven2(Proxy). Vergeben sie als Namen maven-central-proxy. Der Name ist dann Teil der URL unter dem dann im Nexus der Maven Proxy erreichbar ist.

Für die Upstream URL für das Maven Repository https://repo1.maven.org/maven2 ein.

Maven

In Maven kann man die Repositories in der settings.xml verwalten.

<settings>
  <mirrors>
    <mirror>
      <!--This sends everything else to /public -->
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>http://nexus.XXXX.de/repository/maven-central-proxy/</url>
    </mirror>
  </mirrors>
  <profiles>
    <profile>
      <id>nexus</id>
      <!--Enable snapshots for the built in central repo to direct -->
      <!--all requests to nexus via the mirror -->
      <repositories>
        <repository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
     <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <activeProfiles>
    <!--make the profile active all the time -->
    <activeProfile>nexus</activeProfile>
  </activeProfiles>

  <!-- set if non anonymous connection allowed -->
  <servers>
    <server>
      <id>nexus</id>
      <username>XXX</username>
      <password>YYY</password>
    </server>
  </servers>    
</settings>

Gradle

In Gradle hat man mehrere Möglichkeiten ein anderes Repository zu verwenden. Das Naheliegendste ist in dem build.gradle das Repository direkt anzugeben. Doch Gradle bietet noch mehr Wege, um hier einzugreifen.

In https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html wird beschrieben, dass das Maven Central Repository mit der URL https://repo.maven.apache.org/maven2/ verwendet wird. Diese lässt sich zentral für alle Projekte auf dem Host durch ein Skript ersetzen, welches ich in dem nächsten Abschnitt vorstelle.

Gradle Init Skript

Das Gralde Init-Skript wird ausgeführt, wenn man Gradle startet bzw. ein Task ausgeführt wird. Das Init-Skript kann eine Datei sein die init.gradle heißt und im Ordner ~/.gradle liegt oder es ist eine Datei die mit .gradle endet und in dem Verzeichnis ~/.gradle/init.d liegt.

Ich habe mit für letzteres entschieden und eine Datei maven.gradle in dem Verzeichnis ~/.gradle/init.d erstellt. Es muss zunächst die nexusUrl angepasst werden. Diese setzt sich aus der Server URL und dem Namen des Maven2 Proxy Repositories zusammen. Hier also maven-central-proxy. Das Skript filter Repositories die mit https://repo.maven.apache.org/maven2 heraus und fügt dafür die URL von dem Nexus Server ein. Das Ganze wird auf der Konsole geloggt, sodass man auch gleich erkennt welche Repositories in dem Projekt verwendet werden.

Das Skript maven.gradle:

//
// init.d/maven.gradle
// -------------------
//
// This replaces all occurance of mavenCentral repositories with the local Nexus repository
//

def nexusUrl = "https://nexus.XXXX.de/repository/maven-central-proxy"
def prefix = "> Init.d/maven.gradle :"

allprojects {
    buildscript {
        repositories {
            all { ArtifactRepository repo -> 
                if ((repo instanceof MavenArtifactRepository) && repo.url.toString().startsWith("https://repo.maven.apache.org/maven2")) {
                    project.logger.warn "${prefix}Buildscript Repository: ${repo.url} removed. Only $nexusUrl is allowed"
                    remove repo
                }
            }
            maven {
                url "$nexusUrl"
            }
        }
    }

    repositories {
        all { ArtifactRepository repo ->
            if ((repo instanceof MavenArtifactRepository) && repo.url.toString().startsWith("https://repo.maven.apache.org/maven2")) {
                project.logger.warn "${prefix}Repository: change ${repo.url} ---> $nexusUrl"
                remove repo
            } else {
               println "${prefix}Repository: " + repo.url.toString()
            }
        }
        maven { 
            url "$nexusUrl" 
        }
    }
}

Hinweis: Gradle missachtet hier die settings.xml. Wenn eine Authentifizierung am Nexus nötig ist (kein anonymous), dann müssen in den beiden maven-Blöcken jeweils ein Credentials Block eingefügt werden. Sonst erhält man ein 401 HTTP Error.

credentials {
  username "XXX"
  password "YYY"
} 

Gradle Build

Wenn man jetzt einen Build startet, dann kann man auf der Konsole erkennen, wie das Skript das Maven Central Repository, welches über mavenCentral() eingebunden wurde, ersetzt wird.

[sascha@Workstation TestServer]$ ./gradlew build
> Init.d/maven.gradle :Repository: https://nexus.XXXX.de/repository/maven-central-proxy

> Configure project :
> Init.d/maven.gradle :Repository: change https://repo.maven.apache.org/maven2/ ---> https://nexus.XXXX.de/repository/maven-central-proxy
> Init.d/maven.gradle :Repository: https://repo.spring.io/snapshot
> Init.d/maven.gradle :Repository: https://repo.spring.io/milestone