Skip to content

Commit

Permalink
[Feature] go.mod & go.work FilePath ReplaceDirective support
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanpenner authored and Stefan Penner committed Apr 12, 2024
1 parent a8d0fbf commit 28f5602
Show file tree
Hide file tree
Showing 28 changed files with 475 additions and 63 deletions.
2 changes: 2 additions & 0 deletions cmd/fetch_repo/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go_library(
"fetch_repo.go",
"go_mod_download.go",
"module.go",
"path.go",
"vcs.go",
],
importpath = "github.com/bazelbuild/bazel-gazelle/cmd/fetch_repo",
Expand Down Expand Up @@ -40,6 +41,7 @@ filegroup(
"fetch_repo_test.go",
"go_mod_download.go",
"module.go",
"path.go",
"vcs.go",
],
visibility = ["//visibility:public"],
Expand Down
31 changes: 29 additions & 2 deletions cmd/fetch_repo/fetch_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
var (
// Common flags
importpath = flag.String("importpath", "", "Go importpath to the repository fetch")
path = flag.String("path", "", "path to a local go module")
dest = flag.String("dest", "", "destination directory")

// Repository flags
Expand All @@ -54,17 +55,43 @@ func main() {
log.SetPrefix("fetch_repo: ")

flag.Parse()
if *importpath == "" {

if *importpath == "" && *path == "" {
log.Fatal("-importpath must be set")
}

if *dest == "" {
log.Fatal("-dest must be set")
}
if flag.NArg() != 0 {
log.Fatal("fetch_repo does not accept positional arguments")
}

if *version != "" {
if *path != "" {
if *importpath != "" {
log.Fatal("-importpath must not be set")
}
if *remote != "" {
log.Fatal("-remote must not be set in module path mode")
}
if *cmd != "" {
log.Fatal("-vcs must not be set in module path mode")
}
if *rev != "" {
log.Fatal("-rev must not be set in module path mode")
}
if *version != "" {
log.Fatal("-version must not be set in module path mode")
}
if *sum != "" {
log.Fatal("-sum must not be set in module path mode")
}
log.Printf("module from\n from: %s,\n to: %s", *path, *dest)

if err := moduleFromPath(*path, *dest); err != nil {
log.Fatal(err)
}
} else if *version != "" {
if *remote != "" {
log.Fatal("-remote must not be set in module mode")
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/fetch_repo/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ package main

import (
"fmt"
"golang.org/x/mod/sumdb/dirhash"
"os"

"golang.org/x/mod/sumdb/dirhash"
)

func fetchModule(dest, importpath, version, sum string) error {
Expand All @@ -33,7 +34,6 @@ func fetchModule(dest, importpath, version, sum string) error {
// so we create a dummy module in the current directory (which should be
// empty).
w, err := os.OpenFile("go.mod", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o666)

if err != nil {
return fmt.Errorf("error creating temporary go.mod: %v", err)
}
Expand All @@ -46,7 +46,7 @@ func fetchModule(dest, importpath, version, sum string) error {
return fmt.Errorf("error closing temporary go.mod: %v", err)
}

var dl = GoModDownloadResult{}
dl := GoModDownloadResult{}
err = runGoModDownload(&dl, dest, importpath, version)
os.Remove("go.mod")
if err != nil {
Expand Down
47 changes: 47 additions & 0 deletions cmd/fetch_repo/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* Copyright 2019 The Bazel Authors. All rights reserved.
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
http://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 (
"bytes"
"fmt"
"os/exec"
"strings"
)

func moduleFromPath(from string, dest string) error {
err := copyTree(dest, from)
if err != nil {
return err
}

cmd := exec.Command(findGoPath(), "mod", "download", "-json")
cmd.Dir = dest
cmd.Args = append(cmd.Args, "-modcacherw")

buf := &bytes.Buffer{}
bufErr := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = bufErr
fmt.Printf("Running: %s %s\n", cmd.Path, strings.Join(cmd.Args, " "))
// TODO: handle errors
cmd.Run()

// if _, ok := dlErr.(*exec.ExitError); !ok {
// return fmt.Errorf("error running 'go mod download': %v", dlErr)
// }
return nil
}
50 changes: 38 additions & 12 deletions internal/bzlmod/go_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

load("//internal:go_repository.bzl", "go_repository")
load(":go_mod.bzl", "deps_from_go_mod", "parse_go_work", "sums_from_go_mod", "sums_from_go_work")
load(":go_mod.bzl", "deps_from_go_mod", "go_work_from_label", "sums_from_go_mod", "sums_from_go_work")
load(
":default_gazelle_overrides.bzl",
"DEFAULT_BUILD_EXTRA_ARGS_BY_PATH",
Expand Down Expand Up @@ -83,12 +83,6 @@ _GAZELLE_ATTRS = {
),
}

def go_work_from_label(module_ctx, go_work_label):
"""Loads deps from a go.work file"""
go_work_path = module_ctx.path(go_work_label)
go_work_content = module_ctx.read(go_work_path)
return parse_go_work(go_work_content, go_work_label)

def _fail_on_non_root_overrides(module_ctx, module, tag_class):
if module.is_root:
return
Expand Down Expand Up @@ -299,6 +293,10 @@ def check_for_version_conflict(version, previous, module_tag, module_name_to_go_
# version is the same, skip because we won't error
return

if hasattr(module_tag, "file_path"):
# overrides are not considered for version conflicts
return

# When using go.work, duplicate dependency versions are possible.
# This can cause issues, so we fail with a hopefully actionable error.
current_label = module_tag._parent_label
Expand Down Expand Up @@ -432,7 +430,10 @@ def _go_deps_impl(module_ctx):
]

if module.is_root or getattr(module_ctx, "is_isolated", False):
replace_map.update(go_mod_replace_map)
# for the replace_map, first in wins
for mod_path, mod in go_mod_replace_map.items():
if not mod_path in replace_map:
replace_map[mod_path] = mod
else:
# Register this Bazel module as providing the specified Go module. It participates
# in version resolution using its registry version, which uses a relaxed variant of
Expand Down Expand Up @@ -487,7 +488,7 @@ def _go_deps_impl(module_ctx):
# for direct dependencies. For manually specified go_deps.module
# tags, we always report version upgrades unless users override with
# the "indirect" attribute.
if module.is_root and not module_tag.indirect:
if module.is_root and not module_tag.indirect: # and not module_tag.file_path:
root_versions[module_tag.path] = raw_version
if _is_dev_dependency(module_ctx, module_tag):
root_module_direct_dev_deps[_repo_name(module_tag.path)] = None
Expand All @@ -504,10 +505,21 @@ def _go_deps_impl(module_ctx):
paths[module_tag.path] = struct(version = version, module_tag = module_tag)

if module_tag.path not in module_resolutions or version > module_resolutions[module_tag.path].version:
to_path = None
file_path = None

if module_tag.path in replace_map:
replacement = replace_map[module_tag.path]

to_path = replacement.to_path
file_path = replacement.file_path

module_resolutions[module_tag.path] = struct(
repo_name = _repo_name(module_tag.path),
version = version,
raw_version = raw_version,
to_path = to_path,
file_path = file_path,
)

_fail_on_unmatched_overrides(archive_overrides.keys(), module_resolutions, "archive_overrides")
Expand All @@ -533,6 +545,7 @@ def _go_deps_impl(module_ctx):
version = new_version,
raw_version = replace.version,
)

if path in root_versions:
if replace != replace.to_path:
# If the root module replaces a Go module with a completely different one, do
Expand All @@ -542,7 +555,6 @@ def _go_deps_impl(module_ctx):
root_versions[path] = replace.version

for path, bazel_dep in bazel_deps.items():
# We can't apply overrides to Bazel dependencies and thus fall back to using the Go module.
if path in archive_overrides or path in gazelle_overrides or path in module_overrides or path in replace_map:
continue

Expand Down Expand Up @@ -582,7 +594,6 @@ def _go_deps_impl(module_ctx):
# Do not create a go_repository for a dep shared with the non-isolated instance of
# go_deps.
continue

go_repository_args = {
"name": module.repo_name,
"importpath": path,
Expand All @@ -602,6 +613,12 @@ def _go_deps_impl(module_ctx):
"patches": _get_patches(path, archive_overrides),
"patch_args": _get_patch_args(path, archive_overrides),
})
elif module.file_path:
go_repository_args.update({
# the version is now meaningless
"version": None,
"file_path": module.file_path,
})
else:
go_repository_args.update({
"sum": _get_sum_from_module(path, module, sums),
Expand Down Expand Up @@ -656,7 +673,12 @@ def _get_sum_from_module(path, module, sums):
entry = (module.replace, module.raw_version)

if entry not in sums:
fail("No sum for {}@{} found. You may need to run: bazel run @rules_go//go -- mod tidy".format(path, module.raw_version))
if module.raw_version == "{":
# replacement have no sums, so we can skip this
return None
elif module.file_path == None:
# TODO: this error likely needs to convey where to run go mod tidy, as it may be in a subdir
fail("No sum for {}@{} from {} found. You may need to run: bazel run @rules_go//go -- mod tidy".format(path, module.raw_version, "parent-label-todo")) #module.parent_label))

return sums[entry]

Expand Down Expand Up @@ -703,6 +725,10 @@ _module_tag = tag_class(
),
"build_naming_convention": attr.string(doc = """Removed, do not use""", default = ""),
"build_file_proto_mode": attr.string(doc = """Removed, do not use""", default = ""),
"file_path": attr.string(
doc = """For when a module is replaced by one residing in a local directory path """,
mandatory = False,
),
},
)

Expand Down
Loading

0 comments on commit 28f5602

Please sign in to comment.