Skip to content

Commit

Permalink
crosslink: Add work command (#311)
Browse files Browse the repository at this point in the history
* Add go.work files to Git ignore

* Add test_data temp files to Git ignore

* Add Work with failing tests

* Fix tests

* Add work command

* Update changelog

* Implement Work

* Fix flags

* Create go.work when missing

* Refactor tests

* Fix typo

* Add gowork Make target

* Fix gitignore

* Uninted want go.work content

* Fix insertUses comment

* Apply suggestions from code review

Co-authored-by: Pablo Baeyens <pbaeyens31+github@gmail.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* refactor: Add forGoModules function

* Update crosslink/README.md

Co-authored-by: Pablo Baeyens <pbaeyens31+github@gmail.com>

* Update common.go

* Update crosslink/internal/work_test.go

Co-authored-by: bryan-aguilar <46550959+bryan-aguilar@users.noreply.github.com>

* Document go flag

* Add go version validation

---------

Co-authored-by: Pablo Baeyens <pbaeyens31+github@gmail.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
Co-authored-by: bryan-aguilar <46550959+bryan-aguilar@users.noreply.github.com>
  • Loading branch information
4 people authored May 18, 2023
1 parent 6d04deb commit a570d1a
Show file tree
Hide file tree
Showing 14 changed files with 454 additions and 28 deletions.
16 changes: 16 additions & 0 deletions .chloggen/crosslink-work.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. crosslink)
component: crosslink

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "Add work command generating go.work file."

# One or more tracking issues related to the change
issues: [309]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Thumbs.db
*.iml
*.so
coverage.*
/go.work
go.work.sum

crosslink/internal/test_data/
!crosslink/internal/test_data/.placeholder

checkdoc/checkdoc
chloggen/chloggen
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,8 @@ chlog-update: | $(CHLOGGEN)
crosslink: | $(CROSSLINK)
@echo "Updating intra-repository dependencies in all go modules" \
&& $(CROSSLINK) --root=$(shell pwd) --prune

.PHONY: gowork
gowork: | $(CROSSLINK)
$(CROSSLINK) work --root=$(shell pwd)

16 changes: 16 additions & 0 deletions crosslink/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Crosslink is a tool to assist in managing go repositories that contain multiple
intra-repository `go.mod` files. Crosslink automatically scans and inserts
replace statements for direct and transitive intra-repository dependencies.
Crosslink can generate a `go.work` file to facilitate local development of a
repository containing multiple Go modules.
Crosslink also contains functionality to remove any extra replace statements
that are no longer required within reason (see below).

Expand Down Expand Up @@ -128,3 +130,17 @@ Can be disabled when overwriting.

**Quick Tip: Make sure your `go.mod` files are tracked and committed in a VCS
before running crosslink.**

### work

Creates or updates existing `go.work` file by adding use statements
for all intra-repository Go modules. It also removes use statements
for out-dated intra-repository Go modules.

crosslink work --root=/users/foo/multimodule-go-repo

### --go

Go version applied when new `go.work` file is created (default "1.19").

crosslink work --go=1.20
21 changes: 17 additions & 4 deletions crosslink/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type commandConfig struct {
excludeFlags []string
rootCommand cobra.Command
pruneCommand cobra.Command
workCommand cobra.Command
}

func newCommandConfig() *commandConfig {
Expand Down Expand Up @@ -68,8 +69,8 @@ func newCommandConfig() *commandConfig {
return fmt.Errorf("could not create zap logger: %w", err)
}
}
return nil

return nil
}

postRunSetup := func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -105,6 +106,16 @@ func newCommandConfig() *commandConfig {
},
}
c.rootCommand.AddCommand(&c.pruneCommand)

c.workCommand = cobra.Command{
Use: "work",
Short: "Generate or update the go.work file with use statements for intra-repository dependencies",
RunE: func(cmd *cobra.Command, args []string) error {
return cl.Work(c.runConfig)
},
}
c.rootCommand.AddCommand(&c.workCommand)

return c
}

Expand All @@ -123,14 +134,16 @@ func Execute() {
}

