/* * Copyright 2016-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.haskell; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.CxxSourceRuleFactory; import com.facebook.buck.cxx.CxxToolFlags; import com.facebook.buck.cxx.PathShortener; import com.facebook.buck.cxx.Preprocessor; import com.facebook.buck.cxx.PreprocessorFlags; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.rules.AbstractBuildRule; import com.facebook.buck.rules.AddToRuleKey; import com.facebook.buck.rules.BuildContext; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.BuildRuleParams; import com.facebook.buck.rules.BuildableContext; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.Tool; import com.facebook.buck.shell.ShellStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.util.MoreIterables; import com.facebook.buck.util.OptionalCompat; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.io.File; import java.nio.file.Path; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.Stream; public class HaskellCompileRule extends AbstractBuildRule { @AddToRuleKey private final Tool compiler; private final HaskellVersion haskellVersion; @AddToRuleKey private final ImmutableList<String> flags; private final PreprocessorFlags ppFlags; private final CxxPlatform cxxPlatform; @AddToRuleKey private final CxxSourceRuleFactory.PicType picType; @AddToRuleKey private final Optional<String> main; /** * Optional package info. If specified, the package name and version are baked into the * compilation. */ @AddToRuleKey private final Optional<HaskellPackageInfo> packageInfo; @AddToRuleKey private final ImmutableList<SourcePath> includes; /** Packages providing modules that modules from this compilation can directly import. */ @AddToRuleKey private final ImmutableSortedMap<String, HaskellPackage> exposedPackages; /** * Packages that are transitively used by the exposed packages. Modules in this compilation cannot * import modules from these. */ @AddToRuleKey private final ImmutableSortedMap<String, HaskellPackage> packages; @AddToRuleKey private final HaskellSources sources; @AddToRuleKey private final Preprocessor preprocessor; private HaskellCompileRule( BuildRuleParams buildRuleParams, Tool compiler, HaskellVersion haskellVersion, ImmutableList<String> flags, PreprocessorFlags ppFlags, CxxPlatform cxxPlatform, CxxSourceRuleFactory.PicType picType, Optional<String> main, Optional<HaskellPackageInfo> packageInfo, ImmutableList<SourcePath> includes, ImmutableSortedMap<String, HaskellPackage> exposedPackages, ImmutableSortedMap<String, HaskellPackage> packages, HaskellSources sources, Preprocessor preprocessor) { super(buildRuleParams); this.compiler = compiler; this.haskellVersion = haskellVersion; this.flags = flags; this.ppFlags = ppFlags; this.cxxPlatform = cxxPlatform; this.picType = picType; this.main = main; this.packageInfo = packageInfo; this.includes = includes; this.exposedPackages = exposedPackages; this.packages = packages; this.sources = sources; this.preprocessor = preprocessor; } public static HaskellCompileRule from( BuildTarget target, BuildRuleParams baseParams, SourcePathRuleFinder ruleFinder, final Tool compiler, HaskellVersion haskellVersion, ImmutableList<String> flags, final PreprocessorFlags ppFlags, CxxPlatform cxxPlatform, CxxSourceRuleFactory.PicType picType, Optional<String> main, Optional<HaskellPackageInfo> packageInfo, final ImmutableList<SourcePath> includes, final ImmutableSortedMap<String, HaskellPackage> exposedPackages, final ImmutableSortedMap<String, HaskellPackage> packages, final HaskellSources sources, Preprocessor preprocessor) { Supplier<ImmutableSortedSet<BuildRule>> declaredDeps = Suppliers.memoize( () -> ImmutableSortedSet.<BuildRule>naturalOrder() .addAll(compiler.getDeps(ruleFinder)) .addAll(ppFlags.getDeps(ruleFinder)) .addAll(ruleFinder.filterBuildRuleInputs(includes)) .addAll(sources.getDeps(ruleFinder)) .addAll( Stream.of(exposedPackages, packages) .flatMap(packageMap -> packageMap.values().stream()) .flatMap(pkg -> pkg.getDeps(ruleFinder)) .iterator()) .build()); return new HaskellCompileRule( baseParams .withBuildTarget(target) .copyReplacingDeclaredAndExtraDeps( declaredDeps, Suppliers.ofInstance(ImmutableSortedSet.of())), compiler, haskellVersion, flags, ppFlags, cxxPlatform, picType, main, packageInfo, includes, exposedPackages, packages, sources, preprocessor); } @Override public void appendToRuleKey(RuleKeyObjectSink sink) { ppFlags.appendToRuleKey(sink, cxxPlatform.getCompilerDebugPathSanitizer()); sink.setReflectively("headers", ppFlags.getIncludes()); } private Path getObjectDir() { return BuildTargets.getGenPath(getProjectFilesystem(), getBuildTarget(), "%s") .resolve("objects"); } private Path getInterfaceDir() { return BuildTargets.getGenPath(getProjectFilesystem(), getBuildTarget(), "%s") .resolve("interfaces"); } /** @return the path where the compiler places generated FFI stub files. */ private Path getStubDir() { return BuildTargets.getGenPath(getProjectFilesystem(), getBuildTarget(), "%s").resolve("stubs"); } private Iterable<String> getPackageNameArgs() { ImmutableList.Builder<String> builder = ImmutableList.builder(); if (packageInfo.isPresent()) { if (haskellVersion.getMajorVersion() >= 8) { builder.add("-package-name", packageInfo.get().getName()); } else { builder.add( "-package-name", packageInfo.get().getName() + '-' + packageInfo.get().getVersion()); } } return builder.build(); } /** @return the arguments to pass to the compiler to build against package dependencies. */ private Iterable<String> getPackageArgs(SourcePathResolver resolver) { Set<String> packageDbs = new TreeSet<>(); Set<String> hidden = new TreeSet<>(); Set<String> exposed = new TreeSet<>(); for (HaskellPackage haskellPackage : packages.values()) { packageDbs.add(resolver.getAbsolutePath(haskellPackage.getPackageDb()).toString()); hidden.add( String.format( "%s-%s", haskellPackage.getInfo().getName(), haskellPackage.getInfo().getVersion())); } for (HaskellPackage haskellPackage : exposedPackages.values()) { packageDbs.add(resolver.getAbsolutePath(haskellPackage.getPackageDb()).toString()); exposed.add( String.format( "%s-%s", haskellPackage.getInfo().getName(), haskellPackage.getInfo().getVersion())); } // We add all package DBs, and explicit expose or hide packages depending on whether they are // exposed or not. This allows us to support setups that either add `-hide-all-packages` or // not. return ImmutableList.<String>builder() .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-package-db"), packageDbs)) .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-package"), exposed)) .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-hide-package"), hidden)) .build(); } private Iterable<String> getPreprocessorFlags(SourcePathResolver resolver) { CxxToolFlags cxxToolFlags = ppFlags.toToolFlags( resolver, PathShortener.identity(), CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, resolver), preprocessor, /* pch */ Optional.empty()); return MoreIterables.zipAndConcat(Iterables.cycle("-optP"), cxxToolFlags.getAllFlags()); } @Override public ImmutableList<Step> getBuildSteps( BuildContext buildContext, BuildableContext buildableContext) { buildableContext.recordArtifact(getObjectDir()); buildableContext.recordArtifact(getInterfaceDir()); buildableContext.recordArtifact(getStubDir()); return new ImmutableList.Builder<Step>() .addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), getObjectDir())) .addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), getInterfaceDir())) .addAll(MakeCleanDirectoryStep.of(getProjectFilesystem(), getStubDir())) .add( new ShellStep(getProjectFilesystem().getRootPath()) { @Override public ImmutableMap<String, String> getEnvironmentVariables( ExecutionContext context) { return ImmutableMap.<String, String>builder() .putAll(super.getEnvironmentVariables(context)) .putAll(compiler.getEnvironment(buildContext.getSourcePathResolver())) .build(); } @Override protected ImmutableList<String> getShellCommandInternal(ExecutionContext context) { SourcePathResolver resolver = buildContext.getSourcePathResolver(); return ImmutableList.<String>builder() .addAll(compiler.getCommandPrefix(resolver)) .addAll(flags) .add("-no-link") .addAll( picType == CxxSourceRuleFactory.PicType.PIC ? ImmutableList.of("-dynamic", "-fPIC", "-hisuf", "dyn_hi") : ImmutableList.of()) .addAll( MoreIterables.zipAndConcat( Iterables.cycle("-main-is"), OptionalCompat.asSet(main))) .addAll(getPackageNameArgs()) .addAll(getPreprocessorFlags(buildContext.getSourcePathResolver())) .add("-odir", getProjectFilesystem().resolve(getObjectDir()).toString()) .add("-hidir", getProjectFilesystem().resolve(getInterfaceDir()).toString()) .add("-stubdir", getProjectFilesystem().resolve(getStubDir()).toString()) .add( "-i" + includes .stream() .map(resolver::getAbsolutePath) .map(Object::toString) .collect(Collectors.joining(":"))) .addAll(getPackageArgs(buildContext.getSourcePathResolver())) .addAll( sources .getSourcePaths() .stream() .map(resolver::getAbsolutePath) .map(Object::toString) .iterator()) .build(); } @Override public String getShortName() { return "haskell-compile"; } }) .build(); } @Override public boolean isCacheable() { return haskellVersion.getMajorVersion() >= 8; } @Override public SourcePath getSourcePathToOutput() { return new ExplicitBuildTargetSourcePath(getBuildTarget(), getInterfaceDir()); } public ImmutableList<SourcePath> getObjects() { ImmutableList.Builder<SourcePath> objects = ImmutableList.builder(); for (String module : sources.getModuleNames()) { objects.add( new ExplicitBuildTargetSourcePath( getBuildTarget(), getObjectDir().resolve(module.replace('.', File.separatorChar) + ".o"))); } return objects.build(); } public ImmutableSortedSet<String> getModules() { return sources.getModuleNames(); } public SourcePath getInterfaces() { return new ExplicitBuildTargetSourcePath(getBuildTarget(), getInterfaceDir()); } @VisibleForTesting protected ImmutableList<String> getFlags() { return flags; } }