blob: 2977b5e7b6212750bc843d4cbff779006ad72f06 [file] [log] [blame]
/*
* 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.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
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 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 = 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, Predicates.<String>alwaysTrue());
return FluentIterable.from(sortedDeps).transform(
new Function<String, String>() {
@Override
public String apply(String input) {
return input.replaceAll(
OCamlCompilables.OCAML_CMX_REGEX, OCamlCompilables.OCAML_ML).replaceAll(
OCamlCompilables.OCAML_CMI_REGEX, OCamlCompilables.OCAML_MLI);
}
}).toList();
}
private String replaceObjExtWithSourceExt(String name) {
return name.replaceAll(
OCamlCompilables.OCAML_CMX_REGEX,
OCamlCompilables.OCAML_ML)
.replaceAll(
OCamlCompilables.OCAML_CMI_REGEX,
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 source = replaceObjExtWithSourceExt(sourceAndDeps.get(0));
if (source.endsWith(OCamlCompilables.OCAML_ML) ||
source.endsWith(OCamlCompilables.OCAML_MLI)) {
FluentIterable<Path> dependencies = FluentIterable
.from(
Splitter.on(OCAML_DEPS_SEPARATOR)
.trimResults().splitToList(sourceAndDeps.get(1)))
.filter(new Predicate<String>() {
@Override
public boolean apply(String input) {
return !input.isEmpty();
}
})
.transform(
new Function<String, Path>() {
@Override
public Path apply(String input) {
return Paths.get(replaceObjExtWithSourceExt(input));
}
});
mapBuilder.put(
Paths.get(source),
ImmutableList.copyOf(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);
}
}
}
}
}
}