Skip to content

Commit 040bb00

Browse files
authored
feat: support vendor build (#258)
* feat: support vendor build * more comment
1 parent 00b3ca4 commit 040bb00

File tree

6 files changed

+160
-32
lines changed

6 files changed

+160
-32
lines changed

test/helloworld/go.mod

+12-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,15 @@ replace github.com/alibaba/opentelemetry-go-auto-instrumentation => ../../../ope
66

77
replace github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier => ../../../opentelemetry-go-auto-instrumentation/test/verifier
88

9-
require golang.org/x/time v0.5.0
9+
require (
10+
go.opentelemetry.io/otel v1.33.0
11+
golang.org/x/time v0.5.0
12+
)
13+
14+
require (
15+
github.com/go-logr/logr v1.4.2 // indirect
16+
github.com/go-logr/stdr v1.2.2 // indirect
17+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
18+
go.opentelemetry.io/otel/metric v1.33.0 // indirect
19+
go.opentelemetry.io/otel/trace v1.33.0 // indirect
20+
)

test/helloworld/go.sum

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
24
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
35
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
46
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
57
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
6-
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
7-
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
8-
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
9-
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
10-
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
11-
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
8+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
9+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
10+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
13+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
14+
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
15+
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
16+
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
17+
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
18+
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
19+
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
20+
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
21+
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
1222
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
1323
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
24+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
25+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

test/helloworld_test.go

+29-16
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package test
1616

1717
import (
18+
"os/exec"
1819
"path/filepath"
1920
"regexp"
2021
"testing"
@@ -55,21 +56,33 @@ func TestRunHelloworld(t *testing.T) {
5556
}
5657
}
5758

58-
// FIXME: Support vendor build mode
59-
// func TestBuildHelloworldWithVendor1(t *testing.T) {
60-
// UseApp(HelloworldAppName)
61-
// util.RunCmd("go", "mod", "vendor")
62-
// RunGoBuild(t, "-debuglog", "go", "build")
63-
// }
59+
func runModVendor(t *testing.T) {
60+
cmd := exec.Command("go", "mod", "tidy")
61+
out, err := cmd.CombinedOutput()
62+
if err != nil {
63+
t.Fatalf("go mod tidy failed: %v\n%s", err, out)
64+
}
65+
cmd = exec.Command("go", "mod", "vendor")
66+
out, err = cmd.CombinedOutput()
67+
if err != nil {
68+
t.Fatalf("go mod vendor failed: %v\n%s", err, out)
69+
}
70+
}
6471

65-
// func TestBuildHelloworldWithVendor2(t *testing.T) {
66-
// UseApp(HelloworldAppName)
67-
// util.RunCmd("go", "mod", "vendor")
68-
// RunGoBuild(t, "-debuglog", "go", "build", "-mod=vendor")
69-
// }
72+
func TestBuildHelloworldWithVendor1(t *testing.T) {
73+
UseApp(HelloworldAppName)
74+
runModVendor(t)
75+
RunGoBuild(t, "go", "build")
76+
}
7077

71-
// func TestBuildHelloworldWithVendor3(t *testing.T) {
72-
// UseApp(HelloworldAppName)
73-
// util.RunCmd("go", "mod", "vendor")
74-
// RunGoBuild(t, "-debuglog", "go", "build", "-mod", "vendor")
75-
// }
78+
func TestBuildHelloworldWithVendor2(t *testing.T) {
79+
UseApp(HelloworldAppName)
80+
runModVendor(t)
81+
RunGoBuild(t, "go", "build", "-mod=vendor")
82+
}
83+
84+
func TestBuildHelloworldWithVendor3(t *testing.T) {
85+
UseApp(HelloworldAppName)
86+
runModVendor(t)
87+
RunGoBuild(t, "go", "build", "-mod", "vendor")
88+
}

tool/preprocess/match.go

+67
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
package preprocess
1616

1717
import (
18+
"bufio"
1819
"encoding/json"
1920
"fmt"
2021
"os"
22+
"path/filepath"
2123
"strings"
2224

2325
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg"
@@ -35,6 +37,7 @@ const (
3537

3638
type ruleMatcher struct {
3739
availableRules map[string][]resource.InstRule
40+
moduleVersions map[string]string // vendor used only
3841
}
3942

4043
func newRuleMatcher() *ruleMatcher {
@@ -172,7 +175,16 @@ func (rm *ruleMatcher) match(cmdArgs []string) *resource.RuleBundle {
172175
continue
173176
}
174177
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
175182
version := shared.ExtractVersion(file)
183+
if rm.moduleVersions != nil {
184+
if v, ok := rm.moduleVersions[importPath]; ok {
185+
version = v
186+
}
187+
}
176188

177189
for i := len(availables) - 1; i >= 0; i-- {
178190
rule := availables[i]
@@ -280,6 +292,47 @@ func findFlagValue(cmd []string, flag string) string {
280292
return ""
281293
}
282294

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+
283336
func runMatch(matcher *ruleMatcher, cmd string, ch chan *resource.RuleBundle) {
284337
bundle := matcher.match(shared.SplitCmds(cmd))
285338
ch <- bundle
@@ -288,6 +341,20 @@ func runMatch(matcher *ruleMatcher, cmd string, ch chan *resource.RuleBundle) {
288341
func (dp *DepProcessor) matchRules(compileCmds []string) error {
289342
defer util.PhaseTimer("Match")()
290343
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+
291358
// Find used instrumentation rule according to compile commands
292359
ch := make(chan *resource.RuleBundle)
293360
for _, cmd := range compileCmds {

tool/preprocess/preprocess.go

+22-9
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ type DepProcessor struct {
108108
rule2Dir map[*resource.InstFuncRule]string
109109
ruleCache embed.FS
110110
goBuildCmd []string
111+
vendorBuild bool
111112
}
112113

113114
func newDepProcessor() *DepProcessor {
@@ -118,6 +119,7 @@ func newDepProcessor() *DepProcessor {
118119
importCandidates: nil,
119120
rule2Dir: map[*resource.InstFuncRule]string{},
120121
ruleCache: pkg.ExportRuleCache(),
122+
vendorBuild: shared.IsVendorBuild(),
121123
}
122124
// There is a tricky, all arguments after the tool itself are saved for
123125
// later use, which means the subcommand "go build" are also included
@@ -473,6 +475,12 @@ func runModTidy() error {
473475
return err
474476
}
475477

478+
func runModVendor() error {
479+
out, err := util.RunCmdOutput("go", "mod", "vendor")
480+
util.Log("Run go mod vendor: %v", out)
481+
return err
482+
}
483+
476484
func runGoGet(dep string) error {
477485
out, err := util.RunCmdOutput("go", "get", dep)
478486
util.Log("Run go get %v: %v", dep, out)
@@ -596,15 +604,6 @@ func precheck() error {
596604
if err != nil {
597605
return fmt.Errorf("failed to check go.mod: %w", err)
598606
}
599-
// Check if the project is build with vendor mode
600-
projRoot, err := shared.GetGoModDir()
601-
if err != nil {
602-
return fmt.Errorf("failed to get project root: %w", err)
603-
}
604-
vendor := filepath.Join(projRoot, shared.VendorDir)
605-
if exist, _ := util.PathExists(vendor); exist {
606-
return fmt.Errorf("vendor mode is not supported")
607-
}
608607

609608
// Check if the build arguments is sane
610609
if len(os.Args) < 3 {
@@ -678,6 +677,13 @@ func (dp *DepProcessor) setupDeps() error {
678677
return fmt.Errorf("failed to run mod tidy: %w", err)
679678
}
680679

680+
if dp.vendorBuild {
681+
err = runModVendor()
682+
if err != nil {
683+
return fmt.Errorf("failed to run mod vendor: %w", err)
684+
}
685+
}
686+
681687
// Run dry build to the build blueprint
682688
err = runDryBuild(dp.goBuildCmd)
683689
if err != nil {
@@ -766,6 +772,13 @@ func Preprocess() error {
766772
return fmt.Errorf("failed to run mod tidy: %w", err)
767773
}
768774

775+
if dp.vendorBuild {
776+
err = runModVendor()
777+
if err != nil {
778+
return fmt.Errorf("failed to run mod vendor: %w", err)
779+
}
780+
}
781+
769782
// // Retain otel rules and modified user files for debugging
770783
dp.saveDebugFiles()
771784
}

tool/shared/shared.go

+12
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,15 @@ func SplitCmds(input string) []string {
330330
}
331331
return args
332332
}
333+
334+
func IsVendorBuild() bool {
335+
projRoot, err := GetGoModDir()
336+
if err != nil {
337+
return false
338+
}
339+
vendor := filepath.Join(projRoot, VendorDir)
340+
if exist, _ := util.PathExists(vendor); exist {
341+
return true
342+
}
343+
return false
344+
}

0 commit comments

Comments
 (0)