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  }