blob: 3012fcd3076aad801d009704716abe3b417821db [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.cxx.CxxPreprocessorInput;
import com.facebook.buck.cxx.CxxSource;
import com.facebook.buck.cxx.NativeLinkableInput;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.UnflavoredBuildTarget;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.util.MoreIterables;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.annotation.Nullable;
public class OCamlBuildContext {
static final String OCAML_COMPILED_BYTECODE_DIR = "bc";
static final String OCAML_COMPILED_DIR = "opt";
private static final String OCAML_GENERATED_SOURCE_DIR = "gen";
static final Path DEFAULT_OCAML_BYTECODE_COMPILER =
Paths.get("/usr/bin/ocamlc.opt");
static final Path DEFAULT_OCAML_YACC_COMPILER = Paths.get("/usr/bin/ocamlyacc");
static final Path DEFAULT_OCAML_LEX_COMPILER = Paths.get("/usr/bin/ocamllex.opt");
static final Path DEFAULT_OCAML_COMPILER = Paths.get("/usr/bin/ocamlopt.opt");
static final Path DEFAULT_OCAML_DEP_TOOL = Paths.get("/usr/bin/ocamldep.opt");
static final Path DEFAULT_OCAML_DEBUG = Paths.get("/usr/bin/ocamldebug");
static final Path DEFAULT_OCAML_INTEROP_INCLUDE_DIR = Paths.get("/usr/local/lib/ocaml");
@Nullable
private OCamlBuckConfig config;
@Nullable
private Path ocamlDepTool;
@Nullable
private Path ocamlCompiler;
@Nullable
private Path ocamlBytecodeCompiler;
@Nullable
private Path ocamlDebug;
private boolean isLibrary;
@Nullable
private ImmutableList<String> flags;
@Nullable
private Path output;
@Nullable
private Path bytecodeOutput;
@Nullable
private ImmutableList<Path> input;
@Nullable
private ImmutableList<String> includes;
@Nullable
private NativeLinkableInput linkableInput;
@Nullable
private ImmutableList<OCamlLibrary> ocamlInput;
@Nullable
private Path yaccCompiler;
@Nullable
private Path lexCompiler;
@Nullable
private Path compileOutputDir;
@Nullable
private Path compileBytecodeOutputDir;
@Nullable
private Path generatedSourceDir;
@Nullable
private CxxPreprocessorInput cxxPreprocessorInput;
@Nullable
private ImmutableSet<Path> cInput;
@Nullable
private ImmutableSet<Path> lexInput;
@Nullable
private ImmutableSet<Path> yaccInputs;
@Nullable
private ImmutableSet<Path> mlInput;
@Nullable
private ImmutableList<String> bytecodeIncludes;
private static Path getArchiveOutputPath(UnflavoredBuildTarget target) {
return BuildTargets.getGenPath(
target,
"%s/lib" + target.getShortName() + OCamlCompilables.OCAML_CMXA);
}
private static Path getArchiveBytecodeOutputPath(UnflavoredBuildTarget target) {
return BuildTargets.getGenPath(
target,
"%s/lib" + target.getShortName() + OCamlCompilables.OCAML_CMA);
}
public static Path getOutputPath(BuildTarget target, boolean isLibrary) {
UnflavoredBuildTarget plainTarget = target.getUnflavoredBuildTarget();
if (isLibrary) {
return getArchiveOutputPath(plainTarget);
} else {
return BuildTargets.getBinPath(
plainTarget,
"%s/" + plainTarget.getShortName() + ".opt");
}
}
public static Path getBytecodeOutputPath(BuildTarget target, boolean isLibrary) {
UnflavoredBuildTarget plainTarget = target.getUnflavoredBuildTarget();
if (isLibrary) {
return getArchiveBytecodeOutputPath(plainTarget);
} else {
return BuildTargets.getBinPath(
plainTarget,
"%s/" + plainTarget.getShortName());
}
}
private static Path getGeneratedSourceDir(BuildTarget buildTarget, boolean isLibrary) {
return getOutputPath(buildTarget, isLibrary).getParent().resolve(
OCAML_GENERATED_SOURCE_DIR);
}
public static Path getCompileOutputDir(BuildTarget buildTarget, boolean isLibrary) {
return getOutputPath(buildTarget, isLibrary).getParent().resolve(
OCAML_COMPILED_DIR);
}
public static Path getCompileBytecodeOutputDir(BuildTarget buildTarget, boolean isLibrary) {
return getOutputPath(buildTarget, isLibrary).getParent().resolve(
OCAML_COMPILED_BYTECODE_DIR);
}
public Path getCOutput(Path cSrc) {
String inputFileName = cSrc.getFileName().toString();
String outputFileName = inputFileName
.replaceFirst(
OCamlCompilables.OCAML_C_REGEX,
OCamlCompilables.OCAML_O);
return getCompileOutputDir().resolve(outputFileName);
}
public Function<Path, Path> toCOutput() {
return new Function<Path, Path>() {
@Override
public Path apply(Path input) {
return getCOutput(input);
}
};
}
public static Builder builder(OCamlBuckConfig config, SourcePathResolver resolver) {
return new Builder(new OCamlBuildContext(), config, resolver);
}
public Path getOcamlDepTool() {
return Preconditions.checkNotNull(ocamlDepTool);
}
public Path getOcamlCompiler() {
return Preconditions.checkNotNull(ocamlCompiler);
}
public Path getOcamlDebug() {
return Preconditions.checkNotNull(ocamlDebug);
}
public boolean isLibrary() {
return isLibrary;
}
public ImmutableList<String> getFlags() {
return Preconditions.checkNotNull(flags);
}
public Path getOutput() {
return Preconditions.checkNotNull(output);
}
public Path getBytecodeOutput() {
return Preconditions.checkNotNull(bytecodeOutput);
}
public ImmutableList<Path> getInput() {
return Preconditions.checkNotNull(input);
}
public ImmutableList<String> getIncludes() {
return Preconditions.checkNotNull(includes);
}
public NativeLinkableInput getLinkableInput() {
return Preconditions.checkNotNull(linkableInput);
}
public ImmutableList<OCamlLibrary> getOCamlInput() {
return Preconditions.checkNotNull(ocamlInput);
}
public Path getYaccCompiler() {
return Preconditions.checkNotNull(yaccCompiler);
}
public Path getLexCompiler() {
return Preconditions.checkNotNull(lexCompiler);
}
public Path getCompileOutputDir() {
return Preconditions.checkNotNull(compileOutputDir);
}
public Path getGeneratedSourceDir() {
return Preconditions.checkNotNull(generatedSourceDir);
}
public CxxPreprocessorInput getCxxPreprocessorInput() {
return Preconditions.checkNotNull(cxxPreprocessorInput);
}
public Path getCompileBytecodeOutputDir() {
return Preconditions.checkNotNull(compileBytecodeOutputDir);
}
public Path getOcamlBytecodeCompiler() {
return Preconditions.checkNotNull(ocamlBytecodeCompiler);
}
public ImmutableList<String> getIncludeDirectories(boolean isBytecode, boolean excludeDeps) {
Preconditions.checkNotNull(mlInput);
ImmutableSet.Builder<String> includeDirs = ImmutableSet.builder();
for (Path mlFile : mlInput) {
Path parent = mlFile.getParent();
if (parent != null) {
includeDirs.add(parent.toString());
}
}
if (!excludeDeps) {
includeDirs.addAll(isBytecode ? this.getBytecodeIncludes() : this.getIncludes());
}
return ImmutableList.copyOf(includeDirs.build());
}
public ImmutableList<String> getIncludeFlags(boolean isBytecode, boolean excludeDeps) {
Preconditions.checkNotNull(mlInput);
return ImmutableList.copyOf(
MoreIterables.zipAndConcat(
Iterables.cycle(OCamlCompilables.OCAML_INCLUDE_FLAG),
getIncludeDirectories(isBytecode, excludeDeps)));
}
public ImmutableList<String> getBytecodeIncludeFlags() {
return ImmutableList.copyOf(
MoreIterables.zipAndConcat(
Iterables.cycle(OCamlCompilables.OCAML_INCLUDE_FLAG),
getBytecodeIncludeDirectories()));
}
public ImmutableList<String> getBytecodeIncludeDirectories() {
ImmutableList.Builder<String> includesBuilder = ImmutableList.builder();
includesBuilder.addAll(getIncludeDirectories(true, /* excludeDeps */ true));
includesBuilder.add(getCompileBytecodeOutputDir().toString());
return includesBuilder.build();
}
protected FluentIterable<Path> getLexOutput(ImmutableSet<Path> lexInputs) {
return FluentIterable.from(lexInputs)
.transform(
new Function<Path, Path>() {
@Override
public Path apply(Path lexInput) {
return getGeneratedSourceDir().resolve(
lexInput.getFileName().toString().replaceFirst(
OCamlCompilables.OCAML_MLL_REGEX,
OCamlCompilables.OCAML_ML));
}
});
}
protected FluentIterable<Path> getYaccOutput(ImmutableSet<Path> yaccInputs) {
return FluentIterable.from(yaccInputs)
.transformAndConcat(
new Function<Path, Iterable<? extends Path>>() {
@Override
public Iterable<? extends Path> apply(Path yaccInput) {
String yaccFileName = yaccInput.getFileName().toString();
return ImmutableList.of(
getGeneratedSourceDir().resolve(
yaccFileName.replaceFirst(
OCamlCompilables.OCAML_MLY_REGEX,
OCamlCompilables.OCAML_ML)),
getGeneratedSourceDir().resolve(
yaccFileName.replaceFirst(
OCamlCompilables.OCAML_MLY_REGEX,
OCamlCompilables.OCAML_MLI)));
}
});
}
public ImmutableList<Path> getCInput() {
return Preconditions.checkNotNull(cInput).asList();
}
public ImmutableList<Path> getLexInput() {
return Preconditions.checkNotNull(lexInput).asList();
}
public ImmutableList<Path> getYaccInput() {
return Preconditions.checkNotNull(yaccInputs).asList();
}
public ImmutableList<Path> getMLInput() {
return Preconditions.checkNotNull(mlInput).asList();
}
public RuleKey.Builder appendDetailsToRuleKey(RuleKey.Builder builder) {
return builder
.setReflectively("ocamlDepTool", getOcamlDepTool())
.setReflectively("ocamlCompiler", getOcamlCompiler())
.setReflectively("ocamlBytecodeCompiler", getOcamlBytecodeCompiler())
.setReflectively("ocamlDebug", getOcamlDebug())
.setReflectively("yaccCompiler", getYaccCompiler())
.setReflectively("lexCompiler", getLexCompiler())
.setReflectively("flags", getFlags());
}
public ImmutableList<String> getBytecodeIncludes() {
return Preconditions.checkNotNull(bytecodeIncludes);
}
public ImmutableList<String> getCCompileFlags() {
ImmutableList.Builder<String> compileFlags = ImmutableList.builder();
CxxPreprocessorInput cxxPreprocessorInput = getCxxPreprocessorInput();
for (Path includes : cxxPreprocessorInput.getIncludeRoots()) {
compileFlags.add("-ccopt", "-I" + includes.toString());
}
for (Path includes : cxxPreprocessorInput.getSystemIncludeRoots()) {
compileFlags.add("-ccopt", "-isystem" + includes.toString());
}
for (String cFlag : cxxPreprocessorInput.getPreprocessorFlags().get(CxxSource.Type.C)) {
compileFlags.add("-ccopt", cFlag);
}
return compileFlags.build();
}
private static ImmutableList<String> addPrefix(String prefix, Iterable<String> flags) {
return ImmutableList.copyOf(
MoreIterables.zipAndConcat(
Iterables.cycle(prefix),
flags));
}
public ImmutableList<String> getCommonCFlags() {
ImmutableList.Builder<String> builder = ImmutableList.builder();
builder.addAll(addPrefix("-ccopt", Preconditions.checkNotNull(config).getCFlags()));
builder.add("-ccopt",
"-isystem" +
config.getOCamlInteropIncludesDir()
.or(DEFAULT_OCAML_INTEROP_INCLUDE_DIR.toString()));
return builder.build();
}
public ImmutableList<String> getCommonCLinkerFlags() {
Preconditions.checkNotNull(config);
return addPrefix("-ccopt",
Iterables.concat(
config.getCLinkerFlags(),
addPrefix("-Xlinker", config.getLdFlags())));
}
public static class Builder {
private final OCamlBuildContext context;
private final SourcePathResolver resolver;
private Builder(
OCamlBuildContext context,
OCamlBuckConfig config,
SourcePathResolver resolver) {
this.context = context;
this.resolver = resolver;
context.config = config;
context.ocamlDepTool = config.getOCamlDepTool().or(DEFAULT_OCAML_DEP_TOOL);
context.ocamlCompiler = config.getOCamlCompiler().or(DEFAULT_OCAML_COMPILER);
context.ocamlBytecodeCompiler = config.getOCamlBytecodeCompiler()
.or(DEFAULT_OCAML_BYTECODE_COMPILER);
context.ocamlDebug = config.getOCamlDebug().or(DEFAULT_OCAML_DEBUG);
context.yaccCompiler = config.getYaccCompiler()
.or(DEFAULT_OCAML_YACC_COMPILER);
context.lexCompiler = config.getLexCompiler().or(DEFAULT_OCAML_LEX_COMPILER);
}
Builder setFlags(ImmutableList<String> flags) {
context.flags = flags;
return this;
}
Builder setInput(ImmutableList<SourcePath> input) {
Preconditions.checkNotNull(
context.getGeneratedSourceDir(),
"You should initialize directories before the call to setInput");
FluentIterable<Path> inputPaths = FluentIterable.from(input)
.transform(resolver.getPathFunction());
context.input = inputPaths.toList();
context.cInput = inputPaths.filter(OCamlUtil.ext(OCamlCompilables.OCAML_C)).toSet();
context.lexInput = inputPaths.filter(OCamlUtil.ext(OCamlCompilables.OCAML_MLL)).toSet();
context.yaccInputs = inputPaths.filter(OCamlUtil.ext(OCamlCompilables.OCAML_MLY)).toSet();
context.mlInput = ImmutableSet.copyOf(
Iterables.concat(
inputPaths.filter(
OCamlUtil.ext(
OCamlCompilables.OCAML_ML,
OCamlCompilables.OCAML_MLI)),
context.getLexOutput(Preconditions.checkNotNull(context.lexInput)),
context.getYaccOutput(Preconditions.checkNotNull(context.yaccInputs))));
return this;
}
Builder setIncludes(ImmutableList<String> includes) {
context.includes = includes;
return this;
}
Builder setLinkableInput(NativeLinkableInput linkableInput) {
context.linkableInput = linkableInput;
return this;
}
public Builder setOcamlInput(ImmutableList<OCamlLibrary> ocamlInput) {
context.ocamlInput = ocamlInput;
return this;
}
Builder setUpDirectories(BuildTarget buildTarget, boolean isLibrary) {
context.isLibrary = isLibrary;
context.output = getOutputPath(buildTarget, isLibrary);
context.bytecodeOutput = getBytecodeOutputPath(buildTarget, isLibrary);
context.compileOutputDir = getCompileOutputDir(buildTarget, isLibrary);
context.compileBytecodeOutputDir = getCompileBytecodeOutputDir(buildTarget, isLibrary);
context.generatedSourceDir = getGeneratedSourceDir(buildTarget, isLibrary);
return this;
}
public Builder setCxxPreprocessorInput(CxxPreprocessorInput cxxPreprocessorInputFromDeps) {
context.cxxPreprocessorInput = cxxPreprocessorInputFromDeps;
return this;
}
OCamlBuildContext build() {
return context;
}
public Builder setBytecodeIncludes(ImmutableList<String> bytecodeIncludes) {
context.bytecodeIncludes = bytecodeIncludes;
return this;
}
}
}