github.com/please-build/go-rules/tools/please_go@v0.0.0-20240319165128-ea27d6f5caba/install/toolchain/toolchain.go (about)

     1  package toolchain
     2  
     3  import (
     4  	"fmt"
     5  	"go/build"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/please-build/go-rules/tools/please_go/install/exec"
    13  )
    14  
    15  var versionRegex = regexp.MustCompile("go version go1.([0-9]+).+")
    16  
    17  type Toolchain struct {
    18  	CcTool        string
    19  	GoTool        string
    20  	PkgConfigTool string
    21  
    22  	Exec *exec.Executor
    23  }
    24  
    25  func paths(ps []string) string {
    26  	return strings.Join(ps, " ")
    27  }
    28  
    29  func argsFile(args []string) (string, error) {
    30  	f, err := ioutil.TempFile("", "")
    31  	if err != nil {
    32  		return "", err
    33  	}
    34  
    35  	p := f.Name()
    36  
    37  	_, err = f.WriteString(strings.Join(args, "\n"))
    38  	if err != nil {
    39  		return "", err
    40  	}
    41  
    42  	err = f.Close()
    43  	return p, err
    44  }
    45  
    46  // CGO invokes go tool cgo to generate cgo sources in the target's object directory
    47  func (tc *Toolchain) CGO(sourceDir string, objectDir string, cFlags []string, cgoFiles []string) ([]string, []string, error) {
    48  	// Looking at `go build -work -n -a`, there's also `_cgo_main.c` that gets taken into account,
    49  	// which results in a couple more commands being run.
    50  	// Although we seem to ignore this file, it doesn't seem to cause things to break so far, but
    51  	// leaving this note here for future reference.
    52  	goFiles := []string{filepath.Join(objectDir, "_cgo_gotypes.go")}
    53  	cFiles := []string{filepath.Join(objectDir, "_cgo_export.c")}
    54  
    55  	for _, cgoFile := range cgoFiles {
    56  		baseGoFile := strings.TrimSuffix(filepath.Base(cgoFile), ".go") + ".cgo1.go"
    57  		baseCFile := strings.TrimSuffix(filepath.Base(cgoFile), ".go") + ".cgo2.c"
    58  
    59  		goFiles = append(goFiles, filepath.Join(objectDir, baseGoFile))
    60  		cFiles = append(cFiles, filepath.Join(objectDir, baseCFile))
    61  	}
    62  
    63  	// Although we don't set the `-importpath` flag here, it shows up in `go build -work -n -a`.
    64  	// It doesn't seem to cause things to break without it so far, but leaving this note here for future reference.
    65  	if err := tc.Exec.Run("(cd %s; %s tool cgo -objdir %s -- -I %s %s %s)", sourceDir, tc.GoTool, objectDir, objectDir, strings.Join(cFlags, " "), paths(cgoFiles)); err != nil {
    66  		return nil, nil, err
    67  	}
    68  
    69  	return goFiles, cFiles, nil
    70  }
    71  
    72  // GoCompile will compile the go sources and the generated .cgo1.go sources for the CGO files (if any)
    73  func (tc *Toolchain) GoCompile(sourceDir, importpath, importcfg, out, trimpath, embedCfg string, goFiles []string) error {
    74  	if importpath != "" {
    75  		importpath = fmt.Sprintf("-p %s", importpath)
    76  	}
    77  	if trimpath != "" {
    78  		trimpath = fmt.Sprintf("-trimpath %s", trimpath)
    79  	}
    80  	if embedCfg != "" {
    81  		embedCfg = fmt.Sprintf("-embedcfg %s", embedCfg)
    82  	}
    83  
    84  	argf, err := argsFile(goFiles)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	return tc.Exec.Run("%s tool compile -pack %s %s %s -importcfg %s -o %s @%s", tc.GoTool, importpath, trimpath, embedCfg, importcfg, out, argf)
    90  }
    91  
    92  // GoAsmCompile will compile the go sources linking to the the abi symbols generated from symabis()
    93  func (tc *Toolchain) GoAsmCompile(importpath, importcfg, out, trimpath, embedCfg string, goFiles []string, asmH, symabys string) error {
    94  	if importpath != "" {
    95  		importpath = fmt.Sprintf("-p %s", importpath)
    96  	}
    97  	if trimpath != "" {
    98  		trimpath = fmt.Sprintf("-trimpath %s", trimpath)
    99  	}
   100  	if embedCfg != "" {
   101  		embedCfg = fmt.Sprintf("-embedcfg %s", embedCfg)
   102  	}
   103  	return tc.Exec.Run("%s tool compile -pack %s %s %s -importcfg %s -asmhdr %s -symabis %s -o %s %s", tc.GoTool, importpath, embedCfg, trimpath, importcfg, asmH, symabys, out, paths(goFiles))
   104  }
   105  
   106  // CCompile will compile C/CXX sources and return the object files that will be generated
   107  func (tc *Toolchain) CCompile(sourceDir, objectDir string, ccFiles, ccFlags []string) ([]string, error) {
   108  	objFiles := make([]string, len(ccFiles))
   109  
   110  	for i, ccFile := range ccFiles {
   111  		baseObjFile := strings.TrimSuffix(filepath.Base(ccFile), filepath.Ext(ccFile)) + ".o"
   112  		objFiles[i] = filepath.Join(objectDir, baseObjFile)
   113  
   114  		if err := tc.Exec.Run("(cd %s; %s -Wno-error -Wno-unused-parameter -c %s -I . -o %s %s)", sourceDir, tc.CcTool, strings.Join(ccFlags, " "), objFiles[i], ccFile); err != nil {
   115  			return nil, err
   116  		}
   117  	}
   118  
   119  	return objFiles, nil
   120  }
   121  
   122  // Pack will add the object files in dir to the archive
   123  func (tc *Toolchain) Pack(dir, archive string, objFiles []string) error {
   124  	return tc.Exec.Run("%s tool pack r %s %s", tc.GoTool, archive, paths(objFiles))
   125  }
   126  
   127  // Link will link the archive into an executable
   128  func (tc *Toolchain) Link(archive, out, importcfg string, ldFlags []string) error {
   129  	return tc.Exec.Run("%s tool link -extld %s -extldflags \"%s\" -importcfg %s -o %s %s", tc.GoTool, tc.CcTool, strings.Join(ldFlags, " "), importcfg, out, archive)
   130  }
   131  
   132  // Symabis will generate the asm header as well as the abi symbol file for the provided asm files.
   133  func (tc *Toolchain) Symabis(importpath, sourceDir, objectDir string, asmFiles []string) (string, string, error) {
   134  	asmH := fmt.Sprintf("%s/go_asm.h", objectDir)
   135  	symabis := fmt.Sprintf("%s/symabis", objectDir)
   136  
   137  	if importpath != "" {
   138  		importpath = fmt.Sprintf("-p %s", importpath)
   139  	}
   140  	// the gc Toolchain does this
   141  	if err := tc.Exec.Run("touch %s", asmH); err != nil {
   142  		return "", "", err
   143  	}
   144  
   145  	err := tc.Exec.Run("(cd %s; %s tool asm -I %s -I %s/pkg/include -D GOOS_%s -D GOARCH_%s -gensymabis %s -o %s %s)", sourceDir, tc.GoTool, objectDir, build.Default.GOROOT, build.Default.GOOS, build.Default.GOARCH, importpath, symabis, paths(asmFiles))
   146  
   147  	return asmH, symabis, err
   148  }
   149  
   150  // Asm will compile the asm files and return the objects that are generated
   151  func (tc *Toolchain) Asm(importpath, sourceDir, objectDir, trimpath string, asmFiles []string) ([]string, error) {
   152  	if importpath != "" {
   153  		importpath = fmt.Sprintf("-p %s", importpath)
   154  	}
   155  	if trimpath != "" {
   156  		trimpath = fmt.Sprintf("-trimpath %s", trimpath)
   157  	}
   158  
   159  	objFiles := make([]string, len(asmFiles))
   160  
   161  	for i, asmFile := range asmFiles {
   162  		baseObjFile := strings.TrimSuffix(filepath.Base(asmFile), ".s") + ".o"
   163  		objFiles[i] = filepath.Join(objectDir, baseObjFile)
   164  
   165  		err := tc.Exec.Run("(cd %s; %s tool asm %s %s -I %s -I %s/pkg/include -D GOOS_%s -D GOARCH_%s -o %s %s)", sourceDir, tc.GoTool, importpath, trimpath, objectDir, build.Default.GOROOT, build.Default.GOOS, build.Default.GOARCH, objFiles[i], asmFile)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  	}
   170  
   171  	return objFiles, nil
   172  }
   173  
   174  func (tc *Toolchain) GoMinorVersion() (int, error) {
   175  	out, err := tc.Exec.CombinedOutput(tc.GoTool, "version")
   176  	if err != nil {
   177  		return 0, err
   178  	}
   179  
   180  	return strconv.Atoi(string(versionRegex.FindSubmatch(out)[1]))
   181  }
   182  
   183  func (tc *Toolchain) PkgConfigCFlags(cfgs []string) ([]string, error) {
   184  	return tc.pkgConfig("--cflags", cfgs)
   185  }
   186  
   187  func (tc *Toolchain) PkgConfigLDFlags(cfgs []string) ([]string, error) {
   188  	return tc.pkgConfig("--libs", cfgs)
   189  }
   190  
   191  func (tc *Toolchain) pkgConfig(cmd string, cfgs []string) ([]string, error) {
   192  	args := []string{cmd}
   193  	out, err := tc.Exec.CombinedOutput(tc.PkgConfigTool, append(args, cfgs...)...)
   194  	if err != nil {
   195  		return nil, fmt.Errorf("failed to resolve pkg configs %v: %w", cfgs, err)
   196  	}
   197  	return strings.Fields(string(out)), nil
   198  }