com.facebook.buck.features.haskell.HaskellGhciRule.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.features.haskell.HaskellGhciRule.java

Source

/*
 * 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.features.haskell;

import com.facebook.buck.core.build.buildable.context.BuildableContext;
import com.facebook.buck.core.build.context.BuildContext;
import com.facebook.buck.core.build.execution.context.ExecutionContext;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.impl.BuildTargetPaths;
import com.facebook.buck.core.rulekey.AddToRuleKey;
import com.facebook.buck.core.rulekey.RuleKeyObjectSink;
import com.facebook.buck.core.rules.BuildRule;
import com.facebook.buck.core.rules.BuildRuleParams;
import com.facebook.buck.core.rules.SourcePathRuleFinder;
import com.facebook.buck.core.rules.impl.AbstractBuildRuleWithDeclaredAndExtraDeps;
import com.facebook.buck.core.rules.tool.BinaryBuildRule;
import com.facebook.buck.core.sourcepath.ExplicitBuildTargetSourcePath;
import com.facebook.buck.core.sourcepath.NonHashableSourcePathContainer;
import com.facebook.buck.core.sourcepath.SourcePath;
import com.facebook.buck.core.sourcepath.resolver.SourcePathResolver;
import com.facebook.buck.core.toolchain.tool.Tool;
import com.facebook.buck.core.toolchain.tool.impl.CommandTool;
import com.facebook.buck.io.BuildCellRelativePath;
import com.facebook.buck.io.filesystem.ProjectFilesystem;
import com.facebook.buck.rules.args.SourcePathArg;
import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.facebook.buck.step.fs.CopyStep;
import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
import com.facebook.buck.step.fs.MakeExecutableStep;
import com.facebook.buck.step.fs.StringTemplateStep;
import com.facebook.buck.step.fs.SymlinkFileStep;
import com.facebook.buck.step.fs.WriteFileStep;
import com.facebook.buck.util.MoreIterables;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.stringtemplate.v4.ST;

public class HaskellGhciRule extends AbstractBuildRuleWithDeclaredAndExtraDeps implements BinaryBuildRule {

    @AddToRuleKey
    HaskellSources srcs;

    @AddToRuleKey
    ImmutableList<String> compilerFlags;

    @AddToRuleKey
    Optional<SourcePath> ghciBinDep;

    @AddToRuleKey
    Optional<SourcePath> ghciInit;

    @AddToRuleKey
    BuildRule omnibusSharedObject;

    ImmutableSortedMap<String, SourcePath> solibs;

    ImmutableSortedMap<String, SourcePath> preloadLibs;

    @AddToRuleKey
    ImmutableSet<HaskellPackage> firstOrderHaskellPackages;

    @AddToRuleKey
    ImmutableSet<HaskellPackage> haskellPackages;

    @AddToRuleKey
    ImmutableSet<HaskellPackage> prebuiltHaskellPackages;

    @AddToRuleKey
    boolean enableProfiling;

    @AddToRuleKey(stringify = true)
    Path ghciScriptTemplate;

    @AddToRuleKey
    ImmutableList<SourcePath> extraScriptTemplates;

    @AddToRuleKey(stringify = true)
    Path ghciIservScriptTemplate;

    @AddToRuleKey(stringify = true)
    Path ghciBinutils;

    @AddToRuleKey(stringify = true)
    Path ghciGhc;

    @AddToRuleKey(stringify = true)
    Path ghciIServ;

    @AddToRuleKey(stringify = true)
    Path ghciIServProf;

    @AddToRuleKey(stringify = true)
    Path ghciLib;

    @AddToRuleKey(stringify = true)
    Path ghciCxx;

    @AddToRuleKey(stringify = true)
    Path ghciCc;

    @AddToRuleKey(stringify = true)
    Path ghciCpp;

    @AddToRuleKey(stringify = true)
    Path ghciPackager;

    private HaskellGhciRule(BuildTarget buildTarget, ProjectFilesystem projectFilesystem, BuildRuleParams params,
            HaskellSources srcs, ImmutableList<String> compilerFlags, Optional<SourcePath> ghciBinDep,
            Optional<SourcePath> ghciInit, BuildRule omnibusSharedObject,
            ImmutableSortedMap<String, SourcePath> solibs, ImmutableSortedMap<String, SourcePath> preloadLibs,
            ImmutableSet<HaskellPackage> firstOrderHaskellPackages, ImmutableSet<HaskellPackage> haskellPackages,
            ImmutableSet<HaskellPackage> prebuiltHaskellPackages, boolean enableProfiling, Path ghciScriptTemplate,
            ImmutableList<SourcePath> extraScriptTemplates, Path ghciIservScriptTemplate, Path ghciBinutils,
            Path ghciGhc, Path ghciIServ, Path ghciIServProf, Path ghciLib, Path ghciCxx, Path ghciCc, Path ghciCpp,
            Path ghciPackager) {
        super(buildTarget, projectFilesystem, params);
        this.srcs = srcs;
        this.compilerFlags = compilerFlags;
        this.ghciBinDep = ghciBinDep;
        this.ghciInit = ghciInit;
        this.omnibusSharedObject = omnibusSharedObject;
        this.solibs = solibs;
        this.preloadLibs = preloadLibs;
        this.firstOrderHaskellPackages = firstOrderHaskellPackages;
        this.haskellPackages = haskellPackages;
        this.prebuiltHaskellPackages = prebuiltHaskellPackages;
        this.enableProfiling = enableProfiling;
        this.ghciScriptTemplate = ghciScriptTemplate;
        this.extraScriptTemplates = extraScriptTemplates;
        this.ghciIservScriptTemplate = ghciIservScriptTemplate;
        this.ghciBinutils = ghciBinutils;
        this.ghciGhc = ghciGhc;
        this.ghciIServ = ghciIServ;
        this.ghciIServProf = ghciIServProf;
        this.ghciLib = ghciLib;
        this.ghciCxx = ghciCxx;
        this.ghciCc = ghciCc;
        this.ghciCpp = ghciCpp;
        this.ghciPackager = ghciPackager;
    }

    @Override
    public void appendToRuleKey(RuleKeyObjectSink sink) {
        sink.setReflectively("links", solibsForRuleKey(solibs));
        sink.setReflectively("preloads", solibsForRuleKey(preloadLibs));
    }

    private ImmutableSortedMap<String, NonHashableSourcePathContainer> solibsForRuleKey(
            ImmutableSortedMap<String, SourcePath> libs) {
        ImmutableSortedMap.Builder<String, NonHashableSourcePathContainer> solibMap = ImmutableSortedMap
                .naturalOrder();
        for (Map.Entry<String, SourcePath> entry : libs.entrySet()) {
            solibMap.put(entry.getKey(), new NonHashableSourcePathContainer(entry.getValue()));
        }

        return solibMap.build();
    }

    public static HaskellGhciRule from(BuildTarget buildTarget, ProjectFilesystem projectFilesystem,
            BuildRuleParams params, SourcePathRuleFinder ruleFinder, HaskellSources srcs,
            ImmutableList<String> compilerFlags, Optional<SourcePath> ghciBinDep, Optional<SourcePath> ghciInit,
            BuildRule omnibusSharedObject, ImmutableSortedMap<String, SourcePath> solibs,
            ImmutableSortedMap<String, SourcePath> preloadLibs,
            ImmutableSet<HaskellPackage> firstOrderHaskellPackages, ImmutableSet<HaskellPackage> haskellPackages,
            ImmutableSet<HaskellPackage> prebuiltHaskellPackages, boolean enableProfiling, Path ghciScriptTemplate,
            ImmutableList<SourcePath> extraScriptTemplates, Path ghciIservScriptTemplate, Path ghciBinutils,
            Path ghciGhc, Path ghciIServ, Path ghciIServProf, Path ghciLib, Path ghciCxx, Path ghciCc, Path ghciCpp,
            Path ghciPackager) {

        ImmutableSet.Builder<BuildRule> extraDeps = ImmutableSet.builder();

        extraDeps.add(omnibusSharedObject);

        for (HaskellPackage pkg : haskellPackages) {
            extraDeps.addAll(pkg.getDeps(ruleFinder)::iterator);
        }

        for (HaskellPackage pkg : prebuiltHaskellPackages) {
            extraDeps.addAll(pkg.getDeps(ruleFinder)::iterator);
        }

        ghciBinDep.flatMap(ruleFinder::getRule).ifPresent(extraDeps::add);

        extraDeps.addAll(ruleFinder.filterBuildRuleInputs(solibs.values()));
        extraDeps.addAll(ruleFinder.filterBuildRuleInputs(preloadLibs.values()));
        return new HaskellGhciRule(buildTarget, projectFilesystem, params.copyAppendingExtraDeps(extraDeps.build()),
                srcs, compilerFlags, ghciBinDep, ghciInit, omnibusSharedObject, solibs, preloadLibs,
                firstOrderHaskellPackages, haskellPackages, prebuiltHaskellPackages, enableProfiling,
                ghciScriptTemplate, extraScriptTemplates, ghciIservScriptTemplate, ghciBinutils, ghciGhc, ghciIServ,
                ghciIServProf, ghciLib, ghciCxx, ghciCc, ghciCpp, ghciPackager);
    }

    private Path getOutputDir() {
        return BuildTargetPaths.getGenPath(getProjectFilesystem(), getBuildTarget(), "%s");
    }

    @Override
    public SourcePath getSourcePathToOutput() {
        return ExplicitBuildTargetSourcePath.of(getBuildTarget(), getOutputDir());
    }

    /** Resolves the real path to the lib and generates a symlink to it */
    private class ResolveAndSymlinkStep extends AbstractExecutionStep {

        private SourcePathResolver resolver;
        private Path symlinkDir;
        private String name;
        private SourcePath lib;

        public ResolveAndSymlinkStep(SourcePathResolver resolver, Path symlinkDir, String name, SourcePath lib) {
            super("symlinkLib_" + name);
            this.resolver = resolver;
            this.symlinkDir = symlinkDir;
            this.name = name;
            this.lib = lib;
        }

        @Override
        public StepExecutionResult execute(ExecutionContext context) throws IOException, InterruptedException {
            Path src = resolver.getRelativePath(lib).toRealPath();
            Path dest = symlinkDir.resolve(name);
            SymlinkFileStep.Builder sl = SymlinkFileStep.builder();
            return sl.setFilesystem(getProjectFilesystem()).setExistingFile(src).setDesiredLink(dest).build()
                    .execute(context);
        }
    }

    private void symlinkLibs(SourcePathResolver resolver, Path symlinkDir, ImmutableList.Builder<Step> steps,
            ImmutableSortedMap<String, SourcePath> libs) {
        for (Map.Entry<String, SourcePath> ent : libs.entrySet()) {
            steps.add(new ResolveAndSymlinkStep(resolver, symlinkDir, ent.getKey(), ent.getValue()));
        }
    }

    @Override
    public ImmutableList<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext) {

        SourcePathResolver resolver = context.getSourcePathResolver();

        String name = getBuildTarget().getShortName();
        Path dir = getOutputDir();
        Path so = resolver.getRelativePath(omnibusSharedObject.getSourcePathToOutput());
        Path packagesDir = dir.resolve(name + ".packages");
        Path symlinkDir = dir.resolve(HaskellGhciDescription.getSoLibsRelDir(getBuildTarget()));
        Path symlinkPreloadDir = dir.resolve(name + ".preload-symlinks");

        ImmutableList.Builder<String> compilerFlagsBuilder = ImmutableList.builder();
        compilerFlagsBuilder.addAll(compilerFlags);

        ImmutableList.Builder<Step> steps = ImmutableList.builder();
        steps.addAll(MakeCleanDirectoryStep.of(BuildCellRelativePath
                .fromCellRelativePath(context.getBuildCellRootPath(), getProjectFilesystem(), dir)));
        steps.addAll(MakeCleanDirectoryStep.of(BuildCellRelativePath
                .fromCellRelativePath(context.getBuildCellRootPath(), getProjectFilesystem(), symlinkDir)));
        steps.addAll(MakeCleanDirectoryStep.of(BuildCellRelativePath
                .fromCellRelativePath(context.getBuildCellRootPath(), getProjectFilesystem(), symlinkPreloadDir)));
        steps.addAll(MakeCleanDirectoryStep.of(BuildCellRelativePath
                .fromCellRelativePath(context.getBuildCellRootPath(), getProjectFilesystem(), packagesDir)));

        steps.add(CopyStep.forFile(getProjectFilesystem(), so, dir.resolve(so.getFileName())));

        symlinkLibs(resolver, symlinkDir, steps, solibs);
        symlinkLibs(resolver, symlinkPreloadDir, steps, preloadLibs);

        ImmutableSet.Builder<String> pkgdirs = ImmutableSet.builder();
        for (HaskellPackage pkg : prebuiltHaskellPackages) {
            try {
                pkgdirs.add(resolver.getRelativePath(pkg.getPackageDb()).toRealPath().toString());
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }

        for (HaskellPackage pkg : haskellPackages) {
            String pkgname = pkg.getInfo().getName();
            Path pkgdir = packagesDir.resolve(pkgname);
            steps.addAll(MakeCleanDirectoryStep.of(BuildCellRelativePath
                    .fromCellRelativePath(context.getBuildCellRootPath(), getProjectFilesystem(), pkgdir)));

            Path pkgDbSrc = resolver.getRelativePath(pkg.getPackageDb());
            steps.add(CopyStep.forDirectory(getProjectFilesystem(), pkgDbSrc, pkgdir,
                    CopyStep.DirectoryMode.DIRECTORY_AND_CONTENTS));

            ImmutableSet.Builder<Path> artifacts = ImmutableSet.builder();
            for (SourcePath lib : pkg.getLibraries()) {
                artifacts.add(resolver.getRelativePath(lib).getParent());
            }

            // this is required because the .a files above are thin archives,
            // they merely point to the .o files via a relative path.
            for (SourcePath obj : pkg.getObjects()) {
                artifacts.add(resolver.getRelativePath(obj).getParent());
            }

            for (SourcePath iface : pkg.getInterfaces()) {
                artifacts.add(resolver.getRelativePath(iface).getParent());
            }

            for (Path artifact : artifacts.build()) {
                steps.add(CopyStep.forDirectory(getProjectFilesystem(), artifact, pkgdir,
                        CopyStep.DirectoryMode.DIRECTORY_AND_CONTENTS));
            }

            pkgdirs.add("${DIR}/" + dir.relativize(pkgdir.resolve(pkgDbSrc.getFileName())));
        }

        ImmutableSet.Builder<String> exposedPkgs = ImmutableSet.builder();
        for (HaskellPackage pkg : firstOrderHaskellPackages) {
            exposedPkgs.add(String.format("%s-%s", pkg.getInfo().getName(), pkg.getInfo().getVersion()));
        }

        // iserv script
        Optional<Path> iservScript = Optional.empty();

        if (!preloadLibs.isEmpty()) {
            iservScript = Optional.of(dir.resolve("iserv"));
            compilerFlagsBuilder.add("-fexternal-interpreter");
            steps.add(new AbstractExecutionStep("ghci_iserv_wrapper") {
                @Override
                public StepExecutionResult execute(ExecutionContext context)
                        throws IOException, InterruptedException {
                    String template;
                    template = new String(Files.readAllBytes(ghciIservScriptTemplate), Charsets.UTF_8);
                    ST st = new ST(template);
                    ImmutableSet.Builder<String> preloadLibrariesB = ImmutableSet.builder();
                    for (Map.Entry<String, SourcePath> ent : preloadLibs.entrySet()) {
                        preloadLibrariesB.add("${DIR}/" + dir.relativize(symlinkPreloadDir.resolve(ent.getKey())));
                    }
                    ImmutableSet<String> preloadLibraries = preloadLibrariesB.build();
                    st.add("name", name + "-iserv");
                    st.add("preload_libs", Joiner.on(':').join(preloadLibraries));
                    if (enableProfiling) {
                        st.add("ghci_iserv_path", ghciIServProf.toRealPath().toString());
                    } else {
                        st.add("ghci_iserv_path", ghciIServ.toRealPath().toString());
                    }
                    Path actualIserv = dir.resolve("iserv");
                    if (enableProfiling) {
                        actualIserv = dir.resolve("iserv-prof");
                    }
                    return new WriteFileStep(getProjectFilesystem(), Objects.requireNonNull(st.render()),
                            actualIserv, /* executable */
                            true).execute(context);
                }
            });
        }

        // .ghci file
        StringBuilder startGhciContents = new StringBuilder();
        if (iservScript.isPresent()) {
            // Need to unset preloaded deps for `iserv`
            startGhciContents.append("System.Environment.unsetEnv \"LD_PRELOAD\"\n");
        }
        startGhciContents.append(":set ");
        startGhciContents.append(Joiner.on(' ').join(ImmutableList.<String>builder()
                .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-package"), exposedPkgs.build())).build()));

        if (ghciInit.isPresent()) {
            try {
                startGhciContents.append('\n');
                List<String> lines = Files.readAllLines(resolver.getRelativePath(ghciInit.get()),
                        StandardCharsets.UTF_8);
                startGhciContents.append(Joiner.on('\n').join(lines));
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }

        Path startGhci = dir.resolve("start.ghci");
        steps.add(new WriteFileStep(getProjectFilesystem(), startGhciContents.toString(), startGhci,
                /* executable */ false));

        // ghciBinDep
        ImmutableList.Builder<String> srcpaths = ImmutableList.builder();
        for (SourcePath sp : srcs.getSourcePaths()) {
            srcpaths.add(resolver.getRelativePath(sp).toString());
        }

        String ghcPath = null;
        try {
            if (ghciBinDep.isPresent()) {

                Path binDir = dir.resolve(name + ".bin");
                Path bin = binDir.resolve("ghci");
                SourcePath sp = ghciBinDep.get();

                steps.addAll(MakeCleanDirectoryStep.of(BuildCellRelativePath
                        .fromCellRelativePath(context.getBuildCellRootPath(), getProjectFilesystem(), binDir)));

                steps.add(CopyStep.forFile(getProjectFilesystem(), resolver.getRelativePath(sp), bin));

                ghcPath = "${DIR}/" + dir.relativize(bin) + " -B" + ghciLib.toRealPath();
            } else {
                ghcPath = ghciGhc.toRealPath().toString();
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }

        String pkgdbs = Joiner.on(' ').join(ImmutableList.<String>builder()
                .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-package-db"), pkgdirs.build())).build());
        String exposed = Joiner.on(' ')
                .join(ImmutableList.<String>builder()
                        .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-expose-package"), exposedPkgs.build()))
                        .build());

        if (enableProfiling) {
            compilerFlagsBuilder.addAll(HaskellDescriptionUtils.PROF_FLAGS);
        }

        String ghc = ghcPath;
        ImmutableMap.Builder<String, String> templateArgs = ImmutableMap.builder();
        try {
            templateArgs.put("name", name);
            templateArgs.put("start_ghci", dir.relativize(startGhci).toString());
            templateArgs.put("exposed_packages", exposed);
            templateArgs.put("package_dbs", pkgdbs);
            templateArgs.put("compiler_flags", Joiner.on(' ').join(compilerFlagsBuilder.build()));
            templateArgs.put("srcs", Joiner.on(' ').join(srcpaths.build()));
            templateArgs.put("squashed_so", dir.relativize(dir.resolve(so.getFileName())).toString());
            templateArgs.put("binutils_path", ghciBinutils.toRealPath().toString());
            templateArgs.put("ghc_path", ghc);
            templateArgs.put("cxx_path", ghciCxx.toRealPath().toString());
            templateArgs.put("cc_path", ghciCc.toRealPath().toString());
            templateArgs.put("cpp_path", ghciCpp.toRealPath().toString());
            templateArgs.put("ghc_pkg_path", ghciPackager.toRealPath().toString());
            if (iservScript.isPresent()) {
                templateArgs.put("iserv_path", dir.relativize(iservScript.get()).toString());
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }

        Path script = scriptPath();
        steps.add(new StringTemplateStep(ghciScriptTemplate, getProjectFilesystem(), script, templateArgs.build()));
        steps.add(new MakeExecutableStep(getProjectFilesystem(), script));

        for (SourcePath s : extraScriptTemplates) {
            Path templateAbsPath = resolver.getAbsolutePath(s);
            Path extraScript = dir.resolve(templateAbsPath.getFileName());
            steps.add(new StringTemplateStep(templateAbsPath, getProjectFilesystem(), extraScript,
                    templateArgs.build()));
            steps.add(new MakeExecutableStep(getProjectFilesystem(), extraScript));
        }

        buildableContext.recordArtifact(dir);

        return steps.build();
    }

    private Path scriptPath() {
        return getOutputDir().resolve(getBuildTarget().getShortName());
    }

    @Override
    public Tool getExecutableCommand() {
        SourcePath p = ExplicitBuildTargetSourcePath.of(getBuildTarget(), scriptPath());
        return new CommandTool.Builder().addArg(SourcePathArg.of(p)).build();
    }
}