generate.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. /* Copyright 2018 The Bazel Authors. All rights reserved.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package golang
  13. import (
  14. "fmt"
  15. "go/build"
  16. "log"
  17. "path"
  18. "path/filepath"
  19. "sort"
  20. "strings"
  21. "sync"
  22. "github.com/bazelbuild/bazel-gazelle/config"
  23. "github.com/bazelbuild/bazel-gazelle/language"
  24. "github.com/bazelbuild/bazel-gazelle/language/proto"
  25. "github.com/bazelbuild/bazel-gazelle/pathtools"
  26. "github.com/bazelbuild/bazel-gazelle/rule"
  27. )
  28. func (gl *goLang) GenerateRules(args language.GenerateArgs) language.GenerateResult {
  29. // Extract information about proto files. We need this to exclude .pb.go
  30. // files and generate go_proto_library rules.
  31. c := args.Config
  32. gc := getGoConfig(c)
  33. pcMode := getProtoMode(c)
  34. // This is a collection of proto_library rule names that have a corresponding
  35. // go_proto_library rule already generated.
  36. goProtoRules := make(map[string]struct{})
  37. var protoRuleNames []string
  38. protoPackages := make(map[string]proto.Package)
  39. protoFileInfo := make(map[string]proto.FileInfo)
  40. for _, r := range args.OtherGen {
  41. if r.Kind() == "go_proto_library" {
  42. if proto := r.AttrString("proto"); proto != "" {
  43. goProtoRules[proto] = struct{}{}
  44. }
  45. if protos := r.AttrStrings("protos"); protos != nil {
  46. for _, proto := range protos {
  47. goProtoRules[proto] = struct{}{}
  48. }
  49. }
  50. }
  51. if r.Kind() != "proto_library" {
  52. continue
  53. }
  54. pkg := r.PrivateAttr(proto.PackageKey).(proto.Package)
  55. protoPackages[r.Name()] = pkg
  56. for name, info := range pkg.Files {
  57. protoFileInfo[name] = info
  58. }
  59. protoRuleNames = append(protoRuleNames, r.Name())
  60. }
  61. sort.Strings(protoRuleNames)
  62. var emptyProtoRuleNames []string
  63. for _, r := range args.OtherEmpty {
  64. if r.Kind() == "proto_library" {
  65. emptyProtoRuleNames = append(emptyProtoRuleNames, r.Name())
  66. }
  67. }
  68. // If proto rule generation is enabled, exclude .pb.go files that correspond
  69. // to any .proto files present.
  70. regularFiles := append([]string{}, args.RegularFiles...)
  71. genFiles := append([]string{}, args.GenFiles...)
  72. if !pcMode.ShouldIncludePregeneratedFiles() {
  73. keep := func(f string) bool {
  74. if strings.HasSuffix(f, ".pb.go") {
  75. _, ok := protoFileInfo[strings.TrimSuffix(f, ".pb.go")+".proto"]
  76. return !ok
  77. }
  78. return true
  79. }
  80. filterFiles(&regularFiles, keep)
  81. filterFiles(&genFiles, keep)
  82. }
  83. // Split regular files into files which can determine the package name and
  84. // import path and other files.
  85. var goFiles, otherFiles []string
  86. for _, f := range regularFiles {
  87. if strings.HasSuffix(f, ".go") {
  88. goFiles = append(goFiles, f)
  89. } else {
  90. otherFiles = append(otherFiles, f)
  91. }
  92. }
  93. // Look for a subdirectory named testdata. Only treat it as data if it does
  94. // not contain a buildable package.
  95. var hasTestdata bool
  96. for _, sub := range args.Subdirs {
  97. if sub == "testdata" {
  98. hasTestdata = !gl.goPkgRels[path.Join(args.Rel, "testdata")]
  99. break
  100. }
  101. }
  102. // Build a set of packages from files in this directory.
  103. goPackageMap, goFilesWithUnknownPackage := buildPackages(c, args.Dir, args.Rel, goFiles, hasTestdata)
  104. // Select a package to generate rules for. If there is no package, create
  105. // an empty package so we can generate empty rules.
  106. var protoName string
  107. pkg, err := selectPackage(c, args.Dir, goPackageMap)
  108. if err != nil {
  109. if _, ok := err.(*build.NoGoError); ok {
  110. if len(protoPackages) == 1 {
  111. for name, ppkg := range protoPackages {
  112. if _, ok := goProtoRules[":"+name]; ok {
  113. // if a go_proto_library rule already exists for this
  114. // proto package, treat it as if the proto package
  115. // doesn't exist.
  116. pkg = emptyPackage(c, args.Dir, args.Rel)
  117. break
  118. }
  119. pkg = &goPackage{
  120. name: goProtoPackageName(ppkg),
  121. importPath: goProtoImportPath(gc, ppkg, args.Rel),
  122. proto: protoTargetFromProtoPackage(name, ppkg),
  123. }
  124. protoName = name
  125. break
  126. }
  127. } else {
  128. pkg = emptyPackage(c, args.Dir, args.Rel)
  129. }
  130. } else {
  131. log.Print(err)
  132. }
  133. }
  134. // Try to link the selected package with a proto package.
  135. if pkg != nil {
  136. if pkg.importPath == "" {
  137. if err := pkg.inferImportPath(c); err != nil && pkg.firstGoFile() != "" {
  138. inferImportPathErrorOnce.Do(func() { log.Print(err) })
  139. }
  140. }
  141. for _, name := range protoRuleNames {
  142. ppkg := protoPackages[name]
  143. if pkg.importPath == goProtoImportPath(gc, ppkg, args.Rel) {
  144. protoName = name
  145. pkg.proto = protoTargetFromProtoPackage(name, ppkg)
  146. break
  147. }
  148. }
  149. }
  150. // Generate rules for proto packages. These should come before the other
  151. // Go rules.
  152. g := &generator{
  153. c: c,
  154. rel: args.Rel,
  155. shouldSetVisibility: args.File == nil || !args.File.HasDefaultVisibility(),
  156. }
  157. var res language.GenerateResult
  158. var rules []*rule.Rule
  159. var protoEmbed string
  160. for _, name := range protoRuleNames {
  161. if _, ok := goProtoRules[":"+name]; ok {
  162. // if a go_proto_library rule exists for this proto_library rule
  163. // already, skip creating another go_proto_library for it, assuming
  164. // that a different gazelle extension is responsible for
  165. // go_proto_library rule generation.
  166. continue
  167. }
  168. ppkg := protoPackages[name]
  169. var rs []*rule.Rule
  170. if name == protoName {
  171. protoEmbed, rs = g.generateProto(pcMode, pkg.proto, pkg.importPath)
  172. } else {
  173. target := protoTargetFromProtoPackage(name, ppkg)
  174. importPath := goProtoImportPath(gc, ppkg, args.Rel)
  175. _, rs = g.generateProto(pcMode, target, importPath)
  176. }
  177. rules = append(rules, rs...)
  178. }
  179. for _, name := range emptyProtoRuleNames {
  180. goProtoName := strings.TrimSuffix(name, "_proto") + "_go_proto"
  181. res.Empty = append(res.Empty, rule.NewRule("go_proto_library", goProtoName))
  182. }
  183. if pkg != nil && pcMode == proto.PackageMode && pkg.firstGoFile() == "" {
  184. // In proto package mode, don't generate a go_library embedding a
  185. // go_proto_library unless there are actually go files.
  186. protoEmbed = ""
  187. }
  188. // Complete the Go package and generate rules for that.
  189. if pkg != nil {
  190. // Add files with unknown packages. This happens when there are parse
  191. // or I/O errors. We should keep the file in the srcs list and let the
  192. // compiler deal with the error.
  193. cgo := pkg.haveCgo()
  194. for _, info := range goFilesWithUnknownPackage {
  195. if err := pkg.addFile(c, info, cgo); err != nil {
  196. log.Print(err)
  197. }
  198. }
  199. // Process the other static files.
  200. for _, file := range otherFiles {
  201. info := otherFileInfo(filepath.Join(args.Dir, file))
  202. if err := pkg.addFile(c, info, cgo); err != nil {
  203. log.Print(err)
  204. }
  205. }
  206. // Process generated files. Note that generated files may have the same names
  207. // as static files. Bazel will use the generated files, but we will look at
  208. // the content of static files, assuming they will be the same.
  209. regularFileSet := make(map[string]bool)
  210. for _, f := range regularFiles {
  211. regularFileSet[f] = true
  212. }
  213. // Some of the generated files may have been consumed by other rules
  214. consumedFileSet := make(map[string]bool)
  215. for _, r := range args.OtherGen {
  216. for _, f := range r.AttrStrings("srcs") {
  217. consumedFileSet[f] = true
  218. }
  219. if f := r.AttrString("src"); f != "" {
  220. consumedFileSet[f] = true
  221. }
  222. }
  223. for _, f := range genFiles {
  224. if regularFileSet[f] || consumedFileSet[f] {
  225. continue
  226. }
  227. info := fileNameInfo(filepath.Join(args.Dir, f))
  228. if err := pkg.addFile(c, info, cgo); err != nil {
  229. log.Print(err)
  230. }
  231. }
  232. // Generate Go rules.
  233. if protoName == "" {
  234. // Empty proto rules for deletion.
  235. _, rs := g.generateProto(pcMode, pkg.proto, pkg.importPath)
  236. rules = append(rules, rs...)
  237. }
  238. lib := g.generateLib(pkg, protoEmbed)
  239. var libName string
  240. if !lib.IsEmpty(goKinds[lib.Kind()]) {
  241. libName = lib.Name()
  242. }
  243. rules = append(rules, lib)
  244. rules = append(rules,
  245. g.generateBin(pkg, libName),
  246. g.generateTest(pkg, libName))
  247. }
  248. for _, r := range rules {
  249. if r.IsEmpty(goKinds[r.Kind()]) {
  250. res.Empty = append(res.Empty, r)
  251. } else {
  252. res.Gen = append(res.Gen, r)
  253. res.Imports = append(res.Imports, r.PrivateAttr(config.GazelleImportsKey))
  254. }
  255. }
  256. if args.File != nil || len(res.Gen) > 0 {
  257. gl.goPkgRels[args.Rel] = true
  258. } else {
  259. for _, sub := range args.Subdirs {
  260. if gl.goPkgRels[path.Join(args.Rel, sub)] {
  261. gl.goPkgRels[args.Rel] = true
  262. break
  263. }
  264. }
  265. }
  266. return res
  267. }
  268. func filterFiles(files *[]string, pred func(string) bool) {
  269. w := 0
  270. for r := 0; r < len(*files); r++ {
  271. f := (*files)[r]
  272. if pred(f) {
  273. (*files)[w] = f
  274. w++
  275. }
  276. }
  277. *files = (*files)[:w]
  278. }
  279. func buildPackages(c *config.Config, dir, rel string, goFiles []string, hasTestdata bool) (packageMap map[string]*goPackage, goFilesWithUnknownPackage []fileInfo) {
  280. // Process .go and .proto files first, since these determine the package name.
  281. packageMap = make(map[string]*goPackage)
  282. for _, f := range goFiles {
  283. path := filepath.Join(dir, f)
  284. info := goFileInfo(path, rel)
  285. if info.packageName == "" {
  286. goFilesWithUnknownPackage = append(goFilesWithUnknownPackage, info)
  287. continue
  288. }
  289. if info.packageName == "documentation" {
  290. // go/build ignores this package
  291. continue
  292. }
  293. if _, ok := packageMap[info.packageName]; !ok {
  294. packageMap[info.packageName] = &goPackage{
  295. name: info.packageName,
  296. dir: dir,
  297. rel: rel,
  298. hasTestdata: hasTestdata,
  299. }
  300. }
  301. if err := packageMap[info.packageName].addFile(c, info, false); err != nil {
  302. log.Print(err)
  303. }
  304. }
  305. return packageMap, goFilesWithUnknownPackage
  306. }
  307. var inferImportPathErrorOnce sync.Once
  308. // selectPackages selects one Go packages out of the buildable packages found
  309. // in a directory. If multiple packages are found, it returns the package
  310. // whose name matches the directory if such a package exists.
  311. func selectPackage(c *config.Config, dir string, packageMap map[string]*goPackage) (*goPackage, error) {
  312. buildablePackages := make(map[string]*goPackage)
  313. for name, pkg := range packageMap {
  314. if pkg.isBuildable(c) {
  315. buildablePackages[name] = pkg
  316. }
  317. }
  318. if len(buildablePackages) == 0 {
  319. return nil, &build.NoGoError{Dir: dir}
  320. }
  321. if len(buildablePackages) == 1 {
  322. for _, pkg := range buildablePackages {
  323. return pkg, nil
  324. }
  325. }
  326. if pkg, ok := buildablePackages[defaultPackageName(c, dir)]; ok {
  327. return pkg, nil
  328. }
  329. err := &build.MultiplePackageError{Dir: dir}
  330. for name, pkg := range buildablePackages {
  331. // Add the first file for each package for the error message.
  332. // Error() method expects these lists to be the same length. File
  333. // lists must be non-empty. These lists are only created by
  334. // buildPackage for packages with .go files present.
  335. err.Packages = append(err.Packages, name)
  336. err.Files = append(err.Files, pkg.firstGoFile())
  337. }
  338. return nil, err
  339. }
  340. func emptyPackage(c *config.Config, dir, rel string) *goPackage {
  341. pkg := &goPackage{
  342. name: defaultPackageName(c, dir),
  343. dir: dir,
  344. rel: rel,
  345. }
  346. pkg.inferImportPath(c)
  347. return pkg
  348. }
  349. func defaultPackageName(c *config.Config, rel string) string {
  350. gc := getGoConfig(c)
  351. return pathtools.RelBaseName(rel, gc.prefix, "")
  352. }
  353. type generator struct {
  354. c *config.Config
  355. rel string
  356. shouldSetVisibility bool
  357. }
  358. func (g *generator) generateProto(mode proto.Mode, target protoTarget, importPath string) (string, []*rule.Rule) {
  359. if !mode.ShouldGenerateRules() && mode != proto.LegacyMode {
  360. // Don't create or delete proto rules in this mode. Any existing rules
  361. // are likely hand-written.
  362. return "", nil
  363. }
  364. gc := getGoConfig(g.c)
  365. filegroupName := legacyProtoFilegroupName
  366. protoName := target.name
  367. if protoName == "" {
  368. importPath := inferImportPath(gc, g.rel)
  369. protoName = proto.RuleName(importPath)
  370. }
  371. goProtoName := strings.TrimSuffix(protoName, "_proto") + "_go_proto"
  372. visibility := g.commonVisibility(importPath)
  373. if mode == proto.LegacyMode {
  374. filegroup := rule.NewRule("filegroup", filegroupName)
  375. if target.sources.isEmpty() {
  376. return "", []*rule.Rule{filegroup}
  377. }
  378. filegroup.SetAttr("srcs", target.sources.build())
  379. if g.shouldSetVisibility {
  380. filegroup.SetAttr("visibility", visibility)
  381. }
  382. return "", []*rule.Rule{filegroup}
  383. }
  384. if target.sources.isEmpty() {
  385. return "", []*rule.Rule{
  386. rule.NewRule("filegroup", filegroupName),
  387. rule.NewRule("go_proto_library", goProtoName),
  388. }
  389. }
  390. goProtoLibrary := rule.NewRule("go_proto_library", goProtoName)
  391. goProtoLibrary.SetAttr("proto", ":"+protoName)
  392. g.setImportAttrs(goProtoLibrary, importPath)
  393. if target.hasServices {
  394. goProtoLibrary.SetAttr("compilers", gc.goGrpcCompilers)
  395. } else if gc.goProtoCompilersSet {
  396. goProtoLibrary.SetAttr("compilers", gc.goProtoCompilers)
  397. }
  398. if g.shouldSetVisibility {
  399. goProtoLibrary.SetAttr("visibility", visibility)
  400. }
  401. goProtoLibrary.SetPrivateAttr(config.GazelleImportsKey, target.imports.build())
  402. return goProtoName, []*rule.Rule{goProtoLibrary}
  403. }
  404. func (g *generator) generateLib(pkg *goPackage, embed string) *rule.Rule {
  405. goLibrary := rule.NewRule("go_library", defaultLibName)
  406. if !pkg.library.sources.hasGo() && embed == "" {
  407. return goLibrary // empty
  408. }
  409. var visibility []string
  410. if pkg.isCommand() {
  411. // Libraries made for a go_binary should not be exposed to the public.
  412. visibility = []string{"//visibility:private"}
  413. } else {
  414. visibility = g.commonVisibility(pkg.importPath)
  415. }
  416. g.setCommonAttrs(goLibrary, pkg.rel, visibility, pkg.library, embed)
  417. g.setImportAttrs(goLibrary, pkg.importPath)
  418. return goLibrary
  419. }
  420. func (g *generator) generateBin(pkg *goPackage, library string) *rule.Rule {
  421. name := pathtools.RelBaseName(pkg.rel, getGoConfig(g.c).prefix, g.c.RepoRoot)
  422. goBinary := rule.NewRule("go_binary", name)
  423. if !pkg.isCommand() || pkg.binary.sources.isEmpty() && library == "" {
  424. return goBinary // empty
  425. }
  426. visibility := g.commonVisibility(pkg.importPath)
  427. g.setCommonAttrs(goBinary, pkg.rel, visibility, pkg.binary, library)
  428. return goBinary
  429. }
  430. func (g *generator) generateTest(pkg *goPackage, library string) *rule.Rule {
  431. goTest := rule.NewRule("go_test", defaultTestName)
  432. if !pkg.test.sources.hasGo() {
  433. return goTest // empty
  434. }
  435. g.setCommonAttrs(goTest, pkg.rel, nil, pkg.test, library)
  436. if pkg.hasTestdata {
  437. goTest.SetAttr("data", rule.GlobValue{Patterns: []string{"testdata/**"}})
  438. }
  439. return goTest
  440. }
  441. func (g *generator) setCommonAttrs(r *rule.Rule, pkgRel string, visibility []string, target goTarget, embed string) {
  442. if !target.sources.isEmpty() {
  443. r.SetAttr("srcs", target.sources.buildFlat())
  444. }
  445. if target.cgo {
  446. r.SetAttr("cgo", true)
  447. }
  448. if !target.clinkopts.isEmpty() {
  449. r.SetAttr("clinkopts", g.options(target.clinkopts.build(), pkgRel))
  450. }
  451. if !target.copts.isEmpty() {
  452. r.SetAttr("copts", g.options(target.copts.build(), pkgRel))
  453. }
  454. if g.shouldSetVisibility && len(visibility) > 0 {
  455. r.SetAttr("visibility", visibility)
  456. }
  457. if embed != "" {
  458. r.SetAttr("embed", []string{":" + embed})
  459. }
  460. r.SetPrivateAttr(config.GazelleImportsKey, target.imports.build())
  461. }
  462. func (g *generator) setImportAttrs(r *rule.Rule, importPath string) {
  463. gc := getGoConfig(g.c)
  464. r.SetAttr("importpath", importPath)
  465. // Set importpath_aliases if we need minimal module compatibility.
  466. // If a package is part of a module with a v2+ semantic import version
  467. // suffix, packages that are not part of modules may import it without
  468. // the suffix.
  469. if gc.goRepositoryMode && gc.moduleMode && pathtools.HasPrefix(importPath, gc.prefix) && gc.prefixRel == "" {
  470. if mmcImportPath := pathWithoutSemver(importPath); mmcImportPath != "" {
  471. r.SetAttr("importpath_aliases", []string{mmcImportPath})
  472. }
  473. }
  474. if gc.importMapPrefix != "" {
  475. fromPrefixRel := pathtools.TrimPrefix(g.rel, gc.importMapPrefixRel)
  476. importMap := path.Join(gc.importMapPrefix, fromPrefixRel)
  477. if importMap != importPath {
  478. r.SetAttr("importmap", importMap)
  479. }
  480. }
  481. }
  482. func (g *generator) commonVisibility(importPath string) []string {
  483. // If the Bazel package name (rel) contains "internal", add visibility for
  484. // subpackages of the parent.
  485. // If the import path contains "internal" but rel does not, this is
  486. // probably an internal submodule. Add visibility for all subpackages.
  487. relIndex := pathtools.Index(g.rel, "internal")
  488. importIndex := pathtools.Index(importPath, "internal")
  489. visibility := getGoConfig(g.c).goVisibility
  490. if relIndex >= 0 {
  491. parent := strings.TrimSuffix(g.rel[:relIndex], "/")
  492. visibility = append(visibility, fmt.Sprintf("//%s:__subpackages__", parent))
  493. } else if importIndex >= 0 {
  494. visibility = append(visibility, "//:__subpackages__")
  495. } else {
  496. return []string{"//visibility:public"}
  497. }
  498. // Add visibility for any submodules that have the internal parent as
  499. // a prefix of their module path.
  500. if importIndex >= 0 {
  501. gc := getGoConfig(g.c)
  502. internalRoot := strings.TrimSuffix(importPath[:importIndex], "/")
  503. for _, m := range gc.submodules {
  504. if strings.HasPrefix(m.modulePath, internalRoot) {
  505. visibility = append(visibility, fmt.Sprintf("@%s//:__subpackages__", m.repoName))
  506. }
  507. }
  508. }
  509. return visibility
  510. }
  511. var (
  512. // shortOptPrefixes are strings that come at the beginning of an option
  513. // argument that includes a path, e.g., -Ifoo/bar.
  514. shortOptPrefixes = []string{"-I", "-L", "-F"}
  515. // longOptPrefixes are separate arguments that come before a path argument,
  516. // e.g., -iquote foo/bar.
  517. longOptPrefixes = []string{"-I", "-L", "-F", "-iquote", "-isystem"}
  518. )
  519. // options transforms package-relative paths in cgo options into repository-
  520. // root-relative paths that Bazel can understand. For example, if a cgo file
  521. // in //foo declares an include flag in its copts: "-Ibar", this method
  522. // will transform that flag into "-Ifoo/bar".
  523. func (g *generator) options(opts rule.PlatformStrings, pkgRel string) rule.PlatformStrings {
  524. fixPath := func(opt string) string {
  525. if strings.HasPrefix(opt, "/") {
  526. return opt
  527. }
  528. return path.Clean(path.Join(pkgRel, opt))
  529. }
  530. fixGroups := func(groups []string) ([]string, error) {
  531. fixedGroups := make([]string, len(groups))
  532. for i, group := range groups {
  533. opts := strings.Split(group, optSeparator)
  534. fixedOpts := make([]string, len(opts))
  535. isPath := false
  536. for j, opt := range opts {
  537. if isPath {
  538. opt = fixPath(opt)
  539. isPath = false
  540. goto next
  541. }
  542. for _, short := range shortOptPrefixes {
  543. if strings.HasPrefix(opt, short) && len(opt) > len(short) {
  544. opt = short + fixPath(opt[len(short):])
  545. goto next
  546. }
  547. }
  548. for _, long := range longOptPrefixes {
  549. if opt == long {
  550. isPath = true
  551. goto next
  552. }
  553. }
  554. next:
  555. fixedOpts[j] = escapeOption(opt)
  556. }
  557. fixedGroups[i] = strings.Join(fixedOpts, " ")
  558. }
  559. return fixedGroups, nil
  560. }
  561. opts, errs := opts.MapSlice(fixGroups)
  562. if errs != nil {
  563. log.Panicf("unexpected error when transforming options with pkg %q: %v", pkgRel, errs)
  564. }
  565. return opts
  566. }
  567. func escapeOption(opt string) string {
  568. return strings.NewReplacer(
  569. `\`, `\\`,
  570. `'`, `\'`,
  571. `"`, `\"`,
  572. ` `, `\ `,
  573. "\t", "\\\t",
  574. "\n", "\\\n",
  575. "\r", "\\\r",
  576. ).Replace(opt)
  577. }