15
15
package preprocess
16
16
17
17
import (
18
+ "bufio"
18
19
"encoding/json"
19
20
"fmt"
20
21
"os"
22
+ "path/filepath"
21
23
"strings"
22
24
23
25
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg"
@@ -35,6 +37,7 @@ const (
35
37
36
38
type ruleMatcher struct {
37
39
availableRules map [string ][]resource.InstRule
40
+ moduleVersions map [string ]string // vendor used only
38
41
}
39
42
40
43
func newRuleMatcher () * ruleMatcher {
@@ -172,7 +175,16 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle {
172
175
continue
173
176
}
174
177
file := candidate
178
+
179
+ // If it's a vendor build, we need to extract the version of the module
180
+ // from vendor/modules.txt, otherwise we find the version from source
181
+ // code file path
175
182
version := shared .ExtractVersion (file )
183
+ if rm .moduleVersions != nil {
184
+ if v , ok := rm .moduleVersions [importPath ]; ok {
185
+ version = v
186
+ }
187
+ }
176
188
177
189
for i := len (availables ) - 1 ; i >= 0 ; i -- {
178
190
rule := availables [i ]
@@ -280,6 +292,47 @@ func findFlagValue(cmd []string, flag string) string {
280
292
return ""
281
293
}
282
294
295
+ func parseVendorModules () (map [string ]string , error ) {
296
+ util .Assert (shared .IsVendorBuild (), "why not otherwise" )
297
+ vendorFile := filepath .Join ("vendor" , "modules.txt" )
298
+ if exist , _ := util .PathExists (vendorFile ); ! exist {
299
+ return nil , fmt .Errorf ("vendor/modules.txt not found" )
300
+ }
301
+ // Read the vendor/modules.txt file line by line and parse it in form of
302
+ // #ImportPath Version
303
+ file , err := os .Open (vendorFile )
304
+ if err != nil {
305
+ return nil , err
306
+ }
307
+ defer func (dryRunLog * os.File ) {
308
+ err := dryRunLog .Close ()
309
+ if err != nil {
310
+ util .Log ("Failed to close dry run log file: %v" , err )
311
+ }
312
+ }(file )
313
+
314
+ vendorModules := make (map [string ]string )
315
+ scanner := bufio .NewScanner (file )
316
+ // 10MB should be enough to accommodate most long line
317
+ buffer := make ([]byte , 0 , 10 * 1024 * 1024 )
318
+ scanner .Buffer (buffer , cap (buffer ))
319
+ for scanner .Scan () {
320
+ line := scanner .Text ()
321
+ if strings .HasPrefix (line , "# " ) {
322
+ parts := strings .Split (line , " " )
323
+ if len (parts ) == 3 {
324
+ util .Assert (parts [0 ] == "#" , "sanity check" )
325
+ util .Assert (strings .HasPrefix (parts [2 ], "v" ), "sanity check" )
326
+ vendorModules [parts [1 ]] = parts [2 ]
327
+ }
328
+ }
329
+ }
330
+ if err = scanner .Err (); err != nil {
331
+ return nil , err
332
+ }
333
+ return vendorModules , nil
334
+ }
335
+
283
336
func runMatch (matcher * ruleMatcher , cmd string , ch chan * resource.RuleBundle ) {
284
337
bundle := matcher .match (shared .SplitCmds (cmd ))
285
338
ch <- bundle
@@ -288,6 +341,20 @@ func runMatch(matcher *ruleMatcher, cmd string, ch chan *resource.RuleBundle) {
288
341
func (dp * DepProcessor ) matchRules (compileCmds []string ) error {
289
342
defer util .PhaseTimer ("Match" )()
290
343
matcher := newRuleMatcher ()
344
+
345
+ // If we are in vendor mode, we need to parse the vendor/modules.txt file
346
+ // to get the version of each module for future matching
347
+ if dp .vendorBuild {
348
+ modules , err := parseVendorModules ()
349
+ if err != nil {
350
+ return fmt .Errorf ("failed to parse vendor/modules.txt: %w" , err )
351
+ }
352
+ if config .GetConf ().Verbose {
353
+ util .Log ("Vendor modules: %v" , modules )
354
+ }
355
+ matcher .moduleVersions = modules
356
+ }
357
+
291
358
// Find used instrumentation rule according to compile commands
292
359
ch := make (chan * resource.RuleBundle )
293
360
for _ , cmd := range compileCmds {
0 commit comments