github.com/ajguerrer/rules_go@v0.20.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 := readParamsFiles(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 := compileArchiveMultiFlag{} 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", "", "Path to nogo that should be written") 46 output := flags.String("o", "", "The output object file to write") 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 // Run nogo concurrently. 159 var nogoOutput bytes.Buffer 160 nogoFailed := false 161 if *nogo != "" { 162 var nogoargs []string 163 nogoargs = append(nogoargs, "-p", *packagePath) 164 nogoargs = append(nogoargs, "-importcfg", importcfgName) 165 for _, arc := range archives { 166 if arc.xFile != "" { 167 nogoargs = append(nogoargs, "-fact", fmt.Sprintf("%s=%s", arc.importPath, arc.xFile)) 168 } 169 } 170 nogoargs = append(nogoargs, "-x", *outExport) 171 nogoargs = append(nogoargs, filenames...) 172 nogoCmd := exec.Command(*nogo, nogoargs...) 173 nogoCmd.Stdout, nogoCmd.Stderr = &nogoOutput, &nogoOutput 174 if err := nogoCmd.Run(); err != nil { 175 if _, ok := err.(*exec.ExitError); ok { 176 // Only fail the build if nogo runs and finds errors in source code. 177 nogoFailed = true 178 } else { 179 // All errors related to running nogo will merely be printed. 180 nogoOutput.WriteString(fmt.Sprintf("error running nogo: %v\n", err)) 181 } 182 } 183 } 184 if err := cmd.Wait(); err != nil { 185 return fmt.Errorf("error running compiler: %v", err) 186 } 187 // Only print the output of nogo if compilation succeeds. 188 if nogoFailed { 189 return fmt.Errorf("%s", nogoOutput.String()) 190 } 191 if nogoOutput.Len() != 0 { 192 fmt.Fprintln(os.Stderr, nogoOutput.String()) 193 } 194 return nil 195 }