From 37c9552e065fb4331d8cba84c12392806f1ecbbe Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Fri, 3 Sep 2021 13:33:37 -0400 Subject: [PATCH] cmd/go: improve the creation and editing of go.work files This change changes go.work files so that directory paths are clearly distinguished from module paths by either being rooted absolute paths or starting with '.' or '..' path elements if they are relative paths. go mod initwork now checks that the go.work file doesn't already exist before creating it, and gomod initwork and gomod editwork look up the module path corresponding to a directory and write it to the directory directive's comment. For #45713 Change-Id: I6983779059b7de6fc83d359280ceffb263f6b641 Reviewed-on: https://go-review.googlesource.com/c/go/+/347591 Trust: Michael Matloob Run-TryBot: Michael Matloob TryBot-Result: Go Bot Reviewed-by: Jay Conrod --- src/cmd/go/internal/modcmd/editwork.go | 11 +++++-- src/cmd/go/internal/modload/init.go | 39 +++++++++-------------- src/cmd/go/internal/modload/modfile.go | 40 ++++++++++++++++++++++++ src/cmd/go/testdata/script/work.txt | 3 ++ src/cmd/go/testdata/script/work_edit.txt | 30 ++++++++++-------- 5 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/cmd/go/internal/modcmd/editwork.go b/src/cmd/go/internal/modcmd/editwork.go index f05d9245e7..29895b1620 100644 --- a/src/cmd/go/internal/modcmd/editwork.go +++ b/src/cmd/go/internal/modcmd/editwork.go @@ -15,6 +15,7 @@ import ( "encoding/json" "errors" "os" + "path/filepath" "strings" "golang.org/x/mod/modfile" @@ -191,7 +192,13 @@ func runEditwork(ctx context.Context, cmd *base.Command, args []string) { // flagEditworkDirectory implements the -directory flag. func flagEditworkDirectory(arg string) { workedits = append(workedits, func(f *modfile.WorkFile) { - if err := f.AddDirectory(arg, ""); err != nil { + _, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil) + modulePath := "" + if err == nil { + modulePath = mf.Module.Mod.Path + } + f.AddDirectory(modload.ToDirectoryPath(arg), modulePath) + if err := f.AddDirectory(modload.ToDirectoryPath(arg), ""); err != nil { base.Fatalf("go mod: -directory=%s: %v", arg, err) } }) @@ -200,7 +207,7 @@ func flagEditworkDirectory(arg string) { // flagEditworkDropDirectory implements the -dropdirectory flag. func flagEditworkDropDirectory(arg string) { workedits = append(workedits, func(f *modfile.WorkFile) { - if err := f.DropDirectory(arg); err != nil { + if err := f.DropDirectory(modload.ToDirectoryPath(arg)); err != nil { base.Fatalf("go mod: -dropdirectory=%s: %v", arg, err) } }) diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index bc155c7310..a1016ec5a3 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -634,29 +634,10 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) { var indices []*modFileIndex for _, modroot := range modRoots { gomod := modFilePath(modroot) - var data []byte - var err error - if gomodActual, ok := fsys.OverlayPath(gomod); ok { - // Don't lock go.mod if it's part of the overlay. - // On Plan 9, locking requires chmod, and we don't want to modify any file - // in the overlay. See #44700. - data, err = os.ReadFile(gomodActual) - } else { - data, err = lockedfile.Read(gomodActual) - } - if err != nil { - base.Fatalf("go: %v", err) - } - var fixed bool - f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed)) + data, f, err := ReadModFile(gomod, fixVersion(ctx, &fixed)) if err != nil { - // Errors returned by modfile.Parse begin with file:line. - base.Fatalf("go: errors parsing go.mod:\n%s\n", err) - } - if f.Module == nil { - // No module declaration. Must add module path. - base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod") + base.Fatalf("go: %v", err) } modFiles = append(modFiles, f) @@ -819,7 +800,9 @@ func CreateModFile(ctx context.Context, modPath string) { // CreateWorkFile initializes a new workspace by creating a go.work file. func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) { - _ = TODOWorkspaces("Report an error if the file already exists.") + if _, err := fsys.Stat(workFile); err == nil { + base.Fatalf("go: %s already exists", workFile) + } goV := LatestGoVersion() // Use current Go version by default workF := new(modfile.WorkFile) @@ -827,12 +810,18 @@ func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) { workF.AddGoStmt(goV) for _, dir := range modDirs { - _ = TODOWorkspaces("Add the module path of the module.") - workF.AddDirectory(dir, "") + _, f, err := ReadModFile(filepath.Join(dir, "go.mod"), nil) + if err != nil { + if os.IsNotExist(err) { + base.Fatalf("go: creating workspace file: no go.mod file exists in directory %v", dir) + } + base.Fatalf("go: error parsing go.mod in directory %s: %v", dir, err) + } + workF.AddDirectory(ToDirectoryPath(dir), f.Module.Mod.Path) } data := modfile.Format(workF.Syntax) - lockedfile.Write(workFile, bytes.NewReader(data), 0644) + lockedfile.Write(workFile, bytes.NewReader(data), 0666) } // fixVersion returns a modfile.VersionFixer implemented using the Query function. diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index 79ac1227ca..bf05e92ba2 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -47,6 +47,34 @@ const ( separateIndirectVersionV = "v1.17" ) +// ReadModFile reads and parses the mod file at gomod. ReadModFile properly applies the +// overlay, locks the file while reading, and applies fix, if applicable. +func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfile.File, err error) { + if gomodActual, ok := fsys.OverlayPath(gomod); ok { + // Don't lock go.mod if it's part of the overlay. + // On Plan 9, locking requires chmod, and we don't want to modify any file + // in the overlay. See #44700. + data, err = os.ReadFile(gomodActual) + } else { + data, err = lockedfile.Read(gomodActual) + } + if err != nil { + return nil, nil, err + } + + f, err = modfile.Parse(gomod, data, fix) + if err != nil { + // Errors returned by modfile.Parse begin with file:line. + return nil, nil, fmt.Errorf("errors parsing go.mod:\n%s\n", err) + } + if f.Module == nil { + // No module declaration. Must add module path. + return nil, nil, errors.New("no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod") + } + + return data, f, err +} + // modFileGoVersion returns the (non-empty) Go version at which the requirements // in modFile are interpreted, or the latest Go version if modFile is nil. func modFileGoVersion(modFile *modfile.File) string { @@ -739,3 +767,15 @@ func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (la } var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result + +// ToDirectoryPath adds a prefix if necessary so that path in unambiguously +// an absolute path or a relative path starting with a '.' or '..' +// path component. +func ToDirectoryPath(path string) string { + if modfile.IsDirectoryPath(path) { + return path + } + // The path is not a relative path or an absolute path, so make it relative + // to the current directory. + return "./" + filepath.ToSlash(filepath.Clean(path)) +} diff --git a/src/cmd/go/testdata/script/work.txt b/src/cmd/go/testdata/script/work.txt index 529c1c0bfd..657cd060cc 100644 --- a/src/cmd/go/testdata/script/work.txt +++ b/src/cmd/go/testdata/script/work.txt @@ -1,3 +1,6 @@ +! go mod initwork doesnotexist +stderr 'go: creating workspace file: no go.mod file exists in directory doesnotexist' + go mod initwork ./a ./b cmp go.work go.work.want diff --git a/src/cmd/go/testdata/script/work_edit.txt b/src/cmd/go/testdata/script/work_edit.txt index 0de4069865..001ac7f65c 100644 --- a/src/cmd/go/testdata/script/work_edit.txt +++ b/src/cmd/go/testdata/script/work_edit.txt @@ -33,32 +33,36 @@ cmp stdout go.work.want_json go mod editwork -print -fmt -workfile unformatted cmp stdout formatted +-- m/go.mod -- +module m + +go 1.18 -- go.work.want_initial -- go 1.18 -directory m +directory ./m -- go.work.want_directory_n -- go 1.18 directory ( - m - n + ./m + ./n ) -- go.work.want_go_118 -- go 1.18 directory ( - m - n + ./m + ./n ) -- go.work.want_dropdirectory_m -- go 1.18 -directory n +directory ./n -- go.work.want_add_replaces -- go 1.18 -directory n +directory ./n replace ( x.1 v1.3.0 => y.1 v1.4.0 @@ -69,9 +73,9 @@ go 1.18 directory ( ../a + ./c + ./n /b - c - n ) replace ( @@ -83,7 +87,7 @@ go 1.18 directory ( ../a - c + ./c ) replace ( @@ -95,7 +99,7 @@ go 1.18 directory ( ../a - c + ./c ) replace x.1 v1.3.0 => y.1 v1.4.0 @@ -104,7 +108,7 @@ go 1.19 directory ( ../a - b + ./b ) replace x.1 v1.4.0 => ../z @@ -116,7 +120,7 @@ replace x.1 v1.4.0 => ../z "DiskPath": "../a" }, { - "DiskPath": "b" + "DiskPath": "./b" } ], "Replace": [ -- 2.50.0