github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/tools/winresource/main.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  // This is a utility which binds to libkb to get the correct version
     5  // for printing out or generating compiled resources for the windows
     6  // executable.
     7  
     8  //go:build windows
     9  // +build windows
    10  
    11  package main
    12  
    13  import (
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"log"
    18  	"os"
    19  	"os/exec"
    20  	"reflect"
    21  	"time"
    22  
    23  	"strconv"
    24  
    25  	"github.com/akavel/rsrc/binutil"
    26  	"github.com/akavel/rsrc/coff"
    27  	"github.com/josephspurrier/goversioninfo"
    28  	"github.com/keybase/client/go/libkb"
    29  )
    30  
    31  func getBuildName() string {
    32  	// Todo: use regular build number when not doing prerelease
    33  
    34  	gitHash, err := exec.Command("cmd", "/C", "git", "log", "-1", "--pretty=format:%h").Output()
    35  	if err != nil {
    36  		log.Print("Error generating githash", err)
    37  		os.Exit(3)
    38  	}
    39  
    40  	return fmt.Sprintf("%d%02d%02d%02d%02d%02d+%s", time.Now().Year(), time.Now().Month(), time.Now().Day(), time.Now().Hour(), time.Now().Minute(), time.Now().Second(), gitHash)
    41  
    42  }
    43  
    44  // kbWriteSyso creates a resource file from the version info and optionally an icon.
    45  // arch must be an architecture string accepted by coff.Arch, like "386" or "amd64"
    46  // Note this is partially lifted from josephspurrier/goversioninfo, which we
    47  // extend here to write multiple icons.
    48  func kbWriteSyso(vi *goversioninfo.VersionInfo, filename string, arch string, icons []string) error {
    49  
    50  	// Channel for generating IDs
    51  	newID := make(chan uint16)
    52  	go func() {
    53  		for i := uint16(1); ; i++ {
    54  			newID <- i
    55  		}
    56  	}()
    57  
    58  	// Create a new RSRC section
    59  	coff := coff.NewRSRC()
    60  
    61  	// Set the architechture
    62  	err := coff.Arch(arch)
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	// ID 16 is for Version Information
    68  	coff.AddResource(16, 1, goversioninfo.SizedReader{Buffer: &vi.Buffer})
    69  
    70  	// NOTE: if/when we start using a manifest, it goes here
    71  
    72  	// If icon is enabled
    73  	if vi.IconPath != "" {
    74  		if err := addIcon(coff, vi.IconPath, newID); err != nil {
    75  			return err
    76  		}
    77  	}
    78  	// if extra icons were passed in
    79  	for _, i := range icons {
    80  		if i != "" {
    81  			if err := addIcon(coff, i, newID); err != nil {
    82  				return err
    83  			}
    84  		}
    85  	}
    86  
    87  	coff.Freeze()
    88  
    89  	// Write to file
    90  	return writeCoff(coff, filename)
    91  }
    92  
    93  // From josephspurrier/goversioninfo
    94  func writeCoff(coff *coff.Coff, fnameout string) error {
    95  	out, err := os.Create(fnameout)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	if err = writeCoffTo(out, coff); err != nil {
   100  		return fmt.Errorf("error writing %q: %v", fnameout, err)
   101  	}
   102  	return nil
   103  }
   104  
   105  // From josephspurrier/goversioninfo
   106  func writeCoffTo(w io.WriteCloser, coff *coff.Coff) error {
   107  	bw := binutil.Writer{W: w}
   108  
   109  	// write the resulting file to disk
   110  	binutil.Walk(coff, func(v reflect.Value, path string) error {
   111  		if binutil.Plain(v.Kind()) {
   112  			bw.WriteLE(v.Interface())
   113  			return nil
   114  		}
   115  		vv, ok := v.Interface().(binutil.SizedReader)
   116  		if ok {
   117  			bw.WriteFromSized(vv)
   118  			return binutil.WALK_SKIP
   119  		}
   120  		return nil
   121  	})
   122  
   123  	err := bw.Err
   124  	if closeErr := w.Close(); closeErr != nil && err == nil {
   125  		err = closeErr
   126  	}
   127  	return err
   128  }
   129  
   130  // Create the syso and custom build file
   131  func main() {
   132  
   133  	outPtr := flag.String("o", "rsrc_windows.syso", "resource output pathname")
   134  	printverPtr := flag.Bool("v", false, "print version to console (no .syso output)")
   135  	printCustomVerPtr := flag.Bool("cv", false, "print custom version to console (no .syso output)")
   136  	printCustomBuildPtr := flag.Bool("cb", false, "print custom build number to console (no .syso output)")
   137  	printWinVerPtr := flag.Bool("w", false, "print windows format version to console (no .syso output)")
   138  	iconPtr := flag.String("i", "../../media/icons/Keybase.ico", "icon pathname")
   139  	kbfsIconPtr := flag.String("kbfsicon", "", "icon pathname")
   140  	fileDescriptionPtr := flag.String("d", "Keybase utility", "File Description")
   141  	originalFilenamePtr := flag.String("n", "keybase.exe", "File name")
   142  
   143  	flag.Parse()
   144  
   145  	var fv goversioninfo.FileVersion
   146  
   147  	if int, err := fmt.Sscanf(libkb.Version, "%d.%d.%d", &fv.Major, &fv.Minor, &fv.Patch); int != 3 || err != nil {
   148  		log.Printf("Error parsing version %v", err)
   149  		os.Exit(3)
   150  	}
   151  
   152  	fv.Build, _ = strconv.Atoi(os.Getenv("KEYBASE_WINBUILD"))
   153  	if fv.Build == 0 && libkb.PrereleaseBuild != "" {
   154  		fv.Build, _ = strconv.Atoi(libkb.PrereleaseBuild)
   155  	}
   156  
   157  	semVer := fmt.Sprintf("%d.%d.%d-%d", fv.Major, fv.Minor, fv.Patch, fv.Build)
   158  
   159  	if *printverPtr {
   160  		fmt.Print(semVer)
   161  		return
   162  	}
   163  
   164  	if *printWinVerPtr {
   165  		fmt.Printf("%d.%d.%d.%d", fv.Major, fv.Minor, fv.Patch, fv.Build)
   166  		return
   167  	}
   168  
   169  	if *printCustomVerPtr {
   170  		customVer := fmt.Sprintf("%d.%d.%d-%s", fv.Major, fv.Minor, fv.Patch, getBuildName())
   171  		fmt.Print(customVer)
   172  		return
   173  	}
   174  
   175  	if *printCustomBuildPtr {
   176  		fmt.Printf("%s", getBuildName())
   177  		return
   178  	}
   179  
   180  	// Create a new container
   181  	vi := &goversioninfo.VersionInfo{
   182  		FixedFileInfo: goversioninfo.FixedFileInfo{
   183  			FileVersion:    fv,
   184  			ProductVersion: fv,
   185  			FileFlagsMask:  "3f",
   186  			FileFlags:      "00",
   187  			FileOS:         "040004",
   188  			FileType:       "01",
   189  			FileSubType:    "00",
   190  		},
   191  
   192  		StringFileInfo: goversioninfo.StringFileInfo{
   193  			CompanyName:      "Keybase, Inc.",
   194  			FileDescription:  *fileDescriptionPtr,
   195  			InternalName:     "Keybase",
   196  			LegalCopyright:   "Copyright (c) 2015, Keybase",
   197  			OriginalFilename: *originalFilenamePtr,
   198  			ProductName:      "Keybase",
   199  			ProductVersion:   libkb.VersionString(),
   200  		},
   201  		VarFileInfo: goversioninfo.VarFileInfo{
   202  			Translation: goversioninfo.Translation{
   203  				LangID:    0x409, // english
   204  				CharsetID: 0x4B0, // unicode
   205  			},
   206  		},
   207  	}
   208  
   209  	// Fill the structures with config data
   210  	vi.Build()
   211  
   212  	// Write the data to a buffer
   213  	vi.Walk()
   214  
   215  	// Optionally, embed an icon by path
   216  	// If the icon has multiple sizes, all of the sizes will be embedded
   217  	vi.IconPath = *iconPtr
   218  
   219  	// Create the file
   220  	if err := kbWriteSyso(vi, *outPtr, "386", []string{*kbfsIconPtr}); err != nil {
   221  		log.Printf("Error writing %s: %v", *outPtr, err)
   222  		os.Exit(3)
   223  	}
   224  
   225  }