mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[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:
committed by
intellij-monorepo-bot
parent
1cabecbc96
commit
2d5af4fdc7
@@ -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", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
9
plugins/maven/src/test/data/projects/maven-modules/.gitignore
vendored
Normal file
9
plugins/maven/src/test/data/projects/maven-modules/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
bin/
|
||||
target/
|
||||
*.jar
|
||||
*.class
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
*.versionsBackup
|
||||
.idea
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
22
plugins/maven/src/test/data/projects/maven-modules/pom.xml
Normal file
22
plugins/maven/src/test/data/projects/maven-modules/pom.xml
Normal 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>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()}")
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user