Search code examples
graphvizbazel

bazel workspace for non-bazel packages


I am using a package graphviz as part of a service and to use it I begin the bazel WORKSPACE file like this

new_local_repository(
    name = "graphviz",
    path = "/usr/local/Cellar/graphviz/2.49.1",
    build_file_content = """
package(default_visibility = ["//visibility:public"])
cc_library(
    name = "headers",
    srcs = glob(["**/*.dylib"]),
    hdrs = glob(["**/*.h"])
)
"""
)

...

The problem with it is its depends upon graphviz being downloaded, pre-installed and present in the path /usr/local/Cellar/graphviz/2.49.1. Is there a way to make it part of the bazel build process such that if its not present it will be fetched and put in the right place?


Solution

  • You can use http_archive to download one of graphviz's release archives: https://docs.bazel.build/versions/main/repo/http.html#http_archive

    From https://graphviz.org/download/source/ the 2.49.1 release is available at https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/2.49.1/graphviz-2.49.1.tar.gz

    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    http_archive(
      name = "graphviz",
      url = "https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/2.49.1/graphviz-2.49.1.tar.gz",
      strip_prefix = "graphviz-2.49.1",
      sha256 = "ba1aa7a209025cb3fc5aca1f2c0114e18ea3ad29c481d75e4d445ad44e0fb0f7",
      build_file_content = """
    
    package(default_visibility = ["//visibility:public"])
    cc_library(
        name = "headers",
        srcs = glob(["**/*.dylib"]),
        hdrs = glob(["**/*.h"])
    )
    
    """,
    )
    

    To answer the "if its not present" part of the question, I'm not aware of a straight forward way to accomplish automatically switching between something that's locally installed and downloading something. http_archive will always download the archive, and new_local_repository will always use something local.

    There's the --override_repository flag, which replaces a repository with a local one, e.g. --override_repository=graphviz=/usr/local/Cellar/graphviz/2.49.1 would effectively replace the http_archive with a local_repository pointing to that path. However, bazel would then expect there to be a WORKSPACE file and BUILD file already at that location (i.e., there's no way to specify build_file_content)

    You could specify both repository rules in the WORKSPACE file, and then use some indirection, a Starlark flag, and a select() to switch between the repositories using a command-line flag. It's a little involved though, and also not automatic. Something like this:

    WORKSPACE:

    http_archive(
      name = "graphviz-download",
      ...,
    )
    new_local_repository(
      name = "graphviz-installed",
      ...,
    )
    
    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    http_archive(
        name = "bazel_skylib",
        urls = [
            "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
            "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
        ],
        sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
    )
    load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
    bazel_skylib_workspace()
    

    BUILD (e.g. in //third_party/graphviz):

    load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
    bool_flag(
        name = "use-installed-graphviz",
        build_setting_default = False,
    )
    
    config_setting(
      name = "installed",
      flag_values = {
        ":use-installed-graphviz": "True",
      }
    )
    
    alias(
      name = "headers",
      actual = select({
        ":installed": "@graphviz-installed//:headers",
        "//conditions:default": "@graphviz-download//:headers",
      })
    )
    

    Then your code depends on //third_party/graphviz:headers, by default the alias will point to the downloaded version, and the flag --//third_party/graphviz:use-installed-graphviz will switch it to the installed version:

    $ bazel cquery --output build //third_party/graphviz:headers
    alias(
      name = "headers",
      actual = "@graphviz-download//:headers",
    )
    
    $ bazel cquery --output build //third_party/graphviz:headers --//third_party/graphviz:use-installed-graphviz
    alias(
      name = "headers",
      actual = "@graphviz-installed//:headers",
    )
    

    Another option is to write (or find) a custom repository rule that combines the functionality of http_archive and local_repository, but that would probably be a fair bit of work.

    Generally I think most people just use an http_archive and download the dependencies, and if there are specific needs for being offline or caching, there's --distdir for using already-downloaded artifacts for remote repository rules: https://docs.bazel.build/versions/main/guide.html#distribution-files-directories

    Edit: An example of using rules_foreign_cc with graphviz:

    WORKSPACE:

    load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
    http_archive(
        name = "rules_foreign_cc",
        sha256 = "69023642d5781c68911beda769f91fcbc8ca48711db935a75da7f6536b65047f",
        strip_prefix = "rules_foreign_cc-0.6.0",
        url = "https://github.com/bazelbuild/rules_foreign_cc/archive/0.6.0.tar.gz",
    )
    load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
    # This sets up some common toolchains for building targets. For more details, please see
    # https://bazelbuild.github.io/rules_foreign_cc/0.6.0/flatten.html#rules_foreign_cc_dependencies
    rules_foreign_cc_dependencies()
    
    http_archive(
      name = "graphviz",
      url = "https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/2.49.1/graphviz-2.49.1.tar.gz",
      strip_prefix = "graphviz-2.49.1",
      sha256 = "ba1aa7a209025cb3fc5aca1f2c0114e18ea3ad29c481d75e4d445ad44e0fb0f7",
      build_file_content = """\
    filegroup(
        name = "all_srcs",
        srcs = glob(["**"]),
        visibility = ["//visibility:public"],
    )
    """,
    )
    

    BUILD:

    load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
    
    # see https://bazelbuild.github.io/rules_foreign_cc/0.6.0/configure_make.html
    configure_make(
        name = "graphviz",
        lib_source = "@graphviz//:all_srcs",
        out_shared_libs = ["libcgraph.so"], # or other graphviz libs
    )
    
    cc_binary(
      name = "foo",
      srcs = ["foo.c"],
      deps = [":graphviz"],
    )
    

    foo.c:

    #include "graphviz/cgraph.h"
    
    int main() {
      Agraph_t *g;
      g = agopen("G", Agdirected, NULL);
      agclose(g);
      return 0;
    }
    

    Usage:

    $ bazel build foo
    INFO: Analyzed target //:foo (0 packages loaded, 2 targets configured).
    INFO: Found 1 target...
    Target //:foo up-to-date:
      bazel-bin/foo
    INFO: Elapsed time: 0.229s, Critical Path: 0.06s
    INFO: 7 processes: 5 internal, 2 linux-sandbox.
    INFO: Build completed successfully, 7 total actions