Provide distributions for both maven 3.9.x and 4.0.x (#796)

This commit is contained in:
Guillaume Nodet
2023-03-08 00:03:49 +01:00
committed by GitHub
parent 1f99fb8cb7
commit 95b40a3d8a
38 changed files with 3895 additions and 36 deletions

50
daemon-m39/pom.xml Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd</artifactId>
<version>1.0.0-m5-SNAPSHOT</version>
</parent>
<artifactId>mvnd-daemon-m39</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon - Daemon 3.9.x specifics</name>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>${maven3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>${maven3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon</artifactId>
</dependency>
</dependencies>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.project;
import java.util.Objects;
import org.apache.maven.model.building.ModelCache;
public class SnapshotModelCache implements ModelCache {
private final ModelCache globalCache;
private final ModelCache reactorCache;
public SnapshotModelCache(ModelCache globalCache, ModelCache reactorCache) {
this.globalCache = Objects.requireNonNull(globalCache);
this.reactorCache = Objects.requireNonNull(reactorCache);
}
@Override
public void put(String groupId, String artifactId, String version, String tag, Object data) {
getDelegate(version).put(groupId, artifactId, version, tag, data);
}
@Override
public Object get(String groupId, String artifactId, String version, String tag) {
return getDelegate(version).get(groupId, artifactId, version, tag);
}
private ModelCache getDelegate(String version) {
return version.contains("SNAPSHOT") || version.contains("${") ? reactorCache : globalCache;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.project;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.model.building.ModelCache;
import org.apache.maven.repository.internal.DefaultModelCacheFactory;
import org.apache.maven.repository.internal.ModelCacheFactory;
import org.eclipse.aether.DefaultRepositoryCache;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.sisu.Priority;
@Singleton
@Named
@Priority(10)
public class SnapshotModelCacheFactory implements ModelCacheFactory {
private final ModelCacheFactory factory;
private final ModelCache globalCache;
@Inject
public SnapshotModelCacheFactory(DefaultModelCacheFactory factory) {
this.factory = factory;
DefaultRepositorySystemSession session = new DefaultRepositorySystemSession();
session.setCache(new DefaultRepositoryCache());
this.globalCache = factory.createCache(session);
}
@Override
public ModelCache createCache(RepositorySystemSession session) {
return new SnapshotModelCache(globalCache, factory.createCache(session));
}
}

View File

@@ -0,0 +1,125 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.cache.invalidating;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.DefaultPluginRealmCache;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
import org.eclipse.sisu.Priority;
import org.mvndaemon.mvnd.cache.Cache;
import org.mvndaemon.mvnd.cache.CacheFactory;
@Singleton
@Named
@Priority(10)
public class InvalidatingPluginRealmCache extends DefaultPluginRealmCache {
@FunctionalInterface
public interface PluginRealmSupplier {
CacheRecord load() throws PluginResolutionException, PluginContainerException;
}
protected static class Record implements org.mvndaemon.mvnd.cache.CacheRecord {
final CacheRecord record;
public Record(CacheRecord record) {
this.record = record;
}
@Override
public Stream<Path> getDependencyPaths() {
return record.getArtifacts().stream()
.map(artifact -> artifact.getFile().toPath());
}
@Override
public void invalidate() {
ClassRealm realm = record.getRealm();
try {
realm.getWorld().disposeRealm(realm.getId());
} catch (NoSuchRealmException e) {
// ignore
}
}
}
final Cache<Key, Record> cache;
@Inject
public InvalidatingPluginRealmCache(CacheFactory cacheFactory) {
cache = cacheFactory.newCache();
}
@Override
public CacheRecord get(Key key) {
Record r = cache.get(key);
return r != null ? r.record : null;
}
public CacheRecord get(Key key, PluginRealmSupplier supplier)
throws PluginResolutionException, PluginContainerException {
try {
Record r = cache.computeIfAbsent(key, k -> {
try {
return new Record(supplier.load());
} catch (PluginResolutionException | PluginContainerException e) {
throw new RuntimeException(e);
}
});
return r.record;
} catch (RuntimeException e) {
if (e.getCause() instanceof PluginResolutionException) {
throw (PluginResolutionException) e.getCause();
}
if (e.getCause() instanceof PluginContainerException) {
throw (PluginContainerException) e.getCause();
}
throw e;
}
}
@Override
public CacheRecord put(Key key, ClassRealm pluginRealm, List<Artifact> pluginArtifacts) {
CacheRecord record = super.put(key, pluginRealm, pluginArtifacts);
super.cache.remove(key);
cache.put(key, new Record(record));
return record;
}
@Override
public void flush() {
cache.clear();
}
@Override
public void register(MavenProject project, Key key, CacheRecord record) {}
}

View File

@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.execution;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.Optional;
import org.apache.maven.execution.MavenExecutionResult;
/**
* Instances of this class are responsible for determining whether it makes sense to "resume" a build (i.e., using
* the {@code --resume} flag.
*/
public interface BuildResumptionAnalyzer {
/**
* Construct an instance of {@link BuildResumptionData} based on the outcome of the current Maven build.
*
* @param result Outcome of the current Maven build.
* @return A {@link BuildResumptionData} instance or {@link Optional#empty()} if resuming the build is not
* possible.
*/
Optional<BuildResumptionData> determineBuildResumptionData(final MavenExecutionResult result);
}

View File

@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.execution;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.List;
/**
* This class holds the information required to enable resuming a Maven build with {@code --resume}.
*/
public class BuildResumptionData {
/**
* The list of projects that remain to be built.
*/
private final List<String> remainingProjects;
public BuildResumptionData(final List<String> remainingProjects) {
this.remainingProjects = remainingProjects;
}
/**
* Returns the projects that still need to be built when resuming.
*
* @return A list containing the group and artifact id of the projects.
*/
public List<String> getRemainingProjects() {
return this.remainingProjects;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.execution;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;
/**
* Instances of this interface retrieve and store data for the --resume / -r feature. This data is used to ensure newer
* builds of the same project, that have the -r command-line flag, skip successfully built projects during earlier
* invocations of Maven.
*/
public interface BuildResumptionDataRepository {
/**
* Persists any data needed to resume the build at a later point in time, using a new Maven invocation. This method
* may also decide it is not needed or meaningful to persist such data, and return <code>false</code> to indicate
* so.
*
* @param rootProject The root project that is being built.
* @param buildResumptionData Information needed to resume the build.
* @throws BuildResumptionPersistenceException When an error occurs while persisting data.
*/
void persistResumptionData(final MavenProject rootProject, final BuildResumptionData buildResumptionData)
throws BuildResumptionPersistenceException;
/**
* Uses previously stored resumption data to enrich an existing execution request.
*
* @param request The execution request that will be enriched.
* @param rootProject The root project that is being built.
*/
void applyResumptionData(final MavenExecutionRequest request, final MavenProject rootProject);
/**
* Removes previously stored resumption data.
*
* @param rootProject The root project that is being built.
*/
void removeResumptionData(final MavenProject rootProject);
}

View File

@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.execution;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* This exception will be thrown when something fails while persisting build resumption data.
*
* @see BuildResumptionDataRepository#persistResumptionData
*/
public class BuildResumptionPersistenceException extends Exception {
public BuildResumptionPersistenceException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.execution;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.maven.execution.BuildFailure;
import org.apache.maven.execution.BuildSuccess;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of {@link BuildResumptionAnalyzer}.
*/
@Named
@Singleton
public class DefaultBuildResumptionAnalyzer implements BuildResumptionAnalyzer {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBuildResumptionAnalyzer.class);
@Override
public Optional<BuildResumptionData> determineBuildResumptionData(final MavenExecutionResult result) {
if (!result.hasExceptions()) {
return Optional.empty();
}
List<MavenProject> sortedProjects = result.getTopologicallySortedProjects();
boolean hasNoSuccess =
sortedProjects.stream().noneMatch(project -> result.getBuildSummary(project) instanceof BuildSuccess);
if (hasNoSuccess) {
return Optional.empty();
}
List<String> remainingProjects = sortedProjects.stream()
.filter(project -> result.getBuildSummary(project) == null
|| result.getBuildSummary(project) instanceof BuildFailure)
.map(project -> project.getGroupId() + ":" + project.getArtifactId())
.collect(Collectors.toList());
if (remainingProjects.isEmpty()) {
LOGGER.info("No remaining projects found, resuming the build would not make sense.");
return Optional.empty();
}
return Optional.of(new BuildResumptionData(remainingProjects));
}
}

View File

@@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.execution;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This implementation of {@link BuildResumptionDataRepository} persists information in a properties file. The file is
* stored in the build output directory under the Maven execution root.
*/
@Named
@Singleton
public class DefaultBuildResumptionDataRepository implements BuildResumptionDataRepository {
private static final String RESUME_PROPERTIES_FILENAME = "resume.properties";
private static final String REMAINING_PROJECTS = "remainingProjects";
private static final String PROPERTY_DELIMITER = ", ";
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBuildResumptionDataRepository.class);
@Override
public void persistResumptionData(MavenProject rootProject, BuildResumptionData buildResumptionData)
throws BuildResumptionPersistenceException {
Path directory = Paths.get(rootProject.getBuild().getDirectory());
persistResumptionData(directory, buildResumptionData);
}
public void persistResumptionData(Path directory, BuildResumptionData buildResumptionData)
throws BuildResumptionPersistenceException {
Properties properties = convertToProperties(buildResumptionData);
Path resumeProperties = directory.resolve(RESUME_PROPERTIES_FILENAME);
try {
Files.createDirectories(resumeProperties.getParent());
try (Writer writer = Files.newBufferedWriter(resumeProperties)) {
properties.store(writer, null);
}
} catch (IOException e) {
String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file.";
throw new BuildResumptionPersistenceException(message, e);
}
}
private Properties convertToProperties(final BuildResumptionData buildResumptionData) {
Properties properties = new Properties();
String value = String.join(PROPERTY_DELIMITER, buildResumptionData.getRemainingProjects());
properties.setProperty(REMAINING_PROJECTS, value);
return properties;
}
@Override
public void applyResumptionData(MavenExecutionRequest request, MavenProject rootProject) {
Path directory = Paths.get(rootProject.getBuild().getDirectory());
applyResumptionData(request, directory);
}
public void applyResumptionData(MavenExecutionRequest request, Path directory) {
Properties properties = loadResumptionFile(directory);
applyResumptionProperties(request, properties);
}
@Override
public void removeResumptionData(MavenProject rootProject) {
Path directory = Paths.get(rootProject.getBuild().getDirectory());
removeResumptionData(directory);
}
public void removeResumptionData(Path directory) {
Path resumeProperties = directory.resolve(RESUME_PROPERTIES_FILENAME);
try {
Files.deleteIfExists(resumeProperties);
} catch (IOException e) {
LOGGER.warn("Could not delete {} file. ", RESUME_PROPERTIES_FILENAME, e);
}
}
private Properties loadResumptionFile(Path rootBuildDirectory) {
Properties properties = new Properties();
Path path = rootBuildDirectory.resolve(RESUME_PROPERTIES_FILENAME);
if (!Files.exists(path)) {
LOGGER.warn("The {} file does not exist. The --resume / -r feature will not work.", path);
return properties;
}
try (Reader reader = Files.newBufferedReader(path)) {
properties.load(reader);
} catch (IOException e) {
LOGGER.warn("Unable to read {}. The --resume / -r feature will not work.", path);
}
return properties;
}
// This method is made package-private for testing purposes
void applyResumptionProperties(MavenExecutionRequest request, Properties properties) {
if (properties.containsKey(REMAINING_PROJECTS) && StringUtils.isEmpty(request.getResumeFrom())) {
String propertyValue = properties.getProperty(REMAINING_PROJECTS);
Stream.of(propertyValue.split(PROPERTY_DELIMITER))
.filter(StringUtils::isNotEmpty)
.forEach(request.getSelectedProjects()::add);
LOGGER.info("Resuming from {} due to the --resume / -r feature.", propertyValue);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.plugin;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.plugin.version.PluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.plugin.version.PluginVersionResult;
import org.apache.maven.plugin.version.internal.DefaultPluginVersionResolver;
import org.eclipse.aether.SessionData;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.sisu.Priority;
import org.eclipse.sisu.Typed;
@Named
@Singleton
@Priority(10)
@Typed(PluginVersionResolver.class)
public class CachingPluginVersionResolver extends DefaultPluginVersionResolver {
private static final Object CACHE_KEY = new Object();
@Override
public PluginVersionResult resolve(PluginVersionRequest request) throws PluginVersionResolutionException {
Map<String, PluginVersionResult> cache =
getCache(request.getRepositorySession().getData());
String key = getKey(request);
PluginVersionResult result = cache.get(key);
if (result == null) {
result = super.resolve(request);
cache.putIfAbsent(key, result);
}
return result;
}
@SuppressWarnings("unchecked")
private Map<String, PluginVersionResult> getCache(SessionData data) {
Map<String, PluginVersionResult> cache = (Map<String, PluginVersionResult>) data.get(CACHE_KEY);
while (cache == null) {
cache = new ConcurrentHashMap<>(256);
if (data.set(CACHE_KEY, null, cache)) {
break;
}
cache = (Map<String, PluginVersionResult>) data.get(CACHE_KEY);
}
return cache;
}
private static String getKey(PluginVersionRequest request) {
return Stream.concat(
Stream.of(request.getGroupId(), request.getArtifactId()),
request.getRepositories().stream().map(RemoteRepository::getId))
.collect(Collectors.joining(":"));
}
}

View File

@@ -0,0 +1,779 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.plugin;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.classrealm.ClassRealmManager;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
import org.apache.maven.model.Plugin;
import org.apache.maven.monitor.logging.DefaultLog;
import org.apache.maven.plugin.*;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
import org.apache.maven.plugin.internal.PluginDependenciesResolver;
import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionRequest;
import org.apache.maven.plugin.version.PluginVersionResolutionException;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.project.ExtensionDescriptor;
import org.apache.maven.project.ExtensionDescriptorBuilder;
import org.apache.maven.project.MavenProject;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.apache.maven.session.scope.internal.SessionScopeModule;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ComponentConfigurator;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.component.repository.ComponentDescriptor;
import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfigurationException;
import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.LoggerManager;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
import org.eclipse.sisu.Priority;
import org.eclipse.sisu.Typed;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginDescriptorCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache;
/*
* gnodet: This file is based on maven DefaultMavenPluginManager and changed in order
* to better support parallel builds. See https://github.com/apache/maven-mvnd/issues/310
*/
/**
* Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
* that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
* plugins as well as special purpose plugins like reports.
*
* @author Benjamin Bentmann
* @since 3.0
*/
@Singleton
@Named
@Priority(10)
@Typed(MavenPluginManager.class)
public class CliMavenPluginManager implements MavenPluginManager {
/**
* <p>
* PluginId =&gt; ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the
* same class realm is used to load build extensions and load mojos for extensions=true plugins.
* </p>
* <strong>Note:</strong> This is part of internal implementation and may be changed or removed without notice
*
* @since 3.3.0
*/
public static final String KEY_EXTENSIONS_REALMS = CliMavenPluginManager.class.getName() + "/extensionsRealms";
@Inject
private Logger logger;
@Inject
private LoggerManager loggerManager;
@Inject
private PlexusContainer container;
@Inject
private ClassRealmManager classRealmManager;
@Inject
private InvalidatingPluginDescriptorCache pluginDescriptorCache;
@Inject
private InvalidatingPluginRealmCache pluginRealmCache;
@Inject
private PluginDependenciesResolver pluginDependenciesResolver;
@Inject
private RuntimeInformation runtimeInformation;
@Inject
private ExtensionRealmCache extensionRealmCache;
@Inject
private PluginVersionResolver pluginVersionResolver;
@Inject
private PluginArtifactsCache pluginArtifactsCache;
private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
public PluginDescriptor getPluginDescriptor(
Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
org.eclipse.aether.artifact.Artifact artifact =
pluginDependenciesResolver.resolve(plugin, repositories, session);
Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
return descriptor;
});
pluginDescriptor.setPlugin(plugin);
return pluginDescriptor;
}
private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
PluginDescriptor pluginDescriptor = null;
File pluginFile = pluginArtifact.getFile();
try {
if (pluginFile.isFile()) {
try (JarFile pluginJar = new JarFile(pluginFile, false)) {
ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
if (pluginDescriptorEntry != null) {
InputStream is = pluginJar.getInputStream(pluginDescriptorEntry);
pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath());
}
}
} else {
File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
if (pluginXml.isFile()) {
try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) {
pluginDescriptor = parsePluginDescriptor(is, plugin, pluginXml.getAbsolutePath());
}
}
}
if (pluginDescriptor == null) {
throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
}
} catch (IOException e) {
throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
}
MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact);
validator.validate(pluginDescriptor);
if (validator.hasErrors()) {
throw new InvalidPluginDescriptorException(
"Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors());
}
pluginDescriptor.setPluginArtifact(pluginArtifact);
return pluginDescriptor;
}
private String getPluginDescriptorLocation() {
return "META-INF/maven/plugin.xml";
}
private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation)
throws PluginDescriptorParsingException {
try {
Reader reader = ReaderFactory.newXmlReader(is);
PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation);
return pluginDescriptor;
} catch (IOException | PlexusConfigurationException e) {
throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
}
}
public MojoDescriptor getMojoDescriptor(
Plugin plugin, String goal, List<RemoteRepository> repositories, RepositorySystemSession session)
throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
InvalidPluginDescriptorException {
PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
if (mojoDescriptor == null) {
throw new MojoNotFoundException(goal, pluginDescriptor);
}
return mojoDescriptor;
}
public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
if (StringUtils.isNotBlank(requiredMavenVersion)) {
try {
if (!runtimeInformation.isMavenVersion(requiredMavenVersion)) {
throw new PluginIncompatibleException(
pluginDescriptor.getPlugin(),
"The plugin " + pluginDescriptor.getId() + " requires Maven version "
+ requiredMavenVersion);
}
} catch (RuntimeException e) {
logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage());
}
}
}
public void setupPluginRealm(
PluginDescriptor pluginDescriptor,
MavenSession session,
ClassLoader parent,
List<String> imports,
DependencyFilter filter)
throws PluginResolutionException, PluginContainerException {
Plugin plugin = pluginDescriptor.getPlugin();
MavenProject project = session.getCurrentProject();
if (plugin.isExtensions()) {
ExtensionRealmCache.CacheRecord extensionRecord;
try {
RepositorySystemSession repositorySession = session.getRepositorySession();
extensionRecord = setupExtensionsRealm(project, plugin, repositorySession);
} catch (PluginManagerException e) {
// extensions realm is expected to be fully setup at this point
// any exception means a problem in maven code, not a user error
throw new IllegalStateException(e);
}
ClassRealm pluginRealm = extensionRecord.getRealm();
List<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(pluginRealm);
}
pluginDescriptor.setClassRealm(pluginRealm);
pluginDescriptor.setArtifacts(pluginArtifacts);
} else {
Map<String, ClassLoader> foreignImports = calcImports(project, parent, imports);
PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(
plugin,
parent,
foreignImports,
filter,
project.getRemotePluginRepositories(),
session.getRepositorySession());
PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey, () -> {
createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
return new PluginRealmCache.CacheRecord(
pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
});
if (cacheRecord != null) {
pluginDescriptor.setClassRealm(cacheRecord.getRealm());
pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(cacheRecord.getRealm());
}
}
pluginRealmCache.register(project, cacheKey, cacheRecord);
}
}
private void createPluginRealm(
PluginDescriptor pluginDescriptor,
MavenSession session,
ClassLoader parent,
Map<String, ClassLoader> foreignImports,
DependencyFilter filter)
throws PluginResolutionException, PluginContainerException {
Plugin plugin = Objects.requireNonNull(pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null");
Artifact pluginArtifact = Objects.requireNonNull(
pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null");
MavenProject project = session.getCurrentProject();
final ClassRealm pluginRealm;
final List<Artifact> pluginArtifacts;
RepositorySystemSession repositorySession = session.getRepositorySession();
DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter);
DependencyNode root = pluginDependenciesResolver.resolve(
plugin,
RepositoryUtils.toArtifact(pluginArtifact),
dependencyFilter,
project.getRemotePluginRepositories(),
repositorySession);
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
root.accept(nlg);
pluginArtifacts = toMavenArtifacts(root, nlg);
if (parent == null) {
parent = new URLClassLoader(new URL[0]);
}
pluginRealm = classRealmManager.createPluginRealm(
plugin, parent, null, foreignImports, toAetherArtifacts(pluginArtifacts));
discoverPluginComponents(pluginRealm, plugin, pluginDescriptor);
pluginDescriptor.setClassRealm(pluginRealm);
pluginDescriptor.setArtifacts(pluginArtifacts);
}
private void discoverPluginComponents(
final ClassRealm pluginRealm, Plugin plugin, PluginDescriptor pluginDescriptor)
throws PluginContainerException {
try {
if (pluginDescriptor != null) {
for (ComponentDescriptor<?> componentDescriptor : pluginDescriptor.getComponents()) {
componentDescriptor.setRealm(pluginRealm);
container.addComponentDescriptor(componentDescriptor);
}
}
((DefaultPlexusContainer) container)
.discoverComponents(
pluginRealm, new SessionScopeModule(container), new MojoExecutionScopeModule(container));
} catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
throw new PluginContainerException(
plugin,
pluginRealm,
"Error in component graph of plugin " + plugin.getId() + ": " + e.getMessage(),
e);
}
}
private List<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final List<Artifact> pluginArtifacts) {
return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
}
private List<Artifact> toMavenArtifacts(DependencyNode root, PreorderNodeListGenerator nlg) {
List<Artifact> artifacts = new ArrayList<>(nlg.getNodes().size());
RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections.<String>emptyList(), null);
for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) {
Artifact artifact = it.next();
if (artifact.getFile() == null) {
it.remove();
}
}
return Collections.unmodifiableList(artifacts);
}
private Map<String, ClassLoader> calcImports(MavenProject project, ClassLoader parent, List<String> imports) {
Map<String, ClassLoader> foreignImports = new HashMap<>();
ClassLoader projectRealm = project.getClassRealm();
if (projectRealm != null) {
foreignImports.put("", projectRealm);
} else {
foreignImports.put("", classRealmManager.getMavenApiRealm());
}
if (parent != null && imports != null) {
for (String parentImport : imports) {
foreignImports.put(parentImport, parent);
}
}
return foreignImports;
}
public <T> T getConfiguredMojo(Class<T> mojoInterface, MavenSession session, MojoExecution mojoExecution)
throws PluginConfigurationException, PluginContainerException {
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
if (logger.isDebugEnabled()) {
logger.debug("Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
}
// We are forcing the use of the plugin realm for all lookups that might occur during
// the lifecycle that is part of the lookup. Here we are specifically trying to keep
// lookups that occur in contextualize calls in line with the right realm.
ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(pluginRealm);
try {
T mojo;
try {
mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
} catch (ComponentLookupException e) {
Throwable cause = e.getCause();
while (cause != null
&& !(cause instanceof LinkageError)
&& !(cause instanceof ClassNotFoundException)) {
cause = cause.getCause();
}
if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "'. A required class is missing: "
+ cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
} else if (cause instanceof LinkageError) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ pluginDescriptor.getId() + "' due to an API incompatibility: "
+ e.getClass().getName() + ": " + cause.getMessage());
pluginRealm.display(ps);
throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
}
throw new PluginContainerException(
mojoDescriptor,
pluginRealm,
"Unable to load the mojo '" + mojoDescriptor.getGoal()
+ "' (or one of its required components) from the plugin '"
+ pluginDescriptor.getId() + "'",
e);
}
if (mojo instanceof ContextEnabled) {
MavenProject project = session.getCurrentProject();
Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
if (pluginContext != null) {
pluginContext.put("project", project);
pluginContext.put("pluginDescriptor", pluginDescriptor);
((ContextEnabled) mojo).setPluginContext(pluginContext);
}
}
if (mojo instanceof Mojo) {
Logger mojoLogger = loggerManager.getLoggerForComponent(mojoDescriptor.getImplementation());
((Mojo) mojo).setLog(new DefaultLog(mojoLogger));
}
Xpp3Dom dom = mojoExecution.getConfiguration();
PlexusConfiguration pomConfiguration;
if (dom == null) {
pomConfiguration = new XmlPlexusConfiguration("configuration");
} else {
pomConfiguration = new XmlPlexusConfiguration(dom);
}
ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
populatePluginFields(mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator);
return mojo;
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
container.setLookupRealm(oldLookupRealm);
}
}
private void populatePluginFields(
Object mojo,
MojoDescriptor mojoDescriptor,
ClassRealm pluginRealm,
PlexusConfiguration configuration,
ExpressionEvaluator expressionEvaluator)
throws PluginConfigurationException {
ComponentConfigurator configurator = null;
String configuratorId = mojoDescriptor.getComponentConfigurator();
if (StringUtils.isEmpty(configuratorId)) {
configuratorId = "basic";
}
try {
// TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
// so that this method could entirely be handled by a plexus lookup?
configurator = container.lookup(ComponentConfigurator.class, configuratorId);
ConfigurationListener listener = new DebugConfigurationListener(logger);
ValidatingConfigurationListener validator =
new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
logger.debug(
"Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->");
configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
logger.debug("-- end configuration --");
Collection<Parameter> missingParameters = validator.getMissingParameters();
if (!missingParameters.isEmpty()) {
if ("basic".equals(configuratorId)) {
throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
} else {
/*
* NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
* hard way.
*/
validateParameters(mojoDescriptor, configuration, expressionEvaluator);
}
}
} catch (ComponentConfigurationException e) {
String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
if (e.getFailedConfiguration() != null) {
message += " for parameter " + e.getFailedConfiguration().getName();
}
message += ": " + e.getMessage();
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
} catch (ComponentLookupException e) {
throw new PluginConfigurationException(
mojoDescriptor.getPluginDescriptor(),
"Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
+ mojoDescriptor.getId(),
e);
} catch (NoClassDefFoundError e) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
+ e.getMessage());
pluginRealm.display(ps);
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
} catch (LinkageError e) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
PrintStream ps = new PrintStream(os);
ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
+ ": " + e.getClass().getName() + ": " + e.getMessage());
pluginRealm.display(ps);
throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
} finally {
if (configurator != null) {
try {
container.release(configurator);
} catch (ComponentLifecycleException e) {
logger.debug("Failed to release mojo configurator - ignoring.");
}
}
}
}
private void validateParameters(
MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
throws ComponentConfigurationException, PluginParameterException {
if (mojoDescriptor.getParameters() == null) {
return;
}
List<Parameter> invalidParameters = new ArrayList<>();
for (Parameter parameter : mojoDescriptor.getParameters()) {
if (!parameter.isRequired()) {
continue;
}
Object value = null;
PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
if (config != null) {
String expression = config.getValue(null);
try {
value = expressionEvaluator.evaluate(expression);
if (value == null) {
value = config.getAttribute("default-value", null);
}
} catch (ExpressionEvaluationException e) {
String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
+ configuration.getName() + "'";
throw new ComponentConfigurationException(configuration, msg, e);
}
}
if (value == null && (config == null || config.getChildCount() <= 0)) {
invalidParameters.add(parameter);
}
}
if (!invalidParameters.isEmpty()) {
throw new PluginParameterException(mojoDescriptor, invalidParameters);
}
}
public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
if (mojo != null) {
try {
container.release(mojo);
} catch (ComponentLifecycleException e) {
String goalExecId = mojoExecution.getGoal();
if (mojoExecution.getExecutionId() != null) {
goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
}
logger.debug("Error releasing mojo for " + goalExecId, e);
}
}
}
public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
@SuppressWarnings("unchecked")
Map<String, ExtensionRealmCache.CacheRecord> pluginRealms =
(Map<String, ExtensionRealmCache.CacheRecord>) project.getContextValue(KEY_EXTENSIONS_REALMS);
if (pluginRealms == null) {
pluginRealms = new HashMap<>();
project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
}
final String pluginKey = plugin.getId();
ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
if (extensionRecord != null) {
return extensionRecord;
}
final List<RemoteRepository> repositories = project.getRemotePluginRepositories();
// resolve plugin version as necessary
if (plugin.getVersion() == null) {
PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
try {
plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
} catch (PluginVersionResolutionException e) {
throw new PluginManagerException(plugin, e.getMessage(), e);
}
}
// resolve plugin artifacts
List<Artifact> artifacts;
PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
PluginArtifactsCache.CacheRecord recordArtifacts;
try {
recordArtifacts = pluginArtifactsCache.get(cacheKey);
} catch (PluginResolutionException e) {
throw new PluginManagerException(plugin, e.getMessage(), e);
}
if (recordArtifacts != null) {
artifacts = recordArtifacts.getArtifacts();
} else {
try {
artifacts = resolveExtensionArtifacts(plugin, repositories, session);
recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
} catch (PluginResolutionException e) {
pluginArtifactsCache.put(cacheKey, e);
pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
throw new PluginManagerException(plugin, e.getMessage(), e);
}
}
pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
// create and cache extensions realms
final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
extensionRecord = extensionRealmCache.get(extensionKey);
if (extensionRecord == null) {
ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
// TODO figure out how to use the same PluginDescriptor when running mojos
PluginDescriptor pluginDescriptor = null;
if (plugin.isExtensions() && !artifacts.isEmpty()) {
// ignore plugin descriptor parsing errors at this point
// these errors will reported during calculation of project build execution plan
try {
pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
} catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
// ignore, see above
}
}
discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
ExtensionDescriptor extensionDescriptor = null;
Artifact extensionArtifact = artifacts.get(0);
try {
extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
} catch (IOException e) {
String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
if (logger.isDebugEnabled()) {
logger.error(message, e);
} else {
logger.error(message);
}
}
extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
}
extensionRealmCache.register(project, extensionKey, extensionRecord);
pluginRealms.put(pluginKey, extensionRecord);
return extensionRecord;
}
private List<Artifact> resolveExtensionArtifacts(
Plugin extensionPlugin, List<RemoteRepository> repositories, RepositorySystemSession session)
throws PluginResolutionException {
DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session);
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
root.accept(nlg);
return toMavenArtifacts(root, nlg);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.plugin;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
/**
* A configuration listener to help validate the plugin configuration. For instance, check for required but missing
* parameters.
*
* @author Benjamin Bentmann
*/
class ValidatingConfigurationListener implements ConfigurationListener {
private final Object mojo;
private final ConfigurationListener delegate;
private final Map<String, Parameter> missingParameters;
ValidatingConfigurationListener(Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate) {
this.mojo = mojo;
this.delegate = delegate;
this.missingParameters = new HashMap<>();
if (mojoDescriptor.getParameters() != null) {
for (Parameter param : mojoDescriptor.getParameters()) {
if (param.isRequired()) {
missingParameters.put(param.getName(), param);
}
}
}
}
public Collection<Parameter> getMissingParameters() {
return missingParameters.values();
}
public void notifyFieldChangeUsingSetter(String fieldName, Object value, Object target) {
delegate.notifyFieldChangeUsingSetter(fieldName, value, target);
if (mojo == target) {
notify(fieldName, value);
}
}
public void notifyFieldChangeUsingReflection(String fieldName, Object value, Object target) {
delegate.notifyFieldChangeUsingReflection(fieldName, value, target);
if (mojo == target) {
notify(fieldName, value);
}
}
private void notify(String fieldName, Object value) {
if (value != null) {
missingParameters.remove(fieldName);
}
}
}

