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 }