[maven][IDEA-377217] fix workspace resolution

Merge-request: IJ-MR-179548
Review: IJ-CR-181597

Merged-by: Aleksandr Bubenchikov <alexander.bubenchikov@jetbrains.com>
(cherry picked from commit 01f4f5bc4bf1545e7fd09b2b8a70f679ffae7a05)

GitOrigin-RevId: e5f301aa7ac5daea5e55270db8fa343af8610448
This commit is contained in:
Alexander Bubenchikov
2025-11-06 01:06:27 +00:00
committed by intellij-monorepo-bot
parent 1cabecbc96
commit 2d5af4fdc7
17 changed files with 481 additions and 122 deletions

View File

@@ -22,26 +22,11 @@ import static org.jetbrains.maven.server.SpyConstants.NEWLINE;
@Named("Intellij Idea Maven Embedded Event Spy")
@Singleton
public class IntellijMavenSpy extends AbstractEventSpy {
private EventInfoPrinter myPrinter;
private static EventInfoPrinter ourPrinter = new EventInfoPrinter(s -> System.out.println(s));
@SuppressWarnings("UseOfSystemOutOrSystemErr")
@Override
public void init(Context context) throws Exception {
try {
doInit(context);
}
catch (Throwable ignore) {
}
if (myPrinter == null) {
myPrinter = new EventInfoPrinter(s -> System.out.println(s));
System.err.println("IntellijMavenSpy not initialized, build nodes could be displayed incorrectly");
}
}
private void doInit(Context context)
throws Throwable {
myPrinter = new EventInfoPrinter(s -> System.out.println(s));
public static void printInternalLogging(String message){
ourPrinter.printMavenEventInfo("IDEA_INTERNAL_MESSAGE", "error", message);
}
@Override
@@ -67,7 +52,7 @@ public class IntellijMavenSpy extends AbstractEventSpy {
private void onDependencyResolutionRequest(DependencyResolutionRequest event) {
String projectId = event.getMavenProject() == null ? "unknown" : event.getMavenProject().getId();
myPrinter.printMavenEventInfo("DependencyResolutionRequest", "id", projectId);
ourPrinter.printMavenEventInfo("DependencyResolutionRequest", "id", projectId);
}
private void onDependencyResolutionResult(DependencyResolutionResult event) {
@@ -79,7 +64,7 @@ public class IntellijMavenSpy extends AbstractEventSpy {
}
result.append(e.getMessage());
}
myPrinter.printMavenEventInfo("DependencyResolutionResult", "error", result);
ourPrinter.printMavenEventInfo("DependencyResolutionResult", "error", result);
}
private void collectAndPrintLastLinesForEA(Throwable e) {
@@ -90,7 +75,7 @@ public class IntellijMavenSpy extends AbstractEventSpy {
for (int i = 0; i < lines; i++) {
builder.append(e.getStackTrace()[i]).append("\n");
}
myPrinter.printMavenEventInfo("INTERR", "error", builder);
ourPrinter.printMavenEventInfo("INTERR", "error", builder);
}
private void onRepositoryEvent(RepositoryEvent event) {
@@ -103,7 +88,7 @@ public class IntellijMavenSpy extends AbstractEventSpy {
String errMessage = event.getException() == null ? "" : event.getException().getMessage();
String path = event.getFile() == null ? "" : event.getFile().getPath();
String artifactCoord = event.getArtifact() == null ? "" : event.getArtifact().toString();
myPrinter.printMavenEventInfo(type, "path", path, "artifactCoord", artifactCoord, "error", errMessage);
ourPrinter.printMavenEventInfo(type, "path", path, "artifactCoord", artifactCoord, "error", errMessage);
}
private void onExecutionEvent(ExecutionEvent event) {
@@ -111,17 +96,17 @@ public class IntellijMavenSpy extends AbstractEventSpy {
String projectId = event.getProject() == null ? "unknown" : event.getProject().getId();
if (mojoExec != null) {
String errMessage = event.getException() == null ? "" : getErrorMessage(event.getException());
myPrinter.printMavenEventInfo(event.getType(), "source", String.valueOf(mojoExec.getSource()), "goal", mojoExec.getGoal(), "id",
projectId,
"error",
errMessage);
ourPrinter.printMavenEventInfo(event.getType(), "source", String.valueOf(mojoExec.getSource()), "goal", mojoExec.getGoal(), "id",
projectId,
"error",
errMessage);
}
else {
if (event.getType() == ExecutionEvent.Type.SessionStarted) {
printSessionStartedEventAndReactorData(event, projectId);
}
else {
myPrinter.printMavenEventInfo(event.getType(), "id", projectId);
ourPrinter.printMavenEventInfo(event.getType(), "id", projectId);
}
}
}
@@ -147,10 +132,10 @@ public class IntellijMavenSpy extends AbstractEventSpy {
.append(project.getArtifactId()).append(":")
.append(project.getVersion()).append("&&");
}
myPrinter.printMavenEventInfo(ExecutionEvent.Type.SessionStarted, "id", projectId, "projects", builder);
ourPrinter.printMavenEventInfo(ExecutionEvent.Type.SessionStarted, "id", projectId, "projects", builder);
}
else {
myPrinter.printMavenEventInfo(ExecutionEvent.Type.SessionStarted, "id", projectId, "projects", "");
ourPrinter.printMavenEventInfo(ExecutionEvent.Type.SessionStarted, "id", projectId, "projects", "");
}
}
}

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 org.jetbrains.maven.server.workspace;
import org.codehaus.plexus.component.annotations.Component;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.repository.WorkspaceRepository;
import java.io.File;
import java.util.Collections;
import java.util.List;
@Component(role = WorkspaceReader.class, hint = "ide")
public class IntelliJWorkspaceReader implements WorkspaceReader {
private final WorkspaceRepository myWorkspaceRepository;
public IntelliJWorkspaceReader() {
myWorkspaceRepository = new WorkspaceRepository("ide", getClass());
}
@Override
public int hashCode() {
return 42;
}
@Override
public boolean equals(Object o) {
return o instanceof IntelliJWorkspaceReader;
}
@Override
public WorkspaceRepository getRepository() {
return myWorkspaceRepository;
}
@SuppressWarnings("IO_FILE_USAGE")
@Override
public File findArtifact(Artifact artifact) {
return MavenModuleMap.getInstance().findArtifact(artifact);
}
@Override
public List<String> findVersions(Artifact artifact) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,78 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.maven.server.workspace;
import org.eclipse.aether.artifact.Artifact;
import org.jetbrains.maven.server.IntellijMavenSpy;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;
public final class MavenModuleMap {
private static final MavenModuleMap ourInstance = new MavenModuleMap();
public static final String PATHS_FILE_PROPERTY = "idea.modules.paths.file";
private final Properties myMap = new Properties();
private MavenModuleMap() {
String path = System.getProperty(PATHS_FILE_PROPERTY);
if (path != null) {
IntellijMavenSpy.printInternalLogging("reading idea.modules.paths from " + path);
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(path)))) {
myMap.load(in);
IntellijMavenSpy.printInternalLogging("have read " + myMap.size() + " records");
}
catch (IOException e) {
IntellijMavenSpy.printInternalLogging(e.getMessage());
}
}
else {
IntellijMavenSpy.printInternalLogging("idea.modules.paths.file is not defined");
}
}
public static MavenModuleMap getInstance() {
return ourInstance;
}
@SuppressWarnings("IO_FILE_USAGE")
public File findArtifact(Artifact artifact) {
String key = getKey(artifact);
String value = myMap.getProperty(key);
if (value == null || value.isEmpty()) {
return null;
}
File file = new File(value);
if (!file.exists()) {
return null;
}
return file;
}
private static String getKey(Artifact artifact) {
String type = getType(artifact);
return artifact.getGroupId() + ':' + artifact.getArtifactId() + ':' + type + ':' + artifact.getBaseVersion();
}
private static String getType(Artifact artifact) {
String extension = artifact.getExtension();
String classifier = artifact.getClassifier();
if ("jar".equals(extension) && classifier != null && !classifier.isEmpty()) {
if ("tests".equals(classifier) || "test-jar".equals(classifier)) {
return "test-jar";
}
return classifier;
}
else {
return extension;
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<component-set>
<components>
<component>
<role>org.eclipse.aether.repository.WorkspaceReader</role>
<role-hint>ide</role-hint>
<implementation>org.jetbrains.maven.server.workspace.IntelliJWorkspaceReader</implementation>
<description />
<isolated-realm>false</isolated-realm>
</component>
</components>
</component-set>

View File

@@ -69,7 +69,9 @@ public final class MavenExternalParameters {
/**
* @param runConfiguration used to creation fix if maven home not found
* @deprecated use maven script running instead
*/
@Deprecated
public static JavaParameters createJavaParameters(final @NotNull Project project,
final @NotNull MavenRunnerParameters parameters,
@Nullable MavenGeneralSettings coreSettings,
@@ -140,7 +142,7 @@ public final class MavenExternalParameters {
if (parameters.isResolveToWorkspace()) {
try {
String resolverJar = getArtifactResolverJar(mavenVersion);
String resolverJar = getArtifactResolverJar();
confFile = patchConfFile(confFile, resolverJar);
File modulesPathsFile = dumpModulesPaths(project);
@@ -212,7 +214,7 @@ public final class MavenExternalParameters {
return tmpConf;
}
private static void patchConfFile(Path originalConf, Path dest, String library) throws IOException {
public static void patchConfFile(Path originalConf, Path dest, String library) throws IOException {
try (Scanner sc = new Scanner(originalConf, StandardCharsets.UTF_8);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(dest), StandardCharsets.UTF_8))) {
@@ -234,7 +236,8 @@ public final class MavenExternalParameters {
}
}
private static String getArtifactResolverJar(@Nullable String mavenVersion) throws IOException {
public static String getArtifactResolverJar() throws IOException {
Class<?> marker = MavenArtifactResolvedM31RtMarker.class;
File classDirOrJar = new File(PathUtil.getJarPathForClass(marker));
@@ -257,42 +260,10 @@ public final class MavenExternalParameters {
return tempFile.getAbsolutePath();
}
private static File dumpModulesPaths(@NotNull Project project) throws IOException {
public static File dumpModulesPaths(@NotNull Project project) throws IOException {
ApplicationManager.getApplication().assertReadAccessAllowed();
Properties res = new Properties();
MavenProjectsManager manager = MavenProjectsManager.getInstance(project);
for (Module module : ModuleManager.getInstance(project).getModules()) {
if (manager.isMavenizedModule(module)) {
MavenProject mavenProject = manager.findProject(module);
if (mavenProject != null && !manager.isIgnored(mavenProject)) {
res.setProperty(mavenProject.getMavenId().getGroupId()
+ ':' + mavenProject.getMavenId().getArtifactId()
+ ":pom"
+ ':' + mavenProject.getMavenId().getVersion(),
mavenProject.getFile().getPath());
res.setProperty(mavenProject.getMavenId().getGroupId()
+ ':' + mavenProject.getMavenId().getArtifactId()
+ ':' + mavenProject.getPackaging()
+ ':' + mavenProject.getMavenId().getVersion(),
mavenProject.getOutputDirectory());
res.setProperty(mavenProject.getMavenId().getGroupId()
+ ':' + mavenProject.getMavenId().getArtifactId()
+ ":test-jar"
+ ':' + mavenProject.getMavenId().getVersion(),
mavenProject.getTestOutputDirectory());
addArtifactFileMapping(res, mavenProject, "sources");
addArtifactFileMapping(res, mavenProject, "test-sources");
addArtifactFileMapping(res, mavenProject, "javadoc");
addArtifactFileMapping(res, mavenProject, "test-javadoc");
}
}
}
Properties res = getProjectModuleMap(project);
File file = new File(PathManager.getSystemPath(), "Maven/idea-projects-state-" + project.getLocationHash() + ".properties");
FileUtil.ensureExists(file.getParentFile());
@@ -304,6 +275,43 @@ public final class MavenExternalParameters {
return file;
}
public static @NotNull Properties getProjectModuleMap(@NotNull Project project) {
Properties res = new Properties();
MavenProjectsManager manager = MavenProjectsManager.getInstance(project);
for (MavenProject mavenProject : manager.getProjects()) {
if (!manager.isIgnored(mavenProject)) {
res.setProperty(mavenProject.getMavenId().getGroupId()
+ ':' + mavenProject.getMavenId().getArtifactId()
+ ":pom"
+ ':' + mavenProject.getMavenId().getVersion(),
mavenProject.getFile().getPath());
if ("pom".equals(mavenProject.getPackaging())) {
continue;
}
res.setProperty(mavenProject.getMavenId().getGroupId()
+ ':' + mavenProject.getMavenId().getArtifactId()
+ ':' + mavenProject.getPackaging()
+ ':' + mavenProject.getMavenId().getVersion(),
mavenProject.getOutputDirectory());
res.setProperty(mavenProject.getMavenId().getGroupId()
+ ':' + mavenProject.getMavenId().getArtifactId()
+ ":test-jar"
+ ':' + mavenProject.getMavenId().getVersion(),
mavenProject.getTestOutputDirectory());
addArtifactFileMapping(res, mavenProject, "sources");
addArtifactFileMapping(res, mavenProject, "test-sources");
addArtifactFileMapping(res, mavenProject, "javadoc");
addArtifactFileMapping(res, mavenProject, "test-javadoc");
}
}
return res;
}
private static void addArtifactFileMapping(@NotNull Properties res, @NotNull MavenProject mavenProject, @NotNull String classifier) {
File file = new File(mavenProject.getBuildDirectory(), mavenProject.getFinalName() + '-' + classifier + ".jar");
if (file.exists()) {
@@ -315,7 +323,7 @@ public final class MavenExternalParameters {
}
}
private static @NotNull Sdk getJdk(@Nullable Project project, String jreName, boolean isGlobalRunnerSettings)
public static @NotNull Sdk getJdk(@Nullable Project project, String jreName, boolean isGlobalRunnerSettings)
throws ExecutionException {
if (jreName.equals(MavenRunnerSettings.USE_INTERNAL_JAVA)) {
return JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
@@ -422,8 +430,8 @@ public final class MavenExternalParameters {
* @param runConfiguration used to creation fix if maven home not found
*/
public static @NotNull @NlsSafe String resolveMavenHome(@NotNull MavenGeneralSettings coreSettings,
@NotNull Project project,
@NotNull String workingDir, @Nullable MavenRunConfiguration runConfiguration)
@NotNull Project project,
@NotNull String workingDir, @Nullable MavenRunConfiguration runConfiguration)
throws ExecutionException {
MavenHomeType type = coreSettings.getMavenHomeType();
Path file = null;

View File

@@ -1,4 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.maven.execution.run
import com.intellij.build.*
@@ -19,8 +20,10 @@ import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.ProgramRunner
import com.intellij.execution.testDiscovery.JvmToggleAutoTestAction
import com.intellij.execution.ui.ConsoleView
import com.intellij.execution.util.ProgramParametersUtil
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.application.readAction
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfigurationViewManager
@@ -37,8 +40,10 @@ import com.intellij.platform.eel.provider.utils.EelPathUtils.TransferTarget
import com.intellij.platform.eel.provider.utils.EelPathUtils.transferLocalContentToRemote
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.psi.search.ExecutionSearchScopes
import com.intellij.util.containers.with
import com.intellij.util.io.Compressor
import com.intellij.util.io.outputStream
import com.intellij.util.text.nullize
import org.jetbrains.idea.maven.artifactResolver.common.MavenModuleMap
import org.jetbrains.idea.maven.buildtool.BuildToolConsoleProcessAdapter
import org.jetbrains.idea.maven.buildtool.MavenBuildEventProcessor
import org.jetbrains.idea.maven.execution.*
@@ -48,14 +53,17 @@ import org.jetbrains.idea.maven.project.*
import org.jetbrains.idea.maven.server.*
import org.jetbrains.idea.maven.utils.MavenLog
import org.jetbrains.idea.maven.utils.MavenUtil
import java.io.File
import org.jetbrains.idea.maven.utils.MavenUtil.isRunningFromSources
import java.io.BufferedOutputStream
import java.io.IOException
import java.nio.charset.Charset
import java.nio.charset.UnsupportedCharsetException
import java.nio.file.Path
import java.util.function.Function
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.exists
import kotlin.io.path.isRegularFile
class MavenShCommandLineState(val environment: ExecutionEnvironment, private val myConfiguration: MavenRunConfiguration) : RunProfileState, RemoteConnectionCreator {
private var mavenConnectionWrapper: MavenRemoteConnectionWrapper? = null
@@ -73,14 +81,11 @@ class MavenShCommandLineState(val environment: ExecutionEnvironment, private val
val env = getEnv(eelApi.exec.fetchLoginShellEnvVariables(), debug)
val charset = tryToGetCharset(env, eelApi)
val envWithCharsets = if (charset != null) {
env
}
else {
env.with("JAVA_TOOL_OPTIONS", listOfNotNull(env["JAVA_TOOL_OPTIONS"], "-Dfile.encoding=UTF-8").joinToString(" "))
if (charset != null) {
env["JAVA_TOOL_OPTIONS"] = listOfNotNull(env["JAVA_TOOL_OPTIONS"], "-Dfile.encoding=UTF-8").joinToString(" ")
}
val processHandler = runProcessInEel(eelApi, exe, envWithCharsets, charset ?: Charsets.UTF_8)
val processHandler = runProcessInEel(eelApi, exe, env, charset ?: Charsets.UTF_8)
JavaRunConfigurationExtensionManager.instance
.attachExtensionsToProcess(myConfiguration, processHandler, environment.runnerSettings)
return@runWithModalProgressBlocking processHandler
@@ -129,7 +134,7 @@ class MavenShCommandLineState(val environment: ExecutionEnvironment, private val
@SuppressWarnings("IO_FILE_USAGE") // Here should be java.io.File, since we do not support remote eel run on windows Agent
private fun writeParamsToBatBecauseCmdIsReallyReallyBad(params: ParametersList, isSpyDebug: Boolean): File {
private fun writeParamsToBatBecauseCmdIsReallyReallyBad(params: ParametersList, isSpyDebug: Boolean): java.io.File {
val tmpData = StringBuilder()
val mapEnv = LinkedHashMap<String, String>()
@@ -169,7 +174,7 @@ class MavenShCommandLineState(val environment: ExecutionEnvironment, private val
tmpData.append(commandPrefix).append(batParams.list.joinToString(" ") { CommandLineUtil.escapeParameterOnWindows(it, true) })
val tempDirectory = FileUtilRt.getTempDirectory()
val tmpBat = FileUtil.createTempFile(File(tempDirectory), "mvn-idea-exec", ".bat", false, true)
val tmpBat = FileUtil.createTempFile(java.io.File(tempDirectory), "mvn-idea-exec", ".bat", false, true)
tmpBat.writeText(tmpData.toString())
@@ -402,27 +407,80 @@ class MavenShCommandLineState(val environment: ExecutionEnvironment, private val
private fun addIdeaParameters(args: ParametersList, eel: EelApi) {
args.addProperty("idea.version", MavenUtil.getIdeaVersionToPassToMavenProcess())
args.addProperty(
MavenServerEmbedder.MAVEN_EXT_CLASS_PATH,
transferLocalContentToRemote(
source = MavenServerManager.getInstance().getMavenEventListener().toPath(),
target = TransferTarget.Temporary(eel.descriptor)
).asEelPath().toString()
)
addMavenEventListener(args, eel)
createDumpFileIfRequired(args, eel)
args.addProperty("jansi.passthrough", "true")
args.addProperty("style.color", "always")
}
private fun getEnv(existingEnv: Map<String, String>, debug: Boolean): Map<String, String> {
private fun createDumpFileIfRequired(args: ParametersList, eel: EelApi) {
if (!myConfiguration.runnerParameters.isResolveToWorkspace) {
return
}
val map = MavenExternalParameters.getProjectModuleMap(myConfiguration.project)
val remoteMapFile = transferLocalContentToRemote(
source = FileUtil.createTempFile("idea-" + myConfiguration.project.getLocationHash(), "-mvn.properties").toPath(),
target = TransferTarget.Temporary(eel.descriptor)
)
remoteMapFile.outputStream().use { os ->
map.store(BufferedOutputStream(os), null)
}
args.addProperty(MavenModuleMap.PATHS_FILE_PROPERTY, remoteMapFile.absolutePathString())
}
private fun addMavenEventListener(args: ParametersList, eel: EelApi) {
val path = prepareMavenListener()
args.addProperty(
MavenServerEmbedder.MAVEN_EXT_CLASS_PATH,
transferLocalContentToRemote(
source = path,
target = TransferTarget.Temporary(eel.descriptor)
).asEelPath().toString()
)
}
private fun prepareMavenListener(): Path {
val pathToMavenListener = MavenServerManager.getInstance().getMavenEventListener().toPath()
if (!pathToMavenListener.exists()) {
throw IllegalStateException("$pathToMavenListener does not exist")
}
if (pathToMavenListener.isRegularFile()) return pathToMavenListener
if (isRunningFromSources()) {
val tempFile = FileUtil.createTempFile("idea", "-event-listener.jar", true).toPath()
MavenLog.LOG.warn("compressing maven event listener from $pathToMavenListener")
Compressor.Zip(tempFile)
.use { zip ->
zip.addDirectory(MavenServerManager.getInstance().getMavenEventListener().toPath())
}
return tempFile
}
throw IllegalStateException("$pathToMavenListener does not exist")
}
private suspend fun getJavaHome(): String? {
val defaultSettings = MavenRunner.getInstance(myConfiguration.project).state
val jreName = myConfiguration.runnerSettings?.jreName ?: defaultSettings.jreName
val isGlobalRunnerSettings = defaultSettings === myConfiguration.runnerSettings
return readAction {
MavenExternalParameters.getJdk(myConfiguration.project, jreName, isGlobalRunnerSettings)
}.homePath
}
private suspend fun getEnv(existingEnv: Map<String, String>, debug: Boolean): MutableMap<String, String> {
val map = HashMap<String, String>()
map.putAll(existingEnv)
myConfiguration.runnerSettings?.environmentProperties?.let { map.putAll(map) }
val javaParams = myConfiguration.createJavaParameters(myConfiguration.project)
map["JAVA_HOME"] = Path(javaParams.jdkPath).asEelPath().toString()
val javaHome = getJavaHome() ?: throw ExecutionException(MavenProjectBundle.message("exec.message.failed.to.find.maven.jdk"))
map["JAVA_HOME"] = javaHome
val optsBuilder = StringBuilder(map["MAVEN_OPTS"] ?: "")
myConfiguration.runnerSettings?.vmOptions?.let {
if (optsBuilder.isNotEmpty()) optsBuilder.append(" ")
optsBuilder.append(it)
optsBuilder.append(ProgramParametersUtil.expandPathAndMacros(it, null, myConfiguration.project))
}
val mavenOpts = if (debug && mavenConnectionWrapper != null) {
mavenConnectionWrapper!!.enhanceMavenOpts(optsBuilder.toString())
@@ -432,6 +490,9 @@ class MavenShCommandLineState(val environment: ExecutionEnvironment, private val
}
map["MAVEN_OPTS"] = mavenOpts
myConfiguration.runnerSettings?.environmentProperties?.let { map.putAll(it) }
myConfiguration.runnerParameters.multimoduleDir?.nullize()?.let {
map["MAVEN_BASEDIR"] = ProgramParametersUtil.expandPathAndMacros(it, null, myConfiguration.project)
}
return map
}
@@ -501,13 +562,6 @@ class MavenShCommandLineState(val environment: ExecutionEnvironment, private val
}
}
private fun getOrCreateRemoteConnection(): MavenRemoteConnectionWrapper? {
if (mavenConnectionWrapper == null) {
mavenConnectionWrapper = createRemoteConnection()
}
return mavenConnectionWrapper
}
private fun createRemoteConnection(): MavenRemoteConnectionWrapper? {
for (creator in MavenExtRemoteConnectionCreator.EP_NAME.extensionList) {
val connection = creator.createRemoteConnectionForScript(myConfiguration)

View File

@@ -21,7 +21,8 @@ public enum MavenEventType {
PROJECT_SKIPPED("ProjectSkipped"),
PROJECT_FAILED("ProjectFailed"),
ARTIFACT_RESOLVED("ARTIFACT_RESOLVED"),
ARTIFACT_DOWNLOADING("ARTIFACT_DOWNLOADING");
ARTIFACT_DOWNLOADING("ARTIFACT_DOWNLOADING"),
IDEA_INTERNAL_MESSAGE("IDEA_INTERNAL_MESSAGE");
public final String eventName;

View File

@@ -210,6 +210,7 @@ label.invalid.enable.maven2plugin.with.link=Maven2 support disabled, <a href="{0
dialog.message.configuration.file.not.exists.in.maven.home=Configuration file does not exist in Maven home: {0}
dialog.message.failed.to.run.maven.configuration=Failed to run Maven configuration
exec.message.failed.to.find.maven.jdk=Failed to find JDK for the configuration
command.name.create.new.maven.module=Create New Maven Module
command.name.add.maven.support=Add Maven Support
maven.parent.label.none=<none>

View File

@@ -0,0 +1,9 @@
bin/
target/
*.jar
*.class
.classpath
.project
.settings
*.versionsBackup
.idea

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>test</groupId>
<artifactId>parent</artifactId>
<version>1</version>
</parent>
<artifactId>m1</artifactId>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>m2</artifactId>
<version>1</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,10 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.m1;
import org.example.m2.ClassM2;
public class ClassM1 {
public static void main(String[] args) {
System.out.println(ClassM2.sayHello());
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>test</groupId>
<artifactId>parent</artifactId>
<version>1</version>
</parent>
<artifactId>m2</artifactId>
</project>

View File

@@ -0,0 +1,11 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.example.m2;
public class ClassM2 {
public static final String HELLO = "Hello from Module 2";
public static String sayHello() {
return HELLO;
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>parent</artifactId>
<version>1</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>m1</module>
<module>m2</module>
</modules>
</project>

View File

@@ -29,19 +29,7 @@ abstract class MavenExecutionTest : MavenExecutionTestCase() {
Registry.get("maven.use.scripts").setValue(useScripts, testRootDisposable)
}
@Test
fun testExternalExecutor() = runBlocking {
createProjectSubFile("src/main/java/A.java", "public class A {}")
createProjectPom("""
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1</version>
""".trimIndent())
importProjectAsync()
assertFalse(projectPath.resolve("target").exists())
execute(MavenRunnerParameters(true, projectPath.toCanonicalPath(), null as String?, mutableListOf("compile"), emptyList()))
assertTrue(projectPath.resolve("target").exists())
}
@Test
fun testUpdatingExcludedFoldersAfterExecution() = runBlocking {

View File

@@ -11,19 +11,25 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfigurationViewManager
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toCanonicalPath
import com.intellij.testFramework.replaceService
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.util.io.copyRecursively
import kotlinx.coroutines.runBlocking
import org.jetbrains.idea.maven.MavenCustomNioRepositoryHelper
import org.jetbrains.idea.maven.project.MavenWorkspaceSettingsComponent
import org.jetbrains.idea.maven.project.MavenWrapper
import org.jetbrains.idea.maven.utils.MavenLog
import org.junit.Test
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.io.path.absolutePathString
import kotlin.io.path.exists
import kotlin.io.path.isRegularFile
import kotlin.io.path.listDirectoryEntries
class RealMavenExecutionTest : MavenExecutionTest() {
private val myEvents: MutableList<BuildEvent> = ArrayList()
private val myEvents: MutableList<BuildEvent> = CopyOnWriteArrayList()
private lateinit var myDisposable: Disposable
override fun setUp() {
@@ -50,6 +56,20 @@ class RealMavenExecutionTest : MavenExecutionTest() {
}
}
@Test
fun testExternalExecutor() = runBlocking {
createProjectSubFile("src/main/java/A.java", "public class A {}")
createProjectPom("""
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1</version>
""".trimIndent())
importProjectAsync()
assertFalse(projectPath.resolve("target").exists())
execute(MavenRunnerParameters(true, projectPath.toCanonicalPath(), null as String?, mutableListOf("compile"), emptyList()))
assertTrue(projectPath.resolve("target").exists())
}
@Test
fun testProjectWithVmOptionsWithoutVMOptions() = runBlocking {
@@ -87,13 +107,73 @@ class RealMavenExecutionTest : MavenExecutionTest() {
assertClassCompiled("org.example.Main")
}
@Test
fun testProjectWithResolveToWorkspace() = runBlocking {
useProject("maven-modules")
importProjectAsync()
val executionInfo = execute(
MavenRunnerParameters(true,
projectPath.toCanonicalPath(), null as String?,
mutableListOf("compile"),
emptyList()))
assertExecSucceed(executionInfo)
assertClassCompiled("m1", "org.example.m1.ClassM1")
assertClassCompiled("m2", "org.example.m2.ClassM2")
val failedAppRunningInfo = execute(
MavenRunnerParameters(true,
projectPath.resolve("m1").toCanonicalPath(), null as String?,
mutableListOf("exec:java", "-Dexec.mainClass=org.example.m1.ClassM1"),
emptyList()))
assertTrue("build should fail, output was: ${failedAppRunningInfo}",
failedAppRunningInfo.stdout.contains("BUILD FAILURE", true))
val succeedAppRunningInfo = execute(
MavenRunnerParameters(true,
projectPath.resolve("m1").toCanonicalPath(), null as String?,
mutableListOf("exec:java", "-Dexec.mainClass=org.example.m1.ClassM1"),
emptyList()).also { it.isResolveToWorkspace = true })
assertTrue("build should print output from class, output was: ${succeedAppRunningInfo}",
succeedAppRunningInfo.stdout.contains("Hello from Module 2", false))
}
@Test
fun testProjectWorkspaceMap() = runBlocking {
useProject("maven-modules")
importProjectAsync()
assertModules("parent", "m1", "m2")
val map = MavenExternalParameters.getProjectModuleMap(project)
assertEquals("should contain 7 records", 7, map.size)
assertArtifactMapping(map, "test:parent:pom:1", "pom.xml")
assertArtifactMapping(map, "test:m2:pom:1", "m2/pom.xml")
assertArtifactMapping(map, "test:m1:pom:1", "m1/pom.xml")
assertArtifactMapping(map, "test:m2:jar:1", "m2/target/classes")
assertArtifactMapping(map, "test:m1:jar:1", "m1/target/classes")
assertArtifactMapping(map, "test:m2:test-jar:1", "m2/target/test-classes")
assertArtifactMapping(map, "test:m1:test-jar:1", "m1/target/test-classes")
}
fun assertArtifactMapping(map: Properties, key: String, expected: String) {
val actual = map.getProperty(key)
assertNotNull("Workspace mapping should contain $key", key)
val expectedPath = projectPath.resolve(expected).absolutePathString()
val actualPath = projectPath.resolve(actual).absolutePathString()
assertEquals("Path for $key is wrong", expectedPath, actualPath)
}
private fun assertClassCompiled(className: String) {
val target = projectPath.resolve("target")
assertClassCompiled(".", className)
}
private fun assertClassCompiled(module: String, className: String) {
val subPath = projectPath.resolve(module)
val target = subPath.resolve("target")
assertTrue("No target directory was found", Files.isDirectory(target))
val classFile = target.resolve("classes").resolve(className.replace('.', '/') + ".class")
assertTrue("Compiled class $classFile was not found", classFile.isRegularFile())
}
private fun assertExecFailed() {
@@ -109,15 +189,29 @@ class RealMavenExecutionTest : MavenExecutionTest() {
}
private fun useProject(name: String) {
private suspend fun useProject(name: String) {
val projectsDataDir: Path = MavenCustomNioRepositoryHelper.originalTestDataPath.resolve("projects")
val templateProjectDir = projectsDataDir.resolve(name)
assertTrue("cannot find test project $name in $templateProjectDir", Files.isDirectory(templateProjectDir))
MavenLog.LOG.warn("copying from $templateProjectDir to ${projectPath}")
Files.newDirectoryStream(templateProjectDir).use { stream ->
stream.forEach { n ->
n.copyRecursively(projectPath.resolve(templateProjectDir.relativize(n)))
MavenLog.LOG.warn("copying $n as ${templateProjectDir.relativize(n)}")
val target = projectPath.resolve(templateProjectDir.relativize(n))
n.copyRecursively(target)
LocalFileSystem.getInstance().refreshAndFindFileByNioFile(target)
}
}
LocalFileSystem.getInstance().refreshAndFindFileByNioFile(projectPath.resolve("pom.xml"))?.let {
projectPom = it
}
MavenLog.LOG.warn(" dir path = ${projectPath}")
MavenLog.LOG.warn(" dir exists = ${projectPath.exists()}")
MavenLog.LOG.warn(" dir content = ${projectPath.listDirectoryEntries()}")
}
}

View File

@@ -56,11 +56,14 @@ class ScriptMavenExecutionTest : MavenExecutionTest() {
))
projectsManager.addManagedFiles(listOf(anotherLinkedProject))
createFakeProjectWrapper()
mavenGeneralSettings.mavenHomeType = MavenWrapper
val path = MavenDistributionsCache.resolveEmbeddedMavenHome().mavenHome.toString()
val executionInfo = execute(MavenRunnerParameters(true, anotherLinkedProject.parent.path, null as String?, mutableListOf("verify"), emptyList()))
assertTrue("Should run bundled maven ($path) in this case, but command line was: ${executionInfo.system}",
executionInfo.system.contains(if (SystemInfo.isWindows) "\\bin\\mvn.cmd" else "/bin/mvn"))
waitForImportWithinTimeout {
mavenGeneralSettings.mavenHomeType = MavenWrapper
val path = MavenDistributionsCache.resolveEmbeddedMavenHome().mavenHome.toString()
val executionInfo = execute(MavenRunnerParameters(true, anotherLinkedProject.parent.path, null as String?, mutableListOf("verify"), emptyList()))
assertTrue("Should run bundled maven ($path) in this case, but command line was: ${executionInfo.system}",
executionInfo.system.contains(if (SystemInfo.isWindows) "\\bin\\mvn.cmd" else "/bin/mvn"))
}
}
private fun createFakeProjectWrapper() {