package main import ( "errors" "flag" "fmt" "os" "os/exec" "path/filepath" "runtime" "strconv" "sync" "time" "git.milar.in/milarin/channel" ) var ModulePath string var ProjectName string var ( OS = flag.String("os", "", "comma-separated list of operating systems to compile for (empty for all)") Arch = flag.String("arch", "", "comma-separated list of architectures to compile for (empty for all)") Silent = flag.Bool("s", false, "silent mode (no output)") NoCompress = flag.Bool("c", false, "dont compress any executables") NumThreads = flag.Int("t", runtime.NumCPU(), "amount of threads (0 = infinite)") ) var Runner channel.Runner func main() { var err error flag.Parse() ModulePath, err = filepath.Abs(flag.Arg(0)) if err != nil { panic(err) } ProjectName = filepath.Base(ModulePath) if _, err := exec.LookPath("go"); err != nil { ColorError.Fprintln(os.Stderr, "go not found in PATH. compilation not possible") return } if !*NoCompress { if _, err := exec.LookPath("upx"); err != nil { ColorWarn.Fprintln(os.Stderr, "upx not found in PATH. file compression not possible") *NoCompress = true } } if err := FillCompileConfigs(); err != nil { ColorError.Fprintln(os.Stderr, fmt.Errorf("target architectures could not be determined: %w", err)) return } if _, err := os.Stat(filepath.Join(ModulePath, "go.mod")); errors.Is(err, os.ErrNotExist) { ColorError.Fprintf(os.Stderr, "no Go module found at '%s'\n", ModulePath) return } ch := make(chan *CompileReport, len(CompileConfigs)) wg := new(sync.WaitGroup) doneCh := make(chan struct{}) go Watch(ch, doneCh) Runner = GetRunner() start := time.Now() for _, cfg := range CompileConfigs { cfg := cfg wg.Add(1) go Runner.Run(func() { Compile(cfg, ch, wg) }) } wg.Wait() close(ch) end := time.Now() <-doneCh // wait for Watch if !*Silent { fmt.Printf("compilation took %s (using %s threads)\n", end.Sub(start), GetThreadCount()) } } func Compile(cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) { filename := fmt.Sprintf("%s_%s_%s%s", ProjectName, cfg.OS, cfg.Arch, cfg.FileExt) ch <- &CompileReport{Config: cfg, State: StateCompiling} compileCmd := exec.Command("go", "build", "-o", filename) compileCmd.Dir = ModulePath if err := compileCmd.Start(); err != nil { ch <- &CompileReport{Config: cfg, State: StateCompileError} wg.Done() return } if err := compileCmd.Wait(); err != nil { ch <- &CompileReport{Config: cfg, State: StateCompileError} wg.Done() return } Compress(filename, cfg, ch, wg) // uncomment for independent compile and compress tasks /* ch <- &CompileReport{Config: cfg, State: StateWaiting} go Runner.Run(func() { Compress(filename, cfg, ch, wg) }) */ } func Compress(filename string, cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) { defer wg.Done() ch <- &CompileReport{Config: cfg, State: StateCompressing} if !*NoCompress && cfg.Compress { compressCmd := exec.Command("upx", "--best", filename) compressCmd.Dir = ModulePath if err := compressCmd.Start(); err != nil { ch <- &CompileReport{Config: cfg, State: StateCompressError} return } if err := compressCmd.Wait(); err != nil { ch <- &CompileReport{Config: cfg, State: StateCompressError} return } } ch <- &CompileReport{Config: cfg, State: StateDone} } func GetRunner() channel.Runner { if *NumThreads <= 0 { return channel.NewUnlimitedRunner() } else { return channel.NewLimitedRunner(*NumThreads) } } func GetThreadCount() string { if *NumThreads == 0 { return "infinitely many" } return strconv.Itoa(*NumThreads) }