From 3ad3f5c6fec9bc900e9e6ff0197b5fda36d60ec4 Mon Sep 17 00:00:00 2001 From: Ilyas Selimov Date: Tue, 23 Mar 2021 17:15:02 +0700 Subject: [PATCH] IDEA-161641 - initial Jlink artifacts support IDEA-161641 - moved jlink packages to java.impl and java.compiler.impl modules IDEA-161641 - small refactoring IDEA-161641 - small refactoring IDEA-161641 - fixed jlink tab ui, partially setup build task provider IDEA-161641 - initial jlink implementation GitOrigin-RevId: 2ce649f886ea43d362414d7b949f07feac5d231d --- ...ilders.artifacts.ArtifactBuildTaskProvider | 2 + ....serialization.JpsModelSerializerExtension | 2 + .../jlink/JLinkArtifactBuildTaskProvider.java | 191 ++++++++++++++++++ .../impl/jlink/JpsJLinkArtifactType.java | 13 ++ .../JpsJLinkModelSerializerExtension.java | 44 ++++ .../impl/jlink/JpsJLinkProperties.java | 55 +++++ java/java-impl/src/META-INF/JavaPlugin.xml | 3 + .../jlink/JLinkArtifactProperties.java | 51 +++++ .../jlink/JLinkArtifactPropertiesEditor.java | 64 ++++++ .../JLinkArtifactPropertiesProvider.java | 23 +++ .../packaging/jlink/JLinkArtifactType.java | 33 +++ 11 files changed, 481 insertions(+) create mode 100644 java/compiler/impl/src/META-INF/services/org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider create mode 100644 java/compiler/impl/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension create mode 100644 java/compiler/impl/src/com/intellij/packaging/impl/jlink/JLinkArtifactBuildTaskProvider.java create mode 100644 java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkArtifactType.java create mode 100644 java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkModelSerializerExtension.java create mode 100644 java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkProperties.java create mode 100644 java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactProperties.java create mode 100644 java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesEditor.java create mode 100644 java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesProvider.java create mode 100644 java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactType.java diff --git a/java/compiler/impl/src/META-INF/services/org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider b/java/compiler/impl/src/META-INF/services/org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider new file mode 100644 index 000000000000..256a113595ad --- /dev/null +++ b/java/compiler/impl/src/META-INF/services/org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider @@ -0,0 +1,2 @@ +# Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +com.intellij.packaging.impl.jlink.JLinkArtifactBuildTaskProvider \ No newline at end of file diff --git a/java/compiler/impl/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension b/java/compiler/impl/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension new file mode 100644 index 000000000000..45d5b2f2276d --- /dev/null +++ b/java/compiler/impl/src/META-INF/services/org.jetbrains.jps.model.serialization.JpsModelSerializerExtension @@ -0,0 +1,2 @@ +# Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +com.intellij.packaging.impl.jlink.JpsJLinkModelSerializerExtension \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JLinkArtifactBuildTaskProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JLinkArtifactBuildTaskProvider.java new file mode 100644 index 000000000000..ae0ad02aff84 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JLinkArtifactBuildTaskProvider.java @@ -0,0 +1,191 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.impl.jlink; + +import com.intellij.execution.CommandLineUtil; +import com.intellij.execution.process.BaseOSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.builders.artifacts.ArtifactBuildTaskProvider; +import org.jetbrains.jps.incremental.BuildTask; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.ProjectBuildException; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.model.JpsElement; +import org.jetbrains.jps.model.artifact.JpsArtifact; +import org.jetbrains.jps.model.library.sdk.JpsSdk; + +import java.io.File; +import java.io.IOException; +import java.lang.module.ModuleFinder; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class JLinkArtifactBuildTaskProvider extends ArtifactBuildTaskProvider { + + @Override + public @NotNull List createArtifactBuildTasks(@NotNull JpsArtifact artifact, + @NotNull ArtifactBuildPhase buildPhase) { + if (buildPhase != ArtifactBuildPhase.POST_PROCESSING || !(artifact.getArtifactType() instanceof JpsJLinkArtifactType)) { + return Collections.emptyList(); + } + final JpsElement properties = artifact.getProperties(); + if (!(properties instanceof JpsJLinkProperties)) { + return Collections.emptyList(); + } + return Collections.singletonList(new JLinkBuildTask(artifact)); + } + + private static class JLinkBuildTask extends BuildTask { + private static final Logger LOG = Logger.getInstance(JLinkBuildTask.class); + private final JpsArtifact myArtifact; + + private JLinkBuildTask(@NotNull JpsArtifact artifact) { + myArtifact = artifact; + } + + @Override + public void build(CompileContext context) throws ProjectBuildException { + LOG.warn("jlink task was started"); + + Set> sdks = context.getProjectDescriptor().getProjectJavaSdks(); + if (sdks.isEmpty()) { + return; + } + ModuleFinder moduleFinder = ModuleFinder.of(Path.of(myArtifact.getOutputFilePath())); + String modulesStr = moduleFinder.findAll().stream().map(mr -> mr.descriptor().name()).collect(Collectors.joining(",")); + if (modulesStr.isEmpty()) { + error(context, "No module has been found"); + return; + } + JpsSdk javaSdk = sdks.iterator().next(); + String sdkHomePath = javaSdk.getHomePath(); + String jLinkPath = sdkHomePath + File.separatorChar + "bin" + File.separatorChar + "jlink"; + List commands = new ArrayList<>(); + commands.add(jLinkPath); + JpsJLinkProperties properties = (JpsJLinkProperties)myArtifact.getProperties(); + if (properties.compressionLevel.hasCompression()) { + addOption(commands, "--compress", String.valueOf(properties.compressionLevel.myValue)); + } + if (properties.verbose) { + commands.add("--verbose"); + } + commands.add("--module-path"); + commands.add(myArtifact.getOutputFilePath()); + commands.add("--add-modules"); + commands.add(modulesStr); + String outputPath = myArtifact.getOutputFilePath() + File.separator + "jdk"; + addOption(commands, "--output", outputPath); + + LOG.warn(String.join(" ", commands)); + + try { + FileUtil.delete(Path.of(outputPath)); + } + catch (IOException e) { + error(context,"Couldn't delete existing run-time image: " + e.getMessage()); + } + + final int errorCode = startProcess(context, commands, properties); + if (errorCode != 0) { + error(context,"jlink task has failed"); + } + + LOG.warn("jlink task was finished"); + } + + private int startProcess(@NotNull CompileContext context, @NotNull List commands, @NotNull JpsJLinkProperties properties) { + try { + final AtomicInteger exitCode = new AtomicInteger(); + final @NlsSafe StringBuilder errorOutput = new StringBuilder(); + final List<@NlsSafe String> delayedInfoOutput = new ArrayList<>(); + + final Process process = new ProcessBuilder(CommandLineUtil.toCommandLine(commands)).start(); + BaseOSProcessHandler handler = new BaseOSProcessHandler(process, commands.toString(), null); + handler.addProcessListener(new ProcessAdapter() { + @Override + public void startNotified(@NotNull ProcessEvent event) { + if (properties.verbose) { + LOG.info("Started " + commands); + } + } + + @Override + public void processTerminated(@NotNull ProcessEvent event) { + if (properties.verbose) { + LOG.info("Terminated " + commands + ", exit code: " + event.getExitCode()); + } + exitCode.set(event.getExitCode()); + } + + @Override + public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { + String message = StringUtil.trimTrailing(event.getText()); + if (outputType == ProcessOutputTypes.STDERR) { + LOG.error(message, (Throwable)null); + errorOutput.append(event.getText()); + } + else { + LOG.info(message); + if (properties.verbose) { + info(context, message); + } + else { + delayedInfoOutput.add(message); + } + } + } + }); + + handler.startNotify(); + handler.waitFor(); + + int result = exitCode.get(); + if (result != 0) { + final String message = errorOutput.toString(); + if (!StringUtil.isEmptyOrSpaces(message)) { + error(context, message); + } + for (@NlsSafe String info : delayedInfoOutput) { + error(context, info); + } + } + return result; + } + catch (Exception e) { + error(context, e.getMessage()); + LOG.warn(e); + return -1; + } + } + + private static void addOption(List commands, @NotNull String key, @Nullable String value) { + if (!StringUtil.isEmpty(value)) { + commands.add(key); + commands.add(value); + } + } + + private void error(@NotNull CompileContext compileContext, @Nls String message) { + compileContext.processMessage(new CompilerMessage("jlink", BuildMessage.Kind.ERROR, message)); + } + + private void info(@NotNull CompileContext compileContext, @Nls String message) { + compileContext.processMessage(new CompilerMessage("jlink", BuildMessage.Kind.INFO, message)); + } + } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkArtifactType.java b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkArtifactType.java new file mode 100644 index 000000000000..a27b2abc1eaa --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkArtifactType.java @@ -0,0 +1,13 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.impl.jlink; + +import org.jetbrains.jps.model.artifact.JpsArtifactType; +import org.jetbrains.jps.model.ex.JpsElementTypeBase; + +public class JpsJLinkArtifactType extends JpsElementTypeBase implements JpsArtifactType { + + public static final JpsJLinkArtifactType INSTANCE = new JpsJLinkArtifactType(); + + private JpsJLinkArtifactType() { + } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkModelSerializerExtension.java b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkModelSerializerExtension.java new file mode 100644 index 000000000000..452c293e4b1f --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkModelSerializerExtension.java @@ -0,0 +1,44 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.impl.jlink; + +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xmlb.XmlSerializer; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension; +import org.jetbrains.jps.model.serialization.artifact.ArtifactPropertiesState; +import org.jetbrains.jps.model.serialization.artifact.JpsArtifactPropertiesSerializer; + +import java.util.Collections; +import java.util.List; + +public class JpsJLinkModelSerializerExtension extends JpsModelSerializerExtension { + + @Override + public @NotNull List> getArtifactTypePropertiesSerializers() { + return Collections.singletonList(new JpsJLinkArtifactPropertiesSerializer()); + } + + static class JpsJLinkArtifactPropertiesSerializer extends JpsArtifactPropertiesSerializer { + + JpsJLinkArtifactPropertiesSerializer() { + super("jlink", JpsJLinkArtifactType.INSTANCE); + } + + @Override + public JpsJLinkProperties loadProperties(List stateList) { + final ArtifactPropertiesState state = findApplicationProperties(stateList); + if (state != null) { + final Element options = state.getOptions(); + if (options != null) { + return new JpsJLinkProperties(XmlSerializer.deserialize(options, JpsJLinkProperties.class)); + } + } + return new JpsJLinkProperties(); + } + + private static ArtifactPropertiesState findApplicationProperties(List stateList) { + return ContainerUtil.find(stateList, state -> "jlink-properties".equals(state.getId())); + } + } +} diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkProperties.java b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkProperties.java new file mode 100644 index 000000000000..50ebb29c4734 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/packaging/impl/jlink/JpsJLinkProperties.java @@ -0,0 +1,55 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.impl.jlink; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.ex.JpsElementBase; + +import java.util.stream.Stream; + +public class JpsJLinkProperties extends JpsElementBase { + public CompressionLevel compressionLevel = CompressionLevel.ZERO; + public boolean verbose; + + public JpsJLinkProperties() { + } + + public JpsJLinkProperties(@NotNull JpsJLinkProperties properties) { + copyToThis(properties); + } + + @Override + public @NotNull JpsJLinkProperties createCopy() { + return new JpsJLinkProperties(this); + } + + @Override + public void applyChanges(@NotNull JpsJLinkProperties modified) { + copyToThis(modified); + } + + private void copyToThis(@NotNull JpsJLinkProperties copy) { + compressionLevel = copy.compressionLevel; + verbose = copy.verbose; + } + + public enum CompressionLevel { + ZERO(0), + FIRST(1), + SECOND(2); + + public final int myValue; + CompressionLevel(int value) { + this.myValue = value; + } + + @Nullable + public static CompressionLevel getLevelByValue(int value) { + return Stream.of(values()).filter(v -> v.myValue == value).findFirst().orElse(null); + } + + public boolean hasCompression() { + return this == FIRST || this == SECOND; + } + } +} diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 48aa6a5887e6..2795bb948632 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -269,6 +269,7 @@ + + + diff --git a/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactProperties.java b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactProperties.java new file mode 100644 index 000000000000..e21ac7d5ce2a --- /dev/null +++ b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactProperties.java @@ -0,0 +1,51 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.jlink; + +import com.intellij.packaging.artifacts.ArtifactProperties; +import com.intellij.packaging.ui.ArtifactEditorContext; +import com.intellij.packaging.ui.ArtifactPropertiesEditor; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public class JLinkArtifactProperties extends ArtifactProperties { + public CompressionLevel compressionLevel = CompressionLevel.ZERO; + public boolean verbose; + + @Override + public ArtifactPropertiesEditor createEditor(@NotNull ArtifactEditorContext context) { + return new JLinkArtifactPropertiesEditor(this, context.getProject()); + } + + @Override + public @Nullable JLinkArtifactProperties getState() { + return this; + } + + @Override + public void loadState(@NotNull JLinkArtifactProperties state) { + XmlSerializerUtil.copyBean(state, this); + } + + public enum CompressionLevel { + ZERO(0), + FIRST(1), + SECOND(2); + + public final int myValue; + CompressionLevel(int value) { + this.myValue = value; + } + + @Nullable + public static CompressionLevel getLevelByValue(int value) { + return Stream.of(values()).filter(v -> v.myValue == value).findFirst().orElse(null); + } + + public boolean hasCompression() { + return this == FIRST || this == SECOND; + } + } +} diff --git a/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesEditor.java b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesEditor.java new file mode 100644 index 000000000000..73ad8074b835 --- /dev/null +++ b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesEditor.java @@ -0,0 +1,64 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.jlink; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.packaging.ui.ArtifactPropertiesEditor; +import com.intellij.util.ui.FormBuilder; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.EnumSet; +import java.util.Optional; + +import static com.intellij.packaging.jlink.JLinkArtifactProperties.CompressionLevel; + +public class JLinkArtifactPropertiesEditor extends ArtifactPropertiesEditor { + private final JLinkArtifactProperties myProperties; + private final Project myProject; + + private ComboBox myCompressionLevel; + private JCheckBox myVerbose; + + public JLinkArtifactPropertiesEditor(@NotNull JLinkArtifactProperties properties, @NotNull Project project) { + myProperties = properties; + myProject = project; + } + + @Nls + @Override + public String getTabName() { + return "JLink"; + } + + @Override + public @Nullable JComponent createComponent() { + final FormBuilder builder = new FormBuilder(); + + myCompressionLevel = new ComboBox<>(EnumSet.allOf(CompressionLevel.class).stream().map(level -> level.myValue).toArray(Integer[]::new)); + myCompressionLevel.setItem(myProperties.compressionLevel.myValue); + builder.addLabeledComponent("Compress level", myCompressionLevel); + + myVerbose = new JCheckBox("Enable verbose tracing", myProperties.verbose); + builder.addComponent(myVerbose); + + return builder.getPanel(); + } + + @Override + public boolean isModified() { + if (myProperties.compressionLevel != CompressionLevel.getLevelByValue(myCompressionLevel.getItem())) return true; + if (myProperties.verbose != myVerbose.isSelected()) return true; + return false; + } + + @Override + public void apply() { + myProperties.compressionLevel = Optional.ofNullable(myCompressionLevel.getItem()) + .map(i -> CompressionLevel.getLevelByValue(i)) + .orElse(CompressionLevel.ZERO); + myProperties.verbose = myVerbose.isSelected(); + } +} diff --git a/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesProvider.java b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesProvider.java new file mode 100644 index 000000000000..6157e468ef80 --- /dev/null +++ b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactPropertiesProvider.java @@ -0,0 +1,23 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.jlink; + +import com.intellij.packaging.artifacts.ArtifactProperties; +import com.intellij.packaging.artifacts.ArtifactPropertiesProvider; +import com.intellij.packaging.artifacts.ArtifactType; +import org.jetbrains.annotations.NotNull; + +public class JLinkArtifactPropertiesProvider extends ArtifactPropertiesProvider { + protected JLinkArtifactPropertiesProvider() { + super("jlink-properties"); + } + + @Override + public @NotNull ArtifactProperties createProperties(@NotNull ArtifactType artifactType) { + return new JLinkArtifactProperties(); + } + + @Override + public boolean isAvailableFor(@NotNull ArtifactType type) { + return type instanceof JLinkArtifactType; + } +} diff --git a/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactType.java b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactType.java new file mode 100644 index 000000000000..3dab6e26031f --- /dev/null +++ b/java/java-impl/src/com/intellij/packaging/jlink/JLinkArtifactType.java @@ -0,0 +1,33 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.packaging.jlink; + +import com.intellij.icons.AllIcons; +import com.intellij.packaging.artifacts.ArtifactType; +import com.intellij.packaging.elements.CompositePackagingElement; +import com.intellij.packaging.elements.PackagingElementFactory; +import com.intellij.packaging.elements.PackagingElementOutputKind; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class JLinkArtifactType extends ArtifactType { + protected JLinkArtifactType() { + super("jlink", () -> "Run-time image"); + } + + @Override + public @NotNull Icon getIcon() { + return AllIcons.Nodes.Artifact; + } + + @Override + public @Nullable String getDefaultPathFor(@NotNull PackagingElementOutputKind kind) { + return "/"; + } + + @Override + public @NotNull CompositePackagingElement createRootElement(@NotNull String artifactName) { + return PackagingElementFactory.getInstance().createArtifactRootElement(); + } +}