/* * Copyright 2013-present Facebook, Inc. * * 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.facebook.buck.ocaml; import com.facebook.buck.graph.MutableDirectedGraph; import com.facebook.buck.graph.TopologicalSort; import com.facebook.buck.util.MoreCollectors; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.stream.Stream; import javax.annotation.Nullable; /** * Parse output of ocamldep tool and build dependency graph of ocaml source files (*.ml & *.mli) */ public class OcamlDependencyGraphGenerator { private static final String OCAML_SOURCE_AND_DEPS_SEPARATOR = ":"; private static final String OCAML_DEPS_SEPARATOR = " "; private static final String LINE_SEPARATOR = Preconditions.checkNotNull(System.getProperty("line.separator")); @Nullable private MutableDirectedGraph<String> graph; public ImmutableList<String> generate(String depToolOutput) { parseDependencies(depToolOutput); Preconditions.checkNotNull(graph); final ImmutableList<String> sortedDeps = TopologicalSort.sort(graph); // Two copies of dependencies as .cmo can map to .ml or .re return Stream.concat( sortedDeps .stream() .map(input -> replaceObjExtWithSourceExt(input, false /* isReason */)), sortedDeps .stream() .map(input -> replaceObjExtWithSourceExt(input, true /* isReason */))) .collect(MoreCollectors.toImmutableList()); } private String replaceObjExtWithSourceExt(String name, boolean isReason) { return name.replaceAll( OcamlCompilables.OCAML_CMX_REGEX, isReason ? OcamlCompilables.OCAML_RE : OcamlCompilables.OCAML_ML) .replaceAll( OcamlCompilables.OCAML_CMI_REGEX, isReason ? OcamlCompilables.OCAML_REI : OcamlCompilables.OCAML_MLI); } public ImmutableMap<Path, ImmutableList<Path>> generateDependencyMap(String depString) { ImmutableMap.Builder<Path, ImmutableList<Path>> mapBuilder = ImmutableMap.builder(); Iterable<String> lines = Splitter.on(LINE_SEPARATOR).split(depString); for (String line : lines) { List<String> sourceAndDeps = Splitter.on(OCAML_SOURCE_AND_DEPS_SEPARATOR).trimResults().splitToList(line); if (sourceAndDeps.size() >= 1) { String sourceML = replaceObjExtWithSourceExt(sourceAndDeps.get(0), /* isReason */ false); String sourceRE = replaceObjExtWithSourceExt(sourceAndDeps.get(0), /* isReason */ true); if (sourceML.endsWith(OcamlCompilables.OCAML_ML) || sourceML.endsWith(OcamlCompilables.OCAML_MLI)) { // Two copies of dependencies as .cmo can map to .ml or .re ImmutableList<Path> dependencies = Splitter.on(OCAML_DEPS_SEPARATOR) .trimResults() .splitToList(sourceAndDeps.get(1)) .stream() .filter(input -> !input.isEmpty()) .flatMap( input -> Stream.of( Paths.get(replaceObjExtWithSourceExt(input, /* isReason */ false)), Paths.get(replaceObjExtWithSourceExt(input, /* isReason */ true)))) .collect(MoreCollectors.toImmutableList()); mapBuilder.put(Paths.get(sourceML), dependencies).put(Paths.get(sourceRE), dependencies); } } } return mapBuilder.build(); } private void parseDependencies(String stdout) { graph = new MutableDirectedGraph<>(); Iterable<String> lines = Splitter.on(LINE_SEPARATOR).split(stdout); for (String line : lines) { List<String> sourceAndDeps = Splitter.on(OCAML_SOURCE_AND_DEPS_SEPARATOR).trimResults().splitToList(line); if (sourceAndDeps.size() >= 1) { String source = sourceAndDeps.get(0); if (source.endsWith(OcamlCompilables.OCAML_CMX) || source.endsWith(OcamlCompilables.OCAML_CMI)) { addSourceDeps(sourceAndDeps, source); } } } } private void addSourceDeps(List<String> sourceAndDeps, String source) { Preconditions.checkNotNull(graph); graph.addNode(source); if (sourceAndDeps.size() >= 2) { String deps = sourceAndDeps.get(1); if (!deps.isEmpty()) { List<String> dependencies = Splitter.on(OCAML_DEPS_SEPARATOR).trimResults().splitToList(deps); for (String dep : dependencies) { if (!dep.isEmpty()) { graph.addEdge(source, dep); } } } } } }