github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/build/golang/golang.go (about)

     1  package golang
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  
    13  	"github.com/tickoalcantara12/micro/v3/service/build"
    14  	"github.com/tickoalcantara12/micro/v3/service/build/util/tar"
    15  	"github.com/tickoalcantara12/micro/v3/service/build/util/zip"
    16  )
    17  
    18  // NewBuilder returns a golang build which can build a go binary given some source
    19  func NewBuilder() (build.Builder, error) {
    20  	path, err := locateGo()
    21  	if err != nil {
    22  		return nil, fmt.Errorf("Error locating go binary: %v", err)
    23  	}
    24  
    25  	return &golang{
    26  		cmdPath: path,
    27  		tmpDir:  os.TempDir(),
    28  	}, nil
    29  }
    30  
    31  type golang struct {
    32  	cmdPath string
    33  	tmpDir  string
    34  }
    35  
    36  // Build a binary using source. If an archive was used, e.g. tar, this should be specified in the
    37  // options. If no archive option is passed, the build will treat the source as a single file.
    38  func (g *golang) Build(src io.Reader, opts ...build.Option) (io.Reader, error) {
    39  	// parse the options
    40  	var options build.Options
    41  	for _, o := range opts {
    42  		o(&options)
    43  	}
    44  
    45  	// create a tmp dir to contain the source
    46  	dir, err := ioutil.TempDir(g.tmpDir, "src")
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	defer os.RemoveAll(dir)
    51  
    52  	// decode the source and write to the tmp directory
    53  	switch options.Archive {
    54  	case "":
    55  		err = writeFile(src, dir)
    56  	case "tar":
    57  		err = tar.Unarchive(src, dir)
    58  	case "zip":
    59  		err = zip.Unarchive(src, dir)
    60  	default:
    61  		return nil, errors.New("Invalid Archive")
    62  	}
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	// check for vendor directory before setting mod to vendor. the vendor directory wasn't uploaded
    68  	// in early v3 betas so this enables backwards compatability
    69  	args := []string{"build", "-o", "micro_build"}
    70  	if _, err := os.Stat(filepath.Join(dir, "vendor")); err == nil {
    71  		args = append(args, "-mod", "vendor")
    72  	}
    73  
    74  	// build the binary
    75  	cmd := exec.Command(g.cmdPath, append(args, ".")...)
    76  	cmd.Env = append(os.Environ(), "GO111MODULE=auto")
    77  	cmd.Dir = filepath.Join(dir, options.Entrypoint)
    78  
    79  	outp := bytes.NewBuffer(nil)
    80  	cmd.Stderr = outp
    81  
    82  	if err := cmd.Run(); err != nil {
    83  		return nil, fmt.Errorf("%v: %v", err, outp.String())
    84  	}
    85  
    86  	// read the bytes from the file
    87  	dst, err := ioutil.ReadFile(filepath.Join(cmd.Dir, "micro_build"))
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return bytes.NewBuffer(dst), nil
    93  }
    94  
    95  // writeFile takes a single file to a directory
    96  func writeFile(src io.Reader, dir string) error {
    97  	// copy the source to the temp file
    98  	bytes, err := ioutil.ReadAll(src)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// write the file, note: in order for the golang build to access the file, it cannot be
   104  	// os.ModeTemp. This is okay because we delete all the files in the tmp dir at the end of this
   105  	// function.
   106  	return ioutil.WriteFile(filepath.Join(dir, "main.go"), bytes, os.ModePerm)
   107  }
   108  
   109  // locateGo locates the go command
   110  func locateGo() (string, error) {
   111  	if gr := os.Getenv("GOROOT"); len(gr) > 0 {
   112  		return filepath.Join(gr, "bin", "go"), nil
   113  	}
   114  
   115  	// check path
   116  	for _, p := range filepath.SplitList(os.Getenv("PATH")) {
   117  		bin := filepath.Join(p, "go")
   118  		if _, err := os.Stat(bin); err == nil {
   119  			return bin, nil
   120  		}
   121  	}
   122  
   123  	return exec.LookPath("go")
   124  }