Prerequisites

What You'll Learn

What if I get stuck?

The final result of running through this codelab can be found here for reference. If you really get stuck you can find us on gitter!

The easiest way to get started is from an existing Go module:

$ mkdir getting_started_go && cd getting_started_go
$ go mod init example_module
$ plz init --no_prompt

A note about your Please PATH

Please doesn't use your host system's PATH variable. If where you installed Go isn't in this default path, you will need to add the following to .plzconfig:

[build]
path = $YOUR_GO_INSTALL_HERE:/usr/local/bin:/usr/bin:/bin

You can find out where Go is installed with dirname $(which go).

So what just happened?

You will see this has created a number of files in your working folder:

$ tree -a
  .
  ├── go.mod
  ├── pleasew
  └── .plzconfig

The go.mod file was generated by go and contains information about the Go module. While Please doesn't directly use this file, it can be useful for integrating your project with the Go ecosystem and IDEs. You may remove it if you wish.

The pleasew script is a wrapper script that will automatically install Please if it's not already! This means Please projects are portable and can always be built via git clone https://... example_module && cd example_module && ./pleasew build.

Finally, .plzconfig contains the project configuration for Please. Please will have picked up the module's import path and added it to your config:

.plzconfig

[go]
importpath = example_module

Read the config documentation for more information on configuration.

Now we have a Please project, it's time to start adding some code to it! Let's create a "hello world" Go program:

src/main.go

package main

func main(){
  println("Hello, world!")
}

We now need to tell Please about our Go code. Please projects define metadata about the targets that are available to be built in BUILD files. Let's create a BUILD file to build this program:

src/BUILD

go_binary(
  name = "main",
  srcs = ["main.go"],
)

That's it! You can now run this with:

$ plz run //src:main
Hello, world!

There's a lot going on here; first off, go_binary() is one of many built-in functions. This build function creates a "build target" in the src package. A package, in the Please sense, is any directory that contains a BUILD file.

Each build target can be identified by a build label in the format //path/to/package:label, i.e. //src:main. There are a number of things you can do with a build target such as plz build //src:main, however, as you've seen, if the target is a binary, you may run it with plz run.

Let's add a src/greetings package to our Go project:

src/greetings/greetings.go

package greetings

import (
    "math/rand"
)

var greetings = []string{
    "Hello",
    "Bonjour",
    "Marhabaan",
}

func Greeting() string {
  return greetings[rand.Intn(len(greetings))]
}

We then need to tell Please how to compile this library:

src/greetings/BUILD

go_library(
    name = "greetings",
    srcs = ["greetings.go"],
    visibility = ["//src/..."],
)

NB: Unlike many popular build systems, Please doesn't just have one metadata file in the root of the project. Please will typically have one BUILD file per "compilation unit", which in Go terms means one per package. It's not uncommon to see a BUILD file in every folder of your project.

We can then build it like so:

$ plz build //src/greetings
Build finished; total time 290ms, incrementality 50.0%. Outputs:
//src/greetings:greetings:
  plz-out/gen/src/greetings/greetings.a

Here we can see that the output of a go_library rule is a .a file which is stored in plz-out/gen/src/greetings/greetings.a. This is a static library archive representing the compiled output of our package.

We have also provided a visibility list to this rule. This is used to control where this go_library() rule can be used within our project. In this case, any rule under src, denoted by the ... syntax.

NB: This syntax can also be used on the command line e.g. plz build //src/...

src/BUILD

go_binary(
    name = "main",
    srcs = ["main.go"],
    # NB: if the package and rule name are the same, you may omit the name i.e. this could be just //src/greetings
    deps = ["//src/greetings:greetings"],
)

You can see we use a build label to refer to another rule here. Please will make sure that this rule is built before making its outputs available to our rule here.

Then update src/main.go:

src/main.go

package main

import (
    "fmt"

    "example_module/src/greetings"
)

func main(){
    fmt.Printf("%s, world!\n", greetings.Greeting())
}

Give it a whirl:

$ plz run //src:main
Bonjour, world!

Let's create a very simple test for our library:

src/greetings/greetings_test.go

package greetings

import "testing"

func TestGreeting(t *testing.T) {
    if Greeting() == "" {
        panic("Greeting failed to produce a result")
    }
}

We then need to tell Please about our tests:

src/greetings/BUILD

go_library(
    name = "greetings",
    srcs = ["greetings.go"],
    visibility = ["//src/..."],
)

go_test(
    name = "greetings_test",
    srcs = ["greetings_test.go"],
    # Here we have used the shorthand `:greetings` label format. This format can be used to refer to a rule in the same
    # package and is shorthand for `//src/greetings:greetings`.
    deps = [":greetings"],
)

We've used go_test(). This is a special build rule that is considered a test. These rules can be executed as such:

$ plz test //src/...
//src/greetings:greetings_test 1 test run in 3ms; 1 passed
1 test target and 1 test run in 3ms; 1 passed. Total time 90ms.

Please will run all the tests it finds under //src/..., and aggregate the results up. This works even across languages allowing you to test your whole project with a single command.

External tests

Go has a concept of "external" tests. This means that tests can exist in the same folder as the production code, but they have a different package. Please supports this through the external = True argument on go_test():

src/greetings/greetings_test.go

package greetings_test

import (
	"testing"

    // We now need to import the "production" package
	"example_module/src/greetings"
)

func TestGreeting(t *testing.T) {
    if greetings.Greeting() == "" {
        panic("Greeting failed to produce a result")
    }
}

src/greetings/BUILD

go_library(
    name = "greetings",
    srcs = ["greetings.go"],
    visibility = ["//src/..."],
)

go_test(
    name = "greetings_test",
    srcs = ["greetings_test.go"],
    deps = [":greetings"],
    external = True,
)

Check if it works:

$ plz test //src/...
//src/greetings:greetings_test 1 test run in 3ms; 1 passed
  1 test target and 1 test run in 3ms; 1 passed. Total time 90ms.

Using go_get()

Eventually, most projects need to depend on third-party code. Let's include go-playground's basic assertion library. Conventionally, third-party dependencies live under //third_party/... (although they don't have to), so let's create that package:

third_party/go/BUILD

package(default_visibility = ["PUBLIC"])

go_get(
    name = "assert",
    get = "github.com/go-playground/assert",
    revision = "v1.2.1",
)

This will then download and compile github.com/go-playground/assert for us. We use the package() built-in function to set the default visibility for this package. This can be very useful for third-party rules to avoid having to specify visibility = ["PUBLIC"] on every go_get() invocation.

NB: The visibility "PUBLIC" is a special case. Typically, items in the visibility list are labels. "PUBLIC" is equivalent to //....

Updating our tests

We can now use this library in our tests:

src/greetings/greetings_test.go

package greetings_test

import (
    "testing"

    "github.com/go-playground/assert"

    "example_module/src/greetings"
)

func TestGreeting(t *testing.T) {
    assert.NotEqual(t, greetings.Greeting(), "")
}

src/greetings/BUILD

go_library(
    name = "greetings",
    srcs = ["greetings.go"],
    visibility = ["//src/..."],
)

go_test(
    name = "greetings_test",
    srcs = ["greetings_test.go"],
    deps = [
        ":greetings",
        "//third_party/go:assert",
    ],
    external = True,
)

Hopefully you now have an idea as to how to build Go with Please. Please is capable of so much more though!

Otherwise, why not try one of the other codelabs!