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 { // list of [directory, filename, target path] (target path is empty, if directory marker) Files [][3]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"` } type FileOrDir struct { Directory []string File string } 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) } files := make(map[string]*FileOrDir) for _, file := range input.Files { directory := file[0] filename := file[1] contents := file[2] dirslash := directory if directory != "" { dirslash = directory + "/" } if contents == "" { _, ok := files[dirslash+filename] if !ok { files[dirslash+filename] = &FileOrDir{} } } else { files[dirslash+filename] = &FileOrDir{File: contents} } parent, ok := files[directory] if !ok { parent = &FileOrDir{} files[directory] = parent } parent.Directory = append(parent.Directory, dirslash+filename) } 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:])[1:] contents, ok := files[dir] if !ok { return nil, fs.ErrNotExist } infos := make([]fs.FileInfo, len(contents.Directory)) for i, filename := range contents.Directory { file := files[filename] if file.File != "" { stat, err := os.Stat(file.File) if err != nil { return nil, err } infos[i] = &WrappedStat{FileInfo: stat, newName: path.Base(filename)} } else { infos[i] = &DirStat{path.Base(filename)} } } 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:])[1:] fmt.Printf("-> OpenFile(%q)\n", pth) fil := files[pth] data, err := os.Open(fil.File) if err != nil { return data, fmt.Errorf("OpenFile(%q; %q): %w", opth, pth, 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:])[1:] contents, ok := files[dir] fmt.Printf("IsDir -> %q, %v, %v\n", dir, contents, ok) return ok && contents.File == "" }, 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 _, filedata := range input.Files { if filedata[2] != "" { filenames = append(filenames, filepath.Join(filedata[0], filedata[1])) } } outfiles := make(map[string]Output) for dir, filelist := range files { if filelist.File != "" { continue } isGo := false for _, file := range filelist.Directory { 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 base == "." { base = "" } 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 { dirname := filepath.Dir(match) if dirname == "." { dirname = "" } split = append(split, []string{match[len(dir)+1:], dirname, filepath.Base(match)}) } out.Embeds[pattern] = split } outfiles[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(outfiles); err != nil { panic(err) } out.Close() }