Skip to content

Instantly share code, notes, and snippets.

@dragonsinth
Created September 22, 2022 15:53
Show Gist options
  • Save dragonsinth/5204cee53950b490c6e646b4da70343b to your computer and use it in GitHub Desktop.
Save dragonsinth/5204cee53950b490c6e646b4da70343b to your computer and use it in GitHub Desktop.
Generate local repository rules from a go.work file that uses on-disk replacements
///usr/bin/env true; exec /usr/bin/env go run "$0" "$@"
package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
"github.com/bazelbuild/bazel-gazelle/label"
)
var (
// Change these for your own setup
includedPrefixes = []string{"./go/src"}
excludePrefixes = []string{"./go/src/fs"}
fileTemplates = map[string]*template.Template{
"BUILD.bazel": template.Must(template.New("").Parse(`
# gazelle:proto disable_global
# gazelle:go_naming_convention import_alias
# gazelle:prefix {{.Pkg}}
`)),
"WORKSPACE": template.Must(template.New("").Parse(`workspace(
name = "{{.Label}}",
)
`)),
}
localRepositoryTemplate = template.Must(template.New("").Parse(`# local_repositories installs the necessary on-disk third party repos we reference when building Go code.
def local_repositories():
{{range $i, $e := .}}{{if $i}}
{{end -}}
{{" "}}# gazelle:repository go_repository name={{.Label}} importpath={{.Pkg}}
native.local_repository(
name = "{{.Label}}",
path = "{{.Path}}",
)
{{end -}}
`))
)
func main() {
if err := run(); err != nil {
log.Println(err)
os.Exit(1)
}
}
func run() error {
// build a gazelle binary in the root repository
cmd := exec.Command("bazel", "build", "@bazel_gazelle//cmd/gazelle")
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to build gazelle: %w", err)
}
gazelleBinary, err := filepath.Abs("./bazel-bin/external/bazel_gazelle/cmd/gazelle/gazelle_/gazelle")
if err != nil {
return fmt.Errorf("failed to resolve gazelle binary: %w", err)
}
// stupidly parse go.work
buf, err := os.ReadFile("go.work")
if err != nil {
return fmt.Errorf("failed to read go.work: %w", err)
}
var paths []string
for _, line := range strings.Split(string(buf), "\n") {
if strings.HasPrefix(line, "\t./go/src/") {
paths = append(paths, strings.TrimPrefix(line, "\t"))
}
}
var repos []repo
for _, path := range paths {
if excluded(path) || !included(path) {
log.Printf("skipping %s", path)
continue
}
pkg := strings.TrimPrefix(path, "./go/src/")
r := repo{
Pkg: pkg,
Label: label.ImportPathToBazelRepoName(pkg),
Path: path,
}
repos = append(repos, r)
log.Printf("setting up %s", r.Pkg)
if err := r.Setup(gazelleBinary); err != nil {
return fmt.Errorf("setting up %s: %w", r.Pkg, err)
}
}
const localPath = "local_repositories.bzl"
out, err := os.Create(localPath)
defer out.Close()
if err != nil {
return fmt.Errorf("could not open: %s: %w", localPath, err)
}
if err := localRepositoryTemplate.Execute(out, repos); err != nil {
return fmt.Errorf("could not write: %s: %w", localPath, err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("could not close: %s: %w", localPath, err)
}
return nil
}
type repo struct {
Pkg string
Label string
Path string
}
func (r repo) Setup(gazelleBinary string) error {
// Emit files
for tmplName, t := range fileTemplates {
fileName := filepath.Join(r.Path, tmplName)
if fi, _ := os.Stat(fileName); fi != nil {
continue // don't overwrite existing files
}
out, err := os.Create(fileName)
if err != nil {
return fmt.Errorf("could not open: %s: %w", fileName, err)
}
if err := t.Execute(out, &r); err != nil {
return fmt.Errorf("could not write: %s: %w", fileName, err)
}
if err := out.Close(); err != nil {
return fmt.Errorf("could not close: %s: %w", fileName, err)
}
}
// must exist on path
cmd := exec.Command(gazelleBinary)
cmd.Dir = r.Path
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run gazelle: %w", err)
}
return nil
}
func included(path string) bool {
for _, pre := range includedPrefixes {
if strings.HasPrefix(path, pre) {
return true
}
}
return false
}
func excluded(path string) bool {
for _, pre := range excludePrefixes {
if strings.HasPrefix(path, pre) {
return true
}
}
return false
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment