4949package main
5050
5151import (
52+ "bytes"
5253 "context"
54+ "encoding/json"
5355 "flag"
5456 "fmt"
57+ "io/fs"
5558 "net/http"
5659 "os"
5760 "os/exec"
5861 "path/filepath"
62+ "sort"
5963 "strings"
6064 "time"
6165
7983 httpAddr = flag .String ("http" , defaultAddr , "HTTP service address to listen for incoming requests on" )
8084 goRepoPath = flag .String ("gorepo" , "" , "path to Go repo on local filesystem" )
8185 useProxy = flag .Bool ("proxy" , false , "fetch from GOPROXY if not found locally" )
86+ devMode = flag .Bool ("dev" , false , "enable developer mode (reload templates on each page load, serve non-minified JS/CSS, etc.)" )
87+ staticFlag = flag .String ("static" , "static" , "path to folder containing static files served" )
8288 // other flags are bound to serverConfig below
8389)
8490
@@ -155,7 +161,7 @@ func buildServer(ctx context.Context, serverCfg serverConfig) (*frontend.Server,
155161 }
156162
157163 cfg := getterConfig {
158- dirs : serverCfg .paths ,
164+ all : serverCfg .useListedMods ,
159165 proxy : serverCfg .proxy ,
160166 }
161167
@@ -168,11 +174,12 @@ func buildServer(ctx context.Context, serverCfg serverConfig) (*frontend.Server,
168174 if err != nil {
169175 return nil , fmt .Errorf ("searching GOPATH: %v" , err )
170176 }
171- }
172-
173- cfg .pattern = "./..."
174- if serverCfg .useListedMods {
175- cfg .pattern = "all"
177+ } else {
178+ var err error
179+ cfg .dirs , err = getModuleDirs (ctx , serverCfg .paths )
180+ if err != nil {
181+ return nil , fmt .Errorf ("searching GOPATH: %v" , err )
182+ }
176183 }
177184
178185 if serverCfg .useCache {
@@ -193,7 +200,24 @@ func buildServer(ctx context.Context, serverCfg serverConfig) (*frontend.Server,
193200 if err != nil {
194201 return nil , err
195202 }
196- return newServer (getters , cfg .proxy )
203+
204+ // Collect unique module paths served by this server.
205+ seenModules := make (map [frontend.LocalModule ]bool )
206+ var allModules []frontend.LocalModule
207+ for _ , modules := range cfg .dirs {
208+ for _ , m := range modules {
209+ if seenModules [m ] {
210+ continue
211+ }
212+ seenModules [m ] = true
213+ allModules = append (allModules , m )
214+ }
215+ }
216+ sort .Slice (allModules , func (i , j int ) bool {
217+ return allModules [i ].ModulePath < allModules [j ].ModulePath
218+ })
219+
220+ return newServer (getters , allModules , cfg .proxy )
197221}
198222
199223func collectPaths (args []string ) []string {
@@ -204,20 +228,53 @@ func collectPaths(args []string) []string {
204228 return paths
205229}
206230
207- // getGOPATHModuleDirs returns the absolute paths to directories in GOPATH
208- // corresponding to the requested module paths.
231+ // getGOPATHModuleDirs returns the set of workspace modules for each directory,
232+ // determined by running go list -m.
233+ //
234+ // An error is returned if any operations failed unexpectedly, or if no
235+ // requested directories contain any valid modules.
236+ func getModuleDirs (ctx context.Context , dirs []string ) (map [string ][]frontend.LocalModule , error ) {
237+ dirModules := make (map [string ][]frontend.LocalModule )
238+ for _ , dir := range dirs {
239+ output , err := runGo (dir , "list" , "-m" , "-json" )
240+ if err != nil {
241+ return nil , fmt .Errorf ("listing modules in %s: %v" , dir , err )
242+ }
243+ var modules []frontend.LocalModule
244+ decoder := json .NewDecoder (bytes .NewBuffer (output ))
245+ for decoder .More () {
246+ var m frontend.LocalModule
247+ if err := decoder .Decode (& m ); err != nil {
248+ return nil , err
249+ }
250+ if m .ModulePath != "command-line-arguments" {
251+ modules = append (modules , m )
252+ }
253+ }
254+ if len (modules ) > 0 {
255+ dirModules [dir ] = modules
256+ }
257+ }
258+ if len (dirs ) > 0 && len (dirModules ) == 0 {
259+ return nil , fmt .Errorf ("no modules in any of the requested directories" )
260+ }
261+ return dirModules , nil
262+ }
263+
264+ // getGOPATHModuleDirs returns local module information for directories in
265+ // GOPATH corresponding to the requested module paths.
209266//
210- // An error is returned if any operations failed unexpectedly. If individual
211- // module paths are not found, an error is logged and the path skipped. An
212- // error is returned only if no module paths resolved to a GOPATH directory .
213- func getGOPATHModuleDirs (ctx context.Context , modulePaths []string ) ([] string , error ) {
267+ // An error is returned if any operations failed unexpectedly, or if no modules
268+ // were resolved. If individual module paths are not found, an error is logged
269+ // and the path skipped .
270+ func getGOPATHModuleDirs (ctx context.Context , modulePaths []string ) (map [ string ][]frontend. LocalModule , error ) {
214271 gopath , err := runGo ("" , "env" , "GOPATH" )
215272 if err != nil {
216273 return nil , err
217274 }
218- gopaths := filepath .SplitList (string (gopath ))
275+ gopaths := filepath .SplitList (strings . TrimSpace ( string (gopath ) ))
219276
220- var dirs [] string
277+ dirs := make ( map [ string ][]frontend. LocalModule )
221278 for _ , path := range modulePaths {
222279 dir := ""
223280 for _ , gopath := range gopaths {
@@ -234,7 +291,7 @@ func getGOPATHModuleDirs(ctx context.Context, modulePaths []string) ([]string, e
234291 if dir == "" {
235292 log .Errorf (ctx , "ERROR: no GOPATH directory contains %q" , path )
236293 } else {
237- dirs = append ( dirs , dir )
294+ dirs [ dir ] = []frontend. LocalModule {{ ModulePath : path , Dir : dir }}
238295 }
239296 }
240297
@@ -247,10 +304,10 @@ func getGOPATHModuleDirs(ctx context.Context, modulePaths []string) ([]string, e
247304// getterConfig defines the set of getters for the server to use.
248305// See buildGetters.
249306type getterConfig struct {
250- dirs [] string // local directories to serve
251- pattern string // go/packages query to load in each directory
252- modCacheDir string // path to module cache, or ""
253- proxy * proxy.Client // proxy client, or nil
307+ all bool // if set, request "all" instead of ["<modulePath>/..."]
308+ dirs map [ string ][]frontend. LocalModule // local modules to serve
309+ modCacheDir string // path to module cache, or ""
310+ proxy * proxy.Client // proxy client, or nil
254311}
255312
256313// buildGetters constructs module getters based on the given configuration.
@@ -263,8 +320,16 @@ func buildGetters(ctx context.Context, cfg getterConfig) ([]fetch.ModuleGetter,
263320 var getters []fetch.ModuleGetter
264321
265322 // Load local getters for each directory.
266- for _ , dir := range cfg .dirs {
267- mg , err := fetch .NewGoPackagesModuleGetter (ctx , dir , cfg .pattern )
323+ for dir , modules := range cfg .dirs {
324+ var patterns []string
325+ if cfg .all {
326+ patterns = append (patterns , "all" )
327+ } else {
328+ for _ , m := range modules {
329+ patterns = append (patterns , fmt .Sprintf ("%s/..." , m ))
330+ }
331+ }
332+ mg , err := fetch .NewGoPackagesModuleGetter (ctx , dir , patterns ... )
268333 if err != nil {
269334 log .Errorf (ctx , "Loading packages from %s: %v" , dir , err )
270335 } else {
@@ -291,16 +356,30 @@ func buildGetters(ctx context.Context, cfg getterConfig) ([]fetch.ModuleGetter,
291356 return getters , nil
292357}
293358
294- func newServer (getters []fetch.ModuleGetter , prox * proxy.Client ) (* frontend.Server , error ) {
359+ func newServer (getters []fetch.ModuleGetter , localModules []frontend. LocalModule , prox * proxy.Client ) (* frontend.Server , error ) {
295360 lds := fetchdatasource.Options {
296361 Getters : getters ,
297362 ProxyClientForLatest : prox ,
298363 BypassLicenseCheck : true ,
299364 }.New ()
365+
366+ // In dev mode, use a dirFS to pick up template/JS/CSS changes without
367+ // restarting the server.
368+ var staticFS fs.FS
369+ if * devMode {
370+ staticFS = os .DirFS (* staticFlag )
371+ } else {
372+ staticFS = static .FS
373+ }
374+
300375 server , err := frontend .NewServer (frontend.ServerConfig {
301376 DataSourceGetter : func (context.Context ) internal.DataSource { return lds },
302377 TemplateFS : template .TrustedFSFromEmbed (static .FS ),
303- StaticFS : static .FS ,
378+ StaticFS : staticFS ,
379+ DevMode : * devMode ,
380+ LocalMode : true ,
381+ LocalModules : localModules ,
382+ StaticPath : * staticFlag ,
304383 ThirdPartyFS : thirdparty .FS ,
305384 })
306385 if err != nil {
0 commit comments