diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 712b3827ca6a..f6685255f1d5 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -10,8 +10,8 @@ import com.intellij.compiler.server.BuildManager; import com.intellij.compiler.server.DefaultMessageHandler; import com.intellij.ide.nls.NlsMessages; import com.intellij.internal.statistic.StructuredIdeActivity; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; +import com.intellij.java.JavaBundle; +import com.intellij.notification.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; @@ -25,17 +25,14 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.CompilerModuleExtension; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.ui.configuration.DefaultModuleConfigurationEditorFactory; +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.NlsContexts; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.HtmlChunk; @@ -69,6 +66,7 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope; @@ -355,6 +353,9 @@ public final class CompileDriver { } } + + private final AtomicBoolean myFallbackSdkHintReported = new AtomicBoolean(false); + @Override protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType(); @@ -398,12 +399,32 @@ public final class CompileDriver { if (event.hasCustomBuilderMessage()) { final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.CustomBuilderMessage message = event.getCustomBuilderMessage(); - if (GlobalOptions.JPS_SYSTEM_BUILDER_ID.equals(message.getBuilderId()) && - GlobalOptions.JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID.equals(message.getMessageType())) { - //noinspection HardCodedStringLiteral - final String text = message.getMessageText(); - if (!StringUtil.isEmpty(text)) { - compileContext.addMessage(CompilerMessageCategory.INFORMATION, text, null, -1, -1); + if (GlobalOptions.JPS_SYSTEM_BUILDER_ID.equals(message.getBuilderId())) { + if (GlobalOptions.JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID.equals(message.getMessageType())) { + //noinspection HardCodedStringLiteral + final String text = message.getMessageText(); + if (!StringUtil.isEmpty(text)) { + compileContext.addMessage(CompilerMessageCategory.INFORMATION, text, null, -1, -1); + } + } + else if (GlobalOptions.JPS_FALLBACK_SDK_SETUP_MESSAGE_ID.equals(message.getMessageType())) { + if (!myFallbackSdkHintReported.getAndSet(true)) { + @NlsSafe String notificationContent = message.getMessageText(); + NotificationGroup notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Unsupported JDK"); + + Notification notification = notificationGroup.createNotification( + JavaBundle.message("unsupported.jdk.notification.title"), + notificationContent, + NotificationType.WARNING + ) + .setImportantSuggestion(true) + .setRemoveWhenExpired(true) + .addAction( + NotificationAction.createSimpleExpiring(ProjectBundle.message("action.text.config.invalid.sdk.configure"), () -> openJdkConfigurationSettings(compileContext.getCompileScope())) + ); + compileContext.getBuildSession().registerCloseAction(notification::expire); + notification.notify(myProject); + } } } } @@ -418,6 +439,18 @@ public final class CompileDriver { }); } + private void openJdkConfigurationSettings(CompileScope compileScope) { + for (Module module : compileScope.getAffectedModules()) { + for (OrderEntry entry : ModuleRootManager.getInstance(module).getOrderEntries()) { + if (entry instanceof JdkOrderEntry) { + ProjectSettingsService.getInstance(myProject).openLibraryOrSdkSettings(entry); + return; + } + } + } + ProjectSettingsService.getInstance(myProject).openProjectSettings(); + } + @RequiresEdt private void startup(final CompileScope scope, final boolean isRebuild, diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java index 1944c50daf45..fad8293487f5 100644 --- a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java @@ -63,7 +63,6 @@ import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.registry.RegistryManager; import com.intellij.openapi.util.registry.RegistryManagerKt; @@ -187,9 +186,6 @@ public final class BuildManager implements Disposable { private static final String JPS_USE_EXPERIMENTAL_STORAGE = "jps.use.experimental.storage"; - private final String myFallbackSdkHome; - private final String myFallbackSdkVersion; - private final Map, Project> myAutomakeFutures = Collections.synchronizedMap(new HashMap<>()); private final Map> myBuildsInProgress = Collections.synchronizedMap(new HashMap<>()); private final Map, OSProcessHandler>>> myPreloadedBuilds = @@ -325,19 +321,6 @@ public final class BuildManager implements Disposable { final Application application = ApplicationManager.getApplication(); IS_UNIT_TEST_MODE = application.isUnitTestMode(); - - String fallbackSdkHome = System.getProperty(GlobalOptions.FALLBACK_JDK_HOME, null); - String fallbackSdkVersion = System.getProperty(GlobalOptions.FALLBACK_JDK_VERSION, null); - if (fallbackSdkHome == null || fallbackSdkVersion == null) { - // default to the IDE's runtime - myFallbackSdkHome = getFallbackSdkHome(); - myFallbackSdkVersion = SystemInfo.JAVA_VERSION; - } - else { - myFallbackSdkHome = fallbackSdkHome; - myFallbackSdkVersion = fallbackSdkVersion; - } - SimpleMessageBusConnection connection = application.getMessageBus().connect(coroutineScope); connection.subscribe(ProjectCloseListener.TOPIC, new ProjectWatcher()); connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { @@ -495,17 +478,6 @@ public final class BuildManager implements Disposable { } } - private static @NotNull String getFallbackSdkHome() { - String home = SystemProperties.getJavaHome(); // should point either to jre or jdk - if (!JdkUtil.checkForJdk(home)) { - String parent = new File(home).getParent(); - if (parent != null && JdkUtil.checkForJdk(parent)) { - home = parent; - } - } - return FileUtilRt.toSystemIndependentName(home); - } - private static @NotNull List getOpenProjects() { final Project[] projects = ProjectManager.getInstance().getOpenProjects(); if (projects.length == 0) { @@ -1210,19 +1182,11 @@ public final class BuildManager implements Disposable { return getRuntimeSdk(project, MINIMUM_REQUIRED_JPS_BUILD_JAVA_VERSION, processed -> { // build's fallback SDK choosing policy: select among unprocessed SDKs in the SDK table the oldest possible one that can be used - // select only SDKs that match project's WSL VM, if any - Supplier projectWslDistribution = new Supplier<>() { - private WSLDistribution val; - - @Override - public WSLDistribution get() { - return val != null ? val : (val = findWSLDistribution(project)); - } - }; - Predicate sdkFilter = sdk -> !processed.contains(sdk) && Objects.equals(projectWslDistribution.get(), findWSLDistribution(sdk)); + // if project is located in a WSL VM, consider only SDKs, configured in the same WSL VM + Predicate wslSdkFilter = getWslSdkFilter(project); return StreamEx.of(ProjectJdkTable.getInstance().getSdksOfType(JavaSdk.getInstance())) - .filter(sdkFilter) + .filter(sdk -> !processed.contains(sdk) && wslSdkFilter.test(sdk)) .mapToEntry(sdk -> JavaVersion.tryParse(sdk.getVersionString())) .filterValues(version -> version != null && version.isAtLeast(MINIMUM_REQUIRED_JPS_BUILD_JAVA_VERSION)) .min(Map.Entry.comparingByValue()) @@ -1232,6 +1196,19 @@ public final class BuildManager implements Disposable { }); } + private static @NotNull Predicate getWslSdkFilter(@NotNull Project project) { + // if WSL is configured, accepts only those SDKs that match project's WSL VM + Supplier projectWslDistribution = new Supplier<>() { + private Ref val; + + @Override + public WSLDistribution get() { + return val != null? val.get() : (val = Ref.create(findWSLDistribution(project))).get(); + } + }; + return sdk -> Objects.equals(projectWslDistribution.get(), findWSLDistribution(sdk)); + } + public static @NotNull Pair<@NotNull Sdk, @Nullable JavaSdkVersion> getJavacRuntimeSdk(@NotNull Project project) { return getRuntimeSdk(project, ExternalJavacProcess.MINIMUM_REQUIRED_JAVA_VERSION, processed -> getIDERuntimeSdk()); } @@ -1288,6 +1265,18 @@ public final class BuildManager implements Disposable { .orElseGet(() -> fallbackSdkProvider.apply(candidates.keySet())); } + private static @Nullable Pair getForkedJavacFallbackSdk(@NotNull Project project, int oldestPossibleVersion) { + // select the oldest SDK version among SDKs that are present in the SDK Table, but not older than + return StreamEx.of(ProjectJdkTable.getInstance().getSdksOfType(JavaSdk.getInstance())) + .filter(getWslSdkFilter(project)) + .mapToEntry(sdk -> JavaVersion.tryParse(sdk.getVersionString())) + .filterValues(version -> version != null && version.isAtLeast(oldestPossibleVersion)) + .min(Map.Entry.comparingByValue()) + .map(p -> Pair.create(p.getKey(), JavaSdkVersion.fromJavaVersion(p.getValue()))) + .filter(p -> p.second != null) + .orElse(null); + } + private static @NotNull Pair getIDERuntimeSdk() { @SuppressWarnings("removal") Sdk sdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); @@ -1707,9 +1696,19 @@ public final class BuildManager implements Disposable { cmdLine.addParameter("-Djps.report.registered.unexistent.output=true"); } - if (localProject && myFallbackSdkHome != null && myFallbackSdkVersion != null) { - cmdLine.addPathParameter("-D" + GlobalOptions.FALLBACK_JDK_HOME + '=', myFallbackSdkHome); - cmdLine.addParameter("-D" + GlobalOptions.FALLBACK_JDK_VERSION + '=' + myFallbackSdkVersion); + String fallbackSdkHome = localProject? System.getProperty(GlobalOptions.FALLBACK_JDK_HOME, null) : null; + String fallbackSdkVersion = localProject? System.getProperty(GlobalOptions.FALLBACK_JDK_VERSION, null) : null; + if (fallbackSdkHome == null || fallbackSdkVersion == null) { + Pair fallback = getForkedJavacFallbackSdk(project, ExternalJavacProcess.MINIMUM_REQUIRED_JAVA_VERSION); + Sdk sdk = fallback != null? fallback.first : null; + if (sdk != null && sdk.getSdkType() instanceof JavaSdkType javaSdk && !FileUtil.pathsEqual(vmExecutablePath, javaSdk.getVMExecutablePath(sdk))) { + fallbackSdkHome = sdk.getHomePath(); + fallbackSdkVersion = fallback.second.getMaxLanguageLevel().getShortText(); + } + } + if (fallbackSdkHome != null && fallbackSdkVersion != null) { + cmdLine.addPathParameter("-D" + GlobalOptions.FALLBACK_JDK_HOME + '=', fallbackSdkHome); + cmdLine.addParameter("-D" + GlobalOptions.FALLBACK_JDK_VERSION + '=' + fallbackSdkVersion); } cmdLine.addParameter("-Dio.netty.noUnsafe=true"); diff --git a/jps/jps-builders/resources/messages/JpsBuildBundle.properties b/jps/jps-builders/resources/messages/JpsBuildBundle.properties index 953c8d92b618..2f38bad4a6d9 100644 --- a/jps/jps-builders/resources/messages/JpsBuildBundle.properties +++ b/jps/jps-builders/resources/messages/JpsBuildBundle.properties @@ -68,6 +68,15 @@ build.message.unsupported.javac.version=Cannot start javac process for {0}: it i but IDE supports compilation using JDK {2} and newer only.\nConsider associating the module with a supported JDK version. Note, that \ it is possible to keep the current Java language level to make produced *.class files compatible with Java {3} even when they are compiled by a newer compiler version. +build.message.fallback.javac.used=Module ''{0}'' is configured for JVM target {1}. Compiling the module using java compiler {2} from the configured fallback SDK. +build.message.cannot.compile.fallback.jdk.not.supported=Cannot compile module ''{0}'' configured for JVM target {1}: the specified fallback SDK version {2} is not supported by the IDE. +build.message.cannot.compile.fallback.jdk.unsupported.jvm.target=Cannot compile module ''{0}'' configured for JVM target {1}: the specified fallback SDK version {2} does not support the required jvm target {1}. +build.message.cannot.compile.associated.jdk.not.supported=Cannot compile module ''{0}'' configured for JVM target {1}: the JDK {2}, currently associated with the module, is not supported by the IDE. +build.message.cannot.compile.associated.jdk.unsupported.jvm.target=Cannot compile module ''{0}'' configured for JVM target {1}: the JDK {2}, currently associated with the module, does not support the required jvm target {1}. +build.message.fallback.jdk.hint=Please add JDK {0} or later to Project Structure | Platform Settings | SDKs. \ + The added JDK should support cross-compilation for JVM target {1}. \ + This JDK will be automatically picked by the IDE to compile for older JVM targets. + #artifacts builder.name.artifacts.builder=Artifacts builder build.message.archive.0.doesn.t.contain.files.so.it.won.t.be.created=Archive ''{0}'' doesn''t contain files so it won''t be created diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java index 0cc34cb810c1..61eb0fcfc0f5 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java @@ -37,6 +37,10 @@ public interface GlobalOptions { // notification about the files changed during compilation, but not compiled in current compilation session String JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID = "!unprocessed_fs_changes_detected!"; + // notification about necessary fallback SDK setup to enable compilation for older JVM targets + @ApiStatus.Internal + String JPS_FALLBACK_SDK_SETUP_MESSAGE_ID = "!fallback_sdk_setup_required!"; + /** * The path to external project config directory (used for external system projects). */ diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java index 27ba4e848004..2b394ae4810a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java @@ -39,6 +39,7 @@ import org.jetbrains.jps.cmdline.ProjectDescriptor; import org.jetbrains.jps.incremental.*; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.FallbackJdkSetupNotification; import org.jetbrains.jps.incremental.messages.ProgressMessage; import org.jetbrains.jps.incremental.storage.BuildDataManager; import org.jetbrains.jps.javac.*; @@ -446,7 +447,7 @@ public final class JavaBuilder extends ModuleLevelBuilder { // when forking external javac, compilers from SDK 1.7 and higher are supported final Pair forkSdk; if (shouldForkCompilerProcess(context, chunk, targetLanguageLevel, compilingTool)) { - forkSdk = getForkedJavacSdk(diagnosticSink, chunk, targetLanguageLevel); + forkSdk = getForkedJavacSdk(context, diagnosticSink, chunk, targetLanguageLevel); if (forkSdk == null) { return false; } @@ -1203,7 +1204,7 @@ public final class JavaBuilder extends ModuleLevelBuilder { return chunkSdkVersion; } - private static @Nullable Pair getForkedJavacSdk(DiagnosticListener diagnostic, ModuleChunk chunk, int targetLanguageLevel) { + private static @Nullable Pair getForkedJavacSdk(CompileContext context, DiagnosticListener diagnostic, ModuleChunk chunk, int targetLanguageLevel) { final Pair, Integer> associatedSdk = getAssociatedSdk(chunk); boolean canRunAssociatedJavac = false; if (associatedSdk != null) { @@ -1238,33 +1239,52 @@ public final class JavaBuilder extends ModuleLevelBuilder { final int fallbackVersion = JpsJavaSdkType.parseVersion(fallbackJdkVersion); if (isTargetReleaseSupported(fallbackVersion, targetLanguageLevel)) { if (fallbackVersion >= ExternalJavacProcess.MINIMUM_REQUIRED_JAVA_VERSION) { + diagnostic.report(new PlainMessageDiagnostic(Diagnostic.Kind.NOTE, + JpsBuildBundle.message( + "build.message.fallback.javac.used", + chunk.getName(), + targetLanguageLevel, + fallbackJdkVersion + ) + )); return Pair.create(fallbackJdkHome, fallbackVersion); } else { + // the configured fallback SDK supports the required JVM target, but cannot be run by JPS LOG.info("Version string for fallback JDK is '" + fallbackJdkVersion + "' (recognized as version '" + fallbackVersion + "')." + " At least version " + ExternalJavacProcess.MINIMUM_REQUIRED_JAVA_VERSION + " is required to launch javac process."); + + diagnostic.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, + JpsBuildBundle.message("build.message.cannot.compile.fallback.jdk.not.supported", chunk.getName(), targetLanguageLevel, fallbackJdkVersion) + )); } } - } - - // at this point, fallbackJdk is not suitable too - if (associatedSdk != null) { - if (canRunAssociatedJavac) { - // although target release is not supported, attempt to start javac, so that javac properly reports this error - return Pair.create(associatedSdk.first.getHomePath(), associatedSdk.second); - } else { + // configured fallback SDK does not support the required jvm target diagnostic.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, - JpsBuildBundle.message( - "build.message.unsupported.javac.version", - chunk.getName(), - associatedSdk.second, - ExternalJavacProcess.MINIMUM_REQUIRED_JAVA_VERSION, - targetLanguageLevel - ) + JpsBuildBundle.message("build.message.cannot.compile.fallback.jdk.unsupported.jvm.target", chunk.getName(), targetLanguageLevel, fallbackJdkVersion) )); } } + else { + // fallback JDK is not configured at all; + // associatedSdk != null, is configured, but either JPS cannot run it, or it does not support the required jvm target + + String errorMessage; + if (canRunAssociatedJavac) { + errorMessage = JpsBuildBundle.message("build.message.cannot.compile.associated.jdk.unsupported.jvm.target", chunk.getName(), targetLanguageLevel, associatedSdk.first.getVersionString()); + } + else { + errorMessage = JpsBuildBundle.message("build.message.cannot.compile.associated.jdk.not.supported", chunk.getName(), targetLanguageLevel, associatedSdk.first.getVersionString()); + } + diagnostic.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, errorMessage)); + } + + String fallbackSDKHint = JpsBuildBundle.message( + "build.message.fallback.jdk.hint", ExternalJavacProcess.MINIMUM_REQUIRED_JAVA_VERSION, targetLanguageLevel + ); + context.processMessage(new FallbackJdkSetupNotification(fallbackSDKHint)); + diagnostic.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, fallbackSDKHint)); return null; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/messages/FallbackJdkSetupNotification.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/messages/FallbackJdkSetupNotification.java new file mode 100644 index 000000000000..b92583037734 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/messages/FallbackJdkSetupNotification.java @@ -0,0 +1,17 @@ +// 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.jps.incremental.messages; + +import com.intellij.openapi.util.NlsSafe; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.jps.api.GlobalOptions; + +@ApiStatus.Internal +public final class FallbackJdkSetupNotification extends CustomBuilderMessage{ + public FallbackJdkSetupNotification(@NlsSafe String notificationMessage) { + super( + GlobalOptions.JPS_SYSTEM_BUILDER_ID, + GlobalOptions.JPS_FALLBACK_SDK_SETUP_MESSAGE_ID, + notificationMessage + ); + } +}