Skip to content

Commit 32f5246

Browse files
authored
chore: refactor CLI baseline using github.com/urfave/cli/v2 (#49)
1 parent 93e342d commit 32f5246

File tree

16 files changed

+319
-148
lines changed

16 files changed

+319
-148
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go 1.23.0
88
require (
99
github.com/dave/dst v0.27.3
1010
github.com/stretchr/testify v1.10.0
11+
github.com/urfave/cli/v3 v3.4.1
1112
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
1213
gopkg.in/yaml.v3 v3.0.1
1314
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
1010
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
1111
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1212
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
13+
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
14+
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
1315
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
1416
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
1517
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=

tool/cmd/cmd-go.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"context"
8+
9+
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/setup"
10+
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util"
11+
"github.com/urfave/cli/v3"
12+
)
13+
14+
//nolint:gochecknoglobals // Implementation of a CLI command
15+
var commandGo = cli.Command{
16+
Name: "go",
17+
Description: "Invoke the go toolchain with toolexec mode",
18+
ArgsUsage: "[go toolchain flags]",
19+
SkipFlagParsing: true,
20+
Before: addLoggerPhaseAttribute,
21+
Action: func(ctx context.Context, cmd *cli.Command) error {
22+
logger := util.LoggerFromContext(ctx)
23+
backupFiles := []string{"go.mod", "go.sum", "go.work", "go.work.sum"}
24+
err := util.BackupFile(backupFiles)
25+
if err != nil {
26+
logger.Warn("failed to back up go.mod, go.sum, go.work, go.work.sum, proceeding despite this", "error", err)
27+
}
28+
defer func() {
29+
err = util.RestoreFile(backupFiles)
30+
if err != nil {
31+
logger.Warn("failed to restore go.mod, go.sum, go.work, go.work.sum", "error", err)
32+
}
33+
}()
34+
35+
err = setup.Setup(ctx)
36+
if err != nil {
37+
return cli.Exit(err, exitCodeFailure)
38+
}
39+
40+
err = setup.BuildWithToolexec(ctx, cmd.Args().Slice())
41+
if err != nil {
42+
return cli.Exit(err, exitCodeFailure)
43+
}
44+
45+
return nil
46+
},
47+
}

tool/cmd/cmd-setup.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"context"
8+
9+
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/setup"
10+
"github.com/urfave/cli/v3"
11+
)
12+
13+
//nolint:gochecknoglobals // Implementation of a CLI command
14+
var commandSetup = cli.Command{
15+
Name: "setup",
16+
Description: "Set up the environment for instrumentation",
17+
Before: addLoggerPhaseAttribute,
18+
Action: func(ctx context.Context, _ *cli.Command) error {
19+
err := setup.Setup(ctx)
20+
if err != nil {
21+
return cli.Exit(err, exitCodeFailure)
22+
}
23+
return nil
24+
},
25+
}

tool/cmd/cmd-toolexec.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"context"
8+
9+
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/instrument"
10+
"github.com/urfave/cli/v3"
11+
)
12+
13+
//nolint:gochecknoglobals // Implementation of a CLI command
14+
var commandToolexec = cli.Command{
15+
Name: "toolexec",
16+
Description: "Wrap a command run by the go toolchain",
17+
SkipFlagParsing: true,
18+
Hidden: true,
19+
Before: addLoggerPhaseAttribute,
20+
Action: func(ctx context.Context, cmd *cli.Command) error {
21+
err := instrument.Toolexec(ctx, cmd.Args().Slice())
22+
if err != nil {
23+
return cli.Exit(err, exitCodeFailure)
24+
}
25+
return nil
26+
},
27+
}

tool/cmd/cmd-version.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"runtime"
10+
11+
"github.com/urfave/cli/v3"
12+
)
13+
14+
//nolint:gochecknoglobals // Implementation of a CLI command
15+
var commandVersion = cli.Command{
16+
Name: "version",
17+
Description: "Print the version of the tool",
18+
Flags: []cli.Flag{
19+
&cli.BoolFlag{
20+
Name: "verbose",
21+
Usage: "Print additional information about the tool",
22+
},
23+
},
24+
Before: addLoggerPhaseAttribute,
25+
Action: func(_ context.Context, cmd *cli.Command) error {
26+
_, err := fmt.Fprintf(cmd.Writer, "otel version %s", Version)
27+
if err != nil {
28+
return cli.Exit(err, exitCodeFailure)
29+
}
30+
31+
if CommitHash != "unknown" {
32+
_, err = fmt.Fprintf(cmd.Writer, "+%s", CommitHash)
33+
if err != nil {
34+
return cli.Exit(err, exitCodeFailure)
35+
}
36+
}
37+
38+
if BuildTime != "unknown" {
39+
_, err = fmt.Fprintf(cmd.Writer, " (%s)", BuildTime)
40+
if err != nil {
41+
return cli.Exit(err, exitCodeFailure)
42+
}
43+
}
44+
45+
_, err = fmt.Fprint(cmd.Writer, "\n")
46+
if err != nil {
47+
return cli.Exit(err, exitCodeFailure)
48+
}
49+
50+
if cmd.Bool("verbose") {
51+
_, err = fmt.Fprintf(cmd.Writer, "%s\n", runtime.Version())
52+
if err != nil {
53+
return cli.Exit(err, exitCodeFailure)
54+
}
55+
}
56+
57+
return nil
58+
},
59+
}

tool/cmd/main.go

Lines changed: 53 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,68 @@
44
package main
55

66
import (
7-
"fmt"
8-
"io"
7+
"context"
98
"log/slog"
109
"os"
10+
"path/filepath"
1111

1212
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex"
13-
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/instrument"
14-
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/setup"
1513
"github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util"
14+
"github.com/urfave/cli/v3"
1615
)
1716

1817
const (
19-
ActionSetup = "setup"
20-
ActionGo = "go"
21-
ActionIntoolexec = "toolexec"
22-
ActionVersion = "version"
23-
DebugLog = "debug.log"
18+
exitCodeFailure = -1
19+
20+
debugLogFilename = "debug.log"
2421
)
2522

26-
func cleanBuildTemp() {
27-
_ = os.RemoveAll(setup.OtelRuntimeFile)
23+
func main() {
24+
app := cli.Command{
25+
Name: "otel",
26+
Usage: "OpenTelemetry Go Compile-Time Instrumentation Tool",
27+
HideVersion: true,
28+
Flags: []cli.Flag{
29+
&cli.StringFlag{
30+
Name: "work-dir",
31+
Aliases: []string{"w"},
32+
Usage: "The path to a directory where working files will be written",
33+
TakesFile: true,
34+
Value: filepath.Join(".", util.BuildTempDir),
35+
Sources: cli.NewValueSourceChain(cli.EnvVar(util.EnvOtelWorkDir)),
36+
},
37+
},
38+
Commands: []*cli.Command{
39+
&commandSetup,
40+
&commandGo,
41+
&commandToolexec,
42+
&commandVersion,
43+
},
44+
Before: initLogger,
45+
}
46+
47+
err := app.Run(context.Background(), os.Args)
48+
if err != nil {
49+
ex.Fatal(err)
50+
}
2851
}
2952

30-
func initLogger(phase string) (*slog.Logger, error) {
31-
var writer io.Writer
32-
switch phase {
33-
case ActionSetup, ActionGo:
34-
// Create .otel-build dir
35-
_, err := os.Stat(util.BuildTempDir)
36-
if os.IsNotExist(err) {
37-
err = os.MkdirAll(util.BuildTempDir, 0o755)
38-
if err != nil {
39-
return nil, ex.Errorf(err, "failed to create .otel-build dir")
40-
}
41-
}
42-
// Configure slog to write to the debug.log file
43-
path := util.GetBuildTemp(DebugLog)
44-
logFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o777)
45-
if err != nil {
46-
return nil, ex.Errorf(err, "failed to open log file")
47-
}
48-
writer = logFile
49-
case ActionIntoolexec:
50-
path := util.GetBuildTemp(DebugLog)
51-
logFile, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0o777)
52-
if err != nil {
53-
return nil, ex.Errorf(err, "failed to open log file")
54-
}
55-
writer = logFile
56-
default:
57-
return nil, ex.Errorf(nil, "invalid action: %s", phase)
53+
func initLogger(ctx context.Context, cmd *cli.Command) (context.Context, error) {
54+
buildTempDir := cmd.String("work-dir")
55+
err := os.MkdirAll(buildTempDir, 0o755)
56+
if err != nil {
57+
return ctx, ex.Errorf(err, "failed to create work directory %q", buildTempDir)
58+
}
59+
60+
logFilename := filepath.Join(buildTempDir, debugLogFilename)
61+
writer, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
62+
if err != nil {
63+
return ctx, ex.Errorf(err, "failed to open log file %q", buildTempDir)
5864
}
5965

6066
// Create a custom handler with shorter time format
6167
// Remove time and level keys as they make no sense for debugging
62-
var handler slog.Handler
63-
handler = slog.NewTextHandler(writer, &slog.HandlerOptions{
68+
handler := slog.NewTextHandler(writer, &slog.HandlerOptions{
6469
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
6570
if a.Key == slog.TimeKey || a.Key == slog.LevelKey {
6671
return slog.Attr{}
@@ -69,75 +74,14 @@ func initLogger(phase string) (*slog.Logger, error) {
6974
},
7075
Level: slog.LevelInfo,
7176
})
72-
handler = handler.WithAttrs([]slog.Attr{
73-
{
74-
Key: "phase",
75-
Value: slog.StringValue(phase),
76-
},
77-
})
7877
logger := slog.New(handler)
79-
return logger, nil
80-
}
81-
82-
func main() {
83-
if len(os.Args) < 2 { //nolint:mnd // number of args
84-
println("Usage: otel <action> <args...>")
85-
println("Actions:")
86-
println(" setup Set up the environment for instrumentation.")
87-
println(" go Invoke the go command with toolexec mode.")
88-
println(" version Print the version of the tool.")
89-
os.Exit(1)
90-
}
91-
92-
action := os.Args[1]
93-
switch action {
94-
case ActionVersion:
95-
_, _ = fmt.Printf("otel version %s_%s_%s\n", Version, CommitHash, BuildTime)
96-
case ActionSetup:
97-
// otel setup - This command is used to set up the environment for
98-
// instrumentation. It should be run before other commands.
99-
logger, err := initLogger(ActionSetup)
100-
if err != nil {
101-
ex.Fatal(err)
102-
}
103-
104-
err = setup.Setup(logger)
105-
if err != nil {
106-
ex.Fatal(err)
107-
}
108-
case ActionGo:
109-
// otel go build - Invoke the go command with toolexec mode. If the setup
110-
// is not done, it will run the setup command first.
111-
defer cleanBuildTemp()
112-
backup := []string{"go.mod", "go.sum", "go.work", "go.work.sum"}
113-
util.BackupFile(backup)
114-
defer util.RestoreFile(backup)
78+
ctx = util.ContextWithLogger(ctx, logger)
11579

116-
logger, err := initLogger(ActionGo)
117-
if err != nil {
118-
ex.Fatal(err)
119-
}
120-
err = setup.Setup(logger)
121-
if err != nil {
122-
ex.Fatal(err)
123-
}
124-
err = setup.BuildWithToolexec(logger, os.Args[1:])
125-
if err != nil {
126-
ex.Fatal(err)
127-
}
128-
case ActionIntoolexec:
129-
ex.Fatalf("It should not be used directly")
130-
default:
131-
// in -toolexec - This should not be used directly, but rather
132-
// invoked by the go command with toolexec mode.
133-
logger, err := initLogger(ActionIntoolexec)
134-
if err != nil {
135-
ex.Fatal(err)
136-
}
80+
return ctx, nil
81+
}
13782

138-
err = instrument.Toolexec(logger, os.Args[1:])
139-
if err != nil {
140-
ex.Fatal(err)
141-
}
142-
}
83+
func addLoggerPhaseAttribute(ctx context.Context, cmd *cli.Command) (context.Context, error) {
84+
logger := util.LoggerFromContext(ctx)
85+
logger = logger.With("phase", cmd.Name)
86+
return util.ContextWithLogger(ctx, logger), nil
14387
}

0 commit comments

Comments
 (0)