// Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).
package com.twitter.intellij.pants.service.project.resolver;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.module.ModuleTypeId;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.twitter.intellij.pants.PantsException;
import com.twitter.intellij.pants.model.TargetAddressInfo;
import com.twitter.intellij.pants.service.PantsCompileOptionsExecutor;
import com.twitter.intellij.pants.service.project.model.graph.BuildGraph;
import com.twitter.intellij.pants.service.project.PantsResolverExtension;
import com.twitter.intellij.pants.service.project.metadata.TargetMetadata;
import com.twitter.intellij.pants.service.project.model.ProjectInfo;
import com.twitter.intellij.pants.service.project.model.TargetInfo;
import com.twitter.intellij.pants.service.project.model.graph.BuildGraphNode;
import com.twitter.intellij.pants.util.PantsConstants;
import com.twitter.intellij.pants.util.PantsUtil;
import icons.PantsIcons;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class PantsCreateModulesExtension implements PantsResolverExtension {
private static Logger logger = Logger.getInstance("#" + PantsCreateModulesExtension.class.getName());
private Integer depthToInclude = null;
@Override
public void resolve(
@NotNull ProjectInfo projectInfo,
@NotNull PantsCompileOptionsExecutor executor,
@NotNull DataNode<ProjectData> projectDataNode,
@NotNull Map<String, DataNode<ModuleData>> modules,
@NotNull Optional<BuildGraph> buildGraph
) {
Set<TargetInfo> targetInfoWithinLevel = null;
if (buildGraph.isPresent()) {
final int maxDepth = buildGraph.get().getMaxDepth();
getDepthToImportFromUser(maxDepth);
if (depthToInclude == null) {
throw new PantsException("Task cancelled");
}
logger.info(String.format("TargetInfo level %s", depthToInclude));
targetInfoWithinLevel = buildGraph
.get()
.getNodesUpToLevel(depthToInclude)
.stream()
.map(BuildGraphNode::getTargetInfo)
.collect(Collectors.toSet());
}
for (Map.Entry<String, TargetInfo> entry : projectInfo.getSortedTargets()) {
if (targetInfoWithinLevel != null && !targetInfoWithinLevel.contains(entry.getValue())) {
continue;
}
final String targetName = entry.getKey();
if (StringUtil.startsWith(targetName, ":scala-library")) {
// we already have it in libs
continue;
}
final TargetInfo targetInfo = entry.getValue();
if (targetInfo.isEmpty()) {
LOG.debug("Skipping " + targetName + " because it is empty");
continue;
}
final DataNode<ModuleData> moduleData =
createModuleData(
projectDataNode,
targetName,
targetInfo,
executor
);
modules.put(targetName, moduleData);
}
}
private void getDepthToImportFromUser(final int maxDepth) {
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
@Override
public void run() {
String result = Messages.showInputDialog(
String.format(
"Enter the depth of transitive dependencies to import min: 0, max: %s.\n" +
"0: root level.\n" +
"1: up to direct dependency.\n" +
"%s: entire build graph", maxDepth, maxDepth
),
"Incremental Import",
PantsIcons.Icon, //icon
String.valueOf(maxDepth), //initial number
null //validator per keystroke, not necessary in this case.
);
depthToInclude = result == null ? null : Integer.valueOf(result);
if (depthToInclude == null || depthToInclude < 0 || depthToInclude > maxDepth) {
throw new PantsException("Invalid input");
}
}
}, ModalityState.NON_MODAL);
}
@NotNull
private DataNode<ModuleData> createModuleData(
@NotNull DataNode<ProjectData> projectInfoDataNode,
@NotNull String targetName,
@NotNull TargetInfo targetInfo,
@NotNull PantsCompileOptionsExecutor executor
) {
final String moduleName = PantsUtil.getCanonicalModuleName(targetName);
final ModuleData moduleData = new ModuleData(
targetName,
PantsConstants.SYSTEM_ID,
ModuleTypeId.JAVA_MODULE,
moduleName,
projectInfoDataNode.getData().getIdeProjectFileDirectoryPath() + "/" + moduleName,
new File(executor.getBuildRoot(), targetName).getAbsolutePath()
);
final DataNode<ModuleData> moduleDataNode = projectInfoDataNode.createChild(ProjectKeys.MODULE, moduleData);
final TargetMetadata metadata = new TargetMetadata(PantsConstants.SYSTEM_ID, moduleName);
metadata.setTargetAddresses(
ContainerUtil.map(
targetInfo.getAddressInfos(),
new Function<TargetAddressInfo, String>() {
@Override
public String fun(TargetAddressInfo info) {
return info.getTargetAddress();
}
}
)
);
metadata.setTargetAddressInfoSet(targetInfo.getAddressInfos());
metadata.setLibraryExcludes(targetInfo.getExcludes());
moduleDataNode.createChild(TargetMetadata.KEY, metadata);
return moduleDataNode;
}
}