tweag / gazelle_cabal

A gazelle extension to produce Haskell rules from cabal files

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

gazelle_cabal

Continuous Integration

This is a gazelle extension that generates and updates Haskell rules for Bazel from Cabal files.

For each Cabal file found, rules are generated in a sibling BUILD file for every buildable component.

Additionally, it generates a stack_snapshot rule in the WORKSPACE file with all of the necessary dependencies.

This example repo shows it in action.

Configuration

Firstly, setup gazelle and rules_haskell. Then import gazelle_cabal.

http_archive(
    name = "io_tweag_gazelle_cabal",
    strip_prefix = "gazelle_cabal-main",
    url = "https://github.com/tweag/gazelle_cabal/archive/main.zip",
)

Additionally, some Haskell packages are needed to build gazelle_cabal. The simplest way to bring them is to use the stack_snapshot rule in the WORKSPACE file as follows.

load("@rules_haskell//haskell:cabal.bzl", "stack_snapshot")
load("@io_tweag_gazelle_cabal//:defs.bzl", "gazelle_cabal_dependencies")
gazelle_cabal_dependencies()

stack_snapshot(
    name = "stackage",
    packages = [
        "json", # keep
        "path", # keep
        "path-io", # keep
    ],
    # Most snapshots of your choice might do
    snapshot = "lts-18.28",
)

Right now there are also some issues with gazelle and as a temporary fix you need to include the following in your WORKSPACE:

go_repository(
    name = "org_golang_x_xerrors",
    importpath = "golang.org/x/xerrors",
    sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=",
    version = "v0.0.0-20200804184101-5ec99f83aff1",
)

Should Haskell packages need to be grabbed from elsewhere, alternative labels can be provided to gazelle_cabal_dependencies.

You can generate or update build rules by adding the following to one of your BUILD files.

load(
    "@bazel_gazelle//:def.bzl",
    "DEFAULT_LANGUAGES",
    "gazelle",
    "gazelle_binary",
)

gazelle(
    name = "gazelle",
    gazelle = ":gazelle_binary",
)

gazelle_binary(
    name = "gazelle_binary",
    languages = DEFAULT_LANGUAGES + ["@io_tweag_gazelle_cabal//gazelle_cabal"],
)

Running

Build and run gazelle with

bazel run //:gazelle

Gazelle's fix command can be used to delete rules when components are removed from the cabal file.

In order to generate or update the stack_snapshot rule, add the following to one of your build files

gazelle(
    name = "gazelle-update-repos",
    command = "update-repos",
    extra_args = [
        "-lang",
        "gazelle_cabal",
        "stackage",
    ],
    gazelle = ":gazelle_binary",
)

Then build and run gazelle with

bazel run //:gazelle-update-repos

gazelle_cabal only fills the packages attribute of stack_snapshot. Either before or after the update, the other mandatory attributes of the rule need to be written by the user.

Directives

# gazelle:resolve gazelle_cabal sodium @libsodium//:libsodium

Maps names in the Cabal file's extra-libraries field to Bazel labels. The labels are added to the deps attribute of the corresponding rule. Names not mentioned in this directive are added to the ghcopts attribute as -l<name>.

# gazelle:cabal_haskell_package_repo stackage

Specifies the name of the repository from where Cabal packages are provided. The default value is stackage. The user is responsible for setting up the repository. The repository for tools is constructed automatically by appending -exe to this.

Dependency resolution

In general, package names in the build-depends field are mapped to @stackage//:<package_name>, except if there is a haskell_library rule in the current repository with the same name, in which case such a target is added to the deps attribute instead.

Similar logic applies in case of internal libraries (a.k.a sublibraries or named libraries). Additionally, please beware of shadowing feature and the fact that internal libraries with cabal's visibility:private do not leak outside of the package where they have been defined. However, their bazel's visibility attribute is by default set to public.

If there is a ghc_plugin rule named as <package name>-plugin and <package name> is listed in the build-depends field, the corresponding label is added to the plugins attribute and omitted in deps.

Tools in the build-tool-depends field are mapped to @stackage-exe//<package_name>:<executable_name> and added to the tools attribute, unless there is a haskell_binary rule in the current repository named as the executable, in which case such a target is added to the tools attribute instead. The path to the tool is defined as a CPP macro via the compiler flags of the corresponding rule as PACKAGE_NAME_EXECUTABLE_NAME_PATH (e.g. TASTY_DISCOVER_TASTY_DISCOVER_PATH).

Currently, gazelle is unable to merge the contents of the components attribute of the stack_snapshot rule if the attribute is already present. But it is still possible to have the attribute automatically generated when it is not present yet. Thus, refreshing the attribute requires to manually remove it first.

Files in the data-files field are placed verbatim in the data attribute of the haskell_library rule if a library component is present in the Cabal file. Otherwise, the data-files field is ignored.

Implementation

gazelle_cabal extracts data from Cabal files using cabalscan, a command line tool written in Haskell, which presents the extracted data in json format to the go part.

The go part consists of a set of functions written in the go programming language, which gazelle will invoke to generate Haskell rules. The most important functions are:

  • GenerateRules: calls the cabalscan command-line tool and produces rules that contain no information about dependencies.

  • Resolve: adds to the previously generated rules all the information about dependencies (deps, plugins, and tools).

  • UpdateRepos: scans the BUILD files for Haskell rules, extracts their dependencies, and puts them in a stack_snapshot rule in the WORKSPACE file.

Limitations

Despite that gazelle_cabal can produce most of the build configuration from Cabal files, Haskell dependencies brought with stack_snapshot might fail to build if their Cabal files use internal libraries or some particular custom Setup.hs files. In these cases, the simpler route to adoption could be to patch the problematic dependencies and add them to a local stack snapshot (see the local_snapshot attribute of stack_snapshot).

Also, support for fields in Cabal files is added in as-needed fashion, which means that when gazelle_cabal is tried on new Cabal files it could require patches to deal with some unsupported features. The Cabal files in the example repo rehearse the range of features currently supported.

If Cabal components use different dependencies depending on Cabal flags, gazelle_cabal will only generate the rules for the configuration with default flag values.

Hacking on gazelle_cabal with nix

If you are running NixOS or if you want to provision ghc and other dependencies using nix, you'll need to set the host_platform config option to @rules_nixpkgs_core//platforms:host:

> bazel build --host_platform=@rules_nixpkgs_core//platforms:host ...

For ease of use, we recommend setting this in your .bazelrc.local file:

echo "build --host_platform=@rules_nixpkgs_core//platforms:host" >> .bazelrc.local

Sponsors

             Symbiont          Tweag I/O

gazelle_cabal was funded by Symbiont and is maintained by Tweag I/O.

Have questions? Need help? Tweet at @tweagio.

About

A gazelle extension to produce Haskell rules from cabal files

License:Apache License 2.0


Languages

Language:Haskell 39.2%Language:Starlark 31.4%Language:Go 28.8%Language:Nix 0.6%