fileinfo.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  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. "bufio"
  15. "bytes"
  16. "errors"
  17. "fmt"
  18. "go/ast"
  19. "go/parser"
  20. "go/token"
  21. "log"
  22. "os"
  23. "path"
  24. "path/filepath"
  25. "strconv"
  26. "strings"
  27. "unicode"
  28. "unicode/utf8"
  29. "github.com/bazelbuild/bazel-gazelle/config"
  30. "github.com/bazelbuild/bazel-gazelle/language/proto"
  31. "github.com/bazelbuild/bazel-gazelle/rule"
  32. )
  33. // fileInfo holds information used to decide how to build a file. This
  34. // information comes from the file's name, from package and import declarations
  35. // (in .go files), and from +build and cgo comments.
  36. type fileInfo struct {
  37. path string
  38. name string
  39. // ext is the type of file, based on extension.
  40. ext ext
  41. // packageName is the Go package name of a .go file, without the
  42. // "_test" suffix if it was present. It is empty for non-Go files.
  43. packageName string
  44. // importPath is the canonical import path for this file's package.
  45. // This may be read from a package comment (in Go) or a go_package
  46. // option (in proto). This field is empty for files that don't specify
  47. // an import path.
  48. importPath string
  49. // isTest is true if the file stem (the part before the extension)
  50. // ends with "_test.go". This is never true for non-Go files.
  51. isTest bool
  52. // imports is a list of packages imported by a file. It does not include
  53. // "C" or anything from the standard library.
  54. imports []string
  55. // isCgo is true for .go files that import "C".
  56. isCgo bool
  57. // goos and goarch contain the OS and architecture suffixes in the filename,
  58. // if they were present.
  59. goos, goarch string
  60. // tags is a list of build tag lines. Each entry is the trimmed text of
  61. // a line after a "+build" prefix.
  62. tags []tagLine
  63. // copts and clinkopts contain flags that are part of CFLAGS, CPPFLAGS,
  64. // CXXFLAGS, and LDFLAGS directives in cgo comments.
  65. copts, clinkopts []taggedOpts
  66. // hasServices indicates whether a .proto file has service definitions.
  67. hasServices bool
  68. }
  69. // tagLine represents the space-separated disjunction of build tag groups
  70. // in a line comment.
  71. type tagLine []tagGroup
  72. // check returns true if at least one of the tag groups is satisfied.
  73. func (l tagLine) check(c *config.Config, os, arch string) bool {
  74. if len(l) == 0 {
  75. return false
  76. }
  77. for _, g := range l {
  78. if g.check(c, os, arch) {
  79. return true
  80. }
  81. }
  82. return false
  83. }
  84. // tagGroup represents a comma-separated conjuction of build tags.
  85. type tagGroup []string
  86. // check returns true if all of the tags are true. Tags that start with
  87. // "!" are negated (but "!!") is not allowed. Go release tags (e.g., "go1.8")
  88. // are ignored. If the group contains an os or arch tag, but the os or arch
  89. // parameters are empty, check returns false even if the tag is negated.
  90. func (g tagGroup) check(c *config.Config, os, arch string) bool {
  91. goConf := getGoConfig(c)
  92. for _, t := range g {
  93. if strings.HasPrefix(t, "!!") { // bad syntax, reject always
  94. return false
  95. }
  96. not := strings.HasPrefix(t, "!")
  97. if not {
  98. t = t[1:]
  99. }
  100. if isIgnoredTag(t) {
  101. // Release tags are treated as "unknown" and are considered true,
  102. // whether or not they are negated.
  103. continue
  104. }
  105. var match bool
  106. if _, ok := rule.KnownOSSet[t]; ok {
  107. if os == "" {
  108. return false
  109. }
  110. match = matchesOS(os, t)
  111. } else if _, ok := rule.KnownArchSet[t]; ok {
  112. if arch == "" {
  113. return false
  114. }
  115. match = arch == t
  116. } else {
  117. match = goConf.genericTags[t]
  118. }
  119. if not {
  120. match = !match
  121. }
  122. if !match {
  123. return false
  124. }
  125. }
  126. return true
  127. }
  128. // taggedOpts a list of compile or link options which should only be applied
  129. // if the given set of build tags are satisfied. These options have already
  130. // been tokenized using the same algorithm that "go build" uses, then joined
  131. // with OptSeparator.
  132. type taggedOpts struct {
  133. tags tagLine
  134. opts string
  135. }
  136. // optSeparator is a special character inserted between options that appeared
  137. // together in a #cgo directive. This allows options to be split, modified,
  138. // and escaped by other packages.
  139. //
  140. // It's important to keep options grouped together in the same string. For
  141. // example, if we have "-framework IOKit" together in a #cgo directive,
  142. // "-framework" shouldn't be treated as a separate string for the purposes of
  143. // sorting and de-duplicating.
  144. const optSeparator = "\x1D"
  145. // ext indicates how a file should be treated, based on extension.
  146. type ext int
  147. const (
  148. // unknownExt is applied files that aren't buildable with Go.
  149. unknownExt ext = iota
  150. // goExt is applied to .go files.
  151. goExt
  152. // cExt is applied to C and C++ files.
  153. cExt
  154. // hExt is applied to header files. If cgo code is present, these may be
  155. // C or C++ headers. If not, they are treated as Go assembly headers.
  156. hExt
  157. // sExt is applied to Go assembly files, ending with .s.
  158. sExt
  159. // csExt is applied to other assembly files, ending with .S. These are built
  160. // with the C compiler if cgo code is present.
  161. csExt
  162. // protoExt is applied to .proto files.
  163. protoExt
  164. )
  165. // fileNameInfo returns information that can be inferred from the name of
  166. // a file. It does not read data from the file.
  167. func fileNameInfo(path_ string) fileInfo {
  168. name := filepath.Base(path_)
  169. var ext ext
  170. switch path.Ext(name) {
  171. case ".go":
  172. ext = goExt
  173. case ".c", ".cc", ".cpp", ".cxx", ".m", ".mm":
  174. ext = cExt
  175. case ".h", ".hh", ".hpp", ".hxx":
  176. ext = hExt
  177. case ".s":
  178. ext = sExt
  179. case ".S":
  180. ext = csExt
  181. case ".proto":
  182. ext = protoExt
  183. default:
  184. ext = unknownExt
  185. }
  186. if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") {
  187. ext = unknownExt
  188. }
  189. // Determine test, goos, and goarch. This is intended to match the logic
  190. // in goodOSArchFile in go/build.
  191. var isTest bool
  192. var goos, goarch string
  193. l := strings.Split(name[:len(name)-len(path.Ext(name))], "_")
  194. if len(l) >= 2 && l[len(l)-1] == "test" {
  195. isTest = ext == goExt
  196. l = l[:len(l)-1]
  197. }
  198. switch {
  199. case len(l) >= 3 && rule.KnownOSSet[l[len(l)-2]] && rule.KnownArchSet[l[len(l)-1]]:
  200. goos = l[len(l)-2]
  201. goarch = l[len(l)-1]
  202. case len(l) >= 2 && rule.KnownOSSet[l[len(l)-1]]:
  203. goos = l[len(l)-1]
  204. case len(l) >= 2 && rule.KnownArchSet[l[len(l)-1]]:
  205. goarch = l[len(l)-1]
  206. }
  207. return fileInfo{
  208. path: path_,
  209. name: name,
  210. ext: ext,
  211. isTest: isTest,
  212. goos: goos,
  213. goarch: goarch,
  214. }
  215. }
  216. // otherFileInfo returns information about a non-.go file. It will parse
  217. // part of the file to determine build tags. If the file can't be read, an
  218. // error will be logged, and partial information will be returned.
  219. func otherFileInfo(path string) fileInfo {
  220. info := fileNameInfo(path)
  221. if info.ext == unknownExt {
  222. return info
  223. }
  224. tags, err := readTags(info.path)
  225. if err != nil {
  226. log.Printf("%s: error reading file: %v", info.path, err)
  227. return info
  228. }
  229. info.tags = tags
  230. return info
  231. }
  232. // goFileInfo returns information about a .go file. It will parse part of the
  233. // file to determine the package name, imports, and build constraints.
  234. // If the file can't be read, an error will be logged, and partial information
  235. // will be returned.
  236. // This function is intended to match go/build.Context.Import.
  237. // TODD(#53): extract canonical import path
  238. func goFileInfo(path, rel string) fileInfo {
  239. info := fileNameInfo(path)
  240. fset := token.NewFileSet()
  241. pf, err := parser.ParseFile(fset, info.path, nil, parser.ImportsOnly|parser.ParseComments)
  242. if err != nil {
  243. log.Printf("%s: error reading go file: %v", info.path, err)
  244. return info
  245. }
  246. info.packageName = pf.Name.Name
  247. if info.isTest && strings.HasSuffix(info.packageName, "_test") {
  248. info.packageName = info.packageName[:len(info.packageName)-len("_test")]
  249. }
  250. for _, decl := range pf.Decls {
  251. d, ok := decl.(*ast.GenDecl)
  252. if !ok {
  253. continue
  254. }
  255. for _, dspec := range d.Specs {
  256. spec, ok := dspec.(*ast.ImportSpec)
  257. if !ok {
  258. continue
  259. }
  260. quoted := spec.Path.Value
  261. path, err := strconv.Unquote(quoted)
  262. if err != nil {
  263. log.Printf("%s: error reading go file: %v", info.path, err)
  264. continue
  265. }
  266. if path == "C" {
  267. if info.isTest {
  268. log.Printf("%s: warning: use of cgo in test not supported", info.path)
  269. }
  270. info.isCgo = true
  271. cg := spec.Doc
  272. if cg == nil && len(d.Specs) == 1 {
  273. cg = d.Doc
  274. }
  275. if cg != nil {
  276. if err := saveCgo(&info, rel, cg); err != nil {
  277. log.Printf("%s: error reading go file: %v", info.path, err)
  278. }
  279. }
  280. continue
  281. }
  282. info.imports = append(info.imports, path)
  283. }
  284. }
  285. tags, err := readTags(info.path)
  286. if err != nil {
  287. log.Printf("%s: error reading go file: %v", info.path, err)
  288. return info
  289. }
  290. info.tags = tags
  291. return info
  292. }
  293. // saveCgo extracts CFLAGS, CPPFLAGS, CXXFLAGS, and LDFLAGS directives
  294. // from a comment above a "C" import. This is intended to match logic in
  295. // go/build.Context.saveCgo.
  296. func saveCgo(info *fileInfo, rel string, cg *ast.CommentGroup) error {
  297. text := cg.Text()
  298. for _, line := range strings.Split(text, "\n") {
  299. orig := line
  300. // Line is
  301. // #cgo [GOOS/GOARCH...] LDFLAGS: stuff
  302. //
  303. line = strings.TrimSpace(line)
  304. if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
  305. continue
  306. }
  307. // Split at colon.
  308. line = strings.TrimSpace(line[4:])
  309. i := strings.Index(line, ":")
  310. if i < 0 {
  311. return fmt.Errorf("%s: invalid #cgo line: %s", info.path, orig)
  312. }
  313. line, optstr := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:])
  314. // Parse tags and verb.
  315. f := strings.Fields(line)
  316. if len(f) < 1 {
  317. return fmt.Errorf("%s: invalid #cgo line: %s", info.path, orig)
  318. }
  319. verb := f[len(f)-1]
  320. tags := parseTagsInGroups(f[:len(f)-1])
  321. // Parse options.
  322. opts, err := splitQuoted(optstr)
  323. if err != nil {
  324. return fmt.Errorf("%s: invalid #cgo line: %s", info.path, orig)
  325. }
  326. var ok bool
  327. for i, opt := range opts {
  328. if opt, ok = expandSrcDir(opt, rel); !ok {
  329. return fmt.Errorf("%s: malformed #cgo argument: %s", info.path, orig)
  330. }
  331. opts[i] = opt
  332. }
  333. joinedStr := strings.Join(opts, optSeparator)
  334. // Add tags to appropriate list.
  335. switch verb {
  336. case "CFLAGS", "CPPFLAGS", "CXXFLAGS":
  337. info.copts = append(info.copts, taggedOpts{tags, joinedStr})
  338. case "LDFLAGS":
  339. info.clinkopts = append(info.clinkopts, taggedOpts{tags, joinedStr})
  340. case "pkg-config":
  341. return fmt.Errorf("%s: pkg-config not supported: %s", info.path, orig)
  342. default:
  343. return fmt.Errorf("%s: invalid #cgo verb: %s", info.path, orig)
  344. }
  345. }
  346. return nil
  347. }
  348. // splitQuoted splits the string s around each instance of one or more consecutive
  349. // white space characters while taking into account quotes and escaping, and
  350. // returns an array of substrings of s or an empty list if s contains only white space.
  351. // Single quotes and double quotes are recognized to prevent splitting within the
  352. // quoted region, and are removed from the resulting substrings. If a quote in s
  353. // isn't closed err will be set and r will have the unclosed argument as the
  354. // last element. The backslash is used for escaping.
  355. //
  356. // For example, the following string:
  357. //
  358. // a b:"c d" 'e''f' "g\""
  359. //
  360. // Would be parsed as:
  361. //
  362. // []string{"a", "b:c d", "ef", `g"`}
  363. //
  364. // Copied from go/build.splitQuoted
  365. func splitQuoted(s string) (r []string, err error) {
  366. var args []string
  367. arg := make([]rune, len(s))
  368. escaped := false
  369. quoted := false
  370. quote := '\x00'
  371. i := 0
  372. for _, rune := range s {
  373. switch {
  374. case escaped:
  375. escaped = false
  376. case rune == '\\':
  377. escaped = true
  378. continue
  379. case quote != '\x00':
  380. if rune == quote {
  381. quote = '\x00'
  382. continue
  383. }
  384. case rune == '"' || rune == '\'':
  385. quoted = true
  386. quote = rune
  387. continue
  388. case unicode.IsSpace(rune):
  389. if quoted || i > 0 {
  390. quoted = false
  391. args = append(args, string(arg[:i]))
  392. i = 0
  393. }
  394. continue
  395. }
  396. arg[i] = rune
  397. i++
  398. }
  399. if quoted || i > 0 {
  400. args = append(args, string(arg[:i]))
  401. }
  402. if quote != 0 {
  403. err = errors.New("unclosed quote")
  404. } else if escaped {
  405. err = errors.New("unfinished escaping")
  406. }
  407. return args, err
  408. }
  409. // expandSrcDir expands any occurrence of ${SRCDIR}, making sure
  410. // the result is safe for the shell.
  411. //
  412. // Copied from go/build.expandSrcDir
  413. func expandSrcDir(str string, srcdir string) (string, bool) {
  414. // "\" delimited paths cause safeCgoName to fail
  415. // so convert native paths with a different delimiter
  416. // to "/" before starting (eg: on windows).
  417. srcdir = filepath.ToSlash(srcdir)
  418. if srcdir == "" {
  419. srcdir = "."
  420. }
  421. // Spaces are tolerated in ${SRCDIR}, but not anywhere else.
  422. chunks := strings.Split(str, "${SRCDIR}")
  423. if len(chunks) < 2 {
  424. return str, safeCgoName(str, false)
  425. }
  426. ok := true
  427. for _, chunk := range chunks {
  428. ok = ok && (chunk == "" || safeCgoName(chunk, false))
  429. }
  430. ok = ok && (srcdir == "" || safeCgoName(srcdir, true))
  431. res := strings.Join(chunks, srcdir)
  432. return res, ok && res != ""
  433. }
  434. // NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN.
  435. // We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay.
  436. // See golang.org/issue/6038.
  437. // The @ is for OS X. See golang.org/issue/13720.
  438. // The % is for Jenkins. See golang.org/issue/16959.
  439. const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%"
  440. const safeSpaces = " "
  441. var safeBytes = []byte(safeSpaces + safeString)
  442. // Copied from go/build.safeCgoName
  443. func safeCgoName(s string, spaces bool) bool {
  444. if s == "" {
  445. return false
  446. }
  447. safe := safeBytes
  448. if !spaces {
  449. safe = safe[len(safeSpaces):]
  450. }
  451. for i := 0; i < len(s); i++ {
  452. if c := s[i]; c < utf8.RuneSelf && bytes.IndexByte(safe, c) < 0 {
  453. return false
  454. }
  455. }
  456. return true
  457. }
  458. // readTags reads and extracts build tags from the block of comments
  459. // and blank lines at the start of a file which is separated from the
  460. // rest of the file by a blank line. Each string in the returned slice
  461. // is the trimmed text of a line after a "+build" prefix.
  462. // Based on go/build.Context.shouldBuild.
  463. func readTags(path string) ([]tagLine, error) {
  464. f, err := os.Open(path)
  465. if err != nil {
  466. return nil, err
  467. }
  468. defer f.Close()
  469. scanner := bufio.NewScanner(f)
  470. // Pass 1: Identify leading run of // comments and blank lines,
  471. // which must be followed by a blank line.
  472. var lines []string
  473. end := 0
  474. for scanner.Scan() {
  475. line := strings.TrimSpace(scanner.Text())
  476. if line == "" {
  477. end = len(lines)
  478. continue
  479. }
  480. if strings.HasPrefix(line, "//") {
  481. lines = append(lines, line[len("//"):])
  482. continue
  483. }
  484. break
  485. }
  486. if err := scanner.Err(); err != nil {
  487. return nil, err
  488. }
  489. lines = lines[:end]
  490. // Pass 2: Process each line in the run.
  491. var tagLines []tagLine
  492. for _, line := range lines {
  493. fields := strings.Fields(line)
  494. if len(fields) > 0 && fields[0] == "+build" {
  495. tagLines = append(tagLines, parseTagsInGroups(fields[1:]))
  496. }
  497. }
  498. return tagLines, nil
  499. }
  500. func parseTagsInGroups(groups []string) tagLine {
  501. var l tagLine
  502. for _, g := range groups {
  503. l = append(l, tagGroup(strings.Split(g, ",")))
  504. }
  505. return l
  506. }
  507. func isOSArchSpecific(info fileInfo, cgoTags tagLine) (osSpecific, archSpecific bool) {
  508. if info.goos != "" {
  509. osSpecific = true
  510. }
  511. if info.goarch != "" {
  512. archSpecific = true
  513. }
  514. lines := info.tags
  515. if len(cgoTags) > 0 {
  516. lines = append(lines, cgoTags)
  517. }
  518. for _, line := range lines {
  519. for _, group := range line {
  520. for _, tag := range group {
  521. if strings.HasPrefix(tag, "!") {
  522. tag = tag[1:]
  523. }
  524. _, osOk := rule.KnownOSSet[tag]
  525. if osOk {
  526. osSpecific = true
  527. }
  528. _, archOk := rule.KnownArchSet[tag]
  529. if archOk {
  530. archSpecific = true
  531. }
  532. }
  533. }
  534. }
  535. return osSpecific, archSpecific
  536. }
  537. // matchesOS checks if a value is equal to either an OS value or to any of its
  538. // aliases.
  539. func matchesOS(os, value string) bool {
  540. if os == value {
  541. return true
  542. }
  543. for _, alias := range rule.OSAliases[os] {
  544. if alias == value {
  545. return true
  546. }
  547. }
  548. return false
  549. }
  550. // checkConstraints determines whether build constraints are satisfied on
  551. // a given platform.
  552. //
  553. // The first few arguments describe the platform. genericTags is the set
  554. // of build tags that are true on all platforms. os and arch are the platform
  555. // GOOS and GOARCH strings. If os or arch is empty, checkConstraints will
  556. // return false in the presence of OS and architecture constraints, even
  557. // if they are negated.
  558. //
  559. // The remaining arguments describe the file being tested. All of these may
  560. // be empty or nil. osSuffix and archSuffix are filename suffixes. fileTags
  561. // is a list tags from +build comments found near the top of the file. cgoTags
  562. // is an extra set of tags in a #cgo directive.
  563. func checkConstraints(c *config.Config, os, arch, osSuffix, archSuffix string, fileTags []tagLine, cgoTags tagLine) bool {
  564. if osSuffix != "" && !matchesOS(os, osSuffix) || archSuffix != "" && archSuffix != arch {
  565. return false
  566. }
  567. for _, l := range fileTags {
  568. if !l.check(c, os, arch) {
  569. return false
  570. }
  571. }
  572. if len(cgoTags) > 0 && !cgoTags.check(c, os, arch) {
  573. return false
  574. }
  575. return true
  576. }
  577. // isIgnoredTag returns whether the tag is "cgo" or is a release tag.
  578. // Release tags match the pattern "go[0-9]\.[0-9]+".
  579. // Gazelle won't consider whether an ignored tag is satisfied when evaluating
  580. // build constraints for a file.
  581. func isIgnoredTag(tag string) bool {
  582. if tag == "cgo" || tag == "race" || tag == "msan" {
  583. return true
  584. }
  585. if len(tag) < 5 || !strings.HasPrefix(tag, "go") {
  586. return false
  587. }
  588. if tag[2] < '0' || tag[2] > '9' || tag[3] != '.' {
  589. return false
  590. }
  591. for _, c := range tag[4:] {
  592. if c < '0' || c > '9' {
  593. return false
  594. }
  595. }
  596. return true
  597. }
  598. // protoFileInfo extracts metadata from a proto file. The proto extension
  599. // already "parses" these and stores metadata in proto.FileInfo, so this is
  600. // just processing relevant options.
  601. func protoFileInfo(path_ string, protoInfo proto.FileInfo) fileInfo {
  602. info := fileNameInfo(path_)
  603. // Look for "option go_package". If there's no / in the package option, then
  604. // it's just a simple package name, not a full import path.
  605. for _, opt := range protoInfo.Options {
  606. if opt.Key != "go_package" {
  607. continue
  608. }
  609. if strings.LastIndexByte(opt.Value, '/') == -1 {
  610. info.packageName = opt.Value
  611. } else {
  612. if i := strings.LastIndexByte(opt.Value, ';'); i != -1 {
  613. info.importPath = opt.Value[:i]
  614. info.packageName = opt.Value[i+1:]
  615. } else {
  616. info.importPath = opt.Value
  617. info.packageName = path.Base(opt.Value)
  618. }
  619. }
  620. }
  621. // Set the Go package name from the proto package name if there was no
  622. // option go_package.
  623. if info.packageName == "" && protoInfo.PackageName != "" {
  624. info.packageName = strings.Replace(protoInfo.PackageName, ".", "_", -1)
  625. }
  626. info.imports = protoInfo.Imports
  627. info.hasServices = protoInfo.HasServices
  628. return info
  629. }