50
daemon-m40/pom.xml Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd</artifactId>
<version>1.0.0-m5-SNAPSHOT</version>
</parent>
<artifactId>mvnd-daemon-m40</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon - Daemon 4.0.x specifics</name>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>${maven4.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>${maven4.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -70,8 +70,6 @@ import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.PluginArtifactsCache;
import org.apache.maven.plugin.PluginRealmCache;
import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifactsCache;
import org.apache.maven.properties.internal.SystemProperties;
@@ -93,7 +91,6 @@ import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.transfer.TransferListener;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingExtensionRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginArtifactsCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingProjectArtifactsCache;
import org.mvndaemon.mvnd.cli.EnvHelper;
import org.mvndaemon.mvnd.common.Environment;
@@ -102,7 +99,6 @@ import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager;
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener;
import org.mvndaemon.mvnd.logging.smart.LoggingOutputStream;
import org.mvndaemon.mvnd.plugin.CachingPluginVersionResolver;
import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
@@ -119,7 +115,7 @@ import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
*
* @author Jason van Zyl
*/
public class DaemonMavenCli {
public class DaemonMavenCli implements DaemonCli {
public static final String LOCAL_REPO_PROPERTY = "maven.repo.local";
public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory";
@@ -522,9 +518,9 @@ public class DaemonMavenCli {
bind(CoreExportsProvider.class).toInstance(new CoreExportsProvider(exports));
bind(ExtensionRealmCache.class).to(InvalidatingExtensionRealmCache.class);
bind(PluginArtifactsCache.class).to(InvalidatingPluginArtifactsCache.class);
bind(PluginRealmCache.class).to(InvalidatingPluginRealmCache.class);
// bind(PluginRealmCache.class).to(InvalidatingPluginRealmCache.class);
bind(ProjectArtifactsCache.class).to(InvalidatingProjectArtifactsCache.class);
bind(PluginVersionResolver.class).to(CachingPluginVersionResolver.class);
// bind(PluginVersionResolver.class).to(CachingPluginVersionResolver.class);
}
});

View File

@@ -18,6 +18,8 @@
*/
package org.apache.maven.project;
import java.util.Objects;
import org.apache.maven.building.Source;
import org.apache.maven.model.building.ModelCache;
@@ -27,8 +29,8 @@ public class SnapshotModelCache implements ModelCache {
private final ModelCache reactorCache;
public SnapshotModelCache(ModelCache globalCache, ModelCache reactorCache) {
this.globalCache = globalCache;
this.reactorCache = reactorCache;
this.globalCache = Objects.requireNonNull(globalCache);
this.reactorCache = Objects.requireNonNull(reactorCache);
}
public Object get(Source path, String tag) {

View File

@@ -0,0 +1,136 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.cache.invalidating;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.DefaultPluginDescriptorCache;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
import org.apache.maven.plugin.PluginResolutionException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.sisu.Priority;
import org.mvndaemon.mvnd.cache.Cache;
import org.mvndaemon.mvnd.cache.CacheFactory;
import org.mvndaemon.mvnd.cache.CacheRecord;
@Singleton
@Named
@Priority(10)
public class InvalidatingPluginDescriptorCache extends DefaultPluginDescriptorCache {
@FunctionalInterface
public interface PluginDescriptorSupplier {
PluginDescriptor load()
throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException;
}
protected static class Record implements CacheRecord {
private final PluginDescriptor descriptor;
public Record(PluginDescriptor descriptor) {
this.descriptor = descriptor;
}
@Override
public Stream<Path> getDependencyPaths() {
return Optional.ofNullable(descriptor.getArtifacts()).orElse(Collections.emptyList()).stream()
.map(artifact -> artifact.getFile().toPath());
}
@Override
public void invalidate() {
ClassRealm realm = descriptor.getClassRealm();
try {
realm.getWorld().disposeRealm(realm.getId());
} catch (NoSuchRealmException e) {
// ignore
}
}
}
final Cache<Key, Record> cache;
@Inject
public InvalidatingPluginDescriptorCache(CacheFactory cacheFactory) {
this.cache = cacheFactory.newCache();
}
@Override
public Key createKey(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session) {
return super.createKey(plugin, repositories, session);
}
@Override
public PluginDescriptor get(Key key) {
Record r = cache.get(key);
return r != null ? clone(r.descriptor) : null;
}
public PluginDescriptor get(Key key, PluginDescriptorSupplier supplier)
throws PluginDescriptorParsingException, PluginResolutionException, InvalidPluginDescriptorException {
try {
Record r = cache.computeIfAbsent(key, k -> {
try {
return new Record(clone(supplier.load()));
} catch (PluginDescriptorParsingException
| PluginResolutionException
| InvalidPluginDescriptorException e) {
throw new RuntimeException(e);
}
});
return clone(r.descriptor);
} catch (RuntimeException e) {
if (e.getCause() instanceof PluginDescriptorParsingException) {
throw (PluginDescriptorParsingException) e.getCause();
}
if (e.getCause() instanceof PluginResolutionException) {
throw (PluginResolutionException) e.getCause();
}
if (e.getCause() instanceof InvalidPluginDescriptorException) {
throw (InvalidPluginDescriptorException) e.getCause();
}
throw e;
}
}
@Override
public void put(Key key, PluginDescriptor descriptor) {
cache.put(key, new Record(clone(descriptor)));
}
@Override
public void flush() {
cache.clear();
}
}

View File

@@ -0,0 +1,190 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.cache.invalidating;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.eventspy.EventSpy;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.project.MavenProject;
import org.eclipse.sisu.Typed;
import org.mvndaemon.mvnd.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Named
@Singleton
@Typed(EventSpy.class)
public class InvalidatingRealmCacheEventSpy extends AbstractEventSpy {
private static final Logger LOG = LoggerFactory.getLogger(InvalidatingRealmCacheEventSpy.class);
private final InvalidatingPluginRealmCache pluginCache;
private final InvalidatingExtensionRealmCache extensionCache;
private final InvalidatingProjectArtifactsCache projectArtifactsCache;
private Path multiModuleProjectDirectory;
private String pattern;
private PathMatcher matcher;
@Inject
public InvalidatingRealmCacheEventSpy(
InvalidatingPluginRealmCache cache,
InvalidatingExtensionRealmCache extensionCache,
InvalidatingProjectArtifactsCache projectArtifactsCache) {
this.pluginCache = cache;
this.extensionCache = extensionCache;
this.projectArtifactsCache = projectArtifactsCache;
}
@Override
public void onEvent(Object event) throws Exception {
try {
if (event instanceof MavenExecutionRequest) {
/* Store the multiModuleProjectDirectory path */
multiModuleProjectDirectory = ((MavenExecutionRequest) event)
.getMultiModuleProjectDirectory()
.toPath();
pattern = Environment.MVND_PLUGIN_REALM_EVICT_PATTERN
.asOptional()
.orElse(Environment.MVND_PLUGIN_REALM_EVICT_PATTERN.getDefault());
if (!pattern.isEmpty()) {
String[] patterns = pattern.split(",");
List<PathMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
if (pattern.startsWith("mvn:")) {
String[] parts = pattern.substring("mvn:".length()).split(":");
String groupId, artifactId, version;
if (parts.length >= 3) {
version = parts[2];
} else {
version = "*";
}
if (parts.length >= 2) {
groupId = parts[0];
artifactId = parts[1];
} else {
groupId = "*";
artifactId = parts[0];
}
pattern = "glob:**/" + ("*".equals(groupId) ? "" : groupId.replace('.', '/') + "/")
+ artifactId + "/" + ("*".equals(version) ? "**" : version + "/**");
}
matchers.add(getPathMatcher(pattern));
}
if (matchers.size() == 1) {
matcher = matchers.iterator().next();
} else {
matcher = path -> matchers.stream().anyMatch(f -> f.matches(path));
}
}
} else if (event instanceof MavenExecutionResult) {
/* Evict the entries referring to jars under multiModuleProjectDirectory */
pluginCache.cache.removeIf(this::shouldEvict);
extensionCache.cache.removeIf(this::shouldEvict);
MavenExecutionResult mer = (MavenExecutionResult) event;
List<MavenProject> projects = mer.getTopologicallySortedProjects();
projectArtifactsCache.cache.removeIf(
(k, r) -> shouldEvict(projects, (InvalidatingProjectArtifactsCache.CacheKey) k, r));
}
} catch (Exception e) {
LOG.warn("Could not notify CliPluginRealmCache", e);
}
}
private boolean shouldEvict(
List<MavenProject> projects,
InvalidatingProjectArtifactsCache.CacheKey k,
InvalidatingProjectArtifactsCache.Record v) {
return projects.stream().anyMatch(p -> k.matches(p.getGroupId(), p.getArtifactId(), p.getVersion()));
}
private boolean shouldEvict(InvalidatingPluginRealmCache.Key k, InvalidatingPluginRealmCache.Record v) {
try {
for (URL url : v.record.getRealm().getURLs()) {
if (url.getProtocol().equals("file")) {
final Path path = Paths.get(url.toURI());
if (path.startsWith(multiModuleProjectDirectory)) {
LOG.debug(
"Removing PluginRealmCache entry {} because it refers to an artifact in the build tree {}",
k,
path);
return true;
} else if (matcher != null && matcher.matches(path)) {
LOG.debug(
"Removing PluginRealmCache entry {} because its components {} matches the eviction pattern '{}'",
k,
path,
pattern);
return true;
}
}
}
return false;
} catch (URISyntaxException e) {
return true;
}
}
private boolean shouldEvict(InvalidatingExtensionRealmCache.Key k, InvalidatingExtensionRealmCache.Record v) {
try {
for (URL url : v.record.getRealm().getURLs()) {
if (url.getProtocol().equals("file")) {
final Path path = Paths.get(url.toURI());
if (path.startsWith(multiModuleProjectDirectory)) {
LOG.debug(
"Removing ExtensionRealmCache entry {} because it refers to an artifact in the build tree {}",
k,
path);
return true;
} else if (matcher != null && matcher.matches(path)) {
LOG.debug(
"Removing ExtensionRealmCache entry {} because its components {} matches the eviction pattern '{}'",
k,
path,
pattern);
return true;
}
}
}
return false;
} catch (URISyntaxException e) {
return true;
}
}
private static PathMatcher getPathMatcher(String pattern) {
if (!pattern.startsWith("glob:") && !pattern.startsWith("regex:")) {
pattern = "glob:" + pattern;
}
return FileSystems.getDefault().getPathMatcher(pattern);
}
}

View File

@@ -28,7 +28,7 @@
<artifactId>mvnd-daemon</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon</name>
<name>Maven Daemon - Daemon</name>
<dependencies>
<dependency>

View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.cli;
import java.util.List;
import java.util.Map;
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
/**
* Simple interface to bridge maven 3.9.x and 4.0.x CLI
*/
public interface DaemonCli {
int main(
List<String> args,
String workingDir,
String projectDir,
Map<String, String> env,
BuildEventListener buildEventListener)
throws Exception;
}

View File

@@ -50,7 +50,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.maven.cli.DaemonMavenCli;
import org.apache.maven.cli.DaemonCli;
import org.mvndaemon.mvnd.builder.SmartBuilder;
import org.mvndaemon.mvnd.common.DaemonConnection;
import org.mvndaemon.mvnd.common.DaemonException;
@@ -87,7 +87,7 @@ public class Server implements AutoCloseable, Runnable {
private final String daemonId;
private final boolean noDaemon;
private final ServerSocketChannel socket;
private final DaemonMavenCli cli;
private final DaemonCli cli;
private volatile DaemonInfo info;
private final DaemonRegistry registry;
@@ -129,7 +129,11 @@ public class Server implements AutoCloseable, Runnable {
.orElse(SocketFamily.inet);
try {
cli = new DaemonMavenCli();
cli = (DaemonCli) getClass()
.getClassLoader()
.loadClass("org.apache.maven.cli.DaemonMavenCli")
.getDeclaredConstructor()
.newInstance();
registry = new DaemonRegistry(Environment.MVND_REGISTRY.asPath());
socket = socketFamily.openServerSocket();
executor = Executors.newScheduledThreadPool(1);

View File

@@ -25,10 +25,10 @@
<version>1.0.0-m5-SNAPSHOT</version>
</parent>
<artifactId>mvnd-dist</artifactId>
<artifactId>mvnd-dist-m39</artifactId>
<packaging>pom</packaging>
<name>Maven Daemon - Distribution</name>
<name>Maven Daemon - Distribution for 3.9.x</name>
<properties>
<maven.compiler.target>11</maven.compiler.target>
@@ -52,6 +52,10 @@
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon-m39</artifactId>
</dependency>
</dependencies>
<build>
@@ -67,7 +71,7 @@
</goals>
<phase>package</phase>
<configuration>
<outputDirectory>${project.build.directory}/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}</outputDirectory>
<outputDirectory>${project.build.directory}/maven-${project.version}-mvnd-m39-${os.detected.name}-${os.detected.arch}</outputDirectory>
</configuration>
</execution>
</executions>

View File

@@ -0,0 +1,93 @@
<!--
Copyright 2019-2021 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<assembly>
<artifactSet to="/">
<artifact id="org.apache.maven:apache-maven:tar.gz:bin:${maven3.version}">
<unpack useRoot="false"
excludes="conf/logging/*,lib/maven-slf4j-provider*" />
</artifact>
</artifactSet>
<artifactSet to="/lib">
<artifact id="ch.qos.logback:logback-classic">
<exclusion id="*:*"/>
</artifact>
<artifact id="ch.qos.logback:logback-core">
<exclusion id="*:*"/>
</artifact>
</artifactSet>
<artifactSet to="/lib/ext">
<artifact id="org.apache.maven.daemon:mvnd-daemon:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-daemon-m39:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-client:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-common:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-agent:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-helper-agent:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-native:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.codehaus.plexus:plexus-interactivity-api">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.jline:jline-terminal">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.jline:jline-terminal-jansi">
<exclusion id="*:*"/>
</artifact>
</artifactSet>
<fileSet to="/">
<directory path="${basedir}/../dist/src/main/distro"/>
<directory path="${basedir}/..">
<include>NOTICE.txt</include>
<include>LICENSE.txt</include>
<include>README.adoc</include>
</directory>
</fileSet>
<fileSet to="/bin">
<directory path="${basedir}/../client/target">
<include>mvnd</include>
<include>mvnd.exe</include>
</directory>
<directory path="${basedir}/../dist/src/main/resources">
<include>platform-${os.detected.name}-${os.detected.arch}</include>
</directory>
</fileSet>
<archive name="maven-${project.version}-mvnd-mvn39-${os.detected.name}-${os.detected.arch}.zip"
executable="**/bin/mvnd"/>
<archive name="maven-${project.version}-mvnd-mvn39-${os.detected.name}-${os.detected.arch}.tar.gz"
executable="**/bin/mvnd"/>
</assembly>

82
dist-m40/pom.xml Normal file
View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019-2021 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd</artifactId>
<version>1.0.0-m5-SNAPSHOT</version>
</parent>
<artifactId>mvnd-dist-m40</artifactId>
<packaging>pom</packaging>
<name>Maven Daemon - Distribution for 4.0.x</name>
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-agent</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-helper-agent</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon-m40</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>ca.vanzyl.provisio.maven.plugins</groupId>
<artifactId>provisio-maven-plugin</artifactId>
<executions>
<execution>
<id>maven-distro</id>
<goals>
<goal>provision</goal>
</goals>
<phase>package</phase>
<configuration>
<outputDirectory>${project.build.directory}/maven-${project.version}-mvnd-m40-${os.detected.name}-${os.detected.arch}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -18,7 +18,7 @@
<assembly>
<artifactSet to="/">
<artifact id="org.apache.maven:apache-maven:tar.gz:bin">
<artifact id="org.apache.maven:apache-maven:tar.gz:bin:${maven4.version}">
<unpack useRoot="false"
excludes="conf/logging/*,lib/maven-slf4j-provider*,lib/plexus-utils-3.*" />
</artifact>
@@ -37,6 +37,9 @@
<artifact id="org.apache.maven.daemon:mvnd-daemon:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-daemon-m40:${project.version}">
<exclusion id="*:*"/>
</artifact>
<artifact id="org.apache.maven.daemon:mvnd-client:${project.version}">
<exclusion id="*:*"/>
</artifact>
@@ -64,7 +67,7 @@
</artifactSet>
<fileSet to="/">
<directory path="${basedir}/src/main/distro"/>
<directory path="${basedir}/../dist/src/main/distro"/>
<directory path="${basedir}/..">
<include>NOTICE.txt</include>
<include>LICENSE.txt</include>
@@ -76,13 +79,15 @@
<include>mvnd</include>
<include>mvnd.exe</include>
</directory>
<file touch="platform-${os.detected.name}-${os.detected.arch}"/>
<directory path="${basedir}/../dist/src/main/resources">
<include>platform-${os.detected.name}-${os.detected.arch}</include>
</directory>
</fileSet>
<archive name="maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}.zip"
<archive name="maven-${project.version}-mvnd-40-${os.detected.name}-${os.detected.arch}.zip"
executable="**/bin/mvnd"/>
<archive name="maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}.tar.gz"
<archive name="maven-${project.version}-mvnd-40-${os.detected.name}-${os.detected.arch}.tar.gz"
executable="**/bin/mvnd"/>
</assembly>

View File

@@ -0,0 +1,13 @@
Copyright 2019-2021 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,13 @@
Copyright 2019-2021 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,13 @@
Copyright 2019-2021 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,13 @@
Copyright 2019-2021 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -32,7 +32,8 @@
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<mvnd.home>${project.basedir}/../dist/target/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}</mvnd.home>
<mvnd.m39.home>${project.basedir}/../dist-m39/target/maven-${project.version}-mvnd-m39-${os.detected.name}-${os.detected.arch}</mvnd.m39.home>
<mvnd.m40.home>${project.basedir}/../dist-m40/target/maven-${project.version}-mvnd-m40-${os.detected.name}-${os.detected.arch}</mvnd.m40.home>
<preinstall.artifacts>org/apache/maven/surefire/surefire-providers/${surefire.version}
org/apache/maven/surefire/surefire-junit-platform/${surefire.version}
org/junit/platform/junit-platform-launcher/${junit-platform-launcher.version}
@@ -56,7 +57,19 @@
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-dist</artifactId>
<artifactId>mvnd-dist-m39</artifactId>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-dist-m40</artifactId>
<type>pom</type>
<scope>test</scope>
<exclusions>
@@ -88,18 +101,51 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<rerunFailingTestsCount>4</rerunFailingTestsCount>
</configuration>
<executions>
<execution>
<id>default-test</id>
<phase>none</phase>
</execution>
<execution>
<id>mvn-39</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
<configuration>
<systemPropertyVariables>
<mvnd.home>${mvnd.m39.home}</mvnd.home>
<project.version>${project.version}</project.version>
<mvnd.home>${mvnd.home}</mvnd.home>
<mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
<os.detected.name>${os.detected.name}</os.detected.name>
<os.detected.arch>${os.detected.arch}</os.detected.arch>
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
</systemPropertyVariables>
<rerunFailingTestsCount>2</rerunFailingTestsCount>
</configuration>
</execution>
<execution>
<id>mvn-40</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
<configuration>
<systemPropertyVariables>
<mvnd.home>${mvnd.m40.home}</mvnd.home>
<project.version>${project.version}</project.version>
<mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
<os.detected.name>${os.detected.name}</os.detected.name>
<os.detected.arch>${os.detected.arch}</os.detected.arch>
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -168,6 +214,7 @@
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<id>native-39</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
@@ -175,7 +222,25 @@
<configuration>
<systemPropertyVariables>
<project.version>${project.version}</project.version>
<mvnd.home>${mvnd.home}</mvnd.home>
<mvnd.home>${mvnd.m39.home}</mvnd.home>
<mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
<os.detected.name>${os.detected.name}</os.detected.name>
<os.detected.arch>${os.detected.arch}</os.detected.arch>
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<id>native-40</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<project.version>${project.version}</project.version>
<mvnd.home>${mvnd.m40.home}</mvnd.home>
<mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
<os.detected.name>${os.detected.name}</os.detected.name>
<os.detected.arch>${os.detected.arch}</os.detected.arch>

25
pom.xml
View File

@@ -51,7 +51,10 @@
<module>common</module>
<module>client</module>
<module>daemon</module>
<module>dist</module>
<module>daemon-m39</module>
<module>daemon-m40</module>
<module>dist-m39</module>
<module>dist-m40</module>
<module>integration-tests</module>
</modules>
@@ -85,6 +88,8 @@
<junit.jupiter.version>5.9.2</junit.jupiter.version>
<logback.version>1.2.11</logback.version>
<maven.version>4.0.0-alpha-4</maven.version>
<maven3.version>3.9.0</maven3.version>
<maven4.version>${maven.version}</maven4.version>
<!-- Keep in sync with Maven -->
<maven.resolver.version>1.9.4</maven.resolver.version>
<slf4j.version>1.7.36</slf4j.version>
@@ -251,7 +256,13 @@
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-dist</artifactId>
<artifactId>mvnd-dist-m39</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-dist-m40</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
@@ -260,6 +271,16 @@
<artifactId>mvnd-daemon</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon-m39</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-daemon-m40</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd-helper-agent</artifactId>