[maven][IDEA-373161] maven compatibility with maven 4 rc

(cherry picked from commit cbbf16794fac04ac4b00e74072568afc28ad377d)

IJ-CR-166737

GitOrigin-RevId: e2c45eaa8fade4c3e7cdf48fc342f2488d3c30bb
This commit is contained in:
Alexander Bubenchikov
2025-06-23 15:10:57 +02:00
committed by intellij-monorepo-bot
parent a1c7c5b229
commit 791ef44df3
9 changed files with 218 additions and 66 deletions

View File

@@ -0,0 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40;
public interface InvokerWithoutCoreExtensions {
void disableCoreExtensions();
}

View File

@@ -1,16 +1,17 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40;
import com.intellij.maven.server.m40.compat.Maven40InvokerRequestFactory;
import com.intellij.maven.server.m40.utils.*;
import com.intellij.maven.server.telemetry.MavenServerOpenTelemetry;
import com.intellij.openapi.util.text.StringUtilRt;
import org.apache.maven.*;
import org.apache.maven.api.*;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.api.cli.mvn.MavenOptions;
import org.apache.maven.api.services.ArtifactResolver;
import org.apache.maven.api.services.ArtifactResolverResult;
import org.apache.maven.api.services.Lookup;
@@ -21,7 +22,6 @@ import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.bridge.MavenRepositorySystem;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvn.MavenInvokerRequest;
import org.apache.maven.cling.invoker.mvn.MavenParser;
import org.apache.maven.execution.*;
import org.apache.maven.internal.impl.DefaultSessionFactory;
@@ -61,6 +61,7 @@ import org.jetbrains.idea.maven.server.*;
import org.jetbrains.idea.maven.server.security.MavenToken;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
@@ -71,7 +72,7 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import static com.intellij.maven.server.m40.utils.Maven40ModelConverter.convertRemoteRepositories;
import static org.apache.maven.cling.invoker.Utils.getCanonicalPath;
import static java.util.Objects.requireNonNull;
public class Maven40ServerEmbedderImpl extends MavenServerEmbeddedBase {
private final @NotNull Maven40Invoker myMavenInvoker;
@@ -171,22 +172,6 @@ public class Maven40ServerEmbedderImpl extends MavenServerEmbeddedBase {
.build();
MavenParser mavenParser = new MavenParser() {
@Override
public MavenInvokerRequest getInvokerRequest(LocalContext context) {
return new Maven40InvokerRequest(
context.parserRequest,
context.parsingFailed,
context.cwd,
context.installationDirectory,
context.userHomeDirectory,
context.userProperties,
context.systemProperties,
context.topDirectory,
context.rootDirectory,
context.extensions,
(MavenOptions)context.options);
}
@Override
public Path getRootDirectory(LocalContext context) {
Path rootDir = super.getRootDirectory(context);
@@ -201,7 +186,7 @@ public class Maven40ServerEmbedderImpl extends MavenServerEmbeddedBase {
InvokerRequest invokerRequest;
List<Logger.Entry> entries = new ArrayList<>();
try {
invokerRequest = mavenParser.parseInvocation(parserRequest);
invokerRequest = Maven40InvokerRequestFactory.createProxy(mavenParser.parseInvocation(parserRequest));
entries.addAll(invokerRequest.parserRequest().logger().drain());
myContainer = myMavenInvoker.invokeAndGetContext(invokerRequest).lookup;
}
@@ -217,6 +202,7 @@ public class Maven40ServerEmbedderImpl extends MavenServerEmbeddedBase {
catch (Exception e) {
throw new RuntimeException(e);
}
if(myContainer == null) throw new IllegalStateException("Cannot create maven container");
myAlwaysUpdateSnapshots = commandLineOptions.contains("-U") || commandLineOptions.contains("--update-snapshots");
@@ -1135,4 +1121,15 @@ public class Maven40ServerEmbedderImpl extends MavenServerEmbeddedBase {
throw wrapToSerializableRuntimeException(e);
}
}
@Nonnull
public static Path getCanonicalPath(Path path) {
requireNonNull(path, "path");
try {
return path.toRealPath();
}
catch (IOException e) {
return getCanonicalPath(path.getParent()).resolve(path.getFileName());
}
}
}

View File

@@ -0,0 +1,70 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40.compat;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.services.Lookup;
import org.apache.maven.cling.invoker.mvn.MavenContext;
import org.apache.maven.cling.invoker.mvn.MavenInvoker;
/**
* Resident invoker implementation, specialization of Maven Invoker, but keeps Maven instance resident. This implies, that
* things like environment, system properties, extensions etc. are loaded only once. It is caller duty to ensure
* that subsequent call is right for the resident instance (ie no env change or different extension needed).
* This implementation "pre-populates" MavenContext with pre-existing stuff (except for very first call)
* and does not let DI container to be closed.
*/
public class CompatResidentMavenInvoker extends MavenInvoker {
private final ConcurrentHashMap<String, MavenContext> residentContext;
public CompatResidentMavenInvoker(Lookup protoLookup) {
super(protoLookup, null);
this.residentContext = new ConcurrentHashMap<>();
}
@Override
public void close() throws InvokerException {
ArrayList<Exception> exceptions = new ArrayList<>();
for (MavenContext context : residentContext.values()) {
try {
context.doCloseContainer();
} catch (Exception e) {
exceptions.add(e);
}
}
if (!exceptions.isEmpty()) {
InvokerException exception = new InvokerException("Could not cleanly shut down context pool");
exceptions.forEach(exception::addSuppressed);
throw exception;
}
}
@Override
protected MavenContext createContext(InvokerRequest invokerRequest) {
// TODO: in a moment Maven stop pushing user properties to system properties (and maybe something more)
// and allow multiple instances per JVM, this may become a pool? derive key based in invokerRequest?
MavenContext result = residentContext.computeIfAbsent(
"resident",
k -> MavenContextFactory.createMavenContext(invokerRequest));
return copyIfDifferent(result, invokerRequest);
}
protected MavenContext copyIfDifferent(MavenContext mavenContext, InvokerRequest invokerRequest) {
if (invokerRequest == mavenContext.invokerRequest) {
return mavenContext;
}
MavenContext shadow = MavenContextFactory.createMavenContext(invokerRequest);
// we carry over only "resident" things
shadow.containerCapsule = mavenContext.containerCapsule;
shadow.lookup = mavenContext.lookup;
shadow.eventSpyDispatcher = mavenContext.eventSpyDispatcher;
shadow.maven = mavenContext.maven;
return shadow;
}
}

View File

@@ -0,0 +1,69 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40.compat;
import com.intellij.maven.server.m40.InvokerWithoutCoreExtensions;
import org.apache.maven.api.cli.InvokerRequest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class Maven40InvokerRequestFactory {
private static final Map<String, Method> INVOKER_METHODS = new HashMap<>();
static {
for (Method m : InvokerRequest.class.getMethods()) {
INVOKER_METHODS.put(m.getName(), m);
}
}
public static InvokerRequest createProxy(InvokerRequest invokerRequest) {
return (InvokerRequest)Proxy.newProxyInstance(
Maven40InvokerRequestFactory.class.getClassLoader(),
new Class[]{InvokerRequest.class, InvokerWithoutCoreExtensions.class},
new InvokerProxyHandler(invokerRequest)
);
}
private static class InvokerProxyHandler implements InvocationHandler {
private final InvokerRequest myInvokerRequest;
private boolean coreExtensionsDisabled = false;
public InvokerProxyHandler(InvokerRequest invoker) {
myInvokerRequest = invoker;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("disableCoreExtensions")) {
coreExtensionsDisabled = true;
return null;
}
else if (method.getName().equals("coreExtensions")) {
if (coreExtensionsDisabled) return Optional.empty();
return myInvokerRequest.coreExtensions();
}
else if (method.getName().equals("toString")) {
return "[Proxy]:" + myInvokerRequest.toString();
}
else if (method.getName().equals("hashCode")) {
return myInvokerRequest.hashCode();
}
else {
Method realMethod = Maven40InvokerRequestFactory.INVOKER_METHODS.get(method.getName());
if (realMethod == null || (args != null && args.length > 0)) {
throw new UnsupportedOperationException(method.getName() + " is not supported in this IDEA version");
}
return realMethod.invoke(myInvokerRequest);
}
}
}
}

View File

@@ -0,0 +1,47 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40.compat;
import com.intellij.util.text.VersionComparatorUtil;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.mvn.MavenOptions;
import org.apache.maven.cling.invoker.mvn.MavenContext;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import static org.jetbrains.idea.maven.server.MavenServerEmbedder.MAVEN_EMBEDDER_VERSION;
public class MavenContextFactory {
public static MavenContext createMavenContext(InvokerRequest invokerRequest) {
String mavenVersion = System.getProperty(MAVEN_EMBEDDER_VERSION);
if (VersionComparatorUtil.compare(mavenVersion, "4.0.0-rc-3") == 0) {
return new MavenContext(invokerRequest, false);
}
else {
Constructor<?>[] constructors = MavenContext.class.getConstructors();
if (constructors.length != 1) throw new UnsupportedOperationException("MavenContext incompatibility with current IDEA version");
try {
MavenOptions options = getOptions(invokerRequest);
return (MavenContext)constructors[0].newInstance(invokerRequest, false, options);
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new UnsupportedOperationException("MavenContext incompatibility with current IDEA version", e);
}
}
}
private static @Nullable MavenOptions getOptions(InvokerRequest invokerRequest) {
try {
Method method = InvokerRequest.class.getMethod("options");
Optional<Options> options = (Optional<Options>)method.invoke(invokerRequest);
return (MavenOptions)options.orElse(null);
}
catch (Exception e) {
throw new UnsupportedOperationException("MavenContext incompatibility with current IDEA version", e);
}
}
}

View File

@@ -277,8 +277,8 @@ public final class Maven40ApiModelConverter {
public static MavenArtifact convertArtifactAndPath(Artifact artifact, Path artifactPath, File localRepository) {
return new MavenArtifact(artifact.getGroupId(),
artifact.getArtifactId(),
artifact.getVersion().asString(),
artifact.getVersion().asString(),
artifact.getVersion().toString(),
artifact.getVersion().toString(),
"", //artifact.getType(),
artifact.getClassifier(),

View File

@@ -1,17 +1,18 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40.utils;
import com.intellij.maven.server.m40.compat.CompatResidentMavenInvoker;
import com.intellij.maven.server.m40.InvokerWithoutCoreExtensions;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.cling.invoker.LookupContext;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvn.MavenContext;
import org.apache.maven.cling.invoker.mvn.MavenInvoker;
import org.apache.maven.cling.invoker.mvn.resident.ResidentMavenInvoker;
import org.apache.maven.execution.MavenExecutionRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.server.MavenServerGlobals;
public class Maven40Invoker extends ResidentMavenInvoker {
public class Maven40Invoker extends CompatResidentMavenInvoker {
MavenContext myContext = null;
public Maven40Invoker(ProtoLookup protoLookup) {
@@ -28,10 +29,11 @@ public class Maven40Invoker extends ResidentMavenInvoker {
activateLogging(context);
helpOrVersionAndMayExit(context);
preCommands(context);
//noinspection CastToIncompatibleInterface
tryRunAndRetryOnFailure(
"container",
() -> container(context),
() -> ((Maven40InvokerRequest)context.invokerRequest).disableCoreExtensions()
() -> ((InvokerWithoutCoreExtensions)context.invokerRequest).disableCoreExtensions()
);
postContainer(context);
pushUserProperties(context); // after PropertyContributor SPI

View File

@@ -1,41 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.maven.server.m40.utils;
import org.apache.maven.api.cli.ParserRequest;
import org.apache.maven.api.cli.extensions.CoreExtension;
import org.apache.maven.api.cli.mvn.MavenOptions;
import org.apache.maven.cling.invoker.mvn.MavenInvokerRequest;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class Maven40InvokerRequest extends MavenInvokerRequest {
public Maven40InvokerRequest(ParserRequest parserRequest,
boolean parseFailed,
Path cwd,
Path installationDirectory,
Path userHomeDirectory,
Map<String, String> userProperties,
Map<String, String> systemProperties,
Path topDirectory,
Path rootDirectory,
List<CoreExtension> coreExtensions,
MavenOptions options) {
super(parserRequest, parseFailed, cwd, installationDirectory, userHomeDirectory, userProperties, systemProperties, topDirectory,
rootDirectory, coreExtensions, options);
}
private boolean coreExtensionsDisabled = false;
public void disableCoreExtensions() {
coreExtensionsDisabled = true;
}
@Override
public Optional<List<CoreExtension>> coreExtensions() {
if (coreExtensionsDisabled) return Optional.empty();
return super.coreExtensions();
}
}

View File

@@ -1384,6 +1384,7 @@ class DependenciesImportingTest : MavenMultiVersionImportingTestCase() {
@Test
fun testPropertyInTheManagedModuleDependencyVersionOfPomType() = runBlocking {
// Registry.get("maven.server.debug").setValue(true, testRootDisposable)
createProjectPom("""
<groupId>test</groupId>
<artifactId>project</artifactId>
@@ -1899,6 +1900,7 @@ class DependenciesImportingTest : MavenMultiVersionImportingTestCase() {
@Test
fun testDoNoRemoveUnusedLibraryIfItWasChanged() = runBlocking {
importProjectAsync("""
<groupId>test</groupId>
<artifactId>project</artifactId>