Skip to content

Commit

Permalink
v5: repository rule and toolchains
Browse files Browse the repository at this point in the history
  • Loading branch information
jayconrod committed Nov 9, 2019
1 parent d076254 commit 36d41b0
Show file tree
Hide file tree
Showing 41 changed files with 2,005 additions and 6 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ version is documented in a series of blog posts at
* **v4**: Moves most of the implementation out of Starlark into a
"builder" binary. Described in
[Moving logic to execution](https://jayconrod.com/posts/109/writing-bazel-rules--moving-logic-to-execution).
* **v5**: Downloads the Go distribution and registers a Bazel toolchain.
Described in
[Repository rules](https://jayconrod.com/posts/110/writing-bazel-rules--repository-rules).
51 changes: 45 additions & 6 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
# The WORKSPACE file should appear in the root directory of the repository.
# It's job is to configure external repositories, which are declared
# with repository rules. This file is only evaluated for builds in *this*
# repository, not for builds in other repositories that depend on this one.
# For this reason, we declare dependencies in a macro that can be loaded
# here *and* in other repositories' WORKSPACE files.
# Its job is to configure external repositories, which are declared
# with repository rules. We also register toolchains here.
#
# This file is only evaluated for builds in *this* repository, not for builds in
# other repositories that depend on this one.

# Each workspace should set a canonical name. This is the name other workspaces
# may use to import it (via an http_archive rule or something similar).
# It's also the name used in labels that refer to this workspace
# (for example @rules_go_simple//v5:deps.bzl).
workspace(name = "rules_go_simple")

load("@rules_go_simple//v4:deps.bzl", "go_rules_dependencies")
load(
"@rules_go_simple//v5:deps.bzl",
"go_download",
"go_rules_dependencies",
)

# go_rules_dependencies declares the dependencies of rules_go_simple. Any
# project that depends on rules_go_simple should call this.
go_rules_dependencies()

# These rules download Go distributions for macOS and Linux.
# They are lazily evaluated, so they won't actually download anything until
# we depend on a target inside these workspaces. We register toolchains
# below though, so that forces both downloads. We could be more clever
# about registering only the toolchain we need.
go_download(
name = "go_darwin_amd64",
goarch = "amd64",
goos = "darwin",
sha256 = "a9088c44a984c4ba64179619606cc65d9d0cb92988012cfc94fbb29ca09edac7",
urls = ["https://dl.google.com/go/go1.13.4.darwin-amd64.tar.gz"],
)

go_download(
name = "go_linux_amd64",
goarch = "amd64",
goos = "linux",
sha256 = "692d17071736f74be04a72a06dab9cac1cd759377bd85316e52b2227604c004c",
urls = ["https://dl.google.com/go/go1.13.4.linux-amd64.tar.gz"],
)

# register_toolchains makes one or more toolchain rules available for Bazel's
# automatic toolchain selection. Bazel will pick whichever toolchain is
# compatible with the execution and target platforms.
register_toolchains(
"@go_darwin_amd64//:toolchain",
"@go_linux_amd64//:toolchain",
)
8 changes: 8 additions & 0 deletions v5/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# toolchain_type defines a name for a kind of toolchain. Our toolchains
# declare that they have this type. Our rules request a toolchain of this type.
# Bazel selects a toolchain of the correct type that satisfies platform
# constraints from among registered toolchains.
toolchain_type(
name = "toolchain_type",
visibility = ["//visibility:public"],
)
29 changes: 29 additions & 0 deletions v5/def.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright Jay Conrod. All rights reserved.

# This file is part of rules_go_simple. Use of this source code is governed by
# the 3-clause BSD license that can be found in the LICENSE.txt file.

# def.bzl contains public definitions that may be used by Bazel projects for
# building Go programs. These definitions should be loaded from here and
# not any internal directory.

load(
"//v5/internal:rules.bzl",
_go_binary = "go_binary",
_go_library = "go_library",
_go_test = "go_test",
)
load(
"//v5/internal:providers.bzl",
_GoLibrary = "GoLibrary",
)
load(
"//v5/internal:toolchain.bzl",
_go_toolchain = "go_toolchain",
)

go_binary = _go_binary
go_library = _go_library
go_test = _go_test
go_toolchain = _go_toolchain
GoLibrary = _GoLibrary
35 changes: 35 additions & 0 deletions v5/deps.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright Jay Conrod. All rights reserved.

# This file is part of rules_go_simple. Use of this source code is governed by
# the 3-clause BSD license that can be found in the LICENSE.txt file.

# deps.bzl exports public definitions from v5 of these rules. Later versions
# require newer, incompatible versions of Skylib, so they have their own
# deps.bzl files. Clients should load the deps.bzl for whichever version
# they need.

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("//v5/internal:repo.bzl", _go_download = "go_download")

go_download = _go_download

def go_rules_dependencies():
"""Declares external repositories that rules_go_simple depends on. This
function should be loaded and called from WORKSPACE files."""

# bazel_skylib is a set of libraries that are useful for writing
# Bazel rules. We use it to handle quoting arguments in shell commands.
_maybe(
http_archive,
name = "bazel_skylib",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz",
],
sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44",
)

def _maybe(rule, name, **kwargs):
"""Declares an external repository if it hasn't been declared already."""
if name not in native.existing_rules():
rule(name = name, **kwargs)
1 change: 1 addition & 0 deletions v5/internal/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty build file, just here to define a package.
55 changes: 55 additions & 0 deletions v5/internal/BUILD.dist.bazel.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This template is used by go_download to generate a build file for
# a downloaded Go distribution.

load("@rules_go_simple//v5:def.bzl", "go_toolchain")
load("@rules_go_simple//v5/internal:rules.bzl", "go_tool_binary")

# tools contains executable files that are part of the toolchain.
filegroup(
name = "tools",
srcs = ["bin/go{exe}"] + glob(["pkg/tool/{goos}_{goarch}/**"]),
visibility = ["//visibility:public"],
)

# std_pkgs contains packages in the standard library.
filegroup(
name = "std_pkgs",
srcs = glob(
["pkg/{goos}_{goarch}/**"],
exclude = ["pkg/{goos}_{goarch}/cmd/**"],
),
visibility = ["//visibility:public"],
)

# builder is an executable used by rules_go_simple to perform most actions.
# builder mostly acts as a wrapper around the compiler and linker.
go_tool_binary(
name = "builder",
srcs = ["@rules_go_simple//v5/internal/builder:builder_srcs"],
std_pkgs = [":std_pkgs"],
tools = [":tools"],
)

# toolchain_impl gathers information about the Go toolchain.
# See the GoToolchain provider.
go_toolchain(
name = "toolchain_impl",
builder = ":builder",
std_pkgs = [":std_pkgs"],
tools = [":tools"],
)

# toolchain is a Bazel toolchain that expresses execution and target
# constraints for toolchain_impl. This target should be registered by
# calling register_toolchains in a WORKSPACE file.
toolchain(
name = "toolchain",
exec_compatible_with = [
{exec_constraints},
],
target_compatible_with = [
{target_constraints},
],
toolchain = ":toolchain_impl",
toolchain_type = "@rules_go_simple//v5:toolchain_type",
)
124 changes: 124 additions & 0 deletions v5/internal/actions.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright Jay Conrod. All rights reserved.

# This file is part of rules_go_simple. Use of this source code is governed by
# the 3-clause BSD license that can be found in the LICENSE.txt file.

load("@bazel_skylib//lib:shell.bzl", "shell")

def go_compile(ctx, srcs, out, importpath = "", deps = []):
"""Compiles a single Go package from sources.
Args:
ctx: analysis context.
srcs: list of source Files to be compiled.
out: output .a File.
importpath: the path other libraries may use to import this package.
deps: list of GoLibrary objects for direct dependencies.
"""
toolchain = ctx.toolchains["@rules_go_simple//v5:toolchain_type"]

args = ctx.actions.args()
args.add("compile")
args.add("-stdimportcfg", toolchain.internal.stdimportcfg)
dep_infos = [d.info for d in deps]
args.add_all(dep_infos, before_each = "-arc", map_each = _format_arc)
if importpath:
args.add("-p", importpath)
args.add("-o", out)
args.add_all(srcs)

inputs = (srcs +
[dep.info.archive for dep in deps] +
[toolchain.internal.stdimportcfg] +
toolchain.internal.tools +
toolchain.internal.std_pkgs)
ctx.actions.run(
outputs = [out],
inputs = inputs,
executable = toolchain.internal.builder,
arguments = [args],
env = toolchain.internal.env,
mnemonic = "GoCompile",
)

def go_link(ctx, out, main, deps = []):
"""Links a Go executable.
Args:
ctx: analysis context.
out: output executable file.
main: archive file for the main package.
deps: list of GoLibrary objects for direct dependencies.
"""
toolchain = ctx.toolchains["@rules_go_simple//v5:toolchain_type"]

transitive_deps = depset(
direct = [d.info for d in deps],
transitive = [d.deps for d in deps],
)
inputs = ([main, toolchain.internal.stdimportcfg] +
[d.archive for d in transitive_deps.to_list()] +
toolchain.internal.tools +
toolchain.internal.std_pkgs)

args = ctx.actions.args()
args.add("link")
args.add("-stdimportcfg", toolchain.internal.stdimportcfg)
args.add_all(transitive_deps, before_each = "-arc", map_each = _format_arc)
args.add("-main", main)
args.add("-o", out)

ctx.actions.run(
outputs = [out],
inputs = inputs,
executable = toolchain.internal.builder,
arguments = [args],
env = toolchain.internal.env,
mnemonic = "GoLink",
)

def go_build_test(ctx, srcs, deps, out, rundir = "", importpath = ""):
"""Compiles and links a Go test executable.
Args:
ctx: analysis context.
srcs: list of source Files to be compiled.
deps: list of GoLibrary objects for direct dependencies.
out: output executable file.
importpath: import path of the internal test archive.
rundir: directory the test should change to before executing.
"""
toolchain = ctx.toolchains["@rules_go_simple//v5:toolchain_type"]
direct_dep_infos = [d.info for d in deps]
transitive_dep_infos = depset(transitive = [d.deps for d in deps]).to_list()
inputs = (srcs +
[toolchain.internal.stdimportcfg] +
[d.archive for d in direct_dep_infos] +
[d.archive for d in transitive_dep_infos] +
toolchain.internal.tools +
toolchain.internal.std_pkgs)

args = ctx.actions.args()
args.add("test")
args.add("-stdimportcfg", toolchain.internal.stdimportcfg)
args.add_all(direct_dep_infos, before_each = "-direct", map_each = _format_arc)
args.add_all(transitive_dep_infos, before_each = "-transitive", map_each = _format_arc)
if rundir != "":
args.add("-dir", rundir)
if importpath != "":
args.add("-p", importpath)
args.add("-o", out)
args.add_all(srcs)

ctx.actions.run(
outputs = [out],
inputs = inputs,
executable = toolchain.internal.builder,
arguments = [args],
env = toolchain.internal.env,
mnemonic = "GoTest",
)

def _format_arc(lib):
"""Formats a GoLibrary.info object as an -arc argument"""
return "{}={}".format(lib.importpath, lib.archive.path)
17 changes: 17 additions & 0 deletions v5/internal/builder/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# builder_srcs is a list of source files used to create the builder binary.
# Each Go distribution will create its own builder with these source files,
# using its own compiler, linker, and standard library.
filegroup(
name = "builder_srcs",
srcs = [
"builder.go",
"compile.go",
"env.go",
"flags.go",
"importcfg.go",
"link.go",
"sourceinfo.go",
"test.go",
],
visibility = ["//visibility:public"],
)
44 changes: 44 additions & 0 deletions v5/internal/builder/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright Jay Conrod. All rights reserved.

// This file is part of rules_go_simple. Use of this source code is governed by
// the 3-clause BSD license that can be found in the LICENSE.txt file.

// builder is a tool used to perform various tasks related to building Go code,
// such as compiling packages, linking executables, and generating
// test sources.
package main

import (
"log"
"os"
)

func main() {
log.SetFlags(0)
log.SetPrefix("builder: ")
if len(os.Args) <= 2 {
log.Fatalf("usage: %s stdimportcfg|compile|link|test options...", os.Args[0])
}
verb := os.Args[1]
args := os.Args[2:]

var action func(args []string) error
switch verb {
case "stdimportcfg":
action = stdImportcfg
case "compile":
action = compile
case "link":
action = link
case "test":
action = test
default:
log.Fatalf("unknown action: %s", verb)
}
log.SetPrefix(verb + ": ")

err := action(args)
if err != nil {
log.Fatal(err)
}
}
Loading

0 comments on commit 36d41b0

Please sign in to comment.