| /* |
| * Copyright 2016 The Bazel Authors. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.idea.blaze.plugin.run; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Ordering; |
| import com.google.idea.blaze.base.command.BlazeCommand; |
| import com.google.idea.blaze.base.command.BlazeCommandName; |
| import com.google.idea.blaze.base.command.BlazeFlags; |
| import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; |
| import com.google.idea.blaze.base.model.primitives.Label; |
| import com.google.idea.blaze.base.model.primitives.TargetExpression; |
| import com.google.idea.blaze.base.projectview.ProjectViewSet; |
| import com.google.idea.blaze.base.run.BlazeConfigurationNameBuilder; |
| import com.google.idea.blaze.base.run.BlazeRunConfiguration; |
| import com.google.idea.blaze.base.run.state.RunConfigurationFlagsState; |
| import com.google.idea.blaze.base.run.state.RunConfigurationStateEditor; |
| import com.google.idea.blaze.base.run.targetfinder.TargetFinder; |
| import com.google.idea.blaze.base.settings.Blaze; |
| import com.google.idea.blaze.base.ui.UiUtil; |
| import com.google.idea.blaze.plugin.IntellijPluginRule; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.Executor; |
| import com.intellij.execution.configurations.ConfigurationFactory; |
| import com.intellij.execution.configurations.JavaCommandLineState; |
| import com.intellij.execution.configurations.JavaParameters; |
| import com.intellij.execution.configurations.LocatableConfigurationBase; |
| import com.intellij.execution.configurations.LogFileOptions; |
| import com.intellij.execution.configurations.ModuleRunConfiguration; |
| import com.intellij.execution.configurations.ParametersList; |
| import com.intellij.execution.configurations.RunProfileState; |
| import com.intellij.execution.configurations.RuntimeConfigurationError; |
| import com.intellij.execution.configurations.RuntimeConfigurationException; |
| import com.intellij.execution.process.OSProcessHandler; |
| import com.intellij.execution.process.ProcessAdapter; |
| import com.intellij.execution.process.ProcessEvent; |
| import com.intellij.execution.runners.ExecutionEnvironment; |
| import com.intellij.openapi.application.JetBrainsProtocolHandler; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.options.ConfigurationException; |
| import com.intellij.openapi.options.SettingsEditor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.ProjectJdkTable; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.roots.ProjectRootManager; |
| import com.intellij.openapi.roots.ui.configuration.JdkComboBox; |
| import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; |
| import com.intellij.openapi.ui.ComboBox; |
| import com.intellij.openapi.ui.LabeledComponent; |
| import com.intellij.openapi.util.InvalidDataException; |
| import com.intellij.openapi.util.WriteExternalException; |
| import com.intellij.ui.ListCellRendererWrapper; |
| import com.intellij.ui.RawCommandLineEditor; |
| import com.intellij.ui.components.JBCheckBox; |
| import com.intellij.util.PlatformUtils; |
| import java.awt.BorderLayout; |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.List; |
| import javax.annotation.Nullable; |
| import javax.swing.DefaultComboBoxModel; |
| import javax.swing.JComponent; |
| import javax.swing.JLabel; |
| import javax.swing.JList; |
| import org.jdom.Element; |
| |
| /** |
| * A run configuration that builds a plugin jar via blaze, copies it to the SDK sandbox, then runs |
| * IJ with the plugin loaded. |
| */ |
| public class BlazeIntellijPluginConfiguration extends LocatableConfigurationBase |
| implements BlazeRunConfiguration, ModuleRunConfiguration { |
| |
| private static final String TARGET_TAG = "blaze-target"; |
| private static final String USER_BLAZE_FLAG_TAG = "blaze-user-flag"; |
| private static final String USER_EXE_FLAG_TAG = "blaze-user-exe-flag"; |
| private static final String SDK_ATTR = "blaze-plugin-sdk"; |
| private static final String VM_PARAMS_ATTR = "blaze-vm-params"; |
| private static final String PROGRAM_PARAMS_ATTR = "blaze-program-params"; |
| private static final String KEEP_IN_SYNC_TAG = "keep-in-sync"; |
| |
| private final String buildSystem; |
| |
| @Nullable private Label target; |
| private final RunConfigurationFlagsState blazeFlags; |
| private final RunConfigurationFlagsState exeFlags; |
| @Nullable private Sdk pluginSdk; |
| @Nullable String vmParameters; |
| @Nullable private String programParameters; |
| |
| // for keeping imported configurations in sync with their source XML |
| @Nullable private Boolean keepInSync = null; |
| |
| public BlazeIntellijPluginConfiguration( |
| Project project, |
| ConfigurationFactory factory, |
| String name, |
| @Nullable TargetIdeInfo initialTarget) { |
| super(project, factory, name); |
| this.buildSystem = Blaze.buildSystemName(project); |
| Sdk projectSdk = ProjectRootManager.getInstance(project).getProjectSdk(); |
| if (IdeaJdkHelper.isIdeaJdk(projectSdk)) { |
| pluginSdk = projectSdk; |
| } |
| if (initialTarget != null) { |
| target = initialTarget.key.label; |
| } |
| blazeFlags = new RunConfigurationFlagsState(USER_BLAZE_FLAG_TAG, buildSystem + " flags:"); |
| exeFlags = new RunConfigurationFlagsState(USER_EXE_FLAG_TAG, "Executable flags:"); |
| } |
| |
| @Override |
| public void setKeepInSync(@Nullable Boolean keepInSync) { |
| this.keepInSync = keepInSync; |
| } |
| |
| @Override |
| @Nullable |
| public Boolean getKeepInSync() { |
| return keepInSync; |
| } |
| |
| @Override |
| @Nullable |
| public Label getTarget() { |
| return target; |
| } |
| |
| public void setTarget(Label target) { |
| this.target = target; |
| } |
| |
| public void setPluginSdk(Sdk sdk) { |
| if (IdeaJdkHelper.isIdeaJdk(sdk)) { |
| pluginSdk = sdk; |
| } |
| } |
| |
| @Override |
| public ArrayList<LogFileOptions> getAllLogFiles() { |
| ArrayList<LogFileOptions> result = new ArrayList<>(); |
| if (pluginSdk == null) { |
| return result; |
| } |
| String sandboxHome = IdeaJdkHelper.getSandboxHome(pluginSdk); |
| String logFile = Paths.get(sandboxHome, "system", "log", "idea.log").toString(); |
| LogFileOptions logFileOptions = new LogFileOptions("idea.log", logFile, true, true, true); |
| result.add(logFileOptions); |
| return result; |
| } |
| |
| /** |
| * Plugin jar has been previously created via blaze build. This method: - copies jar to sandbox |
| * environment - cracks open jar and finds plugin.xml (with ID, etc., needed for JVM args) - sets |
| * up the SDK, etc. (use project SDK?) - sets up the JVM, and returns a JavaCommandLineState |
| */ |
| @Nullable |
| @Override |
| public RunProfileState getState(Executor executor, ExecutionEnvironment env) |
| throws ExecutionException { |
| final Sdk ideaJdk = pluginSdk; |
| if (!IdeaJdkHelper.isIdeaJdk(ideaJdk)) { |
| throw new ExecutionException("Choose an IntelliJ Platform Plugin SDK"); |
| } |
| String sandboxHome = IdeaJdkHelper.getSandboxHome(ideaJdk); |
| if (sandboxHome == null) { |
| throw new ExecutionException("No sandbox specified for IntelliJ Platform Plugin SDK"); |
| } |
| |
| try { |
| sandboxHome = new File(sandboxHome).getCanonicalPath(); |
| } catch (IOException e) { |
| throw new ExecutionException("No sandbox specified for IntelliJ Platform Plugin SDK"); |
| } |
| String buildNumber = IdeaJdkHelper.getBuildNumber(ideaJdk); |
| final BlazeIntellijPluginDeployer deployer = |
| new BlazeIntellijPluginDeployer(getProject(), sandboxHome, buildNumber); |
| deployer.addTarget(getTarget()); |
| |
| // copy license from running instance of idea |
| IdeaJdkHelper.copyIDEALicense(sandboxHome); |
| |
| final JavaCommandLineState state = |
| new JavaCommandLineState(env) { |
| @Override |
| protected JavaParameters createJavaParameters() throws ExecutionException { |
| List<String> pluginIds = deployer.deploy(); |
| |
| final JavaParameters params = new JavaParameters(); |
| |
| ParametersList vm = params.getVMParametersList(); |
| |
| fillParameterList(vm, vmParameters); |
| fillParameterList(params.getProgramParametersList(), programParameters); |
| |
| IntellijWithPluginClasspathHelper.addRequiredVmParams(params, ideaJdk); |
| |
| vm.defineProperty( |
| JetBrainsProtocolHandler.REQUIRED_PLUGINS_KEY, Joiner.on(',').join(pluginIds)); |
| |
| if (!vm.hasProperty(PlatformUtils.PLATFORM_PREFIX_KEY) && buildNumber != null) { |
| String prefix = IdeaJdkHelper.getPlatformPrefix(buildNumber); |
| if (prefix != null) { |
| vm.defineProperty(PlatformUtils.PLATFORM_PREFIX_KEY, prefix); |
| } |
| } |
| return params; |
| } |
| |
| @Override |
| protected OSProcessHandler startProcess() throws ExecutionException { |
| final OSProcessHandler handler = super.startProcess(); |
| handler.addProcessListener( |
| new ProcessAdapter() { |
| @Override |
| public void processTerminated(ProcessEvent event) { |
| deployer.deleteDeployment(); |
| } |
| }); |
| return handler; |
| } |
| }; |
| return state; |
| } |
| |
| private static void fillParameterList(ParametersList list, @Nullable String value) { |
| if (value == null) { |
| return; |
| } |
| |
| for (String parameter : value.split(" ")) { |
| if (parameter != null && parameter.length() > 0) { |
| list.add(parameter); |
| } |
| } |
| } |
| |
| @Override |
| public Module[] getModules() { |
| return Module.EMPTY_ARRAY; |
| } |
| |
| @Override |
| public void checkConfiguration() throws RuntimeConfigurationException { |
| super.checkConfiguration(); |
| |
| Label label = getTarget(); |
| if (label == null) { |
| throw new RuntimeConfigurationError("Select a target to run"); |
| } |
| TargetIdeInfo target = TargetFinder.getInstance().targetForLabel(getProject(), label); |
| if (target == null) { |
| throw new RuntimeConfigurationError("The selected target does not exist."); |
| } |
| if (!IntellijPluginRule.isPluginTarget(target)) { |
| throw new RuntimeConfigurationError("The selected target is not an intellij_plugin"); |
| } |
| if (!IdeaJdkHelper.isIdeaJdk(pluginSdk)) { |
| throw new RuntimeConfigurationError("Select an IntelliJ Platform Plugin SDK"); |
| } |
| } |
| |
| @Override |
| public void readExternal(Element element) throws InvalidDataException { |
| super.readExternal(element); |
| // Target is persisted as a tag to permit multiple targets in the future. |
| Element targetElement = element.getChild(TARGET_TAG); |
| if (targetElement != null && !Strings.isNullOrEmpty(targetElement.getTextTrim())) { |
| target = (Label) TargetExpression.fromString(targetElement.getTextTrim()); |
| } else { |
| target = null; |
| } |
| blazeFlags.readExternal(element); |
| exeFlags.readExternal(element); |
| |
| String sdkName = element.getAttributeValue(SDK_ATTR); |
| if (!Strings.isNullOrEmpty(sdkName)) { |
| pluginSdk = ProjectJdkTable.getInstance().findJdk(sdkName); |
| } |
| vmParameters = Strings.emptyToNull(element.getAttributeValue(VM_PARAMS_ATTR)); |
| programParameters = Strings.emptyToNull(element.getAttributeValue(PROGRAM_PARAMS_ATTR)); |
| |
| String keepInSyncString = element.getAttributeValue(KEEP_IN_SYNC_TAG); |
| keepInSync = keepInSyncString != null ? Boolean.parseBoolean(keepInSyncString) : null; |
| } |
| |
| @Override |
| public void writeExternal(Element element) throws WriteExternalException { |
| super.writeExternal(element); |
| if (target != null) { |
| // Target is persisted as a tag to permit multiple targets in the future. |
| Element targetElement = new Element(TARGET_TAG); |
| targetElement.setText(target.toString()); |
| element.addContent(targetElement); |
| } |
| blazeFlags.writeExternal(element); |
| exeFlags.writeExternal(element); |
| if (pluginSdk != null) { |
| element.setAttribute(SDK_ATTR, pluginSdk.getName()); |
| } |
| if (vmParameters != null) { |
| element.setAttribute(VM_PARAMS_ATTR, vmParameters); |
| } |
| if (programParameters != null) { |
| element.setAttribute(PROGRAM_PARAMS_ATTR, programParameters); |
| } |
| if (keepInSync != null) { |
| element.setAttribute(KEEP_IN_SYNC_TAG, Boolean.toString(keepInSync)); |
| } |
| } |
| |
| @Override |
| public BlazeIntellijPluginConfiguration clone() { |
| final BlazeIntellijPluginConfiguration configuration = |
| (BlazeIntellijPluginConfiguration) super.clone(); |
| configuration.target = target; |
| configuration.blazeFlags.setFlags(blazeFlags.getFlags()); |
| configuration.exeFlags.setFlags(exeFlags.getFlags()); |
| configuration.pluginSdk = pluginSdk; |
| configuration.vmParameters = vmParameters; |
| configuration.programParameters = programParameters; |
| configuration.keepInSync = keepInSync; |
| return configuration; |
| } |
| |
| protected BlazeCommand buildBlazeCommand(Project project, ProjectViewSet projectViewSet) { |
| BlazeCommand.Builder command = |
| BlazeCommand.builder( |
| Blaze.getBuildSystemProvider(project).getBinaryPath(), BlazeCommandName.BUILD) |
| .addTargets(getTarget()) |
| .addBlazeFlags(BlazeFlags.buildFlags(project, projectViewSet)) |
| .addBlazeFlags(blazeFlags.getFlags()) |
| .addExeFlags(exeFlags.getFlags()); |
| return command.build(); |
| } |
| |
| @Override |
| public BlazeIntellijPluginConfigurationSettingsEditor getConfigurationEditor() { |
| List<TargetIdeInfo> javaTargets = |
| TargetFinder.getInstance().findTargets(getProject(), IntellijPluginRule::isPluginTarget); |
| List<Label> javaLabels = Lists.newArrayList(); |
| for (TargetIdeInfo target : javaTargets) { |
| javaLabels.add(target.key.label); |
| } |
| return new BlazeIntellijPluginConfigurationSettingsEditor( |
| javaLabels, blazeFlags.getEditor(getProject()), exeFlags.getEditor(getProject())); |
| } |
| |
| @Override |
| @Nullable |
| public String suggestedName() { |
| Label target = getTarget(); |
| if (target == null) { |
| return null; |
| } |
| return new BlazeConfigurationNameBuilder() |
| .setBuildSystemName(getProject()) |
| .setCommandName("build") |
| .setTargetString(target) |
| .build(); |
| } |
| |
| @VisibleForTesting |
| static class BlazeIntellijPluginConfigurationSettingsEditor |
| extends SettingsEditor<BlazeIntellijPluginConfiguration> { |
| private final ComboBox<Label> targetCombo; |
| private final RunConfigurationStateEditor blazeFlagsEditor; |
| private final RunConfigurationStateEditor exeFlagsEditor; |
| private final JdkComboBox sdkCombo; |
| private final LabeledComponent<RawCommandLineEditor> vmParameters = new LabeledComponent<>(); |
| private final LabeledComponent<RawCommandLineEditor> programParameters = |
| new LabeledComponent<>(); |
| private final JBCheckBox keepInSyncCheckBox; |
| |
| public BlazeIntellijPluginConfigurationSettingsEditor( |
| List<Label> javaLabels, |
| RunConfigurationStateEditor blazeFlagsEditor, |
| RunConfigurationStateEditor exeFlagsEditor) { |
| targetCombo = |
| new ComboBox<>( |
| new DefaultComboBoxModel<>( |
| Ordering.usingToString().sortedCopy(javaLabels).toArray(new Label[0]))); |
| targetCombo.setRenderer( |
| new ListCellRendererWrapper<Label>() { |
| @Override |
| public void customize( |
| JList list, @Nullable Label value, int index, boolean selected, boolean hasFocus) { |
| setText(value == null ? null : value.toString()); |
| } |
| }); |
| this.blazeFlagsEditor = blazeFlagsEditor; |
| this.exeFlagsEditor = exeFlagsEditor; |
| ProjectSdksModel sdksModel = new ProjectSdksModel(); |
| sdksModel.reset(null); |
| sdkCombo = new JdkComboBox(sdksModel, IdeaJdkHelper::isIdeaJdkType); |
| |
| keepInSyncCheckBox = new JBCheckBox("Keep in sync with source XML"); |
| keepInSyncCheckBox.addItemListener(e -> updateEnabledStatus()); |
| } |
| |
| private void updateEnabledStatus() { |
| setEnabled(!keepInSyncCheckBox.isVisible() || !keepInSyncCheckBox.isSelected()); |
| } |
| |
| private void setEnabled(boolean enabled) { |
| targetCombo.setEnabled(enabled); |
| sdkCombo.setEnabled(enabled); |
| vmParameters.getComponent().setEnabled(enabled); |
| programParameters.getComponent().setEnabled(enabled); |
| blazeFlagsEditor.setComponentEnabled(enabled); |
| exeFlagsEditor.setComponentEnabled(enabled); |
| } |
| |
| @VisibleForTesting |
| @Override |
| public void resetEditorFrom(BlazeIntellijPluginConfiguration s) { |
| targetCombo.setSelectedItem(s.getTarget()); |
| blazeFlagsEditor.resetEditorFrom(s.blazeFlags); |
| exeFlagsEditor.resetEditorFrom(s.exeFlags); |
| if (s.pluginSdk != null) { |
| sdkCombo.setSelectedJdk(s.pluginSdk); |
| } else { |
| s.pluginSdk = sdkCombo.getSelectedJdk(); |
| } |
| if (s.vmParameters != null) { |
| vmParameters.getComponent().setText(s.vmParameters); |
| } |
| if (s.programParameters != null) { |
| programParameters.getComponent().setText(s.programParameters); |
| } |
| keepInSyncCheckBox.setVisible(s.keepInSync != null); |
| if (s.keepInSync != null) { |
| keepInSyncCheckBox.setSelected(s.keepInSync); |
| } |
| } |
| |
| @VisibleForTesting |
| @Override |
| public void applyEditorTo(BlazeIntellijPluginConfiguration s) throws ConfigurationException { |
| try { |
| s.target = (Label) targetCombo.getSelectedItem(); |
| } catch (ClassCastException e) { |
| throw new ConfigurationException("Invalid label specified."); |
| } |
| blazeFlagsEditor.applyEditorTo(s.blazeFlags); |
| exeFlagsEditor.applyEditorTo(s.exeFlags); |
| s.pluginSdk = sdkCombo.getSelectedJdk(); |
| s.vmParameters = vmParameters.getComponent().getText(); |
| s.programParameters = programParameters.getComponent().getText(); |
| s.keepInSync = keepInSyncCheckBox.isVisible() ? keepInSyncCheckBox.isSelected() : null; |
| } |
| |
| @Override |
| protected JComponent createEditor() { |
| vmParameters.setText("VM options:"); |
| vmParameters.setComponent(new RawCommandLineEditor()); |
| vmParameters.getComponent().setDialogCaption(vmParameters.getRawText()); |
| vmParameters.setLabelLocation(BorderLayout.WEST); |
| |
| programParameters.setText("Program arguments"); |
| programParameters.setComponent(new RawCommandLineEditor()); |
| programParameters.getComponent().setDialogCaption(programParameters.getRawText()); |
| programParameters.setLabelLocation(BorderLayout.WEST); |
| |
| return UiUtil.createBox( |
| new JLabel("Target:"), |
| targetCombo, |
| new JLabel("Plugin SDK"), |
| sdkCombo, |
| vmParameters.getLabel(), |
| vmParameters.getComponent(), |
| programParameters.getLabel(), |
| programParameters.getComponent(), |
| blazeFlagsEditor.createComponent(), |
| exeFlagsEditor.createComponent(), |
| keepInSyncCheckBox); |
| } |
| } |
| } |