package main import ( "encoding/json" "fmt" "go/build" "io" "io/fs" "os" "path" "path/filepath" "sort" "strings" "time" ) type WrappedStat struct { fs.FileInfo newName string // meow } func (s *WrappedStat) Name() string { return s.newName } type DirStat struct { name string } func (s *DirStat) Name() string { return s.name } func (s *DirStat) Size() int64 { return 1 } func (s *DirStat) Mode() fs.FileMode { return fs.FileMode(fs.ModeDir | 0777) } func (s *DirStat) ModTime() time.Time { return time.Time{} } func (s *DirStat) IsDir() bool { return true } func (s *DirStat) Sys() any { return "zilch" } type Input struct { // directory -> filename -> path Files map[string]map[string]string `json:"files"` GOARCH string `json:"GOARCH"` GOOS string `json:"GOOS"` } type Output struct { Name string `json:"name"` GoFiles []string `json:"goFiles"` SFiles []string `json:"sFiles"` Imports []string `json:"imports"` Embeds map[string][][]string `json:"embeds"` } func main() { inputFile, err := os.Open(os.Args[1]) if err != nil { panic(err) } var input Input err = json.NewDecoder(inputFile).Decode(&input) if err != nil { panic(err) } ctx := build.Context{ GOARCH: input.GOARCH, GOOS: input.GOOS, Compiler: "gc", ToolTags: build.Default.ToolTags, ReleaseTags: build.Default.ReleaseTags, ReadDir: func(dir string) ([]fs.FileInfo, error) { fmt.Printf("ReadDir(%q)\n", dir) if !strings.HasPrefix(dir, "/code") { return nil, fs.ErrNotExist } dir = path.Clean(dir[5:]) if dir == "." { dir = "/" } if !strings.HasSuffix(dir, "/") { dir += "/" } dircontents, ok := input.Files[dir] if !ok { return nil, fs.ErrNotExist } infos := make([]fs.FileInfo, len(dircontents)) i := 0 for name, file := range dircontents { stat, err := os.Stat(file) if err != nil { return nil, err } infos[i] = &WrappedStat{FileInfo: stat, newName: name} i = i + 1 } for key := range input.Files { if path.Dir(key) == dir { base := path.Base(key) if strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") || base == "testdata" { continue } infos = append(infos, &DirStat{base}) } } return infos, nil }, OpenFile: func(pth string) (io.ReadCloser, error) { opth := pth fmt.Printf("OpenFile(%q)\n", pth) if !strings.HasPrefix(pth, "/code") { return nil, fs.ErrNotExist } pth = path.Clean(pth[5:]) dirname, fname := path.Split(pth) if dirname == "." { dirname = "/" } dir := input.Files[dirname] data, err := os.Open(dir[fname]) if err != nil { return data, fmt.Errorf("OpenFile(%q; %q[%q]; %q): %w", opth, dirname, fname, dir[fname], err) } return data, err }, IsDir: func(dir string) bool { fmt.Printf("IsDir(%q)\n", dir) if !strings.HasPrefix(dir, "/code") { return false } dir = path.Clean(dir[5:]) if dir == "." { dir = "/" } if !strings.HasSuffix(dir, "/") { dir += "/" } _, ok := input.Files[dir] fmt.Printf("IsDir -> %q, %v\n", dir, ok) return ok }, HasSubdir: func(root, dir string) (rel string, ok bool) { root = path.Clean(root) dir = path.Clean(dir) return strings.CutPrefix(dir, root) }, } var filenames []string for dirname, files := range input.Files { for filename := range files { filenames = append(filenames, filepath.Join(dirname, filename)) } } files := make(map[string]Output) for dir, filelist := range input.Files { isGo := false for file := range filelist { if strings.HasSuffix(file, ".go") { isGo = true break } } fmt.Printf("Checking %q..\n", dir) if strings.Contains(dir, "/.") || strings.Contains(dir, "/_") || strings.Contains(dir, "/testdata/") { fmt.Printf(" skipping; \n") continue } base := path.Base(dir) if !isGo || strings.HasPrefix(base, "_") || strings.HasPrefix(base, ".") || base == "testdata" { fmt.Printf(" skipping (not go)\n") continue } pkg, err := ctx.Import(".", path.Clean("/code"+dir), 0) if err != nil { if _, ok := err.(*build.NoGoError); ok { continue } panic(err) } out := Output{ Name: pkg.Name, Imports: pkg.Imports, GoFiles: pkg.GoFiles, SFiles: pkg.SFiles, Embeds: make(map[string][][]string), } // _test only, or so if len(pkg.GoFiles) == 0 { continue } sort.Strings(out.Imports) sort.Strings(out.GoFiles) sort.Strings(out.SFiles) sort.Strings(pkg.EmbedPatterns) for _, pattern := range pkg.EmbedPatterns { matchedFiles := []string{} // TODO: proper matching if strings.HasPrefix(pattern, "all:") { pattern = pattern[4:] } fullPattern := filepath.Join(dir, pattern) for _, file := range filenames { if ok, _ := filepath.Match(fullPattern, file); ok { matchedFiles = append(matchedFiles, file) } } if len(matchedFiles) == 0 { dirpattern := fullPattern + "/" for _, file := range filenames { if strings.HasPrefix(file, dirpattern) { matchedFiles = append(matchedFiles, file) } if file == fullPattern { matchedFiles = append(matchedFiles, file) } } } sort.Strings(matchedFiles) var split [][]string for _, match := range matchedFiles { split = append(split, []string{match[len(dir):], filepath.Dir(match) + "/", filepath.Base(match)}) } out.Embeds[pattern] = split } files[dir] = out } out, err := os.OpenFile(os.Getenv("out"), os.O_CREATE|os.O_RDWR, 0666) if err != nil { panic(err) } if err := json.NewEncoder(out).Encode(files); err != nil { panic(err) } out.Close() }