/*
* 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.CxxHeaders;
import com.facebook.buck.cxx.CxxPreprocessorInput;
import com.facebook.buck.cxx.CxxSource;
import com.facebook.buck.cxx.NativeLinkableInput;
import com.facebook.buck.cxx.Preprocessor;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.UnflavoredBuildTarget;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.RuleKeyAppendable;
import com.facebook.buck.rules.RuleKeyObjectSink;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.args.Arg;
import com.facebook.buck.util.MoreIterables;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import org.immutables.value.Value;
/**
* OCaml build context
*
* <p>OCaml has two build modes, "native" (ocamlopt) and "bytecode" (ocamlc), and that terminology
* is used throughout this file -- not to be confused with the "native" terminology used in
* com.facebook.buck.cxx.NativeLinkableInput.
*/
@Value.Immutable
@BuckStyleImmutable
abstract class AbstractOcamlBuildContext implements RuleKeyAppendable {
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_INTEROP_INCLUDE_DIR = Paths.get("/usr/local/lib/ocaml");
public abstract UnflavoredBuildTarget getBuildTarget();
public abstract ProjectFilesystem getProjectFilesystem();
public abstract SourcePathResolver getSourcePathResolver();
public abstract boolean isLibrary();
public abstract List<Arg> getFlags();
public abstract List<SourcePath> getInput();
public abstract List<String> getNativeIncludes();
public abstract List<String> getBytecodeIncludes();
/** Inputs for the native (ocamlopt) build */
public abstract NativeLinkableInput getNativeLinkableInput();
/** Inputs for the bytecode (ocamlc) build */
public abstract NativeLinkableInput getBytecodeLinkableInput();
/** Inputs for the C compiler (both builds) */
public abstract NativeLinkableInput getCLinkableInput();
public abstract List<OcamlLibrary> getOcamlInput();
public abstract CxxPreprocessorInput getCxxPreprocessorInput();
public abstract ImmutableSortedSet<BuildRule> getNativeCompileDeps();
public abstract ImmutableSortedSet<BuildRule> getBytecodeCompileDeps();
public abstract ImmutableSortedSet<BuildRule> getBytecodeLinkDeps();
public abstract Optional<Tool> getOcamlDepTool();
public abstract Optional<Tool> getOcamlCompiler();
public abstract Optional<Tool> getOcamlDebug();
public abstract Optional<Tool> getYaccCompiler();
public abstract Optional<Tool> getLexCompiler();
public abstract Optional<Tool> getOcamlBytecodeCompiler();
protected abstract List<String> getCFlags();
protected abstract Optional<String> getOcamlInteropIncludesDir();
protected abstract List<String> getLdFlags();
protected abstract Preprocessor getCPreprocessor();
public ImmutableList<SourcePath> getCInput() {
return FluentIterable.from(getInput())
.filter(OcamlUtil.sourcePathExt(getSourcePathResolver(), OcamlCompilables.OCAML_C))
.toSet()
.asList();
}
public ImmutableList<SourcePath> getLexInput() {
return FluentIterable.from(getInput())
.filter(OcamlUtil.sourcePathExt(getSourcePathResolver(), OcamlCompilables.OCAML_MLL))
.toSet()
.asList();
}
public ImmutableList<SourcePath> getYaccInput() {
return FluentIterable.from(getInput())
.filter(OcamlUtil.sourcePathExt(getSourcePathResolver(), OcamlCompilables.OCAML_MLY))
.toSet()
.asList();
}
public ImmutableList<SourcePath> getMLInput() {
return FluentIterable.from(getInput())
.filter(
OcamlUtil.sourcePathExt(
getSourcePathResolver(),
OcamlCompilables.OCAML_ML,
OcamlCompilables.OCAML_RE,
OcamlCompilables.OCAML_MLI,
OcamlCompilables.OCAML_REI))
.append(getLexOutput(getLexInput()))
.append(getYaccOutput(getYaccInput()))
.toSet()
.asList();
}
private static Path getArchiveNativeOutputPath(
UnflavoredBuildTarget target, ProjectFilesystem filesystem) {
return BuildTargets.getGenPath(
filesystem,
BuildTarget.of(target),
"%s/lib" + target.getShortName() + OcamlCompilables.OCAML_CMXA);
}
private static Path getArchiveBytecodeOutputPath(
UnflavoredBuildTarget target, ProjectFilesystem filesystem) {
return BuildTargets.getGenPath(
filesystem,
BuildTarget.of(target),
"%s/lib" + target.getShortName() + OcamlCompilables.OCAML_CMA);
}
public Path getNativeOutput() {
return getNativeOutputPath(getBuildTarget(), getProjectFilesystem(), isLibrary());
}
public Path getNativePluginOutput() {
UnflavoredBuildTarget target = getBuildTarget();
return BuildTargets.getGenPath(
getProjectFilesystem(),
BuildTarget.of(target),
"%s/lib" + target.getShortName() + OcamlCompilables.OCAML_CMXS);
}
public static Path getNativeOutputPath(
UnflavoredBuildTarget target, ProjectFilesystem filesystem, boolean isLibrary) {
if (isLibrary) {
return getArchiveNativeOutputPath(target, filesystem);
} else {
return BuildTargets.getScratchPath(
filesystem, BuildTarget.of(target), "%s/" + target.getShortName() + ".opt");
}
}
public Path getBytecodeOutput() {
return getBytecodeOutputPath(getBuildTarget(), getProjectFilesystem(), isLibrary());
}
public static Path getBytecodeOutputPath(
UnflavoredBuildTarget target, ProjectFilesystem filesystem, boolean isLibrary) {
if (isLibrary) {
return getArchiveBytecodeOutputPath(target, filesystem);
} else {
return BuildTargets.getScratchPath(
filesystem, BuildTarget.of(target), "%s/" + target.getShortName());
}
}
public Path getGeneratedSourceDir() {
return getNativeOutput().getParent().resolve(OCAML_GENERATED_SOURCE_DIR);
}
public Path getCompileNativeOutputDir() {
return getCompileNativeOutputDir(getBuildTarget(), getProjectFilesystem(), isLibrary());
}
public static Path getCompileNativeOutputDir(
UnflavoredBuildTarget buildTarget, ProjectFilesystem filesystem, boolean isLibrary) {
return getNativeOutputPath(buildTarget, filesystem, isLibrary)
.getParent()
.resolve(OCAML_COMPILED_DIR);
}
public Path getCompileBytecodeOutputDir() {
return getNativeOutput().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 getCompileNativeOutputDir().resolve(outputFileName);
}
public ImmutableList<String> getIncludeDirectories(boolean isBytecode, boolean excludeDeps) {
ImmutableSet.Builder<String> includeDirs = ImmutableSet.builder();
for (SourcePath mlFile : getMLInput()) {
Path parent = getSourcePathResolver().getAbsolutePath(mlFile).getParent();
if (parent != null) {
includeDirs.add(parent.toString());
}
}
if (!excludeDeps) {
includeDirs.addAll(isBytecode ? this.getBytecodeIncludes() : this.getNativeIncludes());
}
return ImmutableList.copyOf(includeDirs.build());
}
public ImmutableList<String> getIncludeFlags(boolean isBytecode, boolean excludeDeps) {
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<SourcePath> getLexOutput(Iterable<SourcePath> lexInputs) {
return FluentIterable.from(lexInputs)
.transform(
lexInput -> {
Path fileName = getSourcePathResolver().getAbsolutePath(lexInput).getFileName();
Path out =
getGeneratedSourceDir()
.resolve(
fileName
.toString()
.replaceFirst(
OcamlCompilables.OCAML_MLL_REGEX, OcamlCompilables.OCAML_ML));
return new PathSourcePath(getProjectFilesystem(), out);
});
}
protected FluentIterable<SourcePath> getYaccOutput(Iterable<SourcePath> yaccInputs) {
return FluentIterable.from(yaccInputs)
.transformAndConcat(
yaccInput -> {
String yaccFileName =
getSourcePathResolver().getAbsolutePath(yaccInput).getFileName().toString();
ImmutableList.Builder<SourcePath> toReturn = ImmutableList.builder();
toReturn.add(
new PathSourcePath(
getProjectFilesystem(),
getGeneratedSourceDir()
.resolve(
yaccFileName.replaceFirst(
OcamlCompilables.OCAML_MLY_REGEX, OcamlCompilables.OCAML_ML))));
toReturn.add(
new PathSourcePath(
getProjectFilesystem(),
getGeneratedSourceDir()
.resolve(
yaccFileName.replaceFirst(
OcamlCompilables.OCAML_MLY_REGEX, OcamlCompilables.OCAML_MLI))));
return toReturn.build();
});
}
@Override
public void appendToRuleKey(RuleKeyObjectSink sink) {
sink.setReflectively("flags", getFlags())
.setReflectively("input", getInput())
.setReflectively("lexCompiler", getLexCompiler())
.setReflectively("ocamlBytecodeCompiler", getOcamlBytecodeCompiler())
.setReflectively("ocamlCompiler", getOcamlCompiler())
.setReflectively("ocamlDebug", getOcamlDebug())
.setReflectively("ocamlDepTool", getOcamlDepTool())
.setReflectively("yaccCompiler", getYaccCompiler());
}
public ImmutableList<String> getCCompileFlags() {
ImmutableList.Builder<String> compileFlags = ImmutableList.builder();
CxxPreprocessorInput cxxPreprocessorInput = getCxxPreprocessorInput();
compileFlags.addAll(
MoreIterables.zipAndConcat(
Iterables.cycle("-ccopt"),
CxxHeaders.getArgs(
cxxPreprocessorInput.getIncludes(),
getSourcePathResolver(),
Optional.empty(),
getCPreprocessor())));
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", getCFlags()));
builder.add(
"-ccopt",
"-isystem"
+ getOcamlInteropIncludesDir().orElse(DEFAULT_OCAML_INTEROP_INCLUDE_DIR.toString()));
return builder.build();
}
public ImmutableList<String> getCommonCLinkerFlags() {
return addPrefix("-ccopt", getLdFlags());
}
public static OcamlBuildContext.Builder builder(OcamlBuckConfig config) {
return OcamlBuildContext.builder()
.setOcamlDepTool(config.getOcamlDepTool())
.setOcamlCompiler(config.getOcamlCompiler())
.setOcamlDebug(config.getOcamlDebug())
.setYaccCompiler(config.getYaccCompiler())
.setLexCompiler(config.getLexCompiler())
.setOcamlBytecodeCompiler(config.getOcamlBytecodeCompiler())
.setOcamlInteropIncludesDir(config.getOcamlInteropIncludesDir())
.setCFlags(config.getCFlags())
.setLdFlags(config.getLdFlags());
}
}