-
-
Notifications
You must be signed in to change notification settings - Fork 669
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
runfiles: port phst/runfiles to rules_go (#3205)
* runfiles: port phst/runfiles to rules_go Today rules_go provide //go/tools/bazel as the canonical runfiles library for binaries to be executed with `bazel test` and `bazel run`. However, the current implementation pre-date the recent changes in Bazel's upstream. Since then, all of the native runfiles library of Bash, Java, CPP, Python have been refactored to follow a certain convention in locating files. (1) Although these are subjected to change with the incoming BzlMod feature, it would be easier to maintain if we can keep rules_go's runfiles library implementation aligned with native languages' implementation. Today, it seems like https://github.com/phst/runfiles implemented exactly that. So with @fmeum suggestion and @phst permission (2), let's port the newer, more accurate implementation to rules_go. Future refactoring will mark the current exported APIs in //go/tools/bazel as deprecated and/or swapping out the old implementation underneath to use this newer package. Changes in this PR included: - Copy paste repository over - Removal of .git and .gitignore and .githooks dir - Removal of repository specific files: README, WORKSPACE - Rename BUILD to BUILD.bazel - Rename import path for both go and BUILD files - Run gazelle over the packages - Adjusted test cases to reflect new package paths - Removed godoc related to installation instruction - Fixed test to handle window path separator - Updated canonical path criterias - Replaced usages of 'path' with 'path/filepath' Note that some issues with windows still remain unresolved. (1): https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub (2): phst/runfiles#3 (comment) * Consistently take in / and emit \ on Windows * Fix runfiles tests on Windows * Add deprecation note to old `Runfiles` method Co-authored-by: Fabian Meumertzheim <[email protected]>
- Loading branch information
Showing
12 changed files
with
861 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
"@bazel_tools//tools/bash/runfiles", | ||
], | ||
deps = [":runfiles"], | ||
) | ||
|
||
exports_files( | ||
["test.txt"], | ||
visibility = ["//go/tools/bazel/runfiles/testprog:__pkg__"], | ||
) | ||
|
||
alias( | ||
name = "go_default_library", | ||
actual = ":runfiles", | ||
visibility = ["//visibility:public"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// 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" | ||
"runtime" | ||
"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 runtime.GOOS == "windows" { | ||
// Currently the result of | ||
// | ||
// fsys.Path("io_bazel_rules_go/go/tools/bazel/runfiles/test.txt") | ||
// fsys.Path("bazel_tools/tools/bash/runfiles/runfiles.bash") | ||
// fsys.Path("io_bazel_rules_go/go/tools/bazel/runfiles/testprog/testprog") | ||
// | ||
// would be a full path like these | ||
// | ||
// C:\b\bk-windows-1z0z\bazel\rules-go-golang\go\tools\bazel\runfiles\test.txt | ||
// C:\b\zslxztin\external\bazel_tools\tools\bash\runfiles\runfiles.bash | ||
// C:\b\pm4ep4b2\execroot\io_bazel_rules_go\bazel-out\x64_windows-fastbuild\bin\go\tools\bazel\runfiles\testprog\testprog | ||
// | ||
// Which does not follow any particular patter / rules. | ||
// This makes it very hard to define what we are looking for on Windows. | ||
// So let's skip this for now. | ||
return | ||
} | ||
|
||
expected1 := "io_bazel_rules_go/go/tools/bazel/runfiles/test.txt" | ||
expected2 := "io_bazel_rules_go/go/tools/bazel/runfiles/testprog/testprog_/testprog" | ||
expected3 := "bazel_tools/tools/bash/runfiles/runfiles.bash" | ||
if err := fstest.TestFS(fsys, expected1, expected2, expected3); 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"), 0o600); 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) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
runfiles *Runfiles | ||
err error | ||
} | ||
|
||
func (g *global) get() (*Runfiles, error) { | ||
g.once.Do(g.init) | ||
return g.runfiles, g.err | ||
} | ||
|
||
func (g *global) init() { | ||
g.runfiles, g.err = New() | ||
} | ||
|
||
var g global |
Oops, something went wrong.