cmd/go: add clean -modcache

We need an easy way to remove $GOPATH/src/mod,
especially since all the directories are marked read-only.

Change-Id: Ib9e8e47e50048f55ecc4de0229b06c4a416ac114
Reviewed-on: https://go-review.googlesource.com/124382
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go
index fa5af94..40cb324 100644
--- a/src/cmd/go/internal/clean/clean.go
+++ b/src/cmd/go/internal/clean/clean.go
@@ -17,11 +17,12 @@
 	"cmd/go/internal/cache"
 	"cmd/go/internal/cfg"
 	"cmd/go/internal/load"
+	"cmd/go/internal/modfetch"
 	"cmd/go/internal/work"
 )
 
 var CmdClean = &base.Command{
-	UsageLine: "clean [-i] [-r] [-n] [-x] [-cache] [-testcache] [build flags] [packages]",
+	UsageLine: "clean [clean flags] [build flags] [packages]",
 	Short:     "remove object files and cached files",
 	Long: `
 Clean removes object files from package source directories.
@@ -65,6 +66,10 @@
 The -testcache flag causes clean to expire all test results in the
 go build cache.
 
+The -modcache flag causes clean to remove the entire module
+download cache, including unpacked source code of versioned
+dependencies.
+
 For more about build flags, see 'go help build'.
 
 For more about specifying packages, see 'go help packages'.
@@ -75,6 +80,7 @@
 	cleanI         bool // clean -i flag
 	cleanR         bool // clean -r flag
 	cleanCache     bool // clean -cache flag
+	cleanModcache  bool // clean -modcache flag
 	cleanTestcache bool // clean -testcache flag
 )
 
@@ -85,6 +91,7 @@
 	CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
 	CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
 	CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
+	CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
 	CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
 
 	// -n and -x are important enough to be
@@ -138,6 +145,29 @@
 			}
 		}
 	}
+
+	if cleanModcache {
+		if modfetch.SrcMod == "" {
+			base.Fatalf("go clean -modcache: no module cache")
+		}
+		if err := removeAll(modfetch.SrcMod); err != nil {
+			base.Errorf("go clean -modcache: %v", err)
+		}
+	}
+}
+
+func removeAll(dir string) error {
+	// Module cache has 0555 directories; make them writable in order to remove content.
+	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return nil // ignore errors walking in file system
+		}
+		if info.IsDir() {
+			os.Chmod(path, 0777)
+		}
+		return nil
+	})
+	return os.RemoveAll(dir)
 }
 
 var cleaned = map[*load.Package]bool{}
diff --git a/src/cmd/go/mod_test.go b/src/cmd/go/mod_test.go
index a15832f..3e8a010 100644
--- a/src/cmd/go/mod_test.go
+++ b/src/cmd/go/mod_test.go
@@ -1067,6 +1067,11 @@
 		t.Fatalf("%s should be unwritable", filepath.Join(dir, "buggy"))
 	}
 
+	tg.run("clean", "-modcache")
+	if _, err = os.Stat(dir); err == nil {
+		t.Fatal("clean -modcache did not remove download dir")
+	}
+
 	tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
 		module x
 		require rsc.io/quote v1.5.1