| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package commandline provides a command line driver (for v.io/x/lib/cmdline) |
| // for implementing jiri 'profile' subcommands. The intent is to support |
| // project specific instances of such profiles for managing software |
| // dependencies. |
| package commandline |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "strings" |
| "text/template" |
| |
| "v.io/jiri/profiles" |
| "v.io/jiri/project" |
| "v.io/jiri/tool" |
| "v.io/x/lib/cmdline" |
| "v.io/x/lib/textutil" |
| ) |
| |
| func init() { |
| tool.InitializeRunFlags(&CommandLineDriver.Flags) |
| } |
| |
| // CommandLineDriver implements the command line for the 'profile' |
| // subcommand. |
| var CommandLineDriver = &cmdline.Command{ |
| Name: "profile", |
| Short: "Manage profiles", |
| Long: helpMsg, |
| Children: []*cmdline.Command{ |
| cmdInstall, |
| cmdList, |
| cmdEnv, |
| cmdUninstall, |
| cmdUpdate, |
| cmdRecreate, |
| }, |
| } |
| |
| // cmdInstall represents the "profile install" command. |
| var cmdInstall = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runInstall), |
| Name: "install", |
| Short: "Install the given profiles", |
| Long: "Install the given profiles.", |
| ArgsName: "<profiles>", |
| ArgsLong: "<profiles> is a list of profiles to install.", |
| } |
| |
| // cmdList represents the "profile list" command. |
| var cmdList = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runList), |
| Name: "list", |
| Short: "List available or installed profiles", |
| Long: "List available or installed profiles.", |
| ArgsName: "[<profiles>]", |
| ArgsLong: "<profiles> is a list of profiles to list, defaulting to all profiles if none are specifically requested.", |
| } |
| |
| // cmdEnv represents the "profile env" command. |
| var cmdEnv = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runEnv), |
| Name: "env", |
| Short: "Display profile environment variables", |
| Long: ` |
| List profile specific and target specific environment variables. If the |
| requested environment variable name ends in = then only the value will |
| be printed, otherwise both name and value are printed, i.e. GOPATH="foo" vs |
| just "foo". |
| |
| If no environment variable names are requested then all will be printed |
| in <name>=<val> format. |
| `, |
| ArgsName: "[<environment variable names>]", |
| ArgsLong: "[<environment variable names>] is an optional list of environment variables to display", |
| } |
| |
| // cmdUpdate represents the "profile update" command. |
| var cmdUpdate = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runUpdate), |
| Name: "update", |
| Short: "Install the latest default version of the given profiles", |
| Long: "Install the latest default version of the given profiles.", |
| ArgsName: "<profiles>", |
| ArgsLong: "<profiles> is a list of profiles to update, if omitted all profiles are updated.", |
| } |
| |
| // cmdCleanup represents the "profile cleanup" command. |
| var cmdCleanup = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runCleanup), |
| Name: "cleanup", |
| Short: "Uninstall versions of the given profiles that are older than the default", |
| Long: "Uninstall versions of the given profiles that are older than the default.", |
| ArgsName: "<profiles>", |
| ArgsLong: "<profiles> is a list of profiles to cleanup, if omitted all profiles are cleaned.", |
| } |
| |
| // cmdRecreate represents the "profile recreate" command. |
| var cmdRecreate = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runRecreate), |
| Name: "recreate", |
| Short: "Display a list of commands that will recreate the currently installed profiles", |
| Long: "Display a list of commands that will recreate the currently installed profiles.", |
| ArgsName: "<profiles>", |
| ArgsLong: "<profiles> is a list of profiles to be recreated, if omitted commands to recreate all profiles are displayed.", |
| } |
| |
| // cmdUninstall represents the "profile uninstall" command. |
| var cmdUninstall = &cmdline.Command{ |
| Runner: cmdline.RunnerFunc(runUninstall), |
| Name: "uninstall", |
| Short: "Uninstall the given profiles", |
| Long: "Uninstall the given profiles.", |
| ArgsName: "<profiles>", |
| ArgsLong: "<profiles> is a list of profiles to uninstall.", |
| } |
| |
| var ( |
| targetFlag profiles.Target |
| manifestFlag string |
| showManifestFlag bool |
| profilesFlag string |
| rootDir string |
| availableFlag bool |
| verboseFlag bool |
| allFlag bool |
| infoFlag string |
| mergePoliciesFlag profiles.MergePolicies |
| ) |
| |
| func Main(name string) { |
| CommandLineDriver.Name = name |
| cmdline.Main(CommandLineDriver) |
| } |
| |
| func Init(defaultManifestFilename string) { |
| targetFlag = profiles.DefaultTarget() |
| mergePoliciesFlag = profiles.JiriMergePolicies() |
| |
| var err error |
| rootDir, err = project.JiriRoot() |
| if err != nil { |
| panic(err) |
| } |
| |
| // Every sub-command accepts: --manifest |
| for _, fs := range []*flag.FlagSet{ |
| &cmdInstall.Flags, |
| &cmdUpdate.Flags, |
| &cmdCleanup.Flags, |
| &cmdUninstall.Flags, |
| &cmdEnv.Flags, |
| &cmdList.Flags, |
| &cmdRecreate.Flags} { |
| profiles.RegisterManifestFlag(fs, &manifestFlag, defaultManifestFilename) |
| } |
| |
| // install accepts: --target and, --env. |
| profiles.RegisterTargetAndEnvFlags(&cmdInstall.Flags, &targetFlag) |
| |
| // uninstall, env and list accept: --target, |
| for _, fs := range []*flag.FlagSet{ |
| &cmdUninstall.Flags, |
| &cmdEnv.Flags, |
| &cmdList.Flags} { |
| profiles.RegisterTargetFlag(fs, &targetFlag) |
| } |
| |
| // env accepts --profiles and --merge-policies |
| profiles.RegisterProfilesFlag(&cmdEnv.Flags, &profilesFlag) |
| profiles.RegisterMergePoliciesFlag(&cmdEnv.Flags, &mergePoliciesFlag) |
| |
| // uninstall, list and env accept: --v |
| for _, fs := range []*flag.FlagSet{ |
| &cmdUpdate.Flags, |
| &cmdList.Flags, |
| &cmdEnv.Flags} { |
| fs.BoolVar(&verboseFlag, "v", false, "print more detailed information") |
| } |
| |
| // uninstall accept --all-targets but with different defaults. |
| cmdUninstall.Flags.BoolVar(&allFlag, "all-targets", false, "apply to all targets for the specified profile(s)") |
| |
| // list accepts --show-manifest, --available, --dir, --default, --versions |
| cmdList.Flags.BoolVar(&showManifestFlag, "show-manifest", false, "print out the manifest file") |
| cmdList.Flags.BoolVar(&availableFlag, "available", false, "print the list of available profiles") |
| cmdList.Flags.StringVar(&infoFlag, "info", "", infoUsage()) |
| |
| for _, mgr := range profiles.Managers() { |
| profiles.LookupManager(mgr).AddFlags(&cmdInstall.Flags, profiles.Install) |
| profiles.LookupManager(mgr).AddFlags(&cmdUninstall.Flags, profiles.Uninstall) |
| } |
| } |
| |
| func runList(env *cmdline.Env, args []string) error { |
| ctx := tool.NewContextFromEnv(env) |
| if showManifestFlag { |
| data, err := ctx.Run().ReadFile(manifestFlag) |
| if err != nil { |
| return err |
| } |
| fmt.Fprintln(ctx.Stdout(), string(data)) |
| return nil |
| } |
| if verboseFlag { |
| fmt.Fprintf(ctx.Stdout(), "Manifest: %s\n", manifestFlag) |
| } |
| if availableFlag { |
| if verboseFlag { |
| fmt.Fprintf(ctx.Stdout(), "Available Profiles:\n") |
| for _, name := range profiles.Managers() { |
| mgr := profiles.LookupManager(name) |
| vi := mgr.VersionInfo() |
| fmt.Fprintf(ctx.Stdout(), "%s: versions: %s\n", name, vi) |
| } |
| } else { |
| fmt.Fprintf(ctx.Stdout(), "%s\n", strings.Join(profiles.Managers(), ", ")) |
| } |
| } |
| if err := profiles.Read(ctx, manifestFlag); err != nil { |
| fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err) |
| return err |
| } |
| profileNames := args |
| if len(args) == 0 { |
| profileNames = profiles.Profiles() |
| } |
| availableNames := []string{} |
| for _, name := range profileNames { |
| if profiles.LookupProfile(name) != nil { |
| availableNames = append(availableNames, name) |
| } |
| } |
| if verboseFlag { |
| fmt.Fprintf(ctx.Stdout(), "Installed Profiles: ") |
| fmt.Fprintf(ctx.Stdout(), "%s\n", strings.Join(profiles.Profiles(), ", ")) |
| for _, name := range availableNames { |
| profile := profiles.LookupProfile(name) |
| fmt.Fprintf(ctx.Stdout(), "Profile: %s @ %s\n", profile.Name, profile.Root) |
| for _, target := range profile.Targets() { |
| fmt.Fprintf(ctx.Stdout(), "\t%s\n", target.DebugString()) |
| } |
| } |
| } else { |
| for _, name := range availableNames { |
| profile := profiles.LookupProfile(name) |
| mgr := profiles.LookupManager(name) |
| out := &bytes.Buffer{} |
| var targets profiles.OrderedTargets |
| if targetFlag.IsSet() { |
| targets = append(targets, profiles.LookupProfileTarget(name, targetFlag)) |
| } else { |
| targets = profile.Targets() |
| } |
| printHeader := len(availableNames) > 1 || len(targets) > 1 || len(infoFlag) == 0 |
| for _, target := range targets { |
| if printHeader { |
| out.WriteString(fmtHeader(name, target)) |
| out.WriteString(" ") |
| } |
| r, err := fmtInfo(mgr, profile, target) |
| if err != nil { |
| return err |
| } |
| out.WriteString(r) |
| if printHeader { |
| out.WriteString("\n") |
| } |
| } |
| fmt.Fprint(ctx.Stdout(), fmtOutput(ctx, out.String())) |
| } |
| } |
| return nil |
| } |
| |
| func fmtHeader(name string, target *profiles.Target) string { |
| if target == nil { |
| return name |
| } |
| return name + " " + target.String() |
| } |
| |
| type listInfo struct { |
| SchemaVersion profiles.Version |
| Target struct { |
| InstallationDir string |
| CommandLineEnv []string |
| Env []string |
| } |
| Profile struct { |
| Description string |
| Root string |
| DefaultVersion string |
| LatestVersion string |
| Versions []string |
| } |
| } |
| |
| func infoUsage() string { |
| return `The following fields for use with --profile-info are available: |
| SchemaVersion - the version of the profiles implementation. |
| Target.InstallationDir - the installation directory of the requested profile. |
| Target.CommandLineEnv - the environment variables specified via the command line when installing this profile target. |
| Target.Env - the environment variables computed by the profile installation process for this target. |
| Note: if no --target is specified then metadata for all targets will be displayed. |
| Profile.Description - description of the requested profile. |
| Profile.Root - the root directory of the requested profile. |
| Profile.Versions - the set of supported versions for this profile. |
| Profile.DefaultVersion - the default version of the requested profile. |
| Profile.LatestVersion - the latest version available for the requested profile. |
| Note: if no profiles are specified then metadata for all profiles will be displayed.` |
| } |
| |
| func fmtOutput(ctx *tool.Context, o string) string { |
| _, width, err := textutil.TerminalSize() |
| if err != nil { |
| width = 80 |
| } |
| if len(o) < width { |
| return o |
| } |
| out := &bytes.Buffer{} |
| w := textutil.NewUTF8LineWriter(out, width) |
| fmt.Fprint(w, o) |
| w.Flush() |
| return out.String() |
| } |
| |
| func fmtInfo(mgr profiles.Manager, profile *profiles.Profile, target *profiles.Target) (string, error) { |
| if len(infoFlag) > 0 { |
| info := &listInfo{} |
| info.SchemaVersion = profiles.SchemaVersion() |
| if target != nil { |
| info.Target.InstallationDir = target.InstallationDir |
| info.Target.CommandLineEnv = target.CommandLineEnv().Vars |
| info.Target.Env = target.Env.Vars |
| } |
| if profile != nil { |
| info.Profile.Root = profile.Root |
| } |
| if mgr != nil { |
| info.Profile.Description = mgr.Info() |
| vi := mgr.VersionInfo() |
| if supported := vi.Supported(); len(supported) > 0 { |
| info.Profile.Versions = supported |
| info.Profile.LatestVersion = supported[0] |
| } |
| info.Profile.DefaultVersion = vi.Default() |
| } |
| tmpl, err := template.New("list").Parse("{{ ." + infoFlag + "}}") |
| if err != nil { |
| return "", err |
| } |
| out := &bytes.Buffer{} |
| if err = tmpl.Execute(out, info); err != nil { |
| return "", fmt.Errorf("please specify a supported field:\n%s", infoUsage()) |
| } |
| return out.String(), nil |
| } |
| return "", nil |
| } |
| |
| func runRecreate(env *cmdline.Env, args []string) error { |
| ctx := tool.NewContextFromEnv(env) |
| if err := profiles.Read(ctx, manifestFlag); err != nil { |
| fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err) |
| return err |
| } |
| profileNames := args |
| if len(args) == 0 { |
| profileNames = profiles.Profiles() |
| } |
| prefix := "jiri v23-profile install" |
| for _, name := range profileNames { |
| profile := profiles.LookupProfile(name) |
| if profile == nil { |
| return fmt.Errorf("Profile %v is not installed", name) |
| } |
| for _, target := range profile.Targets() { |
| fmt.Fprintf(ctx.Stdout(), "%s --target=%s", prefix, target) |
| cmdEnv := target.CommandLineEnv() |
| if len(cmdEnv.Vars) > 0 { |
| fmt.Fprintf(ctx.Stdout(), " --env=\"%s\"", strings.Join(cmdEnv.Vars, ",")) |
| } |
| fmt.Fprintf(ctx.Stdout(), " %s\n", name) |
| } |
| } |
| return nil |
| } |
| |
| func runEnv(env *cmdline.Env, args []string) error { |
| if len(profilesFlag) == 0 { |
| return fmt.Errorf("no profiles were specified using --profiles") |
| } |
| ctx := tool.NewContextFromEnv(env) |
| ch, err := profiles.NewConfigHelper(ctx, profiles.UseProfiles, manifestFlag) |
| if err != nil { |
| return err |
| } |
| profileNames := strings.Split(profilesFlag, ",") |
| if err := ch.ValidateRequestedProfilesAndTarget(profileNames, targetFlag); err != nil { |
| return err |
| } |
| ch.MergeEnvFromProfiles(mergePoliciesFlag, targetFlag, profileNames...) |
| out := fmtVars(ch.ToMap(), args) |
| if len(out) > 0 { |
| fmt.Fprintln(ctx.Stdout(), out) |
| } |
| return nil |
| } |
| |
| func expr(k, v string, trimmed bool) string { |
| if trimmed { |
| return v |
| } |
| return fmt.Sprintf("%s=%q ", k, v) |
| } |
| |
| func fmtVars(vars map[string]string, args []string) string { |
| buf := bytes.Buffer{} |
| if len(args) == 0 { |
| for k, v := range vars { |
| buf.WriteString(fmt.Sprintf("%s=%q ", k, v)) |
| } |
| } else { |
| for _, arg := range args { |
| name := strings.TrimSuffix(arg, "=") |
| trimmed := name != arg |
| for k, v := range vars { |
| if k == name { |
| buf.WriteString(expr(k, v, trimmed)) |
| } |
| } |
| } |
| } |
| return strings.TrimSuffix(buf.String(), " ") |
| } |
| |
| func initCommand(ctx *tool.Context, args []string) error { |
| if len(args) == 0 { |
| return fmt.Errorf("no profiles specified") |
| } |
| for _, n := range args { |
| if mgr := profiles.LookupManager(n); mgr == nil { |
| return fmt.Errorf("profile %v is not available, use \"list --available\" to see the list of available profiles", n) |
| } |
| } |
| if err := profiles.Read(ctx, manifestFlag); err != nil { |
| fmt.Fprintf(ctx.Stderr(), "Failed to read manifest: %v", err) |
| return err |
| } |
| return nil |
| } |
| |
| func runUpdate(env *cmdline.Env, args []string) error { |
| ctx := tool.NewContextFromEnv(env) |
| if len(args) == 0 { |
| args = profiles.Managers() |
| } |
| if err := initCommand(ctx, args); err != nil { |
| return err |
| } |
| for _, n := range args { |
| mgr := profiles.LookupManager(n) |
| profile := profiles.LookupProfile(n) |
| if profile == nil { |
| continue |
| } |
| vi := mgr.VersionInfo() |
| mgr.SetRoot(rootDir) |
| for _, target := range profile.Targets() { |
| if vi.IsNewerThanDefault(target.Version()) { |
| if verboseFlag { |
| fmt.Fprintf(ctx.Stdout(), "Updating %s %s from %q to %s\n", n, target, target.Version(), vi) |
| } |
| target.SetVersion(vi.Default()) |
| err := mgr.Install(ctx, *target) |
| logResult(ctx, "Update", mgr, *target, err) |
| if err != nil { |
| return err |
| } |
| } else { |
| if verboseFlag { |
| fmt.Fprintf(ctx.Stdout(), "%s %s at %q is up to date(%s)\n", n, target, target.Version(), vi) |
| } |
| } |
| } |
| } |
| return profiles.Write(ctx, manifestFlag) |
| } |
| |
| func runCleanup(env *cmdline.Env, args []string) error { |
| ctx := tool.NewContextFromEnv(env) |
| if len(args) == 0 { |
| args = profiles.Managers() |
| } |
| if err := initCommand(ctx, args); err != nil { |
| return err |
| } |
| for _, n := range args { |
| mgr := profiles.LookupManager(n) |
| vi := mgr.VersionInfo() |
| profile := profiles.LookupProfile(n) |
| for _, target := range profile.Targets() { |
| if vi.IsOlderThanDefault(target.Version()) { |
| err := mgr.Uninstall(ctx, *target) |
| logResult(ctx, "Cleanup", mgr, *target, err) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| return profiles.Write(ctx, manifestFlag) |
| } |
| |
| func logResult(ctx *tool.Context, action string, mgr profiles.Manager, target profiles.Target, err error) { |
| fmt.Fprintf(ctx.Stdout(), "%s: %s %s: ", action, mgr.Name(), target) |
| if err == nil { |
| fmt.Fprintf(ctx.Stdout(), "success\n") |
| } else { |
| fmt.Fprintf(ctx.Stdout(), "%v\n", err) |
| } |
| } |
| |
| func applyCommand(names []string, env *cmdline.Env, ctx *tool.Context, target profiles.Target, fn func(profiles.Manager, *tool.Context, profiles.Target) error) error { |
| for _, n := range names { |
| mgr := profiles.LookupManager(n) |
| version, err := mgr.VersionInfo().Select(target.Version()) |
| if err != nil { |
| return err |
| } |
| target.SetVersion(version) |
| mgr.SetRoot(rootDir) |
| if err := fn(mgr, ctx, target); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func runInstall(env *cmdline.Env, args []string) error { |
| ctx := tool.NewContextFromEnv(env) |
| if err := initCommand(ctx, args); err != nil { |
| return err |
| } |
| names := []string{} |
| if len(args) == 0 { |
| for _, name := range profiles.Managers() { |
| names = append(names, name) |
| } |
| } |
| targetFlag.UseCommandLineEnv() |
| for _, name := range args { |
| if p := profiles.LookupProfileTarget(name, targetFlag); p != nil { |
| fmt.Fprintf(ctx.Stdout(), "%v %v is already installed as %v\n", name, targetFlag, p) |
| continue |
| } |
| names = append(names, name) |
| } |
| if err := applyCommand(names, env, ctx, targetFlag, |
| func(mgr profiles.Manager, ctx *tool.Context, target profiles.Target) error { |
| err := mgr.Install(ctx, target) |
| logResult(ctx, "Install:", mgr, target, err) |
| return err |
| }); err != nil { |
| return err |
| } |
| return profiles.Write(ctx, manifestFlag) |
| } |
| |
| func runUninstall(env *cmdline.Env, args []string) error { |
| ctx := tool.NewContextFromEnv(env) |
| if err := initCommand(ctx, args); err != nil { |
| return err |
| } |
| if allFlag && targetFlag.IsSet() { |
| return fmt.Errorf("don't specify a target (%v) in conjunction with --all-targets", targetFlag) |
| } |
| if allFlag { |
| for _, name := range args { |
| profile := profiles.LookupProfile(name) |
| mgr := profiles.LookupManager(name) |
| if profile == nil || mgr == nil { |
| continue |
| } |
| mgr.SetRoot(rootDir) |
| for _, target := range profile.Targets() { |
| if err := mgr.Uninstall(ctx, *target); err != nil { |
| logResult(ctx, "Uninstall", mgr, *target, err) |
| return err |
| } |
| logResult(ctx, "Uninstall", mgr, *target, nil) |
| } |
| } |
| } else { |
| applyCommand(args, env, ctx, targetFlag, |
| func(mgr profiles.Manager, ctx *tool.Context, target profiles.Target) error { |
| err := mgr.Uninstall(ctx, target) |
| logResult(ctx, "Uninstall", mgr, target, err) |
| return err |
| }) |
| } |
| return profiles.Write(ctx, manifestFlag) |
| } |