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,31 +634,12 @@ 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) - } + var fixed bool + data, f, err := ReadModFile(gomod, fixVersion(ctx, &fixed)) if err != nil { base.Fatalf("go: %v", err) } - var fixed bool - f, err := modfile.Parse(gomod, data, 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") - } - modFiles = append(modFiles, f) mainModule := f.Module.Mod mainModules = append(mainModules, mainModule) @@ -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": [