github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/compile.go (about) 1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // compile compiles .go files with "go tool compile". It is invoked by the 16 // Go rules as an action. 17 package main 18 19 import ( 20 "bytes" 21 "flag" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "strings" 28 ) 29 30 func compile(args []string) error { 31 // Parse arguments. 32 args, err := expandParamsFiles(args) 33 if err != nil { 34 return err 35 } 36 builderArgs, toolArgs := splitArgs(args) 37 flags := flag.NewFlagSet("GoCompile", flag.ExitOnError) 38 unfiltered := multiFlag{} 39 archives := archiveMultiFlag{} 40 goenv := envFlags(flags) 41 packagePath := flags.String("p", "", "The package path (importmap) of the package being compiled") 42 flags.Var(&unfiltered, "src", "A source file to be filtered and compiled") 43 flags.Var(&archives, "arc", "Import path, package path, and file name of a direct dependency, separated by '='") 44 nogo := flags.String("nogo", "", "The nogo binary") 45 outExport := flags.String("x", "", "The output archive file to write export data and nogo facts") 46 output := flags.String("o", "", "The output archive file to write compiled code") 47 asmhdr := flags.String("asmhdr", "", "Path to assembly header file to write") 48 packageList := flags.String("package_list", "", "The file containing the list of standard library packages") 49 testfilter := flags.String("testfilter", "off", "Controls test package filtering") 50 if err := flags.Parse(builderArgs); err != nil { 51 return err 52 } 53 if err := goenv.checkFlags(); err != nil { 54 return err 55 } 56 *output = abs(*output) 57 if *asmhdr != "" { 58 *asmhdr = abs(*asmhdr) 59 } 60 61 // Filter sources using build constraints. 62 all, err := filterAndSplitFiles(unfiltered) 63 if err != nil { 64 return err 65 } 66 goFiles, sFiles, hFiles := all.goSrcs, all.sSrcs, all.hSrcs 67 if len(all.cSrcs) > 0 { 68 return fmt.Errorf("unexpected C file: %s", all.cSrcs[0].filename) 69 } 70 if len(all.cxxSrcs) > 0 { 71 return fmt.Errorf("unexpected C++ file: %s", all.cxxSrcs[0].filename) 72 } 73 switch *testfilter { 74 case "off": 75 case "only": 76 testFiles := make([]fileInfo, 0, len(goFiles)) 77 for _, f := range goFiles { 78 if strings.HasSuffix(f.pkg, "_test") { 79 testFiles = append(testFiles, f) 80 } 81 } 82 goFiles = testFiles 83 case "exclude": 84 libFiles := make([]fileInfo, 0, len(goFiles)) 85 for _, f := range goFiles { 86 if !strings.HasSuffix(f.pkg, "_test") { 87 libFiles = append(libFiles, f) 88 } 89 } 90 goFiles = libFiles 91 default: 92 return fmt.Errorf("invalid test filter %q", *testfilter) 93 } 94 if len(goFiles) == 0 { 95 // We need to run the compiler to create a valid archive, even if there's 96 // nothing in it. GoPack will complain if we try to add assembly or cgo 97 // objects. 98 emptyPath := filepath.Join(filepath.Dir(*output), "_empty.go") 99 if err := ioutil.WriteFile(emptyPath, []byte("package empty\n"), 0666); err != nil { 100 return err 101 } 102 goFiles = append(goFiles, fileInfo{filename: emptyPath, pkg: "empty"}) 103 } 104 105 if *packagePath == "" { 106 *packagePath = goFiles[0].pkg 107 } 108 109 // Check that the filtered sources don't import anything outside of 110 // the standard library and the direct dependencies. 111 imports, err := checkImports(goFiles, archives, *packageList) 112 if err != nil { 113 return err 114 } 115 116 // Build an importcfg file for the compiler. 117 importcfgName, err := buildImportcfgFileForCompile(imports, goenv.installSuffix, filepath.Dir(*output)) 118 if err != nil { 119 return err 120 } 121 defer os.Remove(importcfgName) 122 123 // If there are assembly files, and this is go1.12+, generate symbol ABIs. 124 symabisName, err := buildSymabisFile(goenv, sFiles, hFiles, *asmhdr) 125 if symabisName != "" { 126 defer os.Remove(symabisName) 127 } 128 if err != nil { 129 return err 130 } 131 132 // Compile the filtered files. 133 goargs := goenv.goTool("compile") 134 goargs = append(goargs, "-p", *packagePath) 135 goargs = append(goargs, "-importcfg", importcfgName) 136 goargs = append(goargs, "-pack", "-o", *output) 137 if symabisName != "" { 138 goargs = append(goargs, "-symabis", symabisName) 139 } 140 if *asmhdr != "" { 141 goargs = append(goargs, "-asmhdr", *asmhdr) 142 } 143 goargs = append(goargs, toolArgs...) 144 goargs = append(goargs, "--") 145 filenames := make([]string, 0, len(goFiles)) 146 for _, f := range goFiles { 147 filenames = append(filenames, f.filename) 148 } 149 goargs = append(goargs, filenames...) 150 absArgs(goargs, []string{"-I", "-o", "-trimpath", "-importcfg"}) 151 cmd := exec.Command(goargs[0], goargs[1:]...) 152 cmd.Stdout = os.Stdout 153 cmd.Stderr = os.Stderr 154 if err := cmd.Start(); err != nil { 155 return fmt.Errorf("error starting compiler: %v", err) 156 } 157 158 // tempdir to store nogo facts and pkgdef for packaging later 159 xTempDir, err := ioutil.TempDir(filepath.Dir(*outExport), "x_files") 160 if err != nil { 161 return err 162 } 163 defer os.RemoveAll(xTempDir) 164 // Run nogo concurrently. 165 var nogoOutput bytes.Buffer 166 nogoStatus := nogoNotRun 167 outFact := filepath.Join(xTempDir, nogoFact) 168 if *nogo != "" { 169 var nogoargs []string 170 nogoargs = append(nogoargs, "-p", *packagePath) 171 nogoargs = append(nogoargs, "-importcfg", importcfgName) 172 for _, arc := range archives { 173 nogoargs = append(nogoargs, "-fact", fmt.Sprintf("%s=%s", arc.importPath, arc.file)) 174 } 175 nogoargs = append(nogoargs, "-x", outFact) 176 nogoargs = append(nogoargs, filenames...) 177 nogoCmd := exec.Command(*nogo, nogoargs...) 178 nogoCmd.Stdout, nogoCmd.Stderr = &nogoOutput, &nogoOutput 179 if err := nogoCmd.Run(); err != nil { 180 if _, ok := err.(*exec.ExitError); ok { 181 // Only fail the build if nogo runs and finds errors in source code. 182 nogoStatus = nogoFailed 183 } else { 184 // All errors related to running nogo will merely be printed. 185 nogoOutput.WriteString(fmt.Sprintf("error running nogo: %v\n", err)) 186 nogoStatus = nogoError 187 } 188 } else { 189 nogoStatus = nogoSucceeded 190 } 191 } 192 if err := cmd.Wait(); err != nil { 193 return fmt.Errorf("error running compiler: %v", err) 194 } 195 196 // Only print the output of nogo if compilation succeeds. 197 if nogoStatus == nogoFailed { 198 return fmt.Errorf("%s", nogoOutput.String()) 199 } 200 if nogoOutput.Len() != 0 { 201 fmt.Fprintln(os.Stderr, nogoOutput.String()) 202 } 203 204 // Extract the export data file and pack it in an .x archive together with the 205 // nogo facts file (if there is one). This allows compile actions to depend 206 // on .x files only, so we don't need to recompile a package when one of its 207 // imports changes in a way that doesn't affect export data. 208 // TODO(golang/go#33820): Ideally, we would use -linkobj to tell the compiler 209 // to create separate .a and .x files for compiled code and export data, then 210 // copy the nogo facts into the .x file. Unfortunately, when building a plugin, 211 // the linker needs export data in the .a file. To work around this, we copy 212 // the export data into the .x file ourselves. 213 if err = extractFileFromArchive(*output, xTempDir, pkgDef); err != nil { 214 return err 215 } 216 pkgDefPath := filepath.Join(xTempDir, pkgDef) 217 if nogoStatus == nogoSucceeded { 218 return appendFiles(goenv, *outExport, []string{pkgDefPath, outFact}) 219 } 220 return appendFiles(goenv, *outExport, []string{pkgDefPath}) 221 } 222 223 type nogoResult int 224 225 const ( 226 nogoNotRun nogoResult = iota 227 nogoError 228 nogoFailed 229 nogoSucceeded 230 )