// Copyright 2014 Pants project contributors (see CONTRIBUTORS.md). // Licensed under the Apache License, Version 2.0 (see LICENSE). package com.twitter.intellij.pants.service.project; import com.google.gson.JsonSyntaxException; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.ProcessAdapter; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.model.DataNode; import com.intellij.openapi.externalSystem.model.ExternalSystemException; 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.vfs.VirtualFile; import com.intellij.util.Consumer; import com.twitter.intellij.pants.PantsBundle; import com.twitter.intellij.pants.PantsException; import com.twitter.intellij.pants.model.SimpleExportResult; import com.twitter.intellij.pants.service.PantsCompileOptionsExecutor; import com.twitter.intellij.pants.service.project.model.graph.BuildGraph; import com.twitter.intellij.pants.service.project.model.ProjectInfo; import com.twitter.intellij.pants.util.PantsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class PantsResolver { /** * Bump this version if project resolve changes. It will prompt user to refresh. * E.g. more modules are created or their relationship changes. */ public static final int VERSION = 15; protected static final Logger LOG = Logger.getInstance(PantsResolver.class); protected final PantsCompileOptionsExecutor myExecutor; protected ProjectInfo myProjectInfo = null; public PantsResolver(@NotNull PantsCompileOptionsExecutor executor) { myExecutor = executor; } public static ProjectInfo parseProjectInfoFromJSON(@NotNull String data) throws JsonSyntaxException { final int jsonStart = data.indexOf("\n{"); if (jsonStart > 0) { data = data.substring(jsonStart + 1); } return ProjectInfo.fromJson(data); } @Nullable public ProjectInfo getProjectInfo() { return myProjectInfo; } @TestOnly public void setProjectInfo(ProjectInfo projectInfo) { myProjectInfo = projectInfo; } private void parse(final String output) { myProjectInfo = null; if (output.isEmpty()) throw new ExternalSystemException("Not output from pants"); try { myProjectInfo = parseProjectInfoFromJSON(output); } catch (JsonSyntaxException e) { LOG.warn("Can't parse output\n" + output, e); throw new ExternalSystemException("Can't parse project structure!"); } } public void resolve( @NotNull Consumer<String> statusConsumer, @Nullable ProcessAdapter processAdapter ) { try { String pantsExportResult = myExecutor.loadProjectStructure(statusConsumer, processAdapter); parse(pantsExportResult); } catch (ExecutionException | IOException e) { throw new ExternalSystemException(e); } } public void addInfoTo(@NotNull DataNode<ProjectData> projectInfoDataNode) { if (myProjectInfo == null) return; LOG.debug("Amount of targets before modifiers: " + myProjectInfo.getTargets().size()); for (PantsProjectInfoModifierExtension modifier : PantsProjectInfoModifierExtension.EP_NAME.getExtensions()) { modifier.modify(myProjectInfo, myExecutor, LOG); } LOG.debug("Amount of targets after modifiers: " + myProjectInfo.getTargets().size()); Optional<BuildGraph> buildGraph = constructBuildGraph(projectInfoDataNode); final Map<String, DataNode<ModuleData>> modules = new HashMap<>(); for (PantsResolverExtension resolver : PantsResolverExtension.EP_NAME.getExtensions()) { resolver.resolve(myProjectInfo, myExecutor, projectInfoDataNode, modules, buildGraph); } if (LOG.isDebugEnabled()) { final int amountOfModules = PantsUtil.findChildren(projectInfoDataNode, ProjectKeys.MODULE).size(); LOG.debug("Amount of modules created: " + amountOfModules); } } private Optional<BuildGraph> constructBuildGraph(@NotNull DataNode<ProjectData> projectInfoDataNode) { Optional<BuildGraph> buildGraph; if (myExecutor.getOptions().isEnableIncrementalImport()) { Optional<VirtualFile> pantsExecutable = PantsUtil.findPantsExecutable(projectInfoDataNode.getData().getLinkedExternalProjectPath()); SimpleExportResult result = SimpleExportResult.getExportResult(pantsExecutable.get().getPath()); if (PantsUtil.versionCompare(result.getVersion(), "1.0.9") < 0) { throw new PantsException(PantsBundle.message("pants.resolve.incremental.import.unsupported")); } buildGraph = Optional.of(new BuildGraph(myProjectInfo.getTargets())); } else { buildGraph = Optional.empty(); } return buildGraph; } }