func init() {

comCfg.rootCommand.PersistentFlags().StringVar(&comCfg.runConfig.RootPath, "root", "", `path to root directory of multi-module repository. If --root flag is not provided crosslink will attempt to find a
git repository in the current or a parent directory.`)
comCfg.rootCommand.PersistentFlags().StringSliceVar(&comCfg.excludeFlags, "exclude", []string{}, "list of comma separated go modules that crosslink will ignore in operations."+
"multiple calls of --exclude can be made")
comCfg.rootCommand.PersistentFlags().BoolVarP(&comCfg.runConfig.Verbose, "verbose", "v", false, "verbose output")
comCfg.rootCommand.Flags().StringSliceVar(&comCfg.excludeFlags, "exclude", []string{}, "list of comma separated go modules that crosslink will ignore in operations."+
"multiple calls of --exclude can be made")
comCfg.rootCommand.Flags().BoolVar(&comCfg.runConfig.Overwrite, "overwrite", false, "overwrite flag allows crosslink to make destructive (replacing or updating) actions to existing go.mod files")
comCfg.rootCommand.Flags().BoolVarP(&comCfg.runConfig.Prune, "prune", "p", false, "enables pruning operations on all go.mod files inside root repository")
comCfg.pruneCommand.Flags().StringSliceVar(&comCfg.excludeFlags, "exclude", []string{}, "list of comma separated go modules that crosslink will ignore in operations."+
"multiple calls of --exclude can be made")
comCfg.workCommand.Flags().StringVar(&comCfg.runConfig.GoVersion, "go", "1.19", "Go version applied when new go.work file is created")
}

// transform array slice into map
Expand Down
23 changes: 23 additions & 0 deletions crosslink/internal/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ package crosslink

import (
"fmt"
"io/fs"
"os"
"path/filepath"

"go.uber.org/zap"
"golang.org/x/mod/modfile"
)

Expand Down Expand Up @@ -53,3 +55,24 @@ func writeModule(module *moduleInfo) error {

return nil
}

// forGoModules iterates the fn for all Go modules under rootPath.
// The path argument of fn function is the path to the go.mod file
// relative to rootPath (e.g. go.mod, submodule/go.mod).
func forGoModules(logger *zap.Logger, rootPath string, fn func(path string) error) error {
return fs.WalkDir(os.DirFS(rootPath), ".", func(path string, dir fs.DirEntry, err error) error {
if err != nil {
logger.Warn("File could not be read during fs.WalkDir",
zap.Error(err),
zap.String("path", path))
return nil
}

// find Go module
if dir.Name() != "go.mod" {
return nil
}

return fn(path)
})
}
1 change: 1 addition & 0 deletions crosslink/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type RunConfig struct {
ExcludedPaths map[string]struct{}
Overwrite bool
Prune bool
GoVersion string
Logger *zap.Logger
}

Expand Down
36 changes: 12 additions & 24 deletions crosslink/internal/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package crosslink

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
Expand All @@ -31,34 +30,23 @@ import (
func buildDepedencyGraph(rc RunConfig, rootModulePath string) (map[string]*moduleInfo, error) {
moduleMap := make(map[string]*moduleInfo)

goModFunc := func(filePath string, info fs.FileInfo, err error) error {
err := forGoModules(rc.Logger, rc.RootPath, func(path string) error {
fullPath := filepath.Join(rc.RootPath, path)
modFile, err := os.ReadFile(filepath.Clean(fullPath))
if err != nil {
rc.Logger.Error("File could not be read during filePath.Walk",
zap.Error(err),
zap.String("file_path", filePath))

return nil
return fmt.Errorf("failed to read file: %w", err)
}

if filepath.Base(filePath) == "go.mod" {
modFile, err := os.ReadFile(filepath.Clean(filePath))
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}

modContents, err := modfile.Parse(filePath, modFile, nil)
if err != nil {
rc.Logger.Error("Modfile could not be parsed",
zap.Error(err),
zap.String("file_path", filePath))
}

moduleMap[modfile.ModulePath(modFile)] = newModuleInfo(*modContents)
modContents, err := modfile.Parse(fullPath, modFile, nil)
if err != nil {
rc.Logger.Error("Modfile could not be parsed",
zap.Error(err),
zap.String("file_path", path))
}
return nil
}

err := filepath.Walk(rc.RootPath, goModFunc)
moduleMap[modfile.ModulePath(modFile)] = newModuleInfo(*modContents)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed during file walk: %w", err)
}
Expand Down
13 changes: 13 additions & 0 deletions crosslink/internal/mock_test_data/testWork/go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
go 1.19

// existing valid use statements under root should remain
use ./testA

// invalid use statements under root should be removed ONLY if prune is used
use ./testC

// use statements outside the root should remain
use ../other-module

// replace statements should remain
replace foo.opentelemetery.io/bar => ../bar
3 changes: 3 additions & 0 deletions crosslink/internal/mock_test_data/testWork/gomod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module go.opentelemetry.io/build-tools/crosslink/testroot

go 1.19
3 changes: 3 additions & 0 deletions crosslink/internal/mock_test_data/testWork/testA/gomod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module go.opentelemetry.io/build-tools/crosslink/testroot/testA

go 1.19
3 changes: 3 additions & 0 deletions crosslink/internal/mock_test_data/testWork/testB/gomod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module go.opentelemetry.io/build-tools/crosslink/testroot/testB

go 1.19
Loading

0 comments on commit a570d1a

Please sign in to comment.