1// Copyright 2021 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 15package main 16 17import ( 18 "encoding/json" 19 "fmt" 20 "go/parser" 21 "go/token" 22 "os" 23 "strconv" 24 "strings" 25) 26 27type ResolvePkgFunc func(importPath string) string 28 29// Copy and pasted from golang.org/x/tools/go/packages 30type FlatPackagesError struct { 31 Pos string // "file:line:col" or "file:line" or "" or "-" 32 Msg string 33 Kind FlatPackagesErrorKind 34} 35 36type FlatPackagesErrorKind int 37 38const ( 39 UnknownError FlatPackagesErrorKind = iota 40 ListError 41 ParseError 42 TypeError 43) 44 45func (err FlatPackagesError) Error() string { 46 pos := err.Pos 47 if pos == "" { 48 pos = "-" // like token.Position{}.String() 49 } 50 return pos + ": " + err.Msg 51} 52 53// FlatPackage is the JSON form of Package 54// It drops all the type and syntax fields, and transforms the Imports 55type FlatPackage struct { 56 ID string 57 Name string `json:",omitempty"` 58 PkgPath string `json:",omitempty"` 59 Errors []FlatPackagesError `json:",omitempty"` 60 GoFiles []string `json:",omitempty"` 61 CompiledGoFiles []string `json:",omitempty"` 62 OtherFiles []string `json:",omitempty"` 63 ExportFile string `json:",omitempty"` 64 Imports map[string]string `json:",omitempty"` 65 Standard bool `json:",omitempty"` 66} 67 68type ( 69 PackageFunc func(pkg *FlatPackage) 70 PathResolverFunc func(path string) string 71) 72 73func resolvePathsInPlace(prf PathResolverFunc, paths []string) { 74 for i, path := range paths { 75 paths[i] = prf(path) 76 } 77} 78 79func WalkFlatPackagesFromJSON(jsonFile string, onPkg PackageFunc) error { 80 f, err := os.Open(jsonFile) 81 if err != nil { 82 return fmt.Errorf("unable to open package JSON file: %w", err) 83 } 84 defer f.Close() 85 86 decoder := json.NewDecoder(f) 87 for decoder.More() { 88 pkg := &FlatPackage{} 89 if err := decoder.Decode(&pkg); err != nil { 90 return fmt.Errorf("unable to decode package in %s: %w", f.Name(), err) 91 } 92 93 onPkg(pkg) 94 } 95 return nil 96} 97 98func (fp *FlatPackage) ResolvePaths(prf PathResolverFunc) error { 99 resolvePathsInPlace(prf, fp.CompiledGoFiles) 100 resolvePathsInPlace(prf, fp.GoFiles) 101 resolvePathsInPlace(prf, fp.OtherFiles) 102 fp.ExportFile = prf(fp.ExportFile) 103 return nil 104} 105 106// FilterFilesForBuildTags filters the source files given the current build 107// tags. 108func (fp *FlatPackage) FilterFilesForBuildTags() { 109 fp.GoFiles = filterSourceFilesForTags(fp.GoFiles) 110 fp.CompiledGoFiles = filterSourceFilesForTags(fp.CompiledGoFiles) 111} 112 113func (fp *FlatPackage) IsStdlib() bool { 114 return fp.Standard 115} 116 117func (fp *FlatPackage) ResolveImports(resolve ResolvePkgFunc) error { 118 // Stdlib packages are already complete import wise 119 if fp.IsStdlib() { 120 return nil 121 } 122 123 fset := token.NewFileSet() 124 125 for _, file := range fp.CompiledGoFiles { 126 f, err := parser.ParseFile(fset, file, nil, parser.ImportsOnly) 127 if err != nil { 128 return err 129 } 130 // If the name is not provided, fetch it from the sources 131 if fp.Name == "" { 132 fp.Name = f.Name.Name 133 } 134 135 for _, rawImport := range f.Imports { 136 imp, err := strconv.Unquote(rawImport.Path.Value) 137 if err != nil { 138 continue 139 } 140 // We don't handle CGo for now 141 if imp == "C" { 142 continue 143 } 144 if _, ok := fp.Imports[imp]; ok { 145 continue 146 } 147 148 if pkgID := resolve(imp); pkgID != "" { 149 fp.Imports[imp] = pkgID 150 } 151 } 152 } 153 154 return nil 155} 156 157func (fp *FlatPackage) IsRoot() bool { 158 return strings.HasPrefix(fp.ID, "//") 159} 160