| // 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 profilescmdline 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 profilescmdline |
| |
| import ( |
| "flag" |
| "fmt" |
| "strings" |
| |
| "v.io/jiri/jiri" |
| "v.io/jiri/profiles" |
| "v.io/jiri/profiles/profilesmanager" |
| "v.io/x/lib/cmdline" |
| ) |
| |
| // cmdInstall represents the "profile install" command. |
| var cmdInstall = &cmdline.Command{ |
| Runner: jiri.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.", |
| } |
| |
| // cmdUpdate represents the "profile update" command. |
| var cmdUpdate = &cmdline.Command{ |
| Runner: jiri.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: jiri.RunnerFunc(runCleanup), |
| Name: "cleanup", |
| Short: "Cleanup the locally installed profiles", |
| Long: "Cleanup the locally installed profiles. This is generally required when recovering from earlier bugs or when preparing for a subsequent change to the profiles implementation.", |
| ArgsName: "<profiles>", |
| ArgsLong: "<profiles> is a list of profiles to cleanup, if omitted all profiles are cleaned.", |
| } |
| |
| // cmdUninstall represents the "profile uninstall" command. |
| var cmdUninstall = &cmdline.Command{ |
| Runner: jiri.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.", |
| } |
| |
| func runUpdate(jirix *jiri.X, args []string) error { |
| return updateImpl(jirix, &updateFlags, args) |
| } |
| |
| func runCleanup(jirix *jiri.X, args []string) error { |
| return cleanupImpl(jirix, &cleanupFlags, args) |
| } |
| |
| func runInstall(jirix *jiri.X, args []string) error { |
| return installImpl(jirix, &installFlags, args) |
| } |
| |
| func runUninstall(jirix *jiri.X, args []string) error { |
| return uninstallImpl(jirix, &uninstallFlags, args) |
| } |
| |
| type commonFlagValues struct { |
| // The value of --profiles-db |
| dbFilename string |
| // The value of --profile-root |
| root string |
| } |
| |
| type installFlagValues struct { |
| commonFlagValues |
| // The value of --target and --env |
| target profiles.Target |
| // The value of --force |
| force bool |
| } |
| |
| type uninstallFlagValues struct { |
| commonFlagValues |
| // The value of --target |
| target profiles.Target |
| // The value of --all-targets |
| allTargets bool |
| // The value of --v |
| verbose bool |
| // TODO(cnicolaou): add a flag to remove the profile only from the DB. |
| } |
| |
| type updateFlagValues struct { |
| commonFlagValues |
| // The value of --v |
| verbose bool |
| } |
| |
| type cleanupFlagValues struct { |
| commonFlagValues |
| // The value of --gc |
| gc bool |
| // The value of --ensure-specific-versions-are-set |
| ensureSpecificVersions bool |
| // The value of --rewrite-profiles-manifest |
| rewriteManifest bool |
| // The value of --rm-all |
| rmAll bool |
| // The value of --v |
| verbose bool |
| } |
| |
| var ( |
| installFlags installFlagValues |
| uninstallFlags uninstallFlagValues |
| cleanupFlags cleanupFlagValues |
| updateFlags updateFlagValues |
| ) |
| |
| // RegisterManagementCommands registers the management subcommands: |
| // uninstall, install, update and cleanup. |
| // |
| func RegisterManagementCommands(parent *cmdline.Command, defaultDBFilename string) { |
| initInstallCommand(&cmdInstall.Flags, defaultDBFilename) |
| initUninstallCommand(&cmdUninstall.Flags, defaultDBFilename) |
| initUpdateCommand(&cmdUpdate.Flags, defaultDBFilename) |
| initCleanupCommand(&cmdCleanup.Flags, defaultDBFilename) |
| parent.Children = append(parent.Children, cmdInstall, cmdUninstall, cmdUpdate, cmdCleanup) |
| } |
| |
| func initCommon(flags *flag.FlagSet, c *commonFlagValues, defaultDBFilename string) { |
| RegisterDBFilenameFlag(flags, &c.dbFilename, defaultDBFilename) |
| flags.StringVar(&c.root, "profile-dir", "profiles", "the directory, relative to JIRI_ROOT, that profiles are installed in") |
| } |
| |
| func initInstallCommand(flags *flag.FlagSet, defaultDBFilename string) { |
| initCommon(flags, &installFlags.commonFlagValues, defaultDBFilename) |
| profiles.RegisterTargetAndEnvFlags(flags, &installFlags.target) |
| flags.BoolVar(&installFlags.force, "force", false, "force install the profile even if it is already installed") |
| for _, name := range profilesmanager.Managers() { |
| profilesmanager.LookupManager(name).AddFlags(flags, profiles.Install) |
| } |
| } |
| |
| func initUninstallCommand(flags *flag.FlagSet, defaultDBFilename string) { |
| initCommon(flags, &uninstallFlags.commonFlagValues, defaultDBFilename) |
| profiles.RegisterTargetFlag(flags, &uninstallFlags.target) |
| flags.BoolVar(&uninstallFlags.allTargets, "all-targets", false, "apply to all targets for the specified profile(s)") |
| flags.BoolVar(&uninstallFlags.verbose, "v", false, "print more detailed information") |
| for _, name := range profilesmanager.Managers() { |
| profilesmanager.LookupManager(name).AddFlags(flags, profiles.Uninstall) |
| } |
| } |
| |
| func initUpdateCommand(flags *flag.FlagSet, defaultDBFilename string) { |
| initCommon(flags, &updateFlags.commonFlagValues, defaultDBFilename) |
| flags.BoolVar(&updateFlags.verbose, "v", false, "print more detailed information") |
| } |
| |
| func initCleanupCommand(flags *flag.FlagSet, defaultDBFilename string) { |
| initCommon(flags, &cleanupFlags.commonFlagValues, defaultDBFilename) |
| flags.BoolVar(&cleanupFlags.gc, "gc", false, "uninstall profile targets that are older than the current default") |
| flags.BoolVar(&cleanupFlags.ensureSpecificVersions, "ensure-specific-versions-are-set", false, "ensure that profile targets have a specific version set") |
| flags.BoolVar(&cleanupFlags.rmAll, "rm-all", false, "remove profiles manifest and all profile generated output files.") |
| flags.BoolVar(&cleanupFlags.rewriteManifest, "rewrite-profiles-manifest", false, "rewrite the profiles manifest file to use the latest schema version") |
| flags.BoolVar(&cleanupFlags.verbose, "v", false, "print more detailed information") |
| } |
| |
| // init a command that takes a list of profile managers as its arguments. |
| func initProfileManagersCommand(jirix *jiri.X, dbfile string, args []string) ([]string, *profiles.DB, error) { |
| if len(args) == 0 { |
| args = profilesmanager.Managers() |
| } else { |
| for _, n := range args { |
| if mgr := profilesmanager.LookupManager(n); mgr == nil { |
| avail := profilesmanager.Managers() |
| return nil, nil, fmt.Errorf("profile %v is not one of the available ones: %s", n, strings.Join(avail, ", ")) |
| } |
| } |
| } |
| db := profiles.NewDB() |
| if err := db.Read(jirix, dbfile); err != nil { |
| fmt.Fprintf(jirix.Stderr(), "Failed to read profiles database %q: %v", dbfile, err) |
| return nil, nil, err |
| } |
| return args, db, nil |
| } |
| |
| // init a command that takes a list of profiles (already installed) as |
| // its arguments. |
| func initProfilesCommand(jirix *jiri.X, dbfile string, args []string) ([]string, *profiles.DB, error) { |
| db := profiles.NewDB() |
| if err := db.Read(jirix, dbfile); err != nil { |
| fmt.Fprintf(jirix.Stderr(), "Failed to read profiles database %q: %v", dbfile, err) |
| return nil, nil, err |
| } |
| if len(args) == 0 { |
| args = db.Names() |
| } else { |
| for _, n := range args { |
| if p := db.LookupProfile(n); p == nil { |
| avail := db.Names() |
| return nil, nil, fmt.Errorf("profile %v is not one of the installed ones: %s", n, strings.Join(avail, ", ")) |
| } |
| } |
| } |
| return args, db, nil |
| } |
| |
| func updateImpl(jirix *jiri.X, cl *updateFlagValues, args []string) error { |
| verbose := cl.verbose |
| args, db, err := initProfileManagersCommand(jirix, cl.dbFilename, args) |
| if err != nil { |
| return err |
| } |
| for _, n := range args { |
| mgr := profilesmanager.LookupManager(n) |
| profile := db.LookupProfile(n) |
| if profile == nil { |
| continue |
| } |
| vi := mgr.VersionInfo() |
| for _, target := range profile.Targets() { |
| if vi.IsTargetOlderThanDefault(target.Version()) { |
| if verbose { |
| fmt.Fprintf(jirix.Stdout(), "Updating %s %s from %q to %s\n", n, target, target.Version(), vi) |
| } |
| target.SetVersion(vi.Default()) |
| err := mgr.Install(jirix, db, jiri.NewRelPath(cl.root), *target) |
| logResult(jirix, "Update", mgr, *target, err) |
| if err != nil { |
| return err |
| } |
| } else { |
| if verbose { |
| fmt.Fprintf(jirix.Stdout(), "%s %s at %q is up to date(%s)\n", n, target, target.Version(), vi) |
| } |
| } |
| } |
| } |
| return db.Write(jirix, cl.dbFilename) |
| } |
| |
| func cleanupGC(jirix *jiri.X, db *profiles.DB, root jiri.RelPath, verbose bool, args []string) error { |
| for _, name := range args { |
| mgr := profilesmanager.LookupManager(name) |
| if mgr == nil { |
| fmt.Fprintf(jirix.Stderr(), "%s is not linked into this binary", name) |
| continue |
| } |
| vi := mgr.VersionInfo() |
| profile := db.LookupProfile(name) |
| for _, target := range profile.Targets() { |
| if vi.IsTargetOlderThanDefault(target.Version()) { |
| err := mgr.Uninstall(jirix, db, root, *target) |
| logResult(jirix, "gc", mgr, *target, err) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| func cleanupEnsureVersionsAreSet(jirix *jiri.X, db *profiles.DB, root jiri.RelPath, verbose bool, args []string) error { |
| for _, name := range args { |
| mgr := profilesmanager.LookupManager(name) |
| if mgr == nil { |
| fmt.Fprintf(jirix.Stderr(), "%s is not linked into this binary", name) |
| continue |
| } |
| profile := db.LookupProfile(name) |
| for _, target := range profile.Targets() { |
| if len(target.Version()) == 0 { |
| prior := *target |
| version, err := mgr.VersionInfo().Select(target.Version()) |
| if err != nil { |
| return err |
| } |
| target.SetVersion(version) |
| db.RemoveProfileTarget(name, prior) |
| if err := db.AddProfileTarget(name, *target); err != nil { |
| return err |
| } |
| if verbose { |
| fmt.Fprintf(jirix.Stdout(), "%s %s had no version, now set to: %s\n", name, prior, target) |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| func cleanupRmAll(jirix *jiri.X, db *profiles.DB, root jiri.RelPath, verbose bool, args []string) error { |
| s := jirix.NewSeq() |
| if err := s.AssertFileExists(db.Filename()).Remove(db.Filename()).Done(); err != nil { |
| return err |
| } |
| d := root.Abs(jirix) |
| return s.AssertDirExists(d). |
| Run("chmod", "-R", "u+w", d). |
| RemoveAll(d). |
| Done() |
| } |
| |
| func cleanupImpl(jirix *jiri.X, cl *cleanupFlagValues, args []string) error { |
| args, db, err := initProfilesCommand(jirix, cl.dbFilename, args) |
| if err != nil { |
| return err |
| } |
| verbose := cl.verbose |
| root := jiri.NewRelPath(cl.root) |
| dirty := false |
| if cl.ensureSpecificVersions { |
| if verbose { |
| fmt.Fprintf(jirix.Stdout(), "Ensuring that all targets have a specific version set for %s\n", args) |
| } |
| if err := cleanupEnsureVersionsAreSet(jirix, db, root, verbose, args); err != nil { |
| return fmt.Errorf("ensure-specific-versions-are-set: %v", err) |
| } |
| dirty = true |
| } |
| if cl.gc { |
| if verbose { |
| fmt.Fprintf(jirix.Stdout(), "Removing targets older than the default version for %s\n", args) |
| } |
| if err := cleanupGC(jirix, db, root, verbose, args); err != nil { |
| return fmt.Errorf("gc: %v", err) |
| } |
| dirty = true |
| } |
| if cl.rmAll { |
| if verbose { |
| fmt.Fprintf(jirix.Stdout(), "Removing profile manifest and all profile output files\n") |
| } |
| if err := cleanupRmAll(jirix, db, root, verbose, args); err != nil { |
| return err |
| } |
| // Don't write out the profiles manifest file again. |
| return nil |
| } |
| if cl.rewriteManifest { |
| dirty = true |
| } |
| if !dirty { |
| return fmt.Errorf("at least one option must be specified") |
| } |
| return db.Write(jirix, cl.dbFilename) |
| } |
| |
| func logResult(jirix *jiri.X, action string, mgr profiles.Manager, target profiles.Target, err error) { |
| fmt.Fprintf(jirix.Stdout(), "%s: %s %s: ", action, mgr.Name(), target) |
| if err == nil { |
| fmt.Fprintf(jirix.Stdout(), "success\n") |
| } else { |
| fmt.Fprintf(jirix.Stdout(), "%v\n", err) |
| } |
| } |
| |
| func targetAtDefaultVersion(mgr profiles.Manager, target profiles.Target) (profiles.Target, error) { |
| def := target |
| version, err := mgr.VersionInfo().Select(target.Version()) |
| if err != nil { |
| return profiles.Target{}, err |
| } |
| def.SetVersion(version) |
| return def, nil |
| } |
| |
| func installImpl(jirix *jiri.X, cl *installFlagValues, args []string) error { |
| args, db, err := initProfileManagersCommand(jirix, cl.dbFilename, args) |
| if err != nil { |
| return err |
| } |
| root := jiri.NewRelPath(cl.root) |
| cl.target.UseCommandLineEnv() |
| names := []string{} |
| if cl.force { |
| names = args |
| } else { |
| for _, name := range args { |
| if p := db.LookupProfileTarget(name, cl.target); p != nil { |
| fmt.Fprintf(jirix.Stdout(), "%v %v is already installed as %v\n", name, cl.target, p) |
| continue |
| } |
| names = append(names, name) |
| } |
| } |
| for _, name := range names { |
| mgr := profilesmanager.LookupManager(name) |
| def, err := targetAtDefaultVersion(mgr, cl.target) |
| if err != nil { |
| return err |
| } |
| err = mgr.Install(jirix, db, root, def) |
| logResult(jirix, "Install:", mgr, def, err) |
| if err != nil { |
| return err |
| } |
| } |
| return db.Write(jirix, cl.dbFilename) |
| } |
| |
| func uninstallImpl(jirix *jiri.X, cl *uninstallFlagValues, args []string) error { |
| args, db, err := initProfileManagersCommand(jirix, cl.dbFilename, args) |
| if err != nil { |
| return err |
| } |
| root := jiri.NewRelPath(cl.root) |
| if cl.allTargets && cl.target.IsSet() { |
| fmt.Fprintf(jirix.Stdout(), "ignore target (%v) when used in conjunction with --all-targets\n", cl.target) |
| } |
| for _, name := range args { |
| profile := db.LookupProfile(name) |
| if profile == nil { |
| continue |
| } |
| mgr := profilesmanager.LookupManager(name) |
| var targets []*profiles.Target |
| if cl.allTargets { |
| targets = profile.Targets() |
| } else { |
| def, err := targetAtDefaultVersion(mgr, cl.target) |
| if err != nil { |
| return err |
| } |
| targets = []*profiles.Target{&def} |
| } |
| for _, target := range targets { |
| if err := mgr.Uninstall(jirix, db, root, *target); err != nil { |
| logResult(jirix, "Uninstall", mgr, *target, err) |
| return err |
| } |
| logResult(jirix, "Uninstall", mgr, *target, nil) |
| |
| } |
| } |
| return db.Write(jirix, cl.dbFilename) |
| } |