diff --git a/assets/logo.ico b/assets/logo.ico new file mode 100644 index 00000000..ff3b7405 Binary files /dev/null and b/assets/logo.ico differ diff --git a/build.go b/build.go index 7d400d6f..79c8e72b 100644 --- a/build.go +++ b/build.go @@ -380,6 +380,7 @@ func setup() { "honnef.co/go/tools/cmd/gosimple", "honnef.co/go/tools/cmd/staticcheck", "honnef.co/go/tools/cmd/unused", + "github.com/josephspurrier/goversioninfo", } for _, pkg := range packages { fmt.Println(pkg) @@ -427,6 +428,17 @@ func install(target target, tags []string) { os.Setenv("GOOS", goos) os.Setenv("GOARCH", goarch) + + // On Windows generate a special file which the Go compiler will automatically use when generating Windows binaries + // to set things like the file icon, version, etc. + if goos == "windows" { + sysoPath, err := shouldBuildSyso(cwd) + if err != nil { + log.Printf("Warning: Windows binaries will not have file information encoded: %v", err) + } + defer shouldCleanupSyso(sysoPath) + } + runPrint("go", args...) } @@ -621,6 +633,56 @@ func buildSnap(target target) { runPrint("snapcraft") } +func shouldBuildSyso(dir string) (string, error) { + jsonPath := filepath.Join(dir, "versioninfo.json") + file, err := os.Create(filepath.Join(dir, "versioninfo.json")) + if err != nil { + return "", errors.New("failed to create " + jsonPath + ": " + err.Error()) + } + + major, minor, patch, build := semanticVersion() + fmt.Fprintf(file, `{ + "FixedFileInfo": { + "FileVersion": { + "Major": %s, + "Minor": %s, + "Patch": %s, + "Build": %s + } + }, + "StringFileInfo": { + "FileDescription": "Open Source Continuous File Synchronization", + "LegalCopyright": "The Syncthing Authors", + "ProductVersion": "%s", + "ProductName": "Syncthing" + }, + "IconPath": "assets/logo.ico" +}`, major, minor, patch, build, getVersion()) + file.Close() + defer func() { + if err := os.Remove(jsonPath); err != nil { + log.Printf("Warning: unable to remove generated %s: %v. Please remove it manually.", jsonPath, err) + } + }() + + sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso") + + if _, err := runError("goversioninfo", "-o", sysoPath); err != nil { + return "", errors.New("failed to create " + sysoPath + ": " + err.Error()) + } + + return sysoPath, nil +} + +func shouldCleanupSyso(sysoFilePath string) { + if sysoFilePath == "" { + return + } + if err := os.Remove(sysoFilePath); err != nil { + log.Printf("Warning: unable to remove generated %s: %v. Please remove it manually.", sysoFilePath, err) + } +} + // copyFile copies a file from src to dst, ensuring the containing directory // exists. The permission bits are copied as well. If dst already exists and // the contents are identical to src the modification time is not updated. @@ -797,6 +859,15 @@ func getVersion() string { return "unknown-dev" } +func semanticVersion() (major, minor, patch, build string) { + r := regexp.MustCompile(`v(?P\d+)\.(?P\d+).(?P\d+).*\+(?P\d+)`) + matches := r.FindStringSubmatch(getVersion()) + if len(matches) != 5 { + return "0", "0", "0", "0" + } + return matches[1], matches[2], matches[3], matches[4] +} + func getBranchSuffix() string { bs, err := runError("git", "branch", "-a", "--contains") if err != nil {