github.com/vmware/transport-go@v1.3.4/plank/build.go (about) 1 // Copyright 2021 VMware, Inc. 2 // SPDX-License-Identifier: BSD-2-Clause 3 4 package main 5 6 import ( 7 "bufio" 8 "fmt" 9 "github.com/go-git/go-git/v5" 10 "github.com/go-git/go-git/v5/plumbing" 11 "github.com/go-git/go-git/v5/storage/memory" 12 "log" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "sort" 18 "strings" 19 "sync" 20 ) 21 22 var ( 23 GOOS string 24 GOARCH string 25 versionString string 26 outputPath string 27 LDFLAGS = []string{"-s", "-w"} 28 ) 29 30 // main application to build Plank 31 // specify BUILD_OS and BUILD_ARCH that map to GOOS and GOARCH to support building against 32 // a platform different from the caller's OS. output will be generated at build/ 33 func main() { 34 // read build envrionemnt variables like GOOS and GOARCH 35 GOOS = os.Getenv("BUILD_OS") 36 GOARCH = os.Getenv("BUILD_ARCH") 37 goosSelected := GOOS 38 goarchSelected := GOARCH 39 if len(goosSelected) == 0 { 40 goosSelected = runtime.GOOS 41 } 42 43 if len(goarchSelected) == 0 { 44 goarchSelected = runtime.GOARCH 45 } 46 47 WD, _ := os.Getwd() 48 r, err := cloneCurrentBranch(WD) 49 if err != nil { 50 log.Fatalln(err) 51 } 52 53 // assemble version string 54 // if it's a branch or tag, version string should be ${BRANCH}-${HASH} otherwise ${HASH} 55 branch, err := getBranch(r) 56 if err != nil { 57 log.Fatalln(err) 58 } 59 60 tags, err := getTags(r) 61 if err != nil { 62 log.Fatalln(err) 63 } 64 65 versionString = buildVersionString(branch, tags) 66 LDFLAGS = append(LDFLAGS, "-X main.version="+versionString) 67 68 // set output path 69 binaryExt := "" 70 if goosSelected == "windows" { 71 binaryExt = ".exe" 72 } 73 outputPath = filepath.Join(WD, "build", "plank"+binaryExt) 74 75 fmt.Printf("Building Plank\n") 76 fmt.Println() 77 fmt.Printf("GOOS\t\t%s\n", goosSelected) 78 fmt.Printf("GOARCH\t\t%s\n", goarchSelected) 79 fmt.Printf("Version\t\t%s\n", versionString) 80 fmt.Printf("Output\t\t%s\n", outputPath) 81 fmt.Printf("ldflags\t\t%v\n", LDFLAGS) 82 83 buildCommand := []string{"build", "-o", outputPath, "-ldflags", "'" + strings.Join(LDFLAGS, " ") + "'", "cmd/main.go"} 84 fmt.Println() 85 fmt.Printf("Build command:\ngo %s\n", strings.Join(buildCommand, " ")) 86 87 cmd := exec.Command("go", 88 "build", "-o", outputPath, "-ldflags", strings.Join(LDFLAGS, " "), "cmd/main.go") 89 cmd.Env = append(os.Environ(), "GOOS="+goosSelected, "GOARCH="+goarchSelected) 90 cmd.Dir = WD 91 stdout, _ := cmd.StdoutPipe() 92 stderr, _ := cmd.StderrPipe() 93 outScanner := bufio.NewScanner(stdout) 94 errScanner := bufio.NewScanner(stderr) 95 outScanner.Split(bufio.ScanLines) 96 errScanner.Split(bufio.ScanLines) 97 98 wg := sync.WaitGroup{} 99 wg.Add(2) 100 err = cmd.Start() 101 if err != nil { 102 log.Fatalln(err) 103 } 104 105 go func() { 106 for outScanner.Scan() { 107 txt := outScanner.Text() 108 fmt.Println(txt) 109 } 110 wg.Done() 111 }() 112 113 go func() { 114 for errScanner.Scan() { 115 txt := errScanner.Text() 116 fmt.Println(txt) 117 } 118 wg.Done() 119 }() 120 121 wg.Wait() 122 state, err := cmd.Process.Wait() 123 if err != nil { 124 log.Fatalln(err) 125 } 126 127 fmt.Println() 128 if state.ExitCode() == 0 { 129 log.Println("Build successful") 130 } else { 131 log.Println("Build failed") 132 } 133 os.Exit(state.ExitCode()) 134 } 135 136 // cloneCurrentBranch clones the current branch into memory and return the reference 137 func cloneCurrentBranch(wd string) (*git.Repository, error) { 138 if filepath.Base(wd) != "plank" { 139 return nil, fmt.Errorf("this build application needs to be run inside plank/ folder") 140 } 141 142 r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ 143 URL: filepath.Clean(filepath.Join(wd, "..")), 144 }) 145 return r, err 146 } 147 148 // getTags retrieves all tags and sort by recent versions 149 func getTags(r *git.Repository) ([]string, error) { 150 sortedTags := make([]string, 0) 151 tags, err := r.Tags() 152 if err != nil { 153 return nil, err 154 } 155 156 _ = tags.ForEach(func(ref *plumbing.Reference) error { 157 sortedTags = append(sortedTags, ref.Name().Short()) 158 return nil 159 }) 160 161 // sort tags 162 sort.SliceStable(sortedTags, func(i, j int) bool { 163 aExploded := strings.Split(sortedTags[i], ".") 164 bExploded := strings.Split(sortedTags[j], ".") 165 majorA := aExploded[0] 166 majorB := bExploded[0] 167 minorA := aExploded[1] 168 minorB := bExploded[1] 169 patchA := aExploded[2] 170 patchB := bExploded[2] 171 172 if majorA > majorB { 173 return true 174 } else if majorA < majorB { 175 return false 176 } 177 178 if minorA > minorB { 179 return true 180 } else if minorA < minorB { 181 return false 182 } 183 184 if patchA > patchB { 185 return true 186 } 187 return false 188 }) 189 return sortedTags, nil 190 } 191 192 func getBranch(r *git.Repository) (*plumbing.Reference, error) { 193 branches, err := r.Branches() 194 if err != nil { 195 return nil, err 196 } 197 defer branches.Close() 198 199 return branches.Next() 200 } 201 202 // buildVersionString takes the git object reference and tags as inputs and assembles a version string based on it 203 // being a branch/tag or generic object references 204 func buildVersionString(ref *plumbing.Reference, tags []string) (str string) { 205 if len(tags) > 0 { 206 str = tags[0] + "-" 207 } 208 if ref.Name().IsBranch() || ref.Name().IsTag() { 209 str += ref.Name().Short() + "-" + ref.Hash().String()[:8] 210 } else { 211 str += ref.Hash().String()[:8] 212 } 213 return str 214 }