Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements for GoVPP CLI #135

Merged
merged 3 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion cmd/govpp/cmd_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type HttpCmdOptions struct {
Address string
}

func newHttpCmd() *cobra.Command {
func newHttpCmd(Cli) *cobra.Command {
var opts = HttpCmdOptions{
ApiSocket: socketclient.DefaultSocketName,
Address: DefaultHttpServiceAddress,
Expand Down
Loading