diff --git a/go/tools/bazel/runfiles/BUILD.bazel b/go/tools/bazel/runfiles/BUILD.bazel new file mode 100644 index 0000000000..5946fd0d17 --- /dev/null +++ b/go/tools/bazel/runfiles/BUILD.bazel @@ -0,0 +1,53 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "runfiles", + srcs = [ + "directory.go", + "fs.go", + "global.go", + "manifest.go", + "runfiles.go", + ], + importpath = "github.com/bazelbuild/rules_go/go/tools/bazel/runfiles", + visibility = ["//visibility:public"], +) + +go_test( + name = "runfiles_test", + srcs = [ + "fs_test.go", + "runfiles_test.go", + ], + data = [ + "test.txt", + "//go/tools/bazel/runfiles/testprog", + ], + rundir = ".", + deps = [":runfiles"], +) + +exports_files( + ["test.txt"], + visibility = ["//go/tools/bazel/runfiles/testprog:__pkg__"], +) + +alias( + name = "go_default_library", + actual = ":runfiles", + visibility = ["//visibility:public"], +) diff --git a/go/tools/bazel/runfiles/directory.go b/go/tools/bazel/runfiles/directory.go new file mode 100644 index 0000000000..f5b241326c --- /dev/null +++ b/go/tools/bazel/runfiles/directory.go @@ -0,0 +1,32 @@ +// Copyright 2020, 2021, 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runfiles + +import "path/filepath" + +// Directory specifies the location of the runfiles directory. You can pass +// this as an option to New. If unset or empty, use the value of the +// environmental variable RUNFILES_DIR. +type Directory string + +func (d Directory) new() *Runfiles { + return &Runfiles{d, directoryVar + "=" + string(d)} +} + +func (d Directory) path(s string) (string, error) { + return filepath.Join(string(d), filepath.FromSlash(s)), nil +} + +const directoryVar = "RUNFILES_DIR" diff --git a/go/tools/bazel/runfiles/fs.go b/go/tools/bazel/runfiles/fs.go new file mode 100644 index 0000000000..d5ee3f8796 --- /dev/null +++ b/go/tools/bazel/runfiles/fs.go @@ -0,0 +1,98 @@ +// Copyright 2021, 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build go1.16 + +package runfiles + +import ( + "errors" + "io" + "io/fs" + "os" + "time" +) + +// Open implements fs.FS.Open. +func (r *Runfiles) Open(name string) (fs.File, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} + } + p, err := r.Path(name) + if errors.Is(err, ErrEmpty) { + return emptyFile(name), nil + } + if err != nil { + return nil, pathError("open", name, err) + } + return os.Open(p) +} + +// Stat implements fs.StatFS.Stat. +func (r *Runfiles) Stat(name string) (fs.FileInfo, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid} + } + p, err := r.Path(name) + if errors.Is(err, ErrEmpty) { + return emptyFileInfo(name), nil + } + if err != nil { + return nil, pathError("stat", name, err) + } + return os.Stat(p) +} + +// ReadFile implements fs.ReadFileFS.ReadFile. +func (r *Runfiles) ReadFile(name string) ([]byte, error) { + if !fs.ValidPath(name) { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid} + } + p, err := r.Path(name) + if errors.Is(err, ErrEmpty) { + return nil, nil + } + if err != nil { + return nil, pathError("open", name, err) + } + return os.ReadFile(p) +} + +type emptyFile string + +func (f emptyFile) Stat() (fs.FileInfo, error) { return emptyFileInfo(f), nil } +func (f emptyFile) Read([]byte) (int, error) { return 0, io.EOF } +func (emptyFile) Close() error { return nil } + +type emptyFileInfo string + +func (i emptyFileInfo) Name() string { return string(i) } +func (emptyFileInfo) Size() int64 { return 0 } +func (emptyFileInfo) Mode() fs.FileMode { return 0444 } +func (emptyFileInfo) ModTime() time.Time { return time.Time{} } +func (emptyFileInfo) IsDir() bool { return false } +func (emptyFileInfo) Sys() interface{} { return nil } + +func pathError(op, name string, err error) error { + if err == nil { + return nil + } + var rerr Error + if errors.As(err, &rerr) { + // Unwrap the error because we don’t need the failing name + // twice. + return &fs.PathError{Op: op, Path: rerr.Name, Err: rerr.Err} + } + return &fs.PathError{Op: op, Path: name, Err: err} +} diff --git a/go/tools/bazel/runfiles/fs_test.go b/go/tools/bazel/runfiles/fs_test.go new file mode 100644 index 0000000000..c2a03a405d --- /dev/null +++ b/go/tools/bazel/runfiles/fs_test.go @@ -0,0 +1,96 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build go1.16 +// +build go1.16 + +package runfiles_test + +import ( + "io" + "io/fs" + "os" + "path/filepath" + "testing" + "testing/fstest" + + "github.com/bazelbuild/rules_go/go/tools/bazel/runfiles" +) + +func TestFS(t *testing.T) { + fsys, err := runfiles.New() + if err != nil { + t.Fatal(err) + } + // Ensure that the Runfiles object implements FS interfaces. + var _ fs.FS = fsys + var _ fs.StatFS = fsys + var _ fs.ReadFileFS = fsys + if err := fstest.TestFS(fsys, "io_bazel_rules_go/go/tools/bazel/runfiles/test.txt", "io_bazel_rules_go/go/tools/bazel/runfiles/testprog/testprog"); err != nil { + t.Error(err) + } +} + +func TestFS_empty(t *testing.T) { + dir := t.TempDir() + manifest := filepath.Join(dir, "manifest") + if err := os.WriteFile(manifest, []byte("__init__.py \n"), 0600); err != nil { + t.Fatal(err) + } + fsys, err := runfiles.New(runfiles.ManifestFile(manifest), runfiles.ProgramName("/invalid"), runfiles.Directory("/invalid")) + if err != nil { + t.Fatal(err) + } + t.Run("Open", func(t *testing.T) { + fd, err := fsys.Open("__init__.py") + if err != nil { + t.Fatal(err) + } + defer fd.Close() + got, err := io.ReadAll(fd) + if err != nil { + t.Error(err) + } + if len(got) != 0 { + t.Errorf("got nonempty contents: %q", got) + } + }) + t.Run("Stat", func(t *testing.T) { + got, err := fsys.Stat("__init__.py") + if err != nil { + t.Fatal(err) + } + if got.Name() != "__init__.py" { + t.Errorf("Name: got %q, want %q", got.Name(), "__init__.py") + } + if got.Size() != 0 { + t.Errorf("Size: got %d, want %d", got.Size(), 0) + } + if !got.Mode().IsRegular() { + t.Errorf("IsRegular: got %v, want %v", got.Mode().IsRegular(), true) + } + if got.IsDir() { + t.Errorf("IsDir: got %v, want %v", got.IsDir(), false) + } + }) + t.Run("ReadFile", func(t *testing.T) { + got, err := fsys.ReadFile("__init__.py") + if err != nil { + t.Error(err) + } + if len(got) != 0 { + t.Errorf("got nonempty contents: %q", got) + } + }) +} diff --git a/go/tools/bazel/runfiles/global.go b/go/tools/bazel/runfiles/global.go new file mode 100644 index 0000000000..c049482c0e --- /dev/null +++ b/go/tools/bazel/runfiles/global.go @@ -0,0 +1,60 @@ +// Copyright 2020, 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runfiles + +import "sync" + +// Path returns the absolute path name of a runfile. The runfile name must be +// a relative path, using the slash (not backslash) as directory separator. If +// the runfiles manifest maps s to an empty name (indicating an empty runfile +// not present in the filesystem), Path returns an error that wraps ErrEmpty. +func Path(s string) (string, error) { + r, err := g.get() + if err != nil { + return "", err + } + return r.Path(s) +} + +// Env returns additional environmental variables to pass to subprocesses. +// Each element is of the form “key=value”. Pass these variables to +// Bazel-built binaries so they can find their runfiles as well. See the +// Runfiles example for an illustration of this. +// +// The return value is a newly-allocated slice; you can modify it at will. +func Env() ([]string, error) { + r, err := g.get() + if err != nil { + return nil, err + } + return r.Env(), nil +} + +type global struct { + once sync.Once + r *Runfiles + err error +} + +func (g *global) get() (*Runfiles, error) { + g.once.Do(g.init) + return g.r, g.err +} + +func (g *global) init() { + g.r, g.err = New() +} + +var g global diff --git a/go/tools/bazel/runfiles/manifest.go b/go/tools/bazel/runfiles/manifest.go new file mode 100644 index 0000000000..d135417074 --- /dev/null +++ b/go/tools/bazel/runfiles/manifest.go @@ -0,0 +1,82 @@ +// Copyright 2020, 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runfiles + +import ( + "bufio" + "fmt" + "os" + "path" + "path/filepath" + "strings" +) + +// ManifestFile specifies the location of the runfile manifest file. You can +// pass this as an option to New. If unset or empty, use the value of the +// environmental variable RUNFILES_MANIFEST_FILE. +type ManifestFile string + +func (f ManifestFile) new() (*Runfiles, error) { + m, err := f.parse() + if err != nil { + return nil, err + } + return &Runfiles{m, manifestFileVar + "=" + string(f)}, nil +} + +type manifest map[string]string + +func (f ManifestFile) parse() (manifest, error) { + r, err := os.Open(string(f)) + if err != nil { + return nil, fmt.Errorf("runfiles: can’t open manifest file: %w", err) + } + defer r.Close() + s := bufio.NewScanner(r) + m := make(manifest) + for s.Scan() { + fields := strings.SplitN(s.Text(), " ", 2) + if len(fields) != 2 || fields[0] == "" { + return nil, fmt.Errorf("runfiles: bad manifest line %q in file %s", s.Text(), f) + } + m[fields[0]] = filepath.FromSlash(fields[1]) + } + if err := s.Err(); err != nil { + return nil, fmt.Errorf("runfiles: error parsing manifest file %s: %w", f, err) + } + return m, nil +} + +func (m manifest) path(s string) (string, error) { + r, ok := m[s] + if ok && r == "" { + return "", ErrEmpty + } + if ok { + return r, nil + } + // If path references a runfile that lies under a directory that itself is a + // runfile, then only the directory is listed in the manifest. Look up all + // prefixes of path in the manifest. + for prefix := s; prefix != ""; prefix, _ = path.Split(prefix) { + prefix = strings.TrimSuffix(prefix, "/") + if prefixMatch, ok := m[prefix]; ok { + return prefixMatch + strings.TrimPrefix(s, prefix), nil + } + } + return "", os.ErrNotExist +} + +const manifestFileVar = "RUNFILES_MANIFEST_FILE" diff --git a/go/tools/bazel/runfiles/runfiles.go b/go/tools/bazel/runfiles/runfiles.go new file mode 100644 index 0000000000..ab0859d87c --- /dev/null +++ b/go/tools/bazel/runfiles/runfiles.go @@ -0,0 +1,180 @@ +// Copyright 2020, 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package runfiles provides access to Bazel runfiles. +// +// Usage +// +// This package has two main entry points, the global functions Path and Env, +// and the Runfiles type. +// +// Global functions +// +// For simple use cases that don’t require hermetic behavior, use the Path and +// Env functions to access runfiles. Use Path to find the filesystem location +// of a runfile, and use Env to obtain environmental variables to pass on to +// subprocesses. +// +// Runfiles type +// +// If you need hermetic behavior or want to change the runfiles discovery +// process, use New to create a Runfiles object. New accepts a few options to +// change the discovery process. Runfiles objects have methods Path and Env, +// which correspond to the package-level functions. On Go 1.16, *Runfiles +// implements fs.FS, fs.StatFS, and fs.ReadFileFS. +package runfiles + +import ( + "errors" + "fmt" + "os" + "path" + "strings" +) + +// Runfiles allows access to Bazel runfiles. Use New to create Runfiles +// objects; the zero Runfiles object always returns errors. See +// https://docs.bazel.build/skylark/rules.html#runfiles for some information on +// Bazel runfiles. +type Runfiles struct { + // We don’t need concurrency control since Runfiles objects are + // immutable once created. + impl runfiles + env string +} + +// New creates a given Runfiles object. By default, it uses os.Args and the +// RUNFILES_MANIFEST_FILE and RUNFILES_DIR environmental variables to find the +// runfiles location. This can be overwritten by passing some options. +func New(opts ...Option) (*Runfiles, error) { + var o options + for _, a := range opts { + a.apply(&o) + } + if o.program == "" { + o.program = ProgramName(os.Args[0]) + } + if o.manifest == "" { + o.manifest = ManifestFile(os.Getenv("RUNFILES_MANIFEST_FILE")) + } + if o.directory == "" { + o.directory = Directory(os.Getenv("RUNFILES_DIR")) + } + // See section “Runfiles discovery” in + // https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub. + if o.manifest != "" { + return o.manifest.new() + } + if o.directory != "" { + return o.directory.new(), nil + } + manifest := ManifestFile(o.program + ".runfiles_manifest") + if stat, err := os.Stat(string(manifest)); err == nil && stat.Mode().IsRegular() { + return manifest.new() + } + dir := Directory(o.program + ".runfiles") + if stat, err := os.Stat(string(dir)); err == nil && stat.IsDir() { + return dir.new(), nil + } + return nil, errors.New("runfiles: no runfiles found") +} + +// Path returns the absolute path name of a runfile. The runfile name must be +// a relative path, using the slash (not backslash) as directory separator. If +// r is the zero Runfiles object, Path always returns an error. If the +// runfiles manifest maps s to an empty name (indicating an empty runfile not +// present in the filesystem), Path returns an error that wraps ErrEmpty. +func (r *Runfiles) Path(s string) (string, error) { + // See section “Library interface” in + // https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub. + if s == "" { + return "", errors.New("runfiles: name may not be empty") + } + if path.IsAbs(s) { + return "", fmt.Errorf("runfiles: name %q may not be absolute", s) + } + if s != path.Clean(s) { + return "", fmt.Errorf("runfiles: name %q must be canonical", s) + } + if s == ".." || strings.HasPrefix(s, "../") { + return "", fmt.Errorf("runfiles: name %q may not contain a parent directory", s) + } + impl := r.impl + if impl == nil { + return "", errors.New("runfiles: uninitialized Runfiles object") + } + p, err := impl.path(s) + if err != nil { + return "", Error{s, err} + } + return p, nil +} + +// Env returns additional environmental variables to pass to subprocesses. +// Each element is of the form “key=value”. Pass these variables to +// Bazel-built binaries so they can find their runfiles as well. See the +// Runfiles example for an illustration of this. +// +// The return value is a newly-allocated slice; you can modify it at will. If +// r is the zero Runfiles object, the return value is nil. +func (r *Runfiles) Env() []string { + if r.env == "" { + return nil + } + return []string{r.env} +} + +// Option is an option for the New function to override runfiles discovery. +type Option interface { + apply(*options) +} + +// ProgramName is an Option that sets the program name. If not set, New uses +// os.Args[0]. +type ProgramName string + +// Error represents a failure to look up a runfile. +type Error struct { + // Runfile name that caused the failure. + Name string + + // Underlying error. + Err error +} + +// Error implements error.Error. +func (e Error) Error() string { + return fmt.Sprintf("runfile %q: %s", e.Name, e.Err.Error()) +} + +// Unwrap returns the underlying error, for errors.Unwrap. +func (e Error) Unwrap() error { return e.Err } + +// ErrEmpty indicates that a runfile isn’t present in the filesystem, but +// should be created as an empty file if necessary. +var ErrEmpty = errors.New("empty runfile") + +type options struct { + program ProgramName + manifest ManifestFile + directory Directory +} + +func (p ProgramName) apply(o *options) { o.program = p } +func (m ManifestFile) apply(o *options) { o.manifest = m } +func (d Directory) apply(o *options) { o.directory = d } + +type runfiles interface { + path(string) (string, error) +} diff --git a/go/tools/bazel/runfiles/runfiles_test.go b/go/tools/bazel/runfiles/runfiles_test.go new file mode 100644 index 0000000000..78fb1ce7e5 --- /dev/null +++ b/go/tools/bazel/runfiles/runfiles_test.go @@ -0,0 +1,140 @@ +// Copyright 2020, 2021, 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runfiles_test + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/bazelbuild/rules_go/go/tools/bazel/runfiles" +) + +func ExamplePath() { + path, err := runfiles.Path("io_bazel_rules_go/go/tools/bazel/runfiles/test.txt") + if err != nil { + panic(err) + } + b, err := os.ReadFile(path) + if err != nil { + panic(err) + } + fmt.Println(string(b)) + // Output: hi! +} + +func ExampleRunfiles() { + r, err := runfiles.New() + if err != nil { + panic(err) + } + // The binary “testprog” is itself built with Bazel, and needs + // runfiles. + rlocation := "io_bazel_rules_go/go/tools/bazel/runfiles/testprog/testprog" + if runtime.GOOS == "windows" { + rlocation = strings.ReplaceAll(rlocation, "/", "\\") + } + prog, err := r.Path(rlocation) + if err != nil { + panic(err) + } + cmd := exec.Command(prog) + // We add r.Env() after os.Environ() so that runfile environment + // variables override anything set in the process environment. + cmd.Env = append(os.Environ(), r.Env()...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + panic(err) + } + // Output: hi! +} + +func TestPath_errors(t *testing.T) { + r, err := runfiles.New() + if err != nil { + t.Fatal(err) + } + for _, s := range []string{"", "..", "../", "a/../b", "a//b", "a/./b", "/a"} { + t.Run(s, func(t *testing.T) { + if got, err := r.Path(s); err == nil { + t.Errorf("got %q, want error", got) + } + }) + } +} + +func TestRunfiles_zero(t *testing.T) { + var r runfiles.Runfiles + if got, err := r.Path("a"); err == nil { + t.Errorf("Path: got %q, want error", got) + } + if got := r.Env(); got != nil { + t.Errorf("Env: got %v, want nil", got) + } +} + +func TestRunfiles_empty(t *testing.T) { + dir := t.TempDir() + manifest := filepath.Join(dir, "manifest") + if err := os.WriteFile(manifest, []byte("__init__.py \n"), 0o600); err != nil { + t.Fatal(err) + } + r, err := runfiles.New(runfiles.ManifestFile(manifest)) + if err != nil { + t.Fatal(err) + } + _, got := r.Path("__init__.py") + want := runfiles.ErrEmpty + if !errors.Is(got, want) { + t.Errorf("Path for empty file: got error %q, want something that wraps %q", got, want) + } +} + +func TestRunfiles_manifestWithDir(t *testing.T) { + dir := t.TempDir() + manifest := filepath.Join(dir, "manifest") + if err := os.WriteFile(manifest, []byte("foo/dir path/to/foo/dir\n"), 0o600); err != nil { + t.Fatal(err) + } + r, err := runfiles.New(runfiles.ManifestFile(manifest)) + if err != nil { + t.Fatal(err) + } + for rlocation, want := range map[string]string{ + "foo/dir": "path/to/foo/dir", + "foo/dir/file": "path/to/foo/dir/file", + "foo/dir/deeply/nested/file": "path/to/foo/dir/deeply/nested/file", + } { + t.Run(rlocation, func(t *testing.T) { + if runtime.GOOS == "windows" { + rlocation = strings.ReplaceAll(rlocation, "/", "\\") + want = strings.ReplaceAll(want, "/", "\\") + } + got, err := r.Path(rlocation) + if err != nil { + t.Fatalf("Path failed: got unexpected error %q", err) + } + if got != want { + t.Errorf("Path failed: got %q, want %q", got, want) + } + }) + } +} diff --git a/go/tools/bazel/runfiles/test.txt b/go/tools/bazel/runfiles/test.txt new file mode 100644 index 0000000000..32aad8c353 --- /dev/null +++ b/go/tools/bazel/runfiles/test.txt @@ -0,0 +1 @@ +hi! diff --git a/go/tools/bazel/runfiles/testprog/BUILD.bazel b/go/tools/bazel/runfiles/testprog/BUILD.bazel new file mode 100644 index 0000000000..2e3058cf6f --- /dev/null +++ b/go/tools/bazel/runfiles/testprog/BUILD.bazel @@ -0,0 +1,31 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "testprog_lib", + srcs = ["main.go"], + importpath = "github.com/bazelbuild/rules_go/go/tools/bazel/runfiles/testprog", + visibility = ["//visibility:private"], + deps = ["//go/tools/bazel/runfiles"], +) + +go_binary( + name = "testprog", + out = "testprog", + data = ["//go/tools/bazel/runfiles:test.txt"], + embed = [":testprog_lib"], + visibility = ["//go/tools/bazel/runfiles:__pkg__"], +) diff --git a/go/tools/bazel/runfiles/testprog/main.go b/go/tools/bazel/runfiles/testprog/main.go new file mode 100644 index 0000000000..5bcd8f5db6 --- /dev/null +++ b/go/tools/bazel/runfiles/testprog/main.go @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "io/ioutil" + + "github.com/bazelbuild/rules_go/go/tools/bazel/runfiles" +) + +func main() { + path, err := runfiles.Path("io_bazel_rules_go/go/tools/bazel/runfiles/test.txt") + if err != nil { + panic(err) + } + b, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + fmt.Println(string(b)) +}