1"""A module defining a repository rule for vendoring the dependencies
2of a crate in the current workspace.
3"""
4
5def _impl(repository_ctx):
6    # Link cxx repository into @third-party.
7    lockfile = repository_ctx.path(repository_ctx.attr.lockfile)
8    workspace = lockfile.dirname.dirname
9    repository_ctx.symlink(workspace, "workspace")
10
11    # Copy third-party/Cargo.lock since those are the crate versions that the
12    # BUILD file is written against.
13    vendor_lockfile = repository_ctx.path("workspace/third-party/Cargo.lock")
14    root_lockfile = repository_ctx.path("workspace/Cargo.lock")
15    _copy_file(repository_ctx, src = vendor_lockfile, dst = root_lockfile)
16
17    # Execute cargo vendor.
18    cmd = ["cargo", "vendor", "--versioned-dirs", "third-party/vendor"]
19    result = repository_ctx.execute(
20        cmd,
21        quiet = True,
22        working_directory = "workspace",
23    )
24    _log_cargo_vendor(repository_ctx, result)
25    if result.return_code != 0:
26        fail("failed to execute `{}`".format(" ".join(cmd)))
27
28    # Copy lockfile back to third-party/Cargo.lock to reflect any modification
29    # performed by Cargo.
30    _copy_file(repository_ctx, src = root_lockfile, dst = vendor_lockfile)
31
32    # Produce a token for third_party_glob to depend on so that the necessary
33    # sequencing is visible to Bazel.
34    repository_ctx.file("BUILD", executable = False)
35    repository_ctx.file("vendor.bzl", "vendored = True", executable = False)
36
37def _copy_file(repository_ctx, *, src, dst):
38    content = repository_ctx.read(src)
39    if not dst.exists or content != repository_ctx.read(dst):
40        repository_ctx.file(dst, content = content, executable = False)
41
42def _log_cargo_vendor(repository_ctx, result):
43    relevant = ""
44    for line in result.stderr.splitlines(True):
45        if line.strip() and not line.startswith("To use vendored sources,"):
46            relevant += line
47    if relevant:
48        # Render it as command output.
49        # If we just use print(), Bazel will cache and repeat the output even
50        # when not rerunning the command.
51        print = ["echo", relevant]
52        repository_ctx.execute(print, quiet = False)
53
54vendor = repository_rule(
55    doc = "A rule used to vendor the dependencies of a crate in the current workspace",
56    attrs = {
57        "lockfile": attr.label(
58            doc = "A lockfile providing the set of crates to vendor",
59        ),
60    },
61    local = True,
62    implementation = _impl,
63)
64