github.com/ezbuy/gauge@v0.9.4-0.20171013092048-7ac5bd3931cd/build/make.go (about)

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