github.com/goplus/gop@v1.2.6/cmd/make.go (about) 1 //go:build ignore 2 // +build ignore 3 4 /* 5 * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 package main 21 22 import ( 23 "bytes" 24 "flag" 25 "fmt" 26 "log" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "regexp" 31 "runtime" 32 "strings" 33 "time" 34 ) 35 36 func checkPathExist(path string, isDir bool) bool { 37 stat, err := os.Lstat(path) // Note: os.Lstat() will not follow the symbolic link. 38 isExists := !os.IsNotExist(err) 39 if isDir { 40 return isExists && stat.IsDir() 41 } 42 return isExists && !stat.IsDir() 43 } 44 45 func trimRight(s string) string { 46 return strings.TrimRight(s, " \t\r\n") 47 } 48 49 // Path returns single path to check 50 type Path struct { 51 path string 52 isDir bool 53 } 54 55 func (p *Path) checkExists(rootDir string) bool { 56 absPath := filepath.Join(rootDir, p.path) 57 return checkPathExist(absPath, p.isDir) 58 } 59 60 func getGopRoot() string { 61 pwd, _ := os.Getwd() 62 63 pathsToCheck := []Path{ 64 {path: "cmd/gop", isDir: true}, 65 {path: "builtin", isDir: true}, 66 {path: "go.mod", isDir: false}, 67 {path: "go.sum", isDir: false}, 68 } 69 70 for _, path := range pathsToCheck { 71 if !path.checkExists(pwd) { 72 println("Error: This script should be run at the root directory of gop repository.") 73 os.Exit(1) 74 } 75 } 76 return pwd 77 } 78 79 var gopRoot = getGopRoot() 80 var initCommandExecuteEnv = os.Environ() 81 var commandExecuteEnv = initCommandExecuteEnv 82 83 // Always put `gop` command as the first item, as it will be referenced by below code. 84 var gopBinFiles = []string{"gop", "gopfmt"} 85 86 const ( 87 inWindows = (runtime.GOOS == "windows") 88 ) 89 90 func init() { 91 if inWindows { 92 for index, file := range gopBinFiles { 93 file += ".exe" 94 gopBinFiles[index] = file 95 } 96 } 97 } 98 99 type ExecCmdError struct { 100 Err error 101 Stderr string 102 } 103 104 func (p *ExecCmdError) Error() string { 105 if e := p.Stderr; e != "" { 106 return e 107 } 108 return p.Err.Error() 109 } 110 111 type iGitRemote interface { 112 CheckRemoteUrl() 113 CreateBranchFrom(branch, remote string) error 114 PushCommits(remote, branch string) error 115 DeleteBranch(branch string) error 116 } 117 118 type ( 119 gitRemoteImpl struct{} 120 gitRemoteNone struct{} 121 ) 122 123 func (p *gitRemoteImpl) CheckRemoteUrl() { 124 if getGitRemoteUrl("gop") == "" { 125 log.Fatalln("Error: git remote gop not found, please use `git remote add gop git@github.com:goplus/gop.git`.") 126 } 127 } 128 129 func (p *gitRemoteImpl) CreateBranchFrom(branch, remote string) (err error) { 130 _, err = execCommand("git", "fetch", remote) 131 if err != nil { 132 return 133 } 134 execCommand("git", "branch", "-D", branch) 135 _, err = execCommand("git", "checkout", "-b", branch, remote+"/"+branch) 136 return 137 } 138 139 func (p *gitRemoteImpl) PushCommits(remote, branch string) error { 140 _, err := execCommand("git", "push", remote, branch) 141 return err 142 } 143 144 func (p *gitRemoteImpl) DeleteBranch(branch string) error { 145 _, err := execCommand("git", "branch", "-D", branch) 146 return err 147 } 148 149 func (p *gitRemoteNone) CheckRemoteUrl() {} 150 func (p *gitRemoteNone) CreateBranchFrom(branch, remote string) (err error) { return nil } 151 func (p *gitRemoteNone) PushCommits(remote, branch string) error { return nil } 152 func (p *gitRemoteNone) DeleteBranch(branch string) error { return nil } 153 154 var ( 155 gitRemote iGitRemote = &gitRemoteImpl{} 156 ) 157 158 func execCommand(command string, arg ...string) (string, error) { 159 var stdout, stderr bytes.Buffer 160 cmd := exec.Command(command, arg...) 161 cmd.Stdout = &stdout 162 cmd.Stderr = &stderr 163 cmd.Env = commandExecuteEnv 164 err := cmd.Run() 165 if err != nil { 166 err = &ExecCmdError{Err: err, Stderr: stderr.String()} 167 } 168 return stdout.String(), err 169 } 170 171 func getTagRev(tag string) string { 172 const commit = "commit " 173 stdout, err := execCommand("git", "show", tag) 174 if err != nil || !strings.HasPrefix(stdout, commit) { 175 return "" 176 } 177 data := stdout[len(commit):] 178 if pos := strings.IndexByte(data, '\n'); pos > 0 { 179 return data[:pos] 180 } 181 return "" 182 } 183 184 func getGitRemoteUrl(name string) string { 185 stdout, err := execCommand("git", "remote", "get-url", name) 186 if err != nil { 187 return "" 188 } 189 return stdout 190 } 191 192 func getGitBranch() string { 193 stdout, err := execCommand("git", "rev-parse", "--abbrev-ref", "HEAD") 194 if err != nil { 195 return "" 196 } 197 return trimRight(stdout) 198 } 199 200 func gitTag(tag string) error { 201 _, err := execCommand("git", "tag", tag) 202 return err 203 } 204 205 func gitTagAndPushTo(tag string, remote, branch string) error { 206 if err := gitRemote.PushCommits(remote, branch); err != nil { 207 return err 208 } 209 if err := gitTag(tag); err != nil { 210 return err 211 } 212 return gitRemote.PushCommits(remote, tag) 213 } 214 215 func gitAdd(file string) error { 216 _, err := execCommand("git", "add", file) 217 return err 218 } 219 220 func gitCommit(msg string) error { 221 out, err := execCommand("git", "commit", "-a", "-m", msg) 222 if err != nil { 223 if e := err.(*ExecCmdError); e.Stderr == "" { 224 e.Stderr = out 225 } 226 } 227 return err 228 } 229 230 func gitCheckoutBranch(branch string) error { 231 _, err := execCommand("git", "checkout", branch) 232 return err 233 } 234 235 func isGitRepo() bool { 236 gitDir, err := execCommand("git", "rev-parse", "--git-dir") 237 if err != nil { 238 return false 239 } 240 return checkPathExist(filepath.Join(gopRoot, trimRight(gitDir)), true) 241 } 242 243 func getBuildDateTime() string { 244 now := time.Now() 245 return now.Format("2006-01-02_15-04-05") 246 } 247 248 func getBuildVer() string { 249 latestTagCommit, err := execCommand("git", "rev-list", "--tags", "--max-count=1") 250 if err != nil { 251 return "" 252 } 253 254 stdout, err := execCommand("git", "describe", "--tags", trimRight(latestTagCommit)) 255 if err != nil { 256 return "" 257 } 258 259 return fmt.Sprintf("%s devel", trimRight(stdout)) 260 } 261 262 func getGopBuildFlags() string { 263 defaultGopRoot := gopRoot 264 if gopRootFinal := os.Getenv("GOPROOT_FINAL"); gopRootFinal != "" { 265 defaultGopRoot = gopRootFinal 266 } 267 buildFlags := fmt.Sprintf("-X \"github.com/goplus/gop/env.defaultGopRoot=%s\"", defaultGopRoot) 268 buildFlags += fmt.Sprintf(" -X \"github.com/goplus/gop/env.buildDate=%s\"", getBuildDateTime()) 269 270 version := findGopVersion() 271 buildFlags += fmt.Sprintf(" -X \"github.com/goplus/gop/env.buildVersion=%s\"", version) 272 273 return buildFlags 274 } 275 276 func detectGopBinPath() string { 277 return filepath.Join(gopRoot, "bin") 278 } 279 280 func detectGoBinPath() string { 281 goBin, ok := os.LookupEnv("GOBIN") 282 if ok { 283 return goBin 284 } 285 286 goPath, ok := os.LookupEnv("GOPATH") 287 if ok { 288 list := filepath.SplitList(goPath) 289 if len(list) > 0 { 290 // Put in first directory of $GOPATH. 291 return filepath.Join(list[0], "bin") 292 } 293 } 294 295 homeDir, _ := os.UserHomeDir() 296 return filepath.Join(homeDir, "go", "bin") 297 } 298 299 func linkGoplusToLocalBin() string { 300 println("Start Linking.") 301 302 gopBinPath := detectGopBinPath() 303 goBinPath := detectGoBinPath() 304 if !checkPathExist(gopBinPath, true) { 305 log.Fatalf("Error: %s is not existed, you should build Go+ before linking.\n", gopBinPath) 306 } 307 if !checkPathExist(goBinPath, true) { 308 if err := os.MkdirAll(goBinPath, 0755); err != nil { 309 fmt.Printf("Error: target directory %s is not existed and we can't create one.\n", goBinPath) 310 log.Fatalln(err) 311 } 312 } 313 314 for _, file := range gopBinFiles { 315 sourceFile := filepath.Join(gopBinPath, file) 316 if !checkPathExist(sourceFile, false) { 317 log.Fatalf("Error: %s is not existed, you should build Go+ before linking.\n", sourceFile) 318 } 319 targetLink := filepath.Join(goBinPath, file) 320 if checkPathExist(targetLink, false) { 321 // Delete existed one 322 if err := os.Remove(targetLink); err != nil { 323 log.Fatalln(err) 324 } 325 } 326 if err := os.Symlink(sourceFile, targetLink); err != nil { 327 log.Fatalln(err) 328 } 329 fmt.Printf("Link %s to %s successfully.\n", sourceFile, targetLink) 330 } 331 332 println("End linking.") 333 return goBinPath 334 } 335 336 func buildGoplusTools(useGoProxy bool) { 337 commandsDir := filepath.Join(gopRoot, "cmd") 338 buildFlags := getGopBuildFlags() 339 340 if useGoProxy { 341 println("Info: we will use goproxy.cn as a Go proxy to accelerate installing process.") 342 commandExecuteEnv = append(commandExecuteEnv, 343 "GOPROXY=https://goproxy.cn,direct", 344 ) 345 } 346 347 // Install Go+ binary files under current ./bin directory. 348 gopBinPath := detectGopBinPath() 349 if err := os.Mkdir(gopBinPath, 0755); err != nil && !os.IsExist(err) { 350 println("Error: Go+ can't create ./bin directory to put build assets.") 351 log.Fatalln(err) 352 } 353 354 println("Building Go+ tools...\n") 355 os.Chdir(commandsDir) 356 buildOutput, err := execCommand("go", "build", "-o", gopBinPath, "-v", "-ldflags", buildFlags, "./...") 357 if err != nil { 358 log.Fatalln(err) 359 } 360 print(buildOutput) 361 362 // Clear gop run cache 363 cleanGopRunCache() 364 365 println("\nGo+ tools built successfully!") 366 } 367 368 func showHelpPostInstall(installPath string) { 369 println("\nNEXT STEP:") 370 println("\nWe just installed Go+ into the directory: ", installPath) 371 message := ` 372 To setup a better Go+ development environment, 373 we recommend you add the above install directory into your PATH environment variable. 374 ` 375 println(message) 376 } 377 378 // Install Go+ tools 379 func install() { 380 installPath := linkGoplusToLocalBin() 381 382 println("\nGo+ tools installed successfully!") 383 384 if _, err := execCommand("gop", "version"); err != nil { 385 showHelpPostInstall(installPath) 386 } 387 } 388 389 func runTestcases() { 390 println("Start running testcases.") 391 os.Chdir(gopRoot) 392 393 coverage := "-coverprofile=coverage.txt" 394 gopCommand := filepath.Join(detectGopBinPath(), gopBinFiles[0]) 395 if !checkPathExist(gopCommand, false) { 396 println("Error: Go+ must be installed before running testcases.") 397 os.Exit(1) 398 } 399 400 testOutput, err := execCommand(gopCommand, "test", coverage, "-covermode=atomic", "./...") 401 println(testOutput) 402 if err != nil { 403 println(err.Error()) 404 } 405 406 println("End running testcases.") 407 } 408 409 func clean() { 410 gopBinPath := detectGopBinPath() 411 goBinPath := detectGoBinPath() 412 413 // Clean links 414 for _, file := range gopBinFiles { 415 targetLink := filepath.Join(goBinPath, file) 416 if checkPathExist(targetLink, false) { 417 if err := os.Remove(targetLink); err != nil { 418 log.Fatalln(err) 419 } 420 } 421 } 422 423 // Clean build binary files 424 if checkPathExist(gopBinPath, true) { 425 if err := os.RemoveAll(gopBinPath); err != nil { 426 log.Fatalln(err) 427 } 428 } 429 430 cleanGopRunCache() 431 } 432 433 func cleanGopRunCache() { 434 homeDir, _ := os.UserHomeDir() 435 runCacheDir := filepath.Join(homeDir, ".gop", "run") 436 files := []string{"go.mod", "go.sum"} 437 for _, file := range files { 438 fullPath := filepath.Join(runCacheDir, file) 439 if checkPathExist(fullPath, false) { 440 if err := os.Remove(fullPath); err != nil { 441 log.Fatalln(err) 442 } 443 } 444 } 445 } 446 447 func uninstall() { 448 println("Uninstalling Go+ and related tools.") 449 clean() 450 println("Go+ and related tools uninstalled successfully.") 451 } 452 453 func isInChinaWindows() bool { 454 // Run `systeminfo` command on windows to check locale. 455 out, err := execCommand("systeminfo") 456 if err != nil { 457 fmt.Println("Run [systeminfo] command failed with error: ", err) 458 return false 459 } 460 // Check if output contains `zh-cn;` 461 return strings.Contains(out, "zh-cn;") 462 } 463 464 func isInChina() bool { 465 if inWindows { 466 return isInChinaWindows() 467 } 468 const prefix = "LANG=\"" 469 out, err := execCommand("locale") 470 if err != nil { 471 return false 472 } 473 if strings.HasPrefix(out, prefix) { 474 out = out[len(prefix):] 475 return strings.HasPrefix(out, "zh_CN") 476 } 477 return false 478 } 479 480 // findGopVersion returns current version of gop 481 func findGopVersion() string { 482 versionFile := filepath.Join(gopRoot, "VERSION") 483 // Read version from VERSION file 484 data, err := os.ReadFile(versionFile) 485 if err == nil { 486 version := trimRight(string(data)) 487 return version 488 } 489 490 // Read version from git repo 491 if !isGitRepo() { 492 log.Fatal("Error: must be a git repo or a VERSION file existed.") 493 } 494 version := getBuildVer() // Closet tag on git log 495 return version 496 } 497 498 // releaseNewVersion tags the repo with provided new tag, and writes new tag into VERSION file. 499 func releaseNewVersion(tag string) { 500 if !isGitRepo() { 501 log.Fatalln("Error: Releasing a new version could only be operated under a git repo.") 502 } 503 gitRemote.CheckRemoteUrl() 504 if getTagRev(tag) != "" { 505 log.Fatalln("Error: tag already exists -", tag) 506 } 507 508 version := tag 509 re := regexp.MustCompile(`^v\d+?\.\d+?`) 510 releaseBranch := re.FindString(version) 511 if releaseBranch == "" { 512 log.Fatal("Error: A valid version should be has form: vX.Y.Z") 513 } 514 515 sourceBranch := getGitBranch() 516 517 // Checkout to release breanch 518 if sourceBranch != releaseBranch { 519 if err := gitCheckoutBranch(releaseBranch); err != nil { 520 log.Fatalf("Error: checkout to release branch: %s failed with error: %v.", releaseBranch, err) 521 } 522 defer func() { 523 // Checkout back to source branch 524 if err := gitCheckoutBranch(sourceBranch); err != nil { 525 log.Fatalf("Error: checkout to source branch: %s failed with error: %v.", sourceBranch, err) 526 } 527 gitRemote.DeleteBranch(releaseBranch) 528 }() 529 } 530 531 // Cache new version 532 versionFile := filepath.Join(gopRoot, "VERSION") 533 if err := os.WriteFile(versionFile, []byte(version), 0644); err != nil { 534 log.Fatalf("Error: cache new version with error: %v\n", err) 535 } 536 537 // Commit changes 538 gitAdd(versionFile) 539 if err := gitCommit("release version " + version); err != nil { 540 log.Fatalf("Error: git commit with error: %v\n", err) 541 } 542 543 // Tag the source code 544 if err := gitTagAndPushTo(tag, "gop", releaseBranch); err != nil { 545 log.Fatalf("Error: gitTagAndPushTo with error: %v\n", err) 546 } 547 548 println("Released new version:", version) 549 } 550 551 func runRegtests() { 552 println("\nStart running regtests.") 553 554 cmd := exec.Command(filepath.Join(gopRoot, "bin/"+gopBinFiles[0]), "go", "./...") 555 cmd.Stdout = os.Stdout 556 cmd.Stderr = os.Stderr 557 cmd.Dir = filepath.Join(gopRoot, "testdata") 558 err := cmd.Run() 559 if err != nil { 560 code := cmd.ProcessState.ExitCode() 561 if code == 0 { 562 code = 1 563 } 564 os.Exit(code) 565 } 566 } 567 568 func main() { 569 isInstall := flag.Bool("install", false, "Install Go+") 570 isBuild := flag.Bool("build", false, "Build Go+ tools") 571 isTest := flag.Bool("test", false, "Run testcases") 572 isRegtest := flag.Bool("regtest", false, "Run regtests") 573 isUninstall := flag.Bool("uninstall", false, "Uninstall Go+") 574 isGoProxy := flag.Bool("proxy", false, "Set GOPROXY for people in China") 575 isAutoProxy := flag.Bool("autoproxy", false, "Check to set GOPROXY automatically") 576 noPush := flag.Bool("nopush", false, "Don't push to remote repo") 577 tag := flag.String("tag", "", "Release an new version with specified tag") 578 579 flag.Parse() 580 581 useGoProxy := *isGoProxy 582 if !useGoProxy && *isAutoProxy { 583 useGoProxy = isInChina() 584 } 585 flagActionMap := map[*bool]func(){ 586 isBuild: func() { buildGoplusTools(useGoProxy) }, 587 isInstall: func() { 588 buildGoplusTools(useGoProxy) 589 install() 590 }, 591 isUninstall: uninstall, 592 isTest: runTestcases, 593 isRegtest: runRegtests, 594 } 595 596 // Sort flags, for example: install flag should be checked earlier than test flag. 597 flags := []*bool{isBuild, isInstall, isTest, isRegtest, isUninstall} 598 hasActionDone := false 599 600 if *tag != "" { 601 if *noPush { 602 gitRemote = &gitRemoteNone{} 603 } 604 releaseNewVersion(*tag) 605 hasActionDone = true 606 } 607 608 for _, flag := range flags { 609 if *flag { 610 flagActionMap[flag]() 611 hasActionDone = true 612 } 613 } 614 615 if !hasActionDone { 616 println("Usage:\n") 617 flag.PrintDefaults() 618 } 619 }