github.com/getgauge/gauge@v1.6.9/build/make.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"runtime"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/getgauge/common"
    22  	"github.com/getgauge/gauge/version"
    23  )
    24  
    25  const (
    26  	CGO_ENABLED       = "CGO_ENABLED"
    27  	GOARCH            = "GOARCH"
    28  	GOOS              = "GOOS"
    29  	X86               = "386"
    30  	X86_64            = "amd64"
    31  	ARM64             = "arm64"
    32  	darwin            = "darwin"
    33  	linux             = "linux"
    34  	freebsd           = "freebsd"
    35  	windows           = "windows"
    36  	bin               = "bin"
    37  	gauge             = "gauge"
    38  	deploy            = "deploy"
    39  	CC                = "CC"
    40  	nightlyDatelayout = "2006-01-02"
    41  )
    42  
    43  var deployDir = filepath.Join(deploy, gauge)
    44  
    45  func runProcess(command string, arg ...string) {
    46  	cmd := exec.Command(command, arg...)
    47  	cmd.Stdout = os.Stdout
    48  	cmd.Stderr = os.Stderr
    49  	if *verbose {
    50  		log.Printf("Execute %v\n", cmd.Args)
    51  	}
    52  	err := cmd.Run()
    53  	if err != nil {
    54  		panic(err)
    55  	}
    56  }
    57  
    58  func runCommand(command string, arg ...string) (string, error) {
    59  	cmd := exec.Command(command, arg...)
    60  	bytes, err := cmd.Output()
    61  	return strings.TrimSpace(string(bytes)), err
    62  }
    63  
    64  var buildMetadata string
    65  var commitHash string
    66  
    67  func getBuildVersion() string {
    68  	if buildMetadata != "" {
    69  		return fmt.Sprintf("%s.%s", version.CurrentGaugeVersion.String(), buildMetadata)
    70  	}
    71  	return version.CurrentGaugeVersion.String()
    72  }
    73  
    74  func compileGauge() {
    75  	executablePath := getGaugeExecutablePath(gauge)
    76  	ldflags := fmt.Sprintf("-X github.com/getgauge/gauge/version.BuildMetadata=%s -X github.com/getgauge/gauge/version.CommitHash=%s", buildMetadata, commitHash)
    77  	args := []string{
    78  		"build",
    79  		fmt.Sprintf("-gcflags=-trimpath=%s", os.Getenv("GOPATH")),
    80  		fmt.Sprintf("-asmflags=-trimpath=%s", os.Getenv("GOPATH")),
    81  		"-ldflags", ldflags, "-o", executablePath,
    82  	}
    83  	runProcess("go", args...)
    84  }
    85  
    86  func runTests(coverage bool) {
    87  	if coverage {
    88  		runProcess("go", "test", "-covermode=count", "-coverprofile=count.out")
    89  		if coverage {
    90  			runProcess("go", "tool", "cover", "-html=count.out")
    91  		}
    92  	} else {
    93  		if *verbose {
    94  			runProcess("go", "test", "./...", "-v")
    95  		} else {
    96  			runProcess("go", "test", "./...")
    97  		}
    98  	}
    99  }
   100  
   101  // key will be the source file and value will be the target
   102  func installFiles(files map[string]string, installDir string) {
   103  	for src, dst := range files {
   104  		base := filepath.Base(src)
   105  		installDst := filepath.Join(installDir, dst)
   106  		if *verbose {
   107  			log.Printf("Install %s -> %s\n", src, installDst)
   108  		}
   109  		stat, err := os.Stat(src)
   110  		if err != nil {
   111  			panic(err)
   112  		}
   113  		if stat.IsDir() {
   114  			_, err = common.MirrorDir(src, installDst)
   115  		} else {
   116  			err = common.MirrorFile(src, filepath.Join(installDst, base))
   117  		}
   118  		if err != nil {
   119  			panic(err)
   120  		}
   121  	}
   122  }
   123  
   124  func copyGaugeBinaries(installPath string) {
   125  	files := make(map[string]string)
   126  	files[getGaugeExecutablePath(gauge)] = ""
   127  	installFiles(files, installPath)
   128  }
   129  
   130  func setEnv(envVariables map[string]string) {
   131  	for k, v := range envVariables {
   132  		if err := os.Setenv(k, v); err != nil {
   133  			log.Printf("failed to set env %s", k)
   134  		}
   135  	}
   136  }
   137  
   138  var test = flag.Bool("test", false, "Run the test cases")
   139  var coverage = flag.Bool("coverage", false, "Run the test cases and show the coverage")
   140  var install = flag.Bool("install", false, "Install to the specified prefix")
   141  var nightly = flag.Bool("nightly", false, "Add nightly build information")
   142  var gaugeInstallPrefix = flag.String("prefix", "", "Specifies the prefix where gauge files will be installed")
   143  var allPlatforms = flag.Bool("all-platforms", false, "Compiles for all platforms windows, linux, darwin both x86 and x86_64")
   144  var targetLinux = flag.Bool("target-linux", false, "Compiles for linux only, both x86 and x86_64")
   145  var binDir = flag.String("bin-dir", "", "Specifies OS_PLATFORM specific binaries to install when cross compiling")
   146  var distro = flag.Bool("distro", false, "Create gauge distributable")
   147  var verbose = flag.Bool("verbose", false, "Print verbose details")
   148  var skipWindowsDistro = flag.Bool("skip-windows", false, "Skips creation of windows distributable on unix machines while cross platform compilation")
   149  var certFile = flag.String("certFile", "", "Should be passed for signing the windows installer")
   150  
   151  // Defines all the compile targets
   152  // Each target name is the directory name
   153  var (
   154  	platformEnvs = []map[string]string{
   155  		{GOARCH: ARM64, GOOS: darwin, CGO_ENABLED: "0"},
   156  		{GOARCH: X86_64, GOOS: darwin, CGO_ENABLED: "0"},
   157  		{GOARCH: X86, GOOS: linux, CGO_ENABLED: "0"},
   158  		{GOARCH: X86_64, GOOS: linux, CGO_ENABLED: "0"},
   159  		{GOARCH: ARM64, GOOS: linux, CGO_ENABLED: "0"},
   160  		{GOARCH: X86, GOOS: freebsd, CGO_ENABLED: "0"},
   161  		{GOARCH: X86_64, GOOS: freebsd, CGO_ENABLED: "0"},
   162  		{GOARCH: X86, GOOS: windows, CC: "i586-mingw32-gcc", CGO_ENABLED: "1"},
   163  		{GOARCH: X86_64, GOOS: windows, CC: "x86_64-w64-mingw32-gcc", CGO_ENABLED: "1"},
   164  	}
   165  	osDistroMap = map[string]distroFunc{windows: createWindowsDistro, linux: createLinuxPackage, freebsd: createLinuxPackage, darwin: createDarwinPackage}
   166  )
   167  
   168  func main() {
   169  	flag.Parse()
   170  	commitHash = revParseHead()
   171  	if *nightly {
   172  		buildMetadata = fmt.Sprintf("nightly-%s", time.Now().Format(nightlyDatelayout))
   173  	}
   174  	if *verbose {
   175  		fmt.Println("Build: " + buildMetadata)
   176  	}
   177  	switch {
   178  	case *test:
   179  		runTests(*coverage)
   180  	case *install:
   181  		installGauge()
   182  	case *distro:
   183  		createGaugeDistributables(*allPlatforms)
   184  	default:
   185  		if *allPlatforms {
   186  			crossCompileGauge()
   187  		} else {
   188  			compileGauge()
   189  		}
   190  	}
   191  }
   192  
   193  func revParseHead() string {
   194  	if _, err := os.Stat(".git"); err != nil {
   195  		return ""
   196  	}
   197  	cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
   198  	var hash bytes.Buffer
   199  	cmd.Stdout = &hash
   200  	err := cmd.Run()
   201  	if err != nil {
   202  		log.Fatal(err)
   203  	}
   204  	return strings.TrimSpace(hash.String())
   205  }
   206  
   207  func filteredPlatforms() []map[string]string {
   208  	filteredPlatformEnvs := platformEnvs[:0]
   209  	for _, x := range platformEnvs {
   210  		if *targetLinux {
   211  			if x[GOOS] == linux {
   212  				filteredPlatformEnvs = append(filteredPlatformEnvs, x)
   213  			}
   214  		} else {
   215  			filteredPlatformEnvs = append(filteredPlatformEnvs, x)
   216  		}
   217  	}
   218  	return filteredPlatformEnvs
   219  }
   220  
   221  func crossCompileGauge() {
   222  	for _, platformEnv := range filteredPlatforms() {
   223  		setEnv(platformEnv)
   224  		if *verbose {
   225  			log.Printf("Compiling for platform => OS:%s ARCH:%s \n", platformEnv[GOOS], platformEnv[GOARCH])
   226  		}
   227  		compileGauge()
   228  	}
   229  }
   230  
   231  func installGauge() {
   232  	updateGaugeInstallPrefix()
   233  	copyGaugeBinaries(deployDir)
   234  	if _, err := common.MirrorDir(filepath.Join(deployDir), filepath.Join(*gaugeInstallPrefix, bin)); err != nil {
   235  		panic(fmt.Sprintf("Could not install gauge : %s", err))
   236  	}
   237  }
   238  
   239  func createGaugeDistributables(forAllPlatforms bool) {
   240  	if forAllPlatforms {
   241  		for _, platformEnv := range filteredPlatforms() {
   242  			setEnv(platformEnv)
   243  			if *verbose {
   244  				log.Printf("Creating distro for platform => OS:%s ARCH:%s \n", platformEnv[GOOS], platformEnv[GOARCH])
   245  			}
   246  			createDistro()
   247  		}
   248  	} else {
   249  		createDistro()
   250  	}
   251  }
   252  
   253  type distroFunc func()
   254  
   255  func createDistro() {
   256  	osDistroMap[getGOOS()]()
   257  }
   258  
   259  func createWindowsDistro() {
   260  	if !*skipWindowsDistro {
   261  		createWindowsInstaller()
   262  	}
   263  }
   264  
   265  func createWindowsInstaller() {
   266  	pName := packageName()
   267  	distroDir, err := filepath.Abs(filepath.Join(deploy, pName))
   268  	installerFileName := filepath.Join(filepath.Dir(distroDir), pName)
   269  	if err != nil {
   270  		panic(err)
   271  	}
   272  	executableFile := getGaugeExecutablePath(gauge)
   273  	signExecutable(executableFile, *certFile)
   274  	copyGaugeBinaries(distroDir)
   275  	runProcess("makensis.exe",
   276  		fmt.Sprintf("/DPRODUCT_VERSION=%s", getBuildVersion()),
   277  		fmt.Sprintf("/DGAUGE_DISTRIBUTABLES_DIR=%s", distroDir),
   278  		fmt.Sprintf("/DOUTPUT_FILE_NAME=%s.exe", installerFileName),
   279  		filepath.Join("build", "install", "windows", "gauge-install.nsi"))
   280  	createZipFromUtil(deploy, pName, pName)
   281  	if err := os.RemoveAll(distroDir); err != nil {
   282  		log.Printf("failed to remove %s", distroDir)
   283  	}
   284  	signExecutable(installerFileName+".exe", *certFile)
   285  }
   286  
   287  func signExecutable(exeFilePath string, certFilePath string) {
   288  	if getGOOS() == windows {
   289  		if certFilePath != "" {
   290  			log.Printf("Signing: %s", exeFilePath)
   291  			runProcess("signtool", "sign", "/f", certFilePath, "/debug", "/v", "/tr", "http://timestamp.digicert.com", "/a", "/fd", "sha256", "/td", "sha256", "/as", exeFilePath)
   292  		} else {
   293  			log.Printf("No certificate file passed. Executable won't be signed.")
   294  		}
   295  	}
   296  }
   297  
   298  func createDarwinPackage() {
   299  	distroDir := filepath.Join(deploy, packageName())
   300  	copyGaugeBinaries(distroDir)
   301  	if id := os.Getenv("OS_SIGNING_IDENTITY"); id == "" {
   302  		log.Printf("No signing identity found . Executable won't be signed.")
   303  	} else {
   304  		runProcess("codesign", "-s", id, "--force", "--deep", filepath.Join(distroDir, gauge))
   305  	}
   306  	createZipFromUtil(deploy, packageName(), packageName())
   307  	if err := os.RemoveAll(distroDir); err != nil {
   308  		log.Printf("failed to remove %s", distroDir)
   309  	}
   310  }
   311  
   312  func createLinuxPackage() {
   313  	distroDir := filepath.Join(deploy, packageName())
   314  	copyGaugeBinaries(distroDir)
   315  	createZipFromUtil(deploy, packageName(), packageName())
   316  	if err := os.RemoveAll(distroDir); err != nil {
   317  		log.Printf("failed to remove %s", distroDir)
   318  	}
   319  }
   320  
   321  func packageName() string {
   322  	return fmt.Sprintf("%s-%s-%s.%s", gauge, getBuildVersion(), getGOOS(), getPackageArchSuffix())
   323  }
   324  
   325  func removeUnwatedFiles(dir, currentOS string) error {
   326  	fileList := []string{
   327  		".DS_STORE",
   328  		".localized",
   329  		"$RECYCLE.BIN",
   330  	}
   331  	if currentOS == "windows" {
   332  		fileList = append(fileList, []string{
   333  			"desktop.ini",
   334  			"Thumbs.db",
   335  		}...)
   336  	}
   337  	for _, f := range fileList {
   338  		err := os.RemoveAll(filepath.Join(dir, f))
   339  		if err != nil && !os.IsNotExist(err) {
   340  			return err
   341  		}
   342  	}
   343  	return nil
   344  }
   345  
   346  func createZipFromUtil(dir, zipDir, pkgName string) {
   347  	wd, err := os.Getwd()
   348  	if err != nil {
   349  		panic(err)
   350  	}
   351  	absdir, err := filepath.Abs(dir)
   352  	if err != nil {
   353  		panic(err)
   354  	}
   355  	currentOS := getGOOS()
   356  
   357  	windowsZipScript := filepath.Join(wd, "build", "create_windows_zipfile.ps1")
   358  
   359  	err = removeUnwatedFiles(filepath.Join(dir, zipDir), currentOS)
   360  
   361  	if err != nil {
   362  		panic(fmt.Sprintf("Failed to cleanup unwanted file(s): %s", err))
   363  	}
   364  
   365  	err = os.Chdir(filepath.Join(dir, zipDir))
   366  	if err != nil {
   367  		panic(fmt.Sprintf("Failed to change directory: %s", err))
   368  	}
   369  
   370  	zipcmd := "zip"
   371  	zipargs := []string{"-r", filepath.Join("..", pkgName+".zip"), "."}
   372  	if currentOS == "windows" {
   373  		zipcmd = "powershell.exe"
   374  		zipargs = []string{"-noprofile", "-executionpolicy", "bypass", "-file", windowsZipScript, filepath.Join(absdir, zipDir), filepath.Join(absdir, pkgName+".zip")}
   375  	}
   376  	output, err := runCommand(zipcmd, zipargs...)
   377  	if *verbose {
   378  		fmt.Println(output)
   379  	}
   380  	if err != nil {
   381  		panic(fmt.Sprintf("Failed to zip: %s", err))
   382  	}
   383  	err = os.Chdir(wd)
   384  	if err != nil {
   385  		panic(fmt.Sprintf("Unable to set working directory to %s: %s", wd, err.Error()))
   386  	}
   387  }
   388  
   389  func updateGaugeInstallPrefix() {
   390  	if *gaugeInstallPrefix == "" {
   391  		if runtime.GOOS == "windows" {
   392  			*gaugeInstallPrefix = os.Getenv("PROGRAMFILES")
   393  			if *gaugeInstallPrefix == "" {
   394  				panic(fmt.Errorf("failed to find programfiles"))
   395  			}
   396  			*gaugeInstallPrefix = filepath.Join(*gaugeInstallPrefix, gauge)
   397  		} else {
   398  			*gaugeInstallPrefix = "/usr/local"
   399  		}
   400  	}
   401  }
   402  
   403  func getGaugeExecutablePath(file string) string {
   404  	return filepath.Join(getBinDir(), getExecutableName(file))
   405  }
   406  
   407  func getBinDir() string {
   408  	if *binDir != "" {
   409  		return *binDir
   410  	}
   411  	return filepath.Join(bin, fmt.Sprintf("%s_%s", getGOOS(), getGOARCH()))
   412  }
   413  
   414  func getExecutableName(file string) string {
   415  	if getGOOS() == windows {
   416  		return file + ".exe"
   417  	}
   418  	return file
   419  }
   420  
   421  func getGOARCH() string {
   422  	goArch := os.Getenv(GOARCH)
   423  	if goArch == "" {
   424  		goArch = runtime.GOARCH
   425  	}
   426  	return goArch
   427  }
   428  
   429  func getGOOS() string {
   430  	goOS := os.Getenv(GOOS)
   431  	if goOS == "" {
   432  		goOS = runtime.GOOS
   433  	}
   434  	return goOS
   435  }
   436  
   437  func getPackageArchSuffix() string {
   438  	if strings.HasSuffix(*binDir, "386") {
   439  		return "x86"
   440  	}
   441  
   442  	if strings.HasSuffix(*binDir, "amd64") {
   443  		return "x86_64"
   444  	}
   445  
   446  	if arch := getGOARCH(); arch == "arm64" {
   447  		return "arm64"
   448  	}
   449  
   450  	if arch := getGOARCH(); arch == X86 {
   451  		return "x86"
   452  	}
   453  	return "x86_64"
   454  }