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 }