Third-party dependencies

Sooner or later, most projects end up needing a dependency on some third-party libraries, and one of the jobs of the build system is to manage those as well. Please is no exception to this, but it bears a little discussion since most systems handle this differently.

In Please, third-party dependencies can be created in any BUILD file and manipulated as any other build rule. We encourage methods of fetching them that are repeatable; typically each language has one that matches up to a common package manager, for example:

Each of these require explicit declarations of all their dependencies in the BUILD file; this is how we pin dependencies & guarantee reproducibility.
There are one or two alternatives that show slightly different approaches (e.g. python_wheel which is more standalone, and remote_file which is a general tool to download anything (although often more work is required to actually build it)

The typical idiom we use is to place BUILD files under a third_party directory, to make it clear where they're coming from. Commonly we separate them by language for multi-language repos as well.
See third_party/go, third_party/python and third_party/java in Please's repo for some examples of what these look like.

There's no explicit command to download third-party dependencies (e.g. plz fetch or similar). Dependencies are built as part of the build process along with everything else, so their downloads can be parallelised with compiling other targets.


Please also supports a concept called "subrepos" which allows fetching arbitrary dependencies and attaching build rules to them. These can be depended on from other build rules and generally used as normal.

Subrepos are defined using builtins like http_archive or github_repo. These download a remote file and extract it, and make the contents available to other rules. In most cases you can choose to attach a BUILD file to them, but it can also use an existing one if appropriate.

For example (as seen in the Please repo):

        name = "gtest",
        bazel_compat = True,
        repo = "google/googletest",
        revision = "release-1.8.1",

Rules within subrepos can be referenced using a triple-slash prefix on rules, anywhere where a build rule would normally be accepted. For example:

        name = "my_test",
        deps = [

Note that the subrepo label (third_party/cc/gtest) includes the package we defined it in earlier. In many ways subrepos mirror the feature in Bazel, but in this case are more flexible since they're not limited to being defined at the repo root. For compatibility, we also accept an @ prefix for subrepos instead of ///.

Comparison to other systems

For users familiar with Bazel, we expect that writing BUILD files won't be challenging, the main difference being that there is no direct equivalent to Bazel's WORKSPACE file. As mentioned above, third-party dependencies can occur wherever you choose to put them in normal BUILD files.

If you've used Buck before, the model is pretty similar to fetching Maven jars using the bucklet for it. This is not entirely coincidental since we were previously using Buck so initially Please had to mimic the same interface - but we also quite liked it and decided to keep on in the same way.

If you're coming from Gradle or Maven, it's a little more alien due to being less language-specific and requiring full transitive dependencies to be specified.
There is an add-on rule which is the closest equivalent; it works by communicating with Maven repositories to find dependencies and generating more BUILD rules for them. This can be a little unreliable though, since the Maven package format is complex, and your dependencies aren't fully within your control and can change between builds - we recommend maven_jar instead, but understand it's more work to set up.

requirements.txt files from Python are not usually especially difficult to translate using pip_library; again we require listing transitive dependencies explicitly, but this is not normally too onerous for Python.
Since Please needs to know precisely what will be output, the rules can sometimes need a little tweaking when the output names don't correspond to the package names (or often a package outputs a single .py file instead of a directory).

go_get works pretty similarly to the usual go get tool, but again only outputs a single package at a time. Writing up the dependencies can be eased by using something like go list -f '{{.Deps}}' <package> to discover all the dependencies for the package in question.