Skip to content

Commit

Permalink
Improvements for GoVPP CLI (#135)
Browse files Browse the repository at this point in the history
* Improvements for GoVPP CLI

- added new linter rule MessageSameStatus
- fixed issue with cache of cloned repository
- added option --targz for vppapi export command
- fixed issues with color mode when not using terminal
- added option to vppapi diff command to include comment differences
- added --paths option to filter out specific API files or paths

Signed-off-by: Ondrej Fabry <ofabry@cisco.com>

* Remove test

Signed-off-by: Ondrej Fabry <ofabry@cisco.com>

* Resolve review feedback

Signed-off-by: Ondrej Fabry <ofabry@cisco.com>

---------

Signed-off-by: Ondrej Fabry <ofabry@cisco.com>
  • Loading branch information
ondrej-fabry committed Jun 23, 2023
1 parent 14d176d commit 21b5292
Show file tree
Hide file tree
Showing 24 changed files with 564 additions and 357 deletions.
2 changes: 0 additions & 2 deletions binapigen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ type Generator struct {
opts Options

vppapiSchema *vppapi.Schema
//apiFiles []vppapi.File
//vppVersion string

Files []*File
FilesByName map[string]*File
Expand Down
9 changes: 9 additions & 0 deletions binapigen/vppapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ func normalizeImport(imp string) string {
return imp
}

// SortFilesByName sorts list of files by their name.
func SortFilesByName(apifiles []vppapi.File) {
sort.SliceStable(apifiles, func(i, j int) bool {
a := apifiles[i]
b := apifiles[j]
return a.Name < b.Name
})
}

// SortFilesByImports sorts list of files by their imports.
func SortFilesByImports(apifiles []vppapi.File) {
dependsOn := func(file *vppapi.File, dep string) bool {
Expand Down
28 changes: 16 additions & 12 deletions binapigen/vppapi/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const (
cacheDir = "./.cache"
)

func cloneRepoLocally(repo string, commit string, depth int) (string, error) {
func cloneRepoLocally(repo string, commit string, branch string, depth int) (string, error) {
repoPath := strings.ReplaceAll(repo, "/", "_")
repoPath = strings.ReplaceAll(repoPath, ":", "_")
cachePath := filepath.Join(cacheDir, repoPath)
Expand All @@ -116,36 +116,40 @@ func cloneRepoLocally(repo string, commit string, depth int) (string, error) {
if err := os.MkdirAll(cacheDir, 0755); err != nil {
return "", fmt.Errorf("failed to create cache directory: %w", err)
}
logrus.Tracef("Cloning repository %q", repo)

var args []string
args := []string{"--single-branch"}
if depth > 0 {
args = append(args, fmt.Sprintf("--depth=%d", depth))
}
args = append(args, repo, cachePath)
logrus.Debugf("cloning repo: %v", args)
cmd := exec.Command("git", append([]string{"clone"}, args...)...)
if output, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("failed to clone repository: %w\nOutput: %s", err, output)
}
} else if err != nil {
return "", fmt.Errorf("failed to check if cache exists: %w", err)
} else {
logrus.Debugf("Fetching %q", commit)
cmd := exec.Command("git", "fetch", "origin", commit)
cmd.Dir = cachePath
if output, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("failed to fetch repository: %w\nOutput: %s", err, output)
}
}
logrus.Debugf("local repo dir: %q, fetching %q", cachePath, commit)

cmd := exec.Command("git", "fetch", "-f", "origin", commit)
cmd.Dir = cachePath
if output, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("failed to fetch repository: %w\nOutput: %s", err, output)
}

// Resolve the commit hash for the given branch/tag
commitHash, err := resolveCommitHash(cachePath, commit)
ref := commit
if branch != "" {
ref = "origin/" + branch
}
commitHash, err := resolveCommitHash(cachePath, ref)
if err != nil {
return "", fmt.Errorf("failed to resolve commit hash: %w", err)
}

// Check out the repository at the resolved commit
cmd := exec.Command("git", "checkout", commitHash)
cmd = exec.Command("git", "checkout", commitHash)
cmd.Dir = cachePath
if output, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("failed to check out repository: %w\nOutput: %s", err, output)
Expand Down
12 changes: 1 addition & 11 deletions binapigen/vppapi/input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,11 @@ func Test_runCommandInRepo(t *testing.T) {
},
wantErr: false,
},
{
name: "tag",
args: args{
repo: "https://github.com/FDio/vpp.git",
commit: "v23.02",
command: "bash",
args: []string{"-exc", "bash ./src/scripts/version; make json-api-files && ls ./build-root/install-vpp-native/vpp/share/vpp/api"},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
//, tt.args.command, tt.args.args...
_, err := cloneRepoLocally(tt.args.repo, tt.args.commit, 0)
_, err := cloneRepoLocally(tt.args.repo, tt.args.commit, "", 0)
if (err != nil) != tt.wantErr {
t.Errorf("cloneRepoLocally() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
6 changes: 3 additions & 3 deletions binapigen/vppapi/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (input *InputRef) Retrieve() (*VppInput, error) {
return nil, fmt.Errorf("invalid path in input reference")
}

logrus.Tracef("retrieving input:\n%v", input)
logrus.Tracef("retrieving input: %+v", input)

switch input.Format {
case FormatNone:
Expand All @@ -90,7 +90,7 @@ func (input *InputRef) Retrieve() (*VppInput, error) {
return nil, fmt.Errorf("cannot set both branch and tag")
} else if branch != "" || tag != "" {
if ref != "" {
return nil, fmt.Errorf("cannot set ref if branch or tag is set")
return nil, fmt.Errorf("cannot set rev if branch or tag is set")
}
if branch != "" {
ref = branch
Expand All @@ -115,7 +115,7 @@ func (input *InputRef) Retrieve() (*VppInput, error) {

logrus.Debugf("updating local repo %s to %s", input.Path, commit)

repoDir, err := cloneRepoLocally(input.Path, commit, cloneDepth)
repoDir, err := cloneRepoLocally(input.Path, commit, branch, cloneDepth)
if err != nil {
return nil, err
}
Expand Down
32 changes: 23 additions & 9 deletions binapigen/vppapi/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -53,7 +54,10 @@ func ResolveApiDir(dir string) string {
// check if the API directory exists
_, err := os.Stat(apiDirPath)
if err == nil {
logrus.Tracef("returning %q as api dir", localVPPBuildApiDir)
logrus.Tracef("api dir %q exists, running 'make json-api-files'", localVPPBuildApiDir)
if err := makeJsonApiFiles(dir); err != nil {
logrus.Warnf("make json-api-files failed: %v", err)
}
return apiDirPath
} else if errors.Is(err, os.ErrNotExist) {
logrus.Tracef("api dir %q does not exist, running 'make json-api-files'", localVPPBuildApiDir)
Expand Down Expand Up @@ -94,12 +98,6 @@ func makeJsonApiFiles(dir string) error {
//
// Version resolved here can be overriden by setting VPP_VERSION env var.
func ResolveVPPVersion(apidir string) string {
// check env variable override
if ver := os.Getenv(VPPVersionEnvVar); ver != "" {
logrus.Debugf("VPP version was manually set to %q via %s env var", ver, VPPVersionEnvVar)
return ver
}

// check if inside VPP repo
repoDir, err := findGitRepoRootDir(apidir)
if err != nil {
Expand All @@ -118,12 +116,28 @@ func ResolveVPPVersion(apidir string) string {
// try to read VPP_VERSION file
data, err := os.ReadFile(path.Join(repoDir, "VPP_VERSION"))
if err == nil {
return strings.TrimSpace(string(data))
ver := strings.TrimSpace(string(data))
logrus.Debugf("VPP version was resolved to %q from VPP_VERSION file", ver)
return ver
}
}

// try to read VPP_VERSION file
data, err := os.ReadFile(path.Join(apidir, "VPP_VERSION"))
if err == nil {
ver := strings.TrimSpace(string(data))
logrus.Debugf("VPP version was resolved to %q from VPP_VERSION file", ver)
return ver
}

// check env variable override
if ver := os.Getenv(VPPVersionEnvVar); ver != "" {
logrus.Debugf("VPP version was manually set to %q via %s env var", ver, VPPVersionEnvVar)
return ver
}

// assuming VPP package is installed
if _, err := exec.LookPath("vpp"); err == nil {
if filepath.Clean(apidir) == DefaultDir {
version, err := GetVPPVersionInstalled()
if err != nil {
logrus.Debugf("ERR: failed to resolve VPP version from installed package: %v", err)
Expand Down
108 changes: 108 additions & 0 deletions cmd/govpp/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,111 @@
// limitations under the License.

package main

import (
"io"

"github.com/docker/cli/cli/streams"
"github.com/moby/term"
)

type Cli interface {
Out() *streams.Out
Err() io.Writer
In() *streams.In
Apply(...CliOption) error
}

type AppCli struct {
out *streams.Out
err io.Writer
in *streams.In
}

func NewCli(opt ...CliOption) (Cli, error) {
cli := new(AppCli)
if err := cli.Apply(opt...); err != nil {
return nil, err
}
if cli.out == nil || cli.in == nil || cli.err == nil {
stdin, stdout, stderr := term.StdStreams()
if cli.in == nil {
cli.in = streams.NewIn(stdin)
}
if cli.out == nil {
cli.out = streams.NewOut(stdout)
}
if cli.err == nil {
cli.err = stderr
}
}
return cli, nil
}

func (cli *AppCli) Out() *streams.Out {
return cli.out
}

func (cli *AppCli) Err() io.Writer {
return cli.err
}

func (cli *AppCli) In() *streams.In {
return cli.in
}

func (cli *AppCli) Apply(opt ...CliOption) error {
for _, o := range opt {
if err := o(cli); err != nil {
return err
}
}
return nil
}

type CliOption func(cli *AppCli) error

// WithStandardStreams sets a cli in, out and err streams with the standard streams.
func WithStandardStreams() CliOption {
return func(cli *AppCli) error {
// Set terminal emulation based on platform as required.
stdin, stdout, stderr := term.StdStreams()
cli.in = streams.NewIn(stdin)
cli.out = streams.NewOut(stdout)
cli.err = stderr
return nil
}
}

// WithCombinedStreams uses the same stream for the output and error streams.
func WithCombinedStreams(combined io.Writer) CliOption {
return func(cli *AppCli) error {
cli.out = streams.NewOut(combined)
cli.err = combined
return nil
}
}

// WithInputStream sets a cli input stream.
func WithInputStream(in io.ReadCloser) CliOption {
return func(cli *AppCli) error {
cli.in = streams.NewIn(in)
return nil
}
}

// WithOutputStream sets a cli output stream.
func WithOutputStream(out io.Writer) CliOption {
return func(cli *AppCli) error {
cli.out = streams.NewOut(out)
return nil
}
}

// WithErrorStream sets a cli error stream.
func WithErrorStream(err io.Writer) CliOption {
return func(cli *AppCli) error {
cli.err = err
return nil
}
}
22 changes: 13 additions & 9 deletions cmd/govpp/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,32 @@ const logo = `
`

func Execute() {
root := newRootCmd()
cli, err := NewCli()
if err != nil {
logrus.Fatalf("CLI init error: %v", err)
}
root := newRootCmd(cli)

if err := root.Execute(); err != nil {
logrus.Fatalf("ERROR: %v", err)
}
}

func newRootCmd() *cobra.Command {
func newRootCmd(cli Cli) *cobra.Command {
var (
glob GlobalOptions
)
cmd := &cobra.Command{
Use: "govpp [options] command",
Short: "GoVPP CLI app",
Use: "govpp [OPTIONS] COMMAND",
Short: "GoVPP CLI",
Long: color.Sprintf(logo, version.Short(), version.BuiltBy(), version.BuildTime()),
Version: version.String(),
SilenceUsage: true,
SilenceErrors: true,
TraverseChildren: true,
CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
InitOptions(&glob)
InitOptions(cli, &glob)
logrus.Tracef("global options: %+v", glob)

logrus.Tracef("args: %+v", args)
Expand All @@ -68,10 +72,10 @@ func newRootCmd() *cobra.Command {
glob.InstallFlags(cmd.PersistentFlags())

cmd.AddCommand(
newGenerateCmd(),
newVppapiCmd(),
newHttpCmd(),
newCliCommand(),
newGenerateCmd(cli),
newVppapiCmd(cli),
newHttpCmd(cli),
newCliCommand(cli),
)

cmd.InitDefaultVersionFlag()
Expand Down
2 changes: 1 addition & 1 deletion cmd/govpp/cmd_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type CliOptions struct {
Stdout io.Writer
}

func newCliCommand() *cobra.Command {
func newCliCommand(Cli) *cobra.Command {
var (
opts = CliOptions{
ApiSocket: socketclient.DefaultSocketName,
Expand Down
3 changes: 2 additions & 1 deletion cmd/govpp/cmd_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type GenerateOptions struct {
RunPlugins []string
}

func newGenerateCmd() *cobra.Command {
func newGenerateCmd(cli Cli) *cobra.Command {
var (
opts = GenerateOptions{
RunPlugins: []string{"rpc"},
Expand All @@ -44,6 +44,7 @@ func newGenerateCmd() *cobra.Command {
Use: "generate [apifile...]",
Aliases: []string{"gen"},
Short: "Generate code",
Long: "Generates Go bindings for VPP API",
RunE: func(cmd *cobra.Command, args []string) error {
if opts.Input == "" {
opts.Input = detectVppApiInput()
Expand Down
Loading

0 comments on commit 21b5292

Please sign in to comment.