Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
Timon Ringwald | 20f84c6bf1 | |
Timon Ringwald | c964a85673 | |
Timon Ringwald | f91780d6ce | |
Timon Ringwald | 402b269bf4 | |
Timon Ringwald | 8ec8d32a5b | |
Timon Ringwald | bd0ed87f70 | |
Timon Ringwald | 9569739605 | |
Timon Ringwald | 3c0bc4f1b0 | |
Timon Ringwald | 8616fe67dc | |
Timon Ringwald | 2027f3412c | |
Timon Ringwald | ab7f840215 | |
Timon Ringwald | 115871aeb7 | |
Timon Ringwald | 252d0b7278 |
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2022 Mila Ringwald
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,140 @@
|
|||
# gocc
|
||||
|
||||
A high-performance cross compiler for Go
|
||||
|
||||
## Source code
|
||||
|
||||
You can find the source code here: [git.milar.in](https://git.milar.in/milarin/gocc)
|
||||
|
||||
## Installation
|
||||
|
||||
If you have Go installed, you can simply go install the program: `go install git.milar.in/milarin/gocc@latest`
|
||||
|
||||
There are pre-compiled executables for various platforms on the [repository](https://git.milar.in/milarin/gocc/releases).
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the MIT License. See [LICENSE.md](https://git.milar.in/milarin/gocc/src/branch/main/LICENSE.md)
|
||||
|
||||
## Demo
|
||||
|
||||
[![asciicast](https://asciinema.org/a/YOFZE4hskODO5TkNedyIBa4V8.svg)](https://asciinema.org/a/YOFZE4hskODO5TkNedyIBa4V8)
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic syntax
|
||||
|
||||
`gocc [<arguments>] [<module>]`
|
||||
|
||||
### Arguments
|
||||
|
||||
Using `--help` shows a description for all available arguments:
|
||||
|
||||
```sh
|
||||
$ gocc --help
|
||||
Usage of ./gocc:
|
||||
-arch string
|
||||
comma-separated list of architectures to compile for (empty for all)
|
||||
-c dont compress any executables
|
||||
-f string
|
||||
go template for filenames (default "{{.Name}}-{{.OS}}-{{.Arch}}{{.Ext}}")
|
||||
-findconfig
|
||||
print config file path and exit
|
||||
-ignoreconfig
|
||||
dont read any config file
|
||||
-o string
|
||||
output directory (default "output")
|
||||
-os string
|
||||
comma-separated list of operating systems to compile for (empty for all)
|
||||
-s silent mode (no output)
|
||||
-saveconfig
|
||||
save config file with current configuration and exit
|
||||
-t int
|
||||
amount of threads (0 = infinite) (default 16)
|
||||
-v show version and exit
|
||||
```
|
||||
|
||||
#### Providing a Go module
|
||||
|
||||
By default, `gocc` compiles the module in the current working directory.
|
||||
You can provide a custom module path after all other arguments.
|
||||
|
||||
Example: `gocc -t 4 -ignoreconfig path/to/go/module`
|
||||
|
||||
#### Choosing Operating systems
|
||||
|
||||
You can decide, for which operating systems `gocc` should compile for via `-os`.
|
||||
Provide the operating systems as a comma-separated list.
|
||||
|
||||
Example: `gocc -os "windows,linux"`
|
||||
|
||||
For a list of supported operating systems, run `go tool dist list`.
|
||||
|
||||
Providing no `-os` flag will compile for all available systems (without config file)
|
||||
|
||||
#### Choosing architectures
|
||||
|
||||
Analogous to operating systems, you can provide architectures via `-arch`.
|
||||
|
||||
Example: `gocc -arch "amd64,386"`
|
||||
|
||||
For a list of supported architectures, run `go tool dist list`.
|
||||
|
||||
Providing no `-arch` flag will compile for all available architectures (without config file)
|
||||
|
||||
#### Output path
|
||||
|
||||
By default, `gocc` will save all executables in the folder `output` in the current working directory.
|
||||
You can set a custom path via `-o`
|
||||
|
||||
#### Disable compression
|
||||
|
||||
All executables will be compressed automatically if `upx` is found in `$PATH`.
|
||||
This behavior can be disabled via `-c`.
|
||||
|
||||
#### Multithreaded compilation
|
||||
|
||||
Compilation is multi-threaded by default. You can set the amount of concurrent compilations via `-t <threads>`.
|
||||
If no thread count is provided, the amount of CPU cores will be used.
|
||||
(the default value shown with `--help` is always the amount of CPU cores on the current machine)
|
||||
|
||||
A thread count of `0` runs all tasks at the same time. This could lead to lags and freezes on your machine!
|
||||
|
||||
If you want to disable multi-threaded compilation, just use `-t 1`
|
||||
|
||||
#### Silent mode
|
||||
|
||||
Providing `-s` enables silent mode. When enabled, `gocc` will never show any output.
|
||||
A non-zero exit code indicates errors.
|
||||
|
||||
#### Customize default behavior
|
||||
|
||||
You can change the default values of all other arguments with `-saveconfig`.
|
||||
Simply provide all arguments as you like and save your config in a json file.
|
||||
Next time `gocc` is run, the config file will be loaded and your preferred command line arguments will be automatically set.
|
||||
|
||||
You can still manually overwrite arguments by providing them directly.
|
||||
But that will not always result in the default behavior if no config file exists.
|
||||
For these cases you can use `-ignoreconfig`.
|
||||
|
||||
If you want to get rid of your config file or you want to edit it manually, you can find it by running `gocc -findconfig`
|
||||
|
||||
**No compilation will be done if `-saveconfig` is provided!**
|
||||
|
||||
**`-findconfig` does not work with silent mode (`-s`)!**
|
||||
|
||||
**Always use `-ignoreconfig` in scripts! You don't know what config file your user might have!**
|
||||
|
||||
#### Change executable filename pattern
|
||||
|
||||
You can provide a custom filename pattern for the compiled executables via `-f`.
|
||||
The value feeded into `-f` is a Go template.
|
||||
See the official Go docs for [Go templates](https://pkg.go.dev/text/template) for more information.
|
||||
|
||||
The following values are available inside the template:
|
||||
- `Name`: the module name
|
||||
- `OS`: the operating system
|
||||
- `Arch`: the architecture
|
||||
- `Ext`: the file extension (for windows: `.exe`, for any other operating system: ``)
|
||||
|
||||
The default value is `{{.Name}}-{{.OS}}-{{.Arch}}{{.Ext}}`
|
36
compile.go
36
compile.go
|
@ -1,14 +1,30 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Compile(cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) {
|
||||
filePath := filepath.Join(*OutputDir, fmt.Sprintf("%s_%s_%s%s", ProjectName, cfg.OS, cfg.Arch, cfg.FileExt))
|
||||
start := time.Now()
|
||||
|
||||
// calculate filepath
|
||||
b := new(strings.Builder)
|
||||
data := &OutputFileTmplData{
|
||||
Name: ProjectName,
|
||||
OS: cfg.OS,
|
||||
Arch: cfg.Arch,
|
||||
Ext: cfg.FileExt,
|
||||
}
|
||||
if err := OutputFileTmpl.Execute(b, data); err != nil {
|
||||
ch <- &CompileReport{Config: cfg, State: StateCompileError}
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
filePath := filepath.Join(*OutputDir, b.String())
|
||||
|
||||
filePath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
|
@ -19,7 +35,9 @@ func Compile(cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) {
|
|||
|
||||
ch <- &CompileReport{Config: cfg, State: StateCompiling}
|
||||
|
||||
compileCmd := exec.Command("go", "build", "-o", filePath)
|
||||
args := []string{"build", "-o", filePath, "-ldflags=" + BuildLdFlags(cfg.OS, cfg.Arch)}
|
||||
|
||||
compileCmd := exec.Command("go", args...)
|
||||
compileCmd.Dir = ModulePath
|
||||
|
||||
if err := compileCmd.Start(); err != nil {
|
||||
|
@ -32,17 +50,11 @@ func Compile(cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) {
|
|||
wg.Done()
|
||||
return
|
||||
}
|
||||
Compress(filePath, cfg, ch, wg)
|
||||
|
||||
// uncomment for independent compile and compress tasks
|
||||
// (slightly slower in tests, most likely because of more context switches)
|
||||
/*
|
||||
ch <- &CompileReport{Config: cfg, State: StateWaiting}
|
||||
go Runner.Run(func() { Compress(filePath, cfg, ch, wg) })
|
||||
*/
|
||||
Compress(filePath, start, cfg, ch, wg)
|
||||
}
|
||||
|
||||
func Compress(filePath string, cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) {
|
||||
func Compress(filePath string, start time.Time, cfg *CompileConfig, ch chan<- *CompileReport, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
ch <- &CompileReport{Config: cfg, State: StateCompressing}
|
||||
|
@ -62,5 +74,5 @@ func Compress(filePath string, cfg *CompileConfig, ch chan<- *CompileReport, wg
|
|||
}
|
||||
}
|
||||
|
||||
ch <- &CompileReport{Config: cfg, State: StateDone}
|
||||
ch <- &CompileReport{Config: cfg, State: StateDone, Duration: time.Since(start)}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
type CompileReport struct {
|
||||
Config *CompileConfig
|
||||
State CompileState
|
||||
Config *CompileConfig
|
||||
State CompileState
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
type CompileState string
|
||||
|
|
50
config.go
50
config.go
|
@ -2,6 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -10,14 +12,22 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
OutputDir string `json:"output_dir"`
|
||||
OutputDir string `json:"output_dir"`
|
||||
OutputFile string `json:"output_file"`
|
||||
|
||||
OS []string `json:"os"`
|
||||
Arch []string `json:"arch"`
|
||||
|
||||
Silent bool `json:"silent"`
|
||||
NoCompress bool `json:"no_compress"`
|
||||
NumThreads int `json:"num_threads"`
|
||||
Silent bool `json:"silent"`
|
||||
NoCompress bool `json:"no_compress"`
|
||||
NumThreads int `json:"num_threads"`
|
||||
KeepDebugFlags bool `json:"debug_flags"`
|
||||
}
|
||||
|
||||
func SetFlagIfDefault[T comparable](flag *T, newValue T, defaultValue T) {
|
||||
if *flag == defaultValue {
|
||||
*flag = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig() error {
|
||||
|
@ -34,15 +44,21 @@ func LoadConfig() error {
|
|||
|
||||
cfg := &Config{}
|
||||
if err := json.NewDecoder(file).Decode(cfg); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
*OutputDir = cfg.OutputDir
|
||||
*OS = strings.Join(cfg.OS, ",")
|
||||
*Arch = strings.Join(cfg.Arch, ",")
|
||||
*Silent = cfg.Silent
|
||||
*NoCompress = cfg.NoCompress
|
||||
*NumThreads = cfg.NumThreads
|
||||
SetFlagIfDefault(OutputDir, cfg.OutputDir, DefaultOutputDir)
|
||||
SetFlagIfDefault(OutputFile, cfg.OutputFile, DefaultOutputFile)
|
||||
SetFlagIfDefault(OS, strings.Join(cfg.OS, ","), DefaultOS)
|
||||
SetFlagIfDefault(Arch, strings.Join(cfg.Arch, ","), DefaultArch)
|
||||
SetFlagIfDefault(Silent, cfg.Silent, DefaultSilent)
|
||||
SetFlagIfDefault(NoCompress, cfg.NoCompress, DefaultNoCompress)
|
||||
SetFlagIfDefault(NumThreads, cfg.NumThreads, DefaultNumThreads)
|
||||
SetFlagIfDefault(KeepDebugFlags, cfg.KeepDebugFlags, DefaultKeepDebugFlags)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -66,12 +82,14 @@ func SaveConfig() (string, error) {
|
|||
enc.SetIndent("", "\t")
|
||||
|
||||
cfg := &Config{
|
||||
OutputDir: *OutputDir,
|
||||
OS: strings.Split(*OS, ","),
|
||||
Arch: strings.Split(*Arch, ","),
|
||||
Silent: *Silent,
|
||||
NoCompress: *NoCompress,
|
||||
NumThreads: *NumThreads,
|
||||
OutputDir: *OutputDir,
|
||||
OutputFile: *OutputFile,
|
||||
OS: strings.Split(*OS, ","),
|
||||
Arch: strings.Split(*Arch, ","),
|
||||
Silent: *Silent,
|
||||
NoCompress: *NoCompress,
|
||||
NumThreads: *NumThreads,
|
||||
KeepDebugFlags: *KeepDebugFlags,
|
||||
}
|
||||
|
||||
if err := enc.Encode(cfg); err != nil {
|
||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module git.milar.in/milarin/gocc
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
git.milar.in/milarin/buildinfo v1.0.0
|
||||
git.milar.in/milarin/channel v0.0.7
|
||||
git.milar.in/milarin/configfile v1.0.2
|
||||
git.milar.in/milarin/gmath v0.0.1
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,3 +1,5 @@
|
|||
git.milar.in/milarin/buildinfo v1.0.0 h1:tw98GupUYl/0a/3aPGuezhE4wseycOSsbcLp70hy60U=
|
||||
git.milar.in/milarin/buildinfo v1.0.0/go.mod h1:arI9ZoENOgcZcanv25k9y4dKDUhPp0buJrlVerGruas=
|
||||
git.milar.in/milarin/channel v0.0.7 h1:cVKtwgH/EE7U+XTHcoFCClJ4LR349KanzjX9xKwRcNg=
|
||||
git.milar.in/milarin/channel v0.0.7/go.mod h1:We83LTI8S7u7II3pD+A2ChCDWJfCkcBUCUqii9HjTtM=
|
||||
git.milar.in/milarin/configfile v1.0.2 h1:QgZVSVDsFm3HK7PEg6a2ANeZxqo0JlIooyyJv8VaCNI=
|
||||
|
|
21
init.go
21
init.go
|
@ -7,12 +7,27 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"git.milar.in/milarin/buildinfo"
|
||||
"git.milar.in/milarin/configfile"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
flag.Parse()
|
||||
var err error
|
||||
|
||||
if *ShowVersion {
|
||||
buildinfo.Print(buildinfo.Options{})
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *FindConfigFile {
|
||||
if configFilePath, err := configfile.Path("json"); err == nil {
|
||||
fmt.Println(configFilePath)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
if *SaveConfigFile {
|
||||
if configFilePath, err := SaveConfig(); err == nil {
|
||||
Println(ColorDone.Sprintf("config file saved at '%s'", configFilePath))
|
||||
|
@ -21,12 +36,14 @@ func Init() {
|
|||
Println(ColorError.Sprint(fmt.Errorf("saving config file failed: %w", err)))
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
} else if !*IgnoreConfigFile {
|
||||
if err := LoadConfig(); err != nil {
|
||||
Println(ColorError.Sprint(fmt.Errorf("loading config file failed: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
OutputFileTmpl.Parse(*OutputFile)
|
||||
|
||||
ModulePath, err = filepath.Abs(flag.Arg(0))
|
||||
if err != nil {
|
||||
Println(ColorError.Sprint("determining module path failed"))
|
||||
|
@ -71,7 +88,7 @@ func Init() {
|
|||
|
||||
if !*NoCompress {
|
||||
if _, err := exec.LookPath("upx"); err != nil {
|
||||
Println(ColorWarn.Sprint(os.Stderr, "upx not found in PATH. file compression not possible"))
|
||||
Println(ColorWarn.Sprint("upx not found in PATH. file compression not possible"))
|
||||
*NoCompress = true
|
||||
}
|
||||
}
|
||||
|
|
63
main.go
63
main.go
|
@ -10,6 +10,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"git.milar.in/milarin/channel"
|
||||
|
@ -17,39 +18,69 @@ import (
|
|||
|
||||
// globals
|
||||
var (
|
||||
ModulePath string
|
||||
OutputFileTmpl = template.New("output-file")
|
||||
|
||||
ModulePath string
|
||||
|
||||
ProjectPath string
|
||||
ProjectName string
|
||||
Runner channel.Runner
|
||||
|
||||
// meta data for executables
|
||||
VersionTag string
|
||||
CommitHash string
|
||||
BuildTime string
|
||||
|
||||
Runner channel.Runner
|
||||
|
||||
MaxConfigStringLength int
|
||||
)
|
||||
|
||||
// command line arguments default values
|
||||
var (
|
||||
DefaultOutputDir = "output"
|
||||
DefaultOS = ""
|
||||
DefaultArch = ""
|
||||
DefaultSilent = false
|
||||
DefaultNoCompress = false
|
||||
DefaultNumThreads = runtime.NumCPU()
|
||||
DefaultOutputDir = "output"
|
||||
DefaultOutputFile = "{{.Name}}-{{.OS}}-{{.Arch}}{{.Ext}}"
|
||||
DefaultOS = ""
|
||||
DefaultArch = ""
|
||||
DefaultSilent = false
|
||||
DefaultNoCompress = false
|
||||
DefaultNumThreads = runtime.NumCPU()
|
||||
DefaultKeepDebugFlags = false
|
||||
)
|
||||
|
||||
// command line arguments
|
||||
var (
|
||||
OutputDir = flag.String("o", DefaultOutputDir, "output directory")
|
||||
OutputDir = flag.String("o", DefaultOutputDir, "output directory")
|
||||
OutputFile = flag.String("f", DefaultOutputFile, "go template for filenames")
|
||||
|
||||
OS = flag.String("os", DefaultOS, "comma-separated list of operating systems to compile for (empty for all)")
|
||||
Arch = flag.String("arch", DefaultArch, "comma-separated list of architectures to compile for (empty for all)")
|
||||
|
||||
Silent = flag.Bool("s", DefaultSilent, "silent mode (no output)")
|
||||
NoCompress = flag.Bool("c", DefaultNoCompress, "dont compress any executables")
|
||||
NumThreads = flag.Int("t", DefaultNumThreads, "amount of threads (0 = infinite)")
|
||||
Silent = flag.Bool("s", DefaultSilent, "silent mode (no output)")
|
||||
NoCompress = flag.Bool("c", DefaultNoCompress, "dont compress any executables")
|
||||
NumThreads = flag.Int("t", DefaultNumThreads, "amount of threads (0 = infinite)")
|
||||
KeepDebugFlags = flag.Bool("d", DefaultKeepDebugFlags, "keep debug flags")
|
||||
|
||||
SaveConfigFile = flag.Bool("saveconfig", false, "save config file with current configuration and exit")
|
||||
SaveConfigFile = flag.Bool("saveconfig", false, "save config file with current configuration and exit")
|
||||
IgnoreConfigFile = flag.Bool("ignoreconfig", false, "dont read any config file")
|
||||
FindConfigFile = flag.Bool("findconfig", false, "print config file path and exit")
|
||||
|
||||
DontPrintInstallCmd = flag.Bool("p", false, "print 'go install' command")
|
||||
|
||||
ShowVersion = flag.Bool("v", false, "show version and exit")
|
||||
)
|
||||
|
||||
func main() {
|
||||
Init()
|
||||
GatherMetaData()
|
||||
|
||||
if !*Silent && !*DontPrintInstallCmd {
|
||||
tag := VersionTag
|
||||
if tag == "" || strings.HasPrefix(tag, "(devel") {
|
||||
tag = "latest"
|
||||
}
|
||||
Println(ColorDone.Sprintf("go install -ldflags=\"%s\" %s@%s", BuildLdFlags("", ""), ProjectPath, tag))
|
||||
Println()
|
||||
}
|
||||
|
||||
ch := make(chan *CompileReport, len(CompileConfigs))
|
||||
wg := new(sync.WaitGroup)
|
||||
|
@ -75,7 +106,7 @@ func main() {
|
|||
<-doneCh // wait for Watch routine
|
||||
|
||||
if !*Silent {
|
||||
fmt.Printf("compilation took %s (using %s threads)\n", end.Sub(start), GetThreadCountString())
|
||||
fmt.Printf("compilation took %s (using %s threads)\n", end.Sub(start).Truncate(time.Second/100), GetThreadCountString())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,8 +118,8 @@ func DetermineProjectName() error {
|
|||
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.HasPrefix(line, "module ") {
|
||||
fullName := strings.TrimPrefix(line, "module ")
|
||||
parts := strings.Split(fullName, "/")
|
||||
ProjectPath = strings.TrimPrefix(line, "module ")
|
||||
parts := strings.Split(ProjectPath, "/")
|
||||
ProjectName = parts[len(parts)-1]
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetCommitHash(hashfmt string) (string, error) {
|
||||
gitCmd := exec.Command("git", "log", "-n", "1", "--pretty=format:"+hashfmt)
|
||||
gitCmd.Dir = ModulePath
|
||||
|
||||
stdout, err := gitCmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := gitCmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hashData, err := io.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := gitCmd.Wait(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
trimmedHash := strings.TrimSpace(string(hashData))
|
||||
if trimmedHash == "" {
|
||||
return "", errors.New("no valid hash found")
|
||||
}
|
||||
|
||||
return trimmedHash, nil
|
||||
}
|
||||
|
||||
func GetVersionTag() (string, error) {
|
||||
gitCmd := exec.Command("git", "log", "-n", "1", "--pretty=format:%(describe:tags)")
|
||||
gitCmd.Dir = ModulePath
|
||||
|
||||
stdout, err := gitCmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := gitCmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tagData, err := io.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := gitCmd.Wait(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
trimmedTag := strings.TrimSpace(string(tagData))
|
||||
if trimmedTag == "" {
|
||||
return "", errors.New("no valid version tag found")
|
||||
}
|
||||
|
||||
return trimmedTag, nil
|
||||
}
|
||||
|
||||
func WorkTreeChanged() bool {
|
||||
gitCmd := exec.Command("git", "status", "--porcelain")
|
||||
gitCmd.Dir = ModulePath
|
||||
|
||||
stdout, err := gitCmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := gitCmd.Start(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := gitCmd.Wait(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(data) != 0
|
||||
}
|
||||
|
||||
func GatherMetaData() {
|
||||
BuildTime = time.Now().Format(time.RFC3339)
|
||||
|
||||
if !WorkTreeChanged() {
|
||||
VersionTag, _ = GetVersionTag()
|
||||
CommitHash, _ = GetCommitHash("%H")
|
||||
} else {
|
||||
hash, _ := GetCommitHash("%h")
|
||||
VersionTag = fmt.Sprintf("(devel-%s)", hash)
|
||||
}
|
||||
|
||||
if !*Silent {
|
||||
ColorDone.Println("build info:")
|
||||
fmt.Printf("version: %s\n", VersionTag)
|
||||
fmt.Printf("build time: %s\n", BuildTime)
|
||||
if CommitHash != "" {
|
||||
fmt.Printf("commit hash: %s\n", CommitHash)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func BuildLdFlags(os, arch string) string {
|
||||
b := &strings.Builder{}
|
||||
|
||||
if !*KeepDebugFlags {
|
||||
b.WriteString("-s -w")
|
||||
}
|
||||
|
||||
if CommitHash != "" {
|
||||
b.WriteString(" -X ")
|
||||
b.WriteString("git.milar.in/milarin/buildinfo.Commit")
|
||||
b.WriteString("=")
|
||||
b.WriteString(CommitHash)
|
||||
}
|
||||
|
||||
if VersionTag != "" {
|
||||
b.WriteString(" -X ")
|
||||
b.WriteString("git.milar.in/milarin/buildinfo.Version")
|
||||
b.WriteString("=")
|
||||
b.WriteString(VersionTag)
|
||||
}
|
||||
|
||||
b.WriteString(" -X ")
|
||||
b.WriteString("git.milar.in/milarin/buildinfo.Name")
|
||||
b.WriteString("=")
|
||||
b.WriteString(ProjectName)
|
||||
|
||||
b.WriteString(" -X ")
|
||||
b.WriteString("git.milar.in/milarin/buildinfo.BuildTime")
|
||||
b.WriteString("=")
|
||||
b.WriteString(BuildTime)
|
||||
|
||||
if os != "" {
|
||||
b.WriteString(" -X ")
|
||||
b.WriteString("git.milar.in/milarin/buildinfo.OS")
|
||||
b.WriteString("=")
|
||||
b.WriteString(os)
|
||||
}
|
||||
|
||||
if arch != "" {
|
||||
b.WriteString(" -X ")
|
||||
b.WriteString("git.milar.in/milarin/buildinfo.Arch")
|
||||
b.WriteString("=")
|
||||
b.WriteString(arch)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package main
|
||||
|
||||
type OutputFileTmplData struct {
|
||||
Name string
|
||||
OS string
|
||||
Arch string
|
||||
Ext string
|
||||
}
|
18
watch.go
18
watch.go
|
@ -1,10 +1,15 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Watch(ch <-chan *CompileReport, doneCh chan<- struct{}) {
|
||||
defer close(doneCh)
|
||||
|
||||
times := map[*CompileConfig]time.Duration{}
|
||||
|
||||
states := map[*CompileConfig]CompileState{}
|
||||
for _, cfg := range CompileConfigs {
|
||||
states[cfg] = StateWaiting
|
||||
|
@ -14,8 +19,9 @@ func Watch(ch <-chan *CompileReport, doneCh chan<- struct{}) {
|
|||
|
||||
for report := range ch {
|
||||
states[report.Config] = report.State
|
||||
times[report.Config] = report.Duration.Truncate(time.Second / 100)
|
||||
if !*Silent {
|
||||
PrintStates(states, cleared)
|
||||
PrintStates(states, times, cleared)
|
||||
}
|
||||
cleared = true
|
||||
}
|
||||
|
@ -23,7 +29,7 @@ func Watch(ch <-chan *CompileReport, doneCh chan<- struct{}) {
|
|||
doneCh <- struct{}{}
|
||||
}
|
||||
|
||||
func PrintStates(states map[*CompileConfig]CompileState, clear bool) {
|
||||
func PrintStates(states map[*CompileConfig]CompileState, times map[*CompileConfig]time.Duration, clear bool) {
|
||||
if clear {
|
||||
for i := 0; i < len(CompileConfigs); i++ {
|
||||
goUp()
|
||||
|
@ -38,6 +44,10 @@ func PrintStates(states map[*CompileConfig]CompileState, clear bool) {
|
|||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(fmt.Sprintf("%%-%ds %%s\n", MaxConfigStringLength), cfg.String(), state)
|
||||
if state == StateDone {
|
||||
fmt.Printf(fmt.Sprintf("%%-%ds %%s (%%s)\n", MaxConfigStringLength), cfg.String(), state, times[cfg])
|
||||
} else {
|
||||
fmt.Printf(fmt.Sprintf("%%-%ds %%s\n", MaxConfigStringLength), cfg.String(), state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue