v.io/jiri/profiles: significant refactoring of profiles package:

- break profiles up into smaller packages:
  - profiles contains basic data types
  - profiles/reader contains a convenient reader interface
    for accessing the profiles database
  - profiles/manager contains support for implementing
    profiles.
  - profiles/commandline provides support for flags
    and subcommands that can be used by any command that
    reads and uses profiles.

..

MultiPart: 1/3

Change-Id: I35dbd55b32f0e85783db7f1e64330fa54d79ff08
diff --git a/profiles/.api b/profiles/.api
index e0f7a34..c315f3c 100644
--- a/profiles/.api
+++ b/profiles/.api
@@ -1,88 +1,47 @@
-pkg profiles, const Append MergeAction
-pkg profiles, const AppendJiriProfile AppendJiriProfileMode
 pkg profiles, const DefaultDirPerm os.FileMode
 pkg profiles, const DefaultFilePerm os.FileMode
-pkg profiles, const DoNotAppendJiriProfile bool
-pkg profiles, const First MergeAction
-pkg profiles, const Ignore MergeAction
-pkg profiles, const IgnoreBaseAndAppend MergeAction
-pkg profiles, const IgnoreBaseAndPrepend MergeAction
-pkg profiles, const IgnoreBaseAndUseFirst MergeAction
-pkg profiles, const IgnoreBaseAndUseLast MergeAction
-pkg profiles, const IgnoreProfiles MergeAction
 pkg profiles, const Install Action
-pkg profiles, const Last MergeAction
 pkg profiles, const Original Version
-pkg profiles, const Prepend MergeAction
-pkg profiles, const SkipProfiles ProfilesMode
 pkg profiles, const Uninstall Action
-pkg profiles, const UseProfiles ProfilesMode
 pkg profiles, const V2 Version
 pkg profiles, const V3 Version
 pkg profiles, const V4 Version
-pkg profiles, func AddProfileTarget(string, Target) error
 pkg profiles, func AtomicAction(*jiri.X, func() error, string, string) error
 pkg profiles, func DefaultTarget() Target
-pkg profiles, func EnsureProfileTargetIsInstalled(*jiri.X, string, jiri.RelPath, Target) error
-pkg profiles, func EnsureProfileTargetIsUninstalled(*jiri.X, string, jiri.RelPath, Target) error
-pkg profiles, func EnvFromProfile(Target, string) []string
 pkg profiles, func Fetch(*jiri.X, string, string) error
-pkg profiles, func FindTarget(OrderedTargets, *Target) *Target
-pkg profiles, func FindTargetWithDefault(OrderedTargets, *Target) *Target
-pkg profiles, func GoEnvironmentFromOS() []string
-pkg profiles, func InitProfilesFromFlag(string, AppendJiriProfileMode) []string
-pkg profiles, func InsertTarget(OrderedTargets, *Target) OrderedTargets
+pkg profiles, func FindTarget(Targets, *Target) *Target
+pkg profiles, func FindTargetWithDefault(Targets, *Target) *Target
+pkg profiles, func InsertTarget(Targets, *Target) Targets
 pkg profiles, func InstallPackages(*jiri.X, []string) error
-pkg profiles, func InstallProfile(string, string)
-pkg profiles, func JiriMergePolicies() MergePolicies
-pkg profiles, func LookupManager(string) Manager
-pkg profiles, func LookupProfile(string) *Profile
-pkg profiles, func LookupProfileTarget(string, Target) *Target
-pkg profiles, func Managers() []string
-pkg profiles, func MergeEnv(map[string]MergePolicy, *envvar.Vars, ...[]string)
 pkg profiles, func NativeTarget() Target
-pkg profiles, func NewConfigHelper(*jiri.X, ProfilesMode, string) (*ConfigHelper, error)
-pkg profiles, func NewTarget(string) (Target, error)
-pkg profiles, func NewTargetWithEnv(string, string) (Target, error)
+pkg profiles, func NewDB() *DB
+pkg profiles, func NewTarget(string, ...string) (Target, error)
 pkg profiles, func NewVersionInfo(string, map[string]interface{}, string) *VersionInfo
-pkg profiles, func ProfileMergePolicies() MergePolicies
-pkg profiles, func Profiles() []string
-pkg profiles, func Read(*jiri.X, string) error
-pkg profiles, func Register(string, Manager)
-pkg profiles, func RegisterManifestFlag(*flag.FlagSet, *string, string)
-pkg profiles, func RegisterMergePoliciesFlag(*flag.FlagSet, *MergePolicies)
-pkg profiles, func RegisterProfileFlags(*flag.FlagSet, *ProfilesMode, *string, *string, string, *MergePolicies, *Target)
 pkg profiles, func RegisterProfilesFlag(*flag.FlagSet, *string)
 pkg profiles, func RegisterTargetAndEnvFlags(*flag.FlagSet, *Target)
 pkg profiles, func RegisterTargetFlag(*flag.FlagSet, *Target)
-pkg profiles, func RemoveProfileTarget(string, Target) bool
-pkg profiles, func RemoveTarget(OrderedTargets, *Target) OrderedTargets
-pkg profiles, func SchemaVersion() Version
-pkg profiles, func UnsetGoEnvMap(map[string]string)
-pkg profiles, func UnsetGoEnvVars(*envvar.Vars)
+pkg profiles, func RemoveTarget(Targets, *Target) Targets
 pkg profiles, func Unzip(*jiri.X, string, string) error
-pkg profiles, func UpdateProfileTarget(string, Target) error
-pkg profiles, func WithDefaultVersion(Target) Target
-pkg profiles, func Write(*jiri.X, string) error
-pkg profiles, method (*ConfigHelper) GoPath() string
-pkg profiles, method (*ConfigHelper) JiriProfile() []string
-pkg profiles, method (*ConfigHelper) MergeEnv(map[string]MergePolicy, ...[]string)
-pkg profiles, method (*ConfigHelper) MergeEnvFromProfiles(map[string]MergePolicy, Target, ...string)
-pkg profiles, method (*ConfigHelper) PrependToPATH(string)
-pkg profiles, method (*ConfigHelper) Root() string
-pkg profiles, method (*ConfigHelper) SkippingProfiles() bool
-pkg profiles, method (*ConfigHelper) VDLPath() string
-pkg profiles, method (*ConfigHelper) ValidateRequestedProfilesAndTarget([]string, Target) error
+pkg profiles, method (*DB) AddProfileTarget(string, Target) error
+pkg profiles, method (*DB) EnvFromProfile(string, Target) []string
+pkg profiles, method (*DB) Filename() string
+pkg profiles, method (*DB) InstallProfile(string, string) *Profile
+pkg profiles, method (*DB) LookupProfile(string) *Profile
+pkg profiles, method (*DB) LookupProfileTarget(string, Target) *Target
+pkg profiles, method (*DB) Names() []string
+pkg profiles, method (*DB) Profiles() []*Profile
+pkg profiles, method (*DB) Read(*jiri.X, string) error
+pkg profiles, method (*DB) RemoveProfileTarget(string, Target) bool
+pkg profiles, method (*DB) SchemaVersion() Version
+pkg profiles, method (*DB) UpdateProfileTarget(string, Target) error
+pkg profiles, method (*DB) Write(*jiri.X, string) error
 pkg profiles, method (*Environment) Get() interface{}
 pkg profiles, method (*Environment) Set(string) error
 pkg profiles, method (*Environment) String() string
 pkg profiles, method (*Environment) Usage() string
-pkg profiles, method (*MergePolicy) String() string
-pkg profiles, method (*Profile) Targets() OrderedTargets
-pkg profiles, method (*ProfilesMode) Get() interface{}
-pkg profiles, method (*ProfilesMode) IsBoolFlag() bool
-pkg profiles, method (*ProfilesMode) Set(string) error
-pkg profiles, method (*ProfilesMode) String() string
+pkg profiles, method (*Profile) Name() string
+pkg profiles, method (*Profile) Root() string
+pkg profiles, method (*Profile) Targets() Targets
 pkg profiles, method (*Target) Arch() string
 pkg profiles, method (*Target) Less(*Target) bool
 pkg profiles, method (*Target) OS() string
@@ -99,15 +58,6 @@
 pkg profiles, method (*VersionInfo) Select(string) (string, error)
 pkg profiles, method (*VersionInfo) String() string
 pkg profiles, method (*VersionInfo) Supported() []string
-pkg profiles, method (MergePolicies) DebugString() string
-pkg profiles, method (MergePolicies) Get() interface{}
-pkg profiles, method (MergePolicies) Set(string) error
-pkg profiles, method (MergePolicies) String() string
-pkg profiles, method (MergePolicies) Usage() string
-pkg profiles, method (OrderedTargets) Len() int
-pkg profiles, method (OrderedTargets) Less(int, int) bool
-pkg profiles, method (OrderedTargets) Sort()
-pkg profiles, method (OrderedTargets) Swap(int, int)
 pkg profiles, method (Target) CommandLineEnv() Environment
 pkg profiles, method (Target) CrossCompiling() bool
 pkg profiles, method (Target) DebugString() string
@@ -115,48 +65,27 @@
 pkg profiles, method (Target) IsSet() bool
 pkg profiles, method (Target) Match(*Target) bool
 pkg profiles, method (Target) String() string
+pkg profiles, method (Targets) Len() int
+pkg profiles, method (Targets) Less(int, int) bool
+pkg profiles, method (Targets) Sort()
+pkg profiles, method (Targets) Swap(int, int)
 pkg profiles, type Action int
-pkg profiles, type AppendJiriProfileMode bool
-pkg profiles, type ConfigHelper struct
-pkg profiles, type ConfigHelper struct, embedded *envvar.Vars
+pkg profiles, type DB struct
 pkg profiles, type Environment struct
 pkg profiles, type Environment struct, Vars []string
 pkg profiles, type Manager interface { AddFlags, Info, Install, Name, String, Uninstall, VersionInfo }
 pkg profiles, type Manager interface, AddFlags(*flag.FlagSet, Action)
 pkg profiles, type Manager interface, Info() string
-pkg profiles, type Manager interface, Install(*jiri.X, jiri.RelPath, Target) error
+pkg profiles, type Manager interface, Install(*jiri.X, *DB, jiri.RelPath, Target) error
 pkg profiles, type Manager interface, Name() string
 pkg profiles, type Manager interface, String() string
-pkg profiles, type Manager interface, Uninstall(*jiri.X, jiri.RelPath, Target) error
+pkg profiles, type Manager interface, Uninstall(*jiri.X, *DB, jiri.RelPath, Target) error
 pkg profiles, type Manager interface, VersionInfo() *VersionInfo
-pkg profiles, type MergeAction int
-pkg profiles, type MergePolicies map[string]MergePolicy
-pkg profiles, type MergePolicy struct
-pkg profiles, type MergePolicy struct, Action MergeAction
-pkg profiles, type MergePolicy struct, Separator string
-pkg profiles, type OrderedTargets []*Target
 pkg profiles, type Profile struct
-pkg profiles, type Profile struct, Name string
-pkg profiles, type Profile struct, Root string
-pkg profiles, type ProfilesMode bool
 pkg profiles, type Target struct
 pkg profiles, type Target struct, Env Environment
 pkg profiles, type Target struct, InstallationDir string
 pkg profiles, type Target struct, UpdateTime time.Time
+pkg profiles, type Targets []*Target
 pkg profiles, type Version int
 pkg profiles, type VersionInfo struct
-pkg profiles, var AppendFlag MergePolicy
-pkg profiles, var AppendPath MergePolicy
-pkg profiles, var GoFlags []string
-pkg profiles, var IgnoreBaseAppendFlag MergePolicy
-pkg profiles, var IgnoreBaseAppendPath MergePolicy
-pkg profiles, var IgnoreBasePrependFlag MergePolicy
-pkg profiles, var IgnoreBasePrependPath MergePolicy
-pkg profiles, var IgnoreBaseUseFirst MergePolicy
-pkg profiles, var IgnoreBaseUseLast MergePolicy
-pkg profiles, var IgnoreVariable MergePolicy
-pkg profiles, var PrependFlag MergePolicy
-pkg profiles, var PrependPath MergePolicy
-pkg profiles, var UseBaseIgnoreProfiles MergePolicy
-pkg profiles, var UseFirst MergePolicy
-pkg profiles, var UseLast MergePolicy
diff --git a/profiles/commandline/.api b/profiles/commandline/.api
index 50d42b1..5782e39 100644
--- a/profiles/commandline/.api
+++ b/profiles/commandline/.api
@@ -1,3 +1,17 @@
-pkg commandline, func Init(string)
-pkg commandline, func Main(string)
-pkg commandline, var CommandLineDriver *cmdline.Command
+pkg commandline, const DefaultProfiles ideal-string
+pkg commandline, func IsFlagSet(*flag.FlagSet, string) bool
+pkg commandline, func RegisterDBFilenameFlag(*flag.FlagSet, *string, string)
+pkg commandline, func RegisterManagementCommands(*cmdline.Command, string)
+pkg commandline, func RegisterMergePoliciesFlag(*flag.FlagSet, *reader.MergePolicies)
+pkg commandline, func RegisterProfilesFlag(*flag.FlagSet, *string)
+pkg commandline, func RegisterReaderCommands(*cmdline.Command, string)
+pkg commandline, func RegisterReaderCommandsUsingParent(*cmdline.Command, *ReaderFlagValues, string)
+pkg commandline, func RegisterReaderFlags(*flag.FlagSet, *ReaderFlagValues, string)
+pkg commandline, type ReaderFlagValues struct
+pkg commandline, type ReaderFlagValues struct, DBFilename string
+pkg commandline, type ReaderFlagValues struct, MergePolicies reader.MergePolicies
+pkg commandline, type ReaderFlagValues struct, Profiles string
+pkg commandline, type ReaderFlagValues struct, ProfilesMode reader.ProfilesMode
+pkg commandline, type ReaderFlagValues struct, Target profiles.Target
+pkg commandline, type ReaderFlagValues struct, Verbose bool
+pkg commandline, var HelpMsg string
diff --git a/profiles/commandline/cmd_test.go b/profiles/commandline/cmd_test.go
new file mode 100644
index 0000000..6a02359
--- /dev/null
+++ b/profiles/commandline/cmd_test.go
@@ -0,0 +1,17 @@
+// 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
+
+import (
+	"v.io/jiri/jiri"
+)
+
+func Reset() {
+	cmdList = newCmdList()
+	cmdList.Runner = jiri.RunnerFunc(runList)
+	cmdEnv = newCmdEnv()
+	listFlags.ReaderFlagValues = nil
+	envFlags.ReaderFlagValues = nil
+}
diff --git a/profiles/commandline/commandline_test.go b/profiles/commandline/commandline_test.go
new file mode 100644
index 0000000..af8bd37
--- /dev/null
+++ b/profiles/commandline/commandline_test.go
@@ -0,0 +1,105 @@
+// 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_test
+
+import (
+	"fmt"
+	"testing"
+
+	"v.io/jiri/profiles/commandline"
+	"v.io/jiri/profiles/reader"
+	"v.io/x/lib/cmdline"
+)
+
+var parent = cmdline.Command{
+	Name:     "test",
+	Short:    "test",
+	Long:     "test",
+	ArgsName: "test",
+	ArgsLong: "test",
+}
+
+func TestReaderParent(t *testing.T) {
+	commandline.Reset()
+	p := parent
+	args := []string{"--profiles-db=foo", "--skip-profiles"}
+	var rf commandline.ReaderFlagValues
+	// If RegisterReaderCommandsUsingParent is called, the common reader
+	// flags are hosted by the parent command.
+	commandline.RegisterReaderCommandsUsingParent(&p, &rf, "")
+	if got, want := len(p.Children), 2; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if err := p.Children[0].Flags.Parse(args); err == nil {
+		t.Errorf("this should have failed")
+	}
+	if err := p.Flags.Parse(args); err != nil {
+		t.Error(err)
+	}
+	if got, want := rf.DBFilename, "foo"; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := rf.ProfilesMode, reader.SkipProfiles; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	commandline.Reset()
+	p = parent
+	commandline.RegisterReaderFlags(&p.Flags, &rf, "")
+	if got, want := len(p.Children), 0; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if err := p.Flags.Parse(args); err != nil {
+		t.Fatal(err)
+	}
+	if got, want := rf.DBFilename, "foo"; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	commandline.Reset()
+	p = parent
+	// If RegisterReaderCommands is not called, the common reader
+	// flags are hosted by the subcommands.
+	commandline.RegisterReaderCommands(&p, "")
+	if err := p.Flags.Parse(args); err == nil {
+		t.Fatal(fmt.Errorf("this should have failed"))
+	}
+	if err := p.Children[0].Flags.Parse([]string{"--profiles=a,b"}); err != nil {
+		t.Fatal(err)
+	}
+	// NOTE, that we can't access the actual values of the flags when they
+	// are hosted by the subcommands.
+}
+
+func TestSubcommandFlags(t *testing.T) {
+	commandline.Reset()
+	p := parent
+	var rf commandline.ReaderFlagValues
+	commandline.RegisterReaderCommandsUsingParent(&p, &rf, "")
+	if got, want := len(p.Children), 2; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	args := []string{"--info", "Profile.Root"}
+	if err := p.Flags.Parse(args); err == nil {
+		t.Error("this should have failed")
+	}
+	if err := p.Children[0].Flags.Parse(args); err != nil {
+		t.Error(err)
+	}
+
+	commandline.Reset()
+	p = parent
+	commandline.RegisterReaderCommands(&p, "")
+	if got, want := len(p.Children), 2; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if err := p.Flags.Parse(args); err == nil {
+		t.Error("this should have failed")
+	}
+	if err := p.Children[0].Flags.Parse(args); err != nil {
+		t.Error(err)
+	}
+
+}
diff --git a/profiles/commandline/driver.go b/profiles/commandline/driver.go
deleted file mode 100644
index 2e3679c..0000000
--- a/profiles/commandline/driver.go
+++ /dev/null
@@ -1,671 +0,0 @@
-// 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/jiri"
-	"v.io/jiri/profiles"
-	"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,
-		cmdCleanup,
-	},
-}
-
-// 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.",
-}
-
-// cmdList represents the "profile list" command.
-var cmdList = &cmdline.Command{
-	Runner:   jiri.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: jiri.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:   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.",
-}
-
-// All installed profile data will be stored under profileRoot.
-var profileRoot = jiri.NewRelPath("profiles")
-
-var (
-	targetFlag           profiles.Target
-	manifestFlag         string
-	showManifestFlag     bool
-	profilesFlag         string
-	availableFlag        bool
-	verboseFlag          bool
-	allFlag              bool
-	infoFlag             string
-	mergePoliciesFlag    profiles.MergePolicies
-	specificVersionsFlag bool
-	cleanupFlag          bool
-	rmAllFlag            bool
-	rewriteManifestFlag  bool
-)
-
-func Main(name string) {
-	CommandLineDriver.Name = name
-	cmdline.Main(CommandLineDriver)
-}
-
-func Init(defaultManifestFilename string) {
-	targetFlag = profiles.DefaultTarget()
-	mergePoliciesFlag = profiles.JiriMergePolicies()
-
-	// Every sub-command accepts: --manifest
-	for _, fs := range []*flag.FlagSet{
-		&cmdInstall.Flags,
-		&cmdUpdate.Flags,
-		&cmdUninstall.Flags,
-		&cmdCleanup.Flags,
-		&cmdEnv.Flags,
-		&cmdList.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, env and cleanup accept: --v
-	for _, fs := range []*flag.FlagSet{
-		&cmdUpdate.Flags,
-		&cmdList.Flags,
-		&cmdCleanup.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-profiles-manifest, --available, --dir, --default, --versions
-	cmdList.Flags.BoolVar(&showManifestFlag, "show-profiles-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)
-	}
-
-	// cleanup accepts the following flags:
-	cmdCleanup.Flags.BoolVar(&cleanupFlag, "gc", false, "uninstall profile targets that are older than the current default")
-	cmdCleanup.Flags.BoolVar(&specificVersionsFlag, "ensure-specific-versions-are-set", false, "ensure that profile targets have a specific version set")
-	cmdCleanup.Flags.BoolVar(&rmAllFlag, "rm-all", false, "remove profiles manifest and all profile generated output files.")
-	cmdCleanup.Flags.BoolVar(&rewriteManifestFlag, "rewrite-profiles-manifest", false, "rewrite the profiles manifest file to use the latest schema version")
-}
-
-func runList(jirix *jiri.X, args []string) error {
-	if showManifestFlag {
-		data, err := jirix.NewSeq().ReadFile(manifestFlag)
-		if err != nil {
-			return err
-		}
-		fmt.Fprintln(jirix.Stdout(), string(data))
-		return nil
-	}
-	if verboseFlag {
-		fmt.Fprintf(jirix.Stdout(), "Manifest: %s\n", manifestFlag)
-	}
-	if availableFlag {
-		if verboseFlag {
-			fmt.Fprintf(jirix.Stdout(), "Available Profiles:\n")
-			for _, name := range profiles.Managers() {
-				mgr := profiles.LookupManager(name)
-				vi := mgr.VersionInfo()
-				fmt.Fprintf(jirix.Stdout(), "%s: versions: %s\n", name, vi)
-			}
-		} else {
-			fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(profiles.Managers(), ", "))
-		}
-	}
-	if err := profiles.Read(jirix, manifestFlag); err != nil {
-		fmt.Fprintf(jirix.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(jirix.Stdout(), "Installed Profiles: ")
-		fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(profiles.Profiles(), ", "))
-		for _, name := range availableNames {
-			profile := profiles.LookupProfile(name)
-			fmt.Fprintf(jirix.Stdout(), "Profile: %s @ %s\n", profile.Name, profile.Root)
-			for _, target := range profile.Targets() {
-				fmt.Fprintf(jirix.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(jirix, mgr, profile, target)
-				if err != nil {
-					return err
-				}
-				out.WriteString(r)
-				if printHeader {
-					out.WriteString("\n")
-				}
-			}
-			fmt.Fprint(jirix.Stdout(), 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
-		Command         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.
-	Target.Command - a command that can be used to create this profile.
-	Note: if no --target is specified then the requested field will be displayed for all targets.
-	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 the requested field will be displayed for all profiles.`
-}
-
-func fmtOutput(jirix *jiri.X, 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(jirix *jiri.X, mgr profiles.Manager, profile *profiles.Profile, target *profiles.Target) (string, error) {
-	if len(infoFlag) > 0 {
-		// Populate an instance listInfo
-		info := &listInfo{}
-		name := ""
-		if mgr != nil {
-			// Format the description on its own, without any preceeding
-			// text so that the line breaks work correctly.
-			info.Profile.Description = "\n" + fmtOutput(jirix, mgr.Info()) + "\n"
-			vi := mgr.VersionInfo()
-			if supported := vi.Supported(); len(supported) > 0 {
-				info.Profile.Versions = supported
-				info.Profile.LatestVersion = supported[0]
-			}
-			info.Profile.DefaultVersion = vi.Default()
-			name = mgr.Name()
-		}
-		info.SchemaVersion = profiles.SchemaVersion()
-		if target != nil {
-			info.Target.InstallationDir = jiri.NewRelPath(target.InstallationDir).Abs(jirix)
-			info.Target.CommandLineEnv = target.CommandLineEnv().Vars
-			info.Target.Env = target.Env.Vars
-			clenv := ""
-			if len(info.Target.CommandLineEnv) > 0 {
-				clenv = fmt.Sprintf(" --env=\"%s\" ", strings.Join(info.Target.CommandLineEnv, ","))
-			}
-			info.Target.Command = fmt.Sprintf("jiri v23-profile install --target=%s %s%s", target, clenv, name)
-		}
-		if profile != nil {
-			info.Profile.Root = profileRoot.Abs(jirix)
-		}
-
-		// Use a template to print out any field in our instance of listInfo.
-		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 runEnv(jirix *jiri.X, args []string) error {
-	if len(profilesFlag) == 0 {
-		return fmt.Errorf("no profiles were specified using --profiles")
-	}
-	ch, err := profiles.NewConfigHelper(jirix, 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(jirix.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(jirix *jiri.X, 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(jirix, manifestFlag); err != nil {
-		fmt.Fprintf(jirix.Stderr(), "Failed to read manifest: %v", err)
-		return err
-	}
-	return nil
-}
-
-func runUpdate(jirix *jiri.X, args []string) error {
-	if len(args) == 0 {
-		args = profiles.Managers()
-	}
-	if err := initCommand(jirix, args); err != nil {
-		return err
-	}
-	for _, n := range args {
-		mgr := profiles.LookupManager(n)
-		profile := profiles.LookupProfile(n)
-		if profile == nil {
-			continue
-		}
-		vi := mgr.VersionInfo()
-		for _, target := range profile.Targets() {
-			if vi.IsTargetOlderThanDefault(target.Version()) {
-				if verboseFlag {
-					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, profileRoot, *target)
-				logResult(jirix, "Update", mgr, *target, err)
-				if err != nil {
-					return err
-				}
-			} else {
-				if verboseFlag {
-					fmt.Fprintf(jirix.Stdout(), "%s %s at %q is up to date(%s)\n", n, target, target.Version(), vi)
-				}
-			}
-
-		}
-	}
-	return profiles.Write(jirix, manifestFlag)
-}
-
-func runGC(jirix *jiri.X, args []string) error {
-	for _, n := range args {
-		mgr := profiles.LookupManager(n)
-		vi := mgr.VersionInfo()
-		profile := profiles.LookupProfile(n)
-		for _, target := range profile.Targets() {
-			if vi.IsTargetOlderThanDefault(target.Version()) {
-				err := mgr.Uninstall(jirix, profileRoot, *target)
-				logResult(jirix, "gc", mgr, *target, err)
-				if err != nil {
-					return err
-				}
-			}
-		}
-	}
-	return nil
-}
-
-func runEnsureVersionsAreSet(jirix *jiri.X, args []string) error {
-	for _, name := range args {
-		profile := profiles.LookupProfile(name)
-		mgr := profiles.LookupManager(name)
-		if mgr == nil {
-			fmt.Fprintf(jirix.Stderr(), "%s is not linked into this binary", name)
-			continue
-		}
-		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)
-				profiles.RemoveProfileTarget(name, prior)
-				if err := profiles.AddProfileTarget(name, *target); err != nil {
-					return err
-				}
-				if verboseFlag {
-					fmt.Fprintf(jirix.Stdout(), "%s %s had no version, now set to: %s\n", name, prior, target)
-				}
-			}
-		}
-	}
-	return nil
-}
-
-func runRmAll(jirix *jiri.X) error {
-	s := jirix.NewSeq()
-	if exists, err := s.FileExists(manifestFlag); err != nil || exists {
-		if err := s.Remove(manifestFlag).Done(); err != nil {
-			return err
-		}
-	}
-	d := profileRoot.Abs(jirix)
-	if exists, err := s.DirectoryExists(d); err != nil || exists {
-		if err := s.Run("chmod", "-R", "u+w", d).
-			RemoveAll(d).Done(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func runCleanup(jirix *jiri.X, args []string) error {
-	if err := profiles.Read(jirix, manifestFlag); err != nil {
-		fmt.Fprintf(jirix.Stderr(), "Failed to read manifest: %v", err)
-		return err
-	}
-	if len(args) == 0 {
-		args = profiles.Profiles()
-	}
-	dirty := false
-	if specificVersionsFlag {
-		if verboseFlag {
-			fmt.Fprintf(jirix.Stdout(), "Ensuring that all targets have a specific version set for %s\n", args)
-		}
-		if err := runEnsureVersionsAreSet(jirix, args); err != nil {
-			return fmt.Errorf("ensure-specific-versions-are-set: %v", err)
-		}
-		dirty = true
-	}
-	if cleanupFlag {
-		if verboseFlag {
-			fmt.Fprintf(jirix.Stdout(), "Removing targets older than the default version for %s\n", args)
-		}
-		if err := runGC(jirix, args); err != nil {
-			return fmt.Errorf("gc: %v", err)
-		}
-		dirty = true
-	}
-	if rmAllFlag {
-		if verboseFlag {
-			fmt.Fprintf(jirix.Stdout(), "Removing profile manifest and all profile output files\n")
-		}
-		if err := runRmAll(jirix); err != nil {
-			return err
-		}
-		// Don't write out the profiles manifest file again.
-		return nil
-	}
-	if rewriteManifestFlag {
-		dirty = true
-	}
-	if !dirty {
-		return fmt.Errorf("at least one option must be specified")
-	}
-	return profiles.Write(jirix, manifestFlag)
-}
-
-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 applyCommand(names []string, jirix *jiri.X, target profiles.Target, fn func(profiles.Manager, *jiri.X, profiles.Target) error) error {
-	for _, n := range names {
-		mgr := profiles.LookupManager(n)
-		profileTarget := target
-		version, err := mgr.VersionInfo().Select(profileTarget.Version())
-		if err != nil {
-			return err
-		}
-		profileTarget.SetVersion(version)
-		if err := fn(mgr, jirix, profileTarget); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func runInstall(jirix *jiri.X, args []string) error {
-	if err := initCommand(jirix, 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(jirix.Stdout(), "%v %v is already installed as %v\n", name, targetFlag, p)
-			continue
-		}
-		names = append(names, name)
-	}
-	if err := applyCommand(names, jirix, targetFlag,
-		func(mgr profiles.Manager, jirix *jiri.X, target profiles.Target) error {
-			err := mgr.Install(jirix, profileRoot, target)
-			logResult(jirix, "Install:", mgr, target, err)
-			return err
-		}); err != nil {
-		return err
-	}
-	return profiles.Write(jirix, manifestFlag)
-}
-
-func runUninstall(jirix *jiri.X, args []string) error {
-	if err := initCommand(jirix, args); err != nil {
-		return err
-	}
-	if allFlag && targetFlag.IsSet() {
-		fmt.Fprintf(jirix.Stdout(), "ignore target (%v) when used in conjunction with --all-targets\n", targetFlag)
-	}
-	if allFlag {
-		for _, name := range args {
-			profile := profiles.LookupProfile(name)
-			mgr := profiles.LookupManager(name)
-			if profile == nil || mgr == nil {
-				continue
-			}
-			for _, target := range profile.Targets() {
-				if err := mgr.Uninstall(jirix, profileRoot, *target); err != nil {
-					logResult(jirix, "Uninstall", mgr, *target, err)
-					return err
-				}
-				logResult(jirix, "Uninstall", mgr, *target, nil)
-			}
-		}
-	} else {
-		applyCommand(args, jirix, targetFlag,
-			func(mgr profiles.Manager, jirix *jiri.X, target profiles.Target) error {
-				err := mgr.Uninstall(jirix, profileRoot, target)
-				logResult(jirix, "Uninstall", mgr, target, err)
-				return err
-			})
-	}
-	return profiles.Write(jirix, manifestFlag)
-}
diff --git a/profiles/commandline/help.go b/profiles/commandline/help.go
index 48e3d6f..e4023ea 100644
--- a/profiles/commandline/help.go
+++ b/profiles/commandline/help.go
@@ -4,7 +4,7 @@
 
 package commandline
 
-var helpMsg = `
+var HelpMsg = `
 Profiles are used to manage external sofware dependencies and offer a balance
 between providing no support at all and a full blown package manager.
 Profiles can be built natively as well as being cross compiled.
@@ -19,16 +19,14 @@
 Profiles generally refer to uncompiled source code that needs to be compiled for
 a specific "target". Targets hence represent compiled code and consist of:
 
-1. A 'tag' that can be used a short hand for refering to a target
+1. An 'architecture' that refers to the CPU to be generate code for
 
-2. An 'architecture' that refers to the CPU to be generate code for
+2. An 'operating system' that refers to the operating system to generate code for
 
-3. An 'operating system' that refers to the operating system to generate code for
-
-4. A lexicographically orderd set of supported versions, one of which is designated
+3. A lexicographically orderd set of supported versions, one of which is designated
 as the default.
 
-5. An 'environment' which is a set of environment variables to use when compiling the profile
+4. An 'environment' which is a set of environment variables to use when compiling the profile
 
 Targets thus provide the basic support needed for cross compilation.
 
@@ -57,14 +55,14 @@
 installation and a command (recreate) to generate a list of commands that
 can be run to recreate the currently installed profiles.
 
-The Manifest
+The Profiles Database
 
-The profiles packages manages a manifest that tracks the installed profiles
+The profiles packages manages a database that tracks the installed profiles
 and their configurations. Other command line tools and packages are expected
-to read information about the currently installed profiles from this manifest
+to read information about the currently installed profiles from this database
 via the profiles package. The profile command line tools support displaying the
-manifest (via the list command) or for specifying an alternate version of the
-file (via the -manifest flag) which is generally useful for debugging.
+database (via the list command) or for specifying an alternate version of the
+file (via the -profiles-db flag) which is generally useful for debugging.
 
 Adding Profiles
 
diff --git a/profiles/commandline/manager_cmdline.go b/profiles/commandline/manager_cmdline.go
new file mode 100644
index 0000000..2265477
--- /dev/null
+++ b/profiles/commandline/manager_cmdline.go
@@ -0,0 +1,450 @@
+// 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 (
+	"flag"
+	"fmt"
+	"strings"
+
+	"v.io/jiri/jiri"
+	"v.io/jiri/profiles"
+	"v.io/jiri/profiles/manager"
+	"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")
+}
+
+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")
+}
+
+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 = manager.Managers()
+	} else {
+		for _, n := range args {
+			if mgr := manager.LookupManager(n); mgr == nil {
+				avail := manager.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 := manager.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 := manager.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 := manager.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 exists, err := s.FileExists(db.Filename()); err != nil || exists {
+		if err := s.Remove(db.Filename()).Done(); err != nil {
+			return err
+		}
+	}
+	d := root.Abs(jirix)
+	if exists, err := s.DirectoryExists(d); err != nil || exists {
+		if err := s.Run("chmod", "-R", "u+w", d).
+			RemoveAll(d).Done(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+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 := manager.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 := manager.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)
+}
diff --git a/profiles/commandline/reader_cmdline.go b/profiles/commandline/reader_cmdline.go
new file mode 100644
index 0000000..6c482bb
--- /dev/null
+++ b/profiles/commandline/reader_cmdline.go
@@ -0,0 +1,449 @@
+// 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"
+	"path/filepath"
+	"strings"
+	"text/template"
+
+	"v.io/jiri/jiri"
+	"v.io/jiri/profiles"
+	"v.io/jiri/profiles/manager"
+	"v.io/jiri/profiles/reader"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/textutil"
+)
+
+const DefaultProfiles = "base,jiri"
+
+// IsFlagSet returns true if the specified flag has been set on
+// the command line.
+// TODO(cnicolaou): use this to simplify the implementation of profiles.Target
+func IsFlagSet(fs *flag.FlagSet, name string) bool {
+	found := false
+	fs.Visit(func(f *flag.Flag) {
+		if f.Name == name {
+			found = true
+		}
+	})
+	return true
+}
+
+// NOTE: we use functions to initialize the commands so that we
+// can reinitialize them in tests. cmd_test.go contains a 'Reset' function
+// that is only available to tests for doing so.
+// NOTE: we can't set cmdList.Runner in the initialization loop since runList
+// needs to access cmdList.Flags.
+var (
+	// cmdList represents the "profile list" command.
+	cmdList *cmdline.Command
+	// cmdEnv represents the "profile env" command.
+	cmdEnv *cmdline.Command = newCmdEnv()
+)
+
+func init() {
+	cmdList = newCmdList()
+	cmdList.Runner = jiri.RunnerFunc(runList)
+}
+
+func newCmdList() *cmdline.Command {
+	return &cmdline.Command{
+		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.",
+	}
+}
+
+func newCmdEnv() *cmdline.Command {
+	// cmdEnv represents the "profile env" command.
+	return &cmdline.Command{
+		Runner: jiri.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",
+	}
+}
+
+// ReaderFlagValues contains the values of the command line flags accepted
+// required to configure and use the profiles/Reader package.
+type ReaderFlagValues struct {
+	// The value of --skip-profiles
+	ProfilesMode reader.ProfilesMode
+	// The value of --profiles-db
+	DBFilename string
+	// The value of --profiles
+	Profiles string
+	// The value of --target and --env
+	Target profiles.Target
+	// The value of --merge-policies
+	MergePolicies reader.MergePolicies
+	// The value of -v
+	Verbose bool
+}
+
+// listFlagValues contains the flag values expected by the list subcommand
+type listFlagValues struct {
+	*ReaderFlagValues
+	// The value of --show-profiles-db
+	showProfilesDB bool
+	// The value of --available
+	available bool
+	// The value of --info
+	info string
+}
+
+// envFlagValues contains the flag values expected by the env subcommand
+type envFlagValues struct {
+	*ReaderFlagValues
+}
+
+// All flag values are stored in listFlags and envFlags.
+var (
+	listFlags listFlagValues
+	envFlags  envFlagValues
+)
+
+// RegisterDBFilenameFlag registers the --profiles-db flag with the supplied FlagSet.
+func RegisterDBFilenameFlag(flags *flag.FlagSet, manifest *string, defaultManifest string) {
+	root := jiri.FindRoot()
+	flags.StringVar(manifest, "profiles-db", filepath.Join(root, defaultManifest), "specify the profiles XML manifest filename.")
+	flags.Lookup("profiles-db").DefValue = filepath.Join("$JIRI_ROOT", defaultManifest)
+}
+
+// RegisterProfilesFlag registers the --profiles flag
+func RegisterProfilesFlag(flags *flag.FlagSet, profiles *string) {
+	flags.StringVar(profiles, "profiles", DefaultProfiles, "a comma separated list of profiles to use")
+}
+
+// RegisterMergePoliciesFlag registers the --merge-policies flag
+func RegisterMergePoliciesFlag(flags *flag.FlagSet, policies *reader.MergePolicies) {
+	flags.Var(policies, "merge-policies", "specify policies for merging environment variables")
+}
+
+// RegisterReaderFlags registers the 'reader' flags (see below)
+// with the parent command. The values of the flags can be accessed via
+// the supplied ReaderFlagValues struct.
+// The reader flags are:
+//  --skip-profiles
+//  --profiles-db
+//  --profiles
+//  --merge-policies
+//  --target and --env
+func RegisterReaderFlags(flags *flag.FlagSet, fv *ReaderFlagValues, defaultDBFilename string) {
+	flags.Var(&fv.ProfilesMode, "skip-profiles", "if set, no profiles will be used")
+	RegisterDBFilenameFlag(flags, &fv.DBFilename, defaultDBFilename)
+	RegisterProfilesFlag(flags, &fv.Profiles)
+	fv.MergePolicies = reader.JiriMergePolicies()
+	RegisterMergePoliciesFlag(flags, &fv.MergePolicies)
+	profiles.RegisterTargetAndEnvFlags(flags, &fv.Target)
+}
+
+func initializeReaderFlags(flags *flag.FlagSet, fv *ReaderFlagValues, defaultDBFilename string) {
+	envFlags.ReaderFlagValues = fv
+	listFlags.ReaderFlagValues = fv
+	RegisterReaderFlags(flags, fv, defaultDBFilename)
+}
+
+// RegisterReaderCommandsUsingParent registers the 'reader' flags
+// (see RegisterReaderFlags) with the parent command and creates the
+// list and env subcommands. The values of the flags can be accessed via
+// the supplied ReaderFlagValues struct.
+// RegisterReaderCommandsUsingParent results in a command line of the form:
+// <parent> <reader-flags> [list|env] <list/env specific commands>
+func RegisterReaderCommandsUsingParent(parent *cmdline.Command, fv *ReaderFlagValues, defaultDBFilename string) {
+	initializeReaderFlags(&parent.Flags, fv, defaultDBFilename)
+	RegisterReaderCommands(parent, defaultDBFilename)
+}
+
+// RegisterReaderCommands registers the list and env subcommands. The
+// subcommands will host the 'reader' flags (see RegisterReaderFlags)
+// resulting in a command line of the form:
+// <parent> [list|env] <reader-flags> <list/env specific specific commands>
+func RegisterReaderCommands(parent *cmdline.Command, defaultDBFilename string) {
+	registerListCommand(parent, defaultDBFilename)
+	registerEnvCommand(parent, defaultDBFilename)
+}
+
+func newReaderFlags() *ReaderFlagValues {
+	return &ReaderFlagValues{MergePolicies: reader.JiriMergePolicies()}
+}
+
+// registerListCommand the profiles list subcommand and returns it
+// and a struct containing  the values of the command line flags.
+func registerListCommand(parent *cmdline.Command, defaultDBFilename string) {
+	parent.Children = append(parent.Children, cmdList)
+	if listFlags.ReaderFlagValues == nil {
+		listFlags.ReaderFlagValues = newReaderFlags()
+		RegisterReaderFlags(&cmdList.Flags, listFlags.ReaderFlagValues, defaultDBFilename)
+	}
+	cmdList.Flags.BoolVar(&listFlags.Verbose, "v", false, "print more detailed information")
+	cmdList.Flags.BoolVar(&listFlags.showProfilesDB, "show-profiles-db", false, "print out the profiles database file")
+	cmdList.Flags.BoolVar(&listFlags.available, "available", false, "print the list of available profiles")
+	cmdList.Flags.StringVar(&listFlags.info, "info", "", infoUsage())
+}
+
+// registerEnvCommand the profiles env subcommand and returns it and a
+// struct containing the values of the command line flags.
+func registerEnvCommand(parent *cmdline.Command, defaultDBFilename string) {
+	parent.Children = append(parent.Children, cmdEnv)
+	if envFlags.ReaderFlagValues == nil {
+		envFlags.ReaderFlagValues = newReaderFlags()
+		RegisterReaderFlags(&cmdEnv.Flags, envFlags.ReaderFlagValues, defaultDBFilename)
+	}
+	cmdEnv.Flags.BoolVar(&envFlags.Verbose, "v", false, "print more detailed information")
+}
+
+func runList(jirix *jiri.X, args []string) error {
+	if listFlags.showProfilesDB {
+		data, err := jirix.NewSeq().ReadFile(listFlags.DBFilename)
+		if err != nil {
+			return err
+		}
+		fmt.Fprintln(jirix.Stdout(), string(data))
+		return nil
+	}
+	if listFlags.Verbose {
+		fmt.Fprintf(jirix.Stdout(), "Profiles Database Filename: %s\n", listFlags.DBFilename)
+	}
+	if listFlags.available {
+		if listFlags.Verbose {
+			fmt.Fprintf(jirix.Stdout(), "Available Profiles:\n")
+			for _, name := range manager.Managers() {
+				mgr := manager.LookupManager(name)
+				vi := mgr.VersionInfo()
+				fmt.Fprintf(jirix.Stdout(), "%s: versions: %s\n", name, vi)
+			}
+		} else {
+			fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(manager.Managers(), ", "))
+		}
+	}
+	rd, err := reader.NewReader(jirix, listFlags.ProfilesMode, listFlags.DBFilename)
+	if err != nil {
+		return err
+	}
+	profileNames := args
+	if len(args) == 0 {
+		if IsFlagSet(&cmdList.Flags, "profiles") {
+			profileNames = strings.Split(listFlags.Profiles, ",")
+		} else {
+			profileNames = rd.ProfileNames()
+		}
+	}
+	availableNames := []string{}
+	for _, name := range profileNames {
+		if rd.LookupProfile(name) != nil {
+			availableNames = append(availableNames, name)
+		}
+	}
+	if listFlags.Verbose {
+		fmt.Fprintf(jirix.Stdout(), "Installed Profiles: ")
+		fmt.Fprintf(jirix.Stdout(), "%s\n", strings.Join(rd.ProfileNames(), ", "))
+		for _, name := range availableNames {
+			profile := rd.LookupProfile(name)
+			fmt.Fprintf(jirix.Stdout(), "Profile: %s @ %s\n", profile.Name(), profile.Root())
+			for _, target := range profile.Targets() {
+				fmt.Fprintf(jirix.Stdout(), "\t%s\n", target.DebugString())
+			}
+		}
+	} else {
+		for _, name := range availableNames {
+			profile := rd.LookupProfile(name)
+			mgr := manager.LookupManager(name)
+			out := &bytes.Buffer{}
+			var targets profiles.Targets
+			if listFlags.Target.IsSet() {
+				targets = append(targets, rd.LookupProfileTarget(name, listFlags.Target))
+			} else {
+				targets = profile.Targets()
+			}
+			printHeader := len(availableNames) > 1 || len(targets) > 1 || len(listFlags.info) == 0
+			for _, target := range targets {
+				if printHeader {
+					out.WriteString(fmtHeader(name, target))
+					out.WriteString(" ")
+				}
+				r, err := fmtInfo(jirix, listFlags.info, rd, mgr, profile, target)
+				if err != nil {
+					return err
+				}
+				out.WriteString(r)
+				if printHeader {
+					out.WriteString("\n")
+				}
+			}
+			fmt.Fprint(jirix.Stdout(), 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
+		Command         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.
+	Target.Command - a command that can be used to create this profile.
+	Note: if no --target is specified then the requested field will be displayed for all targets.
+	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 the requested field will be displayed for all profiles.`
+}
+
+func fmtOutput(jirix *jiri.X, 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(jirix *jiri.X, infoFmt string, rd *reader.Reader, mgr profiles.Manager, profile *profiles.Profile, target *profiles.Target) (string, error) {
+	if len(infoFmt) > 0 {
+		// Populate an instance listInfo
+		info := &listInfo{}
+		name := ""
+		if mgr != nil {
+			// Format the description on its own, without any preceeding
+			// text so that the line breaks work correctly.
+			info.Profile.Description = "\n" + fmtOutput(jirix, mgr.Info()) + "\n"
+			vi := mgr.VersionInfo()
+			if supported := vi.Supported(); len(supported) > 0 {
+				info.Profile.Versions = supported
+				info.Profile.LatestVersion = supported[0]
+			}
+			info.Profile.DefaultVersion = vi.Default()
+			name = mgr.Name()
+		}
+		info.SchemaVersion = rd.SchemaVersion()
+		if target != nil {
+			info.Target.InstallationDir = jiri.NewRelPath(target.InstallationDir).Abs(jirix)
+			info.Target.CommandLineEnv = target.CommandLineEnv().Vars
+			info.Target.Env = target.Env.Vars
+			clenv := ""
+			if len(info.Target.CommandLineEnv) > 0 {
+				clenv = fmt.Sprintf(" --env=\"%s\" ", strings.Join(info.Target.CommandLineEnv, ","))
+			}
+			info.Target.Command = fmt.Sprintf("jiri v23-profile install --target=%s %s%s", target, clenv, name)
+		}
+		if profile != nil {
+			rp := jiri.NewRelPath(profile.Root())
+			info.Profile.Root = rp.Abs(jirix)
+		}
+
+		// Use a template to print out any field in our instance of listInfo.
+		tmpl, err := template.New("list").Parse("{{ ." + infoFmt + "}}")
+		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 runEnv(jirix *jiri.X, args []string) error {
+	if len(envFlags.Profiles) == 0 {
+		return fmt.Errorf("no profiles were specified using --profiles")
+	}
+	rd, err := reader.NewReader(jirix, envFlags.ProfilesMode, envFlags.DBFilename)
+	if err != nil {
+		return err
+	}
+	profileNames := strings.Split(envFlags.Profiles, ",")
+	if err := rd.ValidateRequestedProfilesAndTarget(profileNames, envFlags.Target); err != nil {
+		return err
+	}
+	rd.MergeEnvFromProfiles(envFlags.MergePolicies, envFlags.Target, profileNames...)
+	out := fmtVars(rd.ToMap(), args)
+	if len(out) > 0 {
+		fmt.Fprintln(jirix.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(), " ")
+}
diff --git a/profiles/manager.go b/profiles/manager.go
deleted file mode 100644
index 109fa9f..0000000
--- a/profiles/manager.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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 profiles implements support for managing external sofware dependencies.
-// It offers a balance between providing no support at all and a full blown package
-// manager. A profile is a named collection of software required for a given
-// system component or application. The name of the profile refers to all of the
-// required software, which may a single library or a collection of libraries or
-// SDKs. Profiles thus refer to uncompiled source code that needs to be compiled
-// for a specific "target". Targets represent compiled code and consist of:
-//
-// 1. An 'architecture' that refers to the CPU to be generate code for
-// 2. An 'operating system' that refers to the operating system to generate
-//    code for.
-// 3. An 'environment' which is a set of environment variables to use when
-//    compiling and using the profile.
-//
-// Targets provide the essential support for cross compilation.
-//
-// The profiles package provides a registry for profile implementations to
-// register themselves (by calling profiles.Register from an init function
-// for example) and for managing a 'manifest' of the currently built
-// profiles. The manifest is represented as an XML file.
-//
-// Profiles may be installed, updated or removed. When doing so, the name of
-// the profile is required, but the other components of the target are optional
-// and will default to the values of the system that the commands are run on
-// (so-called native builds). These operations are defined by the
-// profiles.Manager interface.
-//
-// The manifest tracks the installed profiles and their configurations.
-// Other command line tools and packages are expected read information about
-// the currently installed profiles from this manifest via profiles.ConfigHelper.
-package profiles
-
-import (
-	"flag"
-	"sort"
-	"sync"
-
-	"v.io/jiri/jiri"
-)
-
-var (
-	registry = struct {
-		sync.Mutex
-		managers map[string]Manager
-	}{
-		managers: make(map[string]Manager),
-	}
-)
-
-// Register is used to register a profile manager. It is an error
-// to call Registerr more than once with the same name, though it
-// is possible to register the same Manager using different names.
-func Register(name string, mgr Manager) {
-	registry.Lock()
-	defer registry.Unlock()
-	if _, present := registry.managers[name]; present {
-		panic("a profile manager is already registered for: " + name)
-	}
-	registry.managers[name] = mgr
-}
-
-// Managers returns the names, in lexicographic order, of all of the currently
-// available profile managers.
-func Managers() []string {
-	registry.Lock()
-	defer registry.Unlock()
-	names := make([]string, 0, len(registry.managers))
-	for name := range registry.managers {
-		names = append(names, name)
-	}
-	sort.Strings(names)
-	return names
-}
-
-// LookupManager returns the manager for the named profile or nil if one is
-// not found.
-func LookupManager(name string) Manager {
-	registry.Lock()
-	defer registry.Unlock()
-	return registry.managers[name]
-}
-
-type Action int
-
-const (
-	Install Action = iota
-	Uninstall
-)
-
-// Manager is the interface that must be implemented in order to
-// manage (i.e. install/uninstall/update) a profile.
-type Manager interface {
-	// AddFlags allows the profile manager to add profile specific flags
-	// to the supplied FlagSet for the specified Action.
-	// They should be named <profile-name>.<flag>.
-	AddFlags(*flag.FlagSet, Action)
-	// Name returns the name of this profile.
-	Name() string
-	// Info returns an informative description of the profile.
-	Info() string
-	// VersionInfo returns the VersionInfo instance for this profile.
-	VersionInfo() *VersionInfo
-	// String returns a string representation of the profile, conventionally this
-	// is its name and version.
-	String() string
-	// Install installs the profile for the specified build target.
-	Install(jirix *jiri.X, root jiri.RelPath, target Target) error
-	// Uninstall uninstalls the profile for the specified build target. When
-	// the last target for any given profile is uninstalled, then the profile
-	// itself (i.e. the source code) will be uninstalled.
-	Uninstall(jirix *jiri.X, root jiri.RelPath, target Target) error
-}
diff --git a/profiles/manager/.api b/profiles/manager/.api
new file mode 100644
index 0000000..f183504
--- /dev/null
+++ b/profiles/manager/.api
@@ -0,0 +1,5 @@
+pkg manager, func EnsureProfileTargetIsInstalled(*jiri.X, *profiles.DB, string, jiri.RelPath, profiles.Target) error
+pkg manager, func EnsureProfileTargetIsUninstalled(*jiri.X, *profiles.DB, string, jiri.RelPath, profiles.Target) error
+pkg manager, func LookupManager(string) profiles.Manager
+pkg manager, func Managers() []string
+pkg manager, func Register(string, profiles.Manager)
diff --git a/profiles/manager/commandline/.api b/profiles/manager/commandline/.api
new file mode 100644
index 0000000..50d42b1
--- /dev/null
+++ b/profiles/manager/commandline/.api
@@ -0,0 +1,3 @@
+pkg commandline, func Init(string)
+pkg commandline, func Main(string)
+pkg commandline, var CommandLineDriver *cmdline.Command
diff --git a/profiles/manager/manager.go b/profiles/manager/manager.go
new file mode 100644
index 0000000..1c407ff
--- /dev/null
+++ b/profiles/manager/manager.go
@@ -0,0 +1,59 @@
+// 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 manager provides support for managing jiri profiles.
+// In particular for installing and uninstalling them. It provides a
+// registration mechanism for profile implementations to call from an init
+// function to add themselves to the suite profiles available within this
+// application.
+package manager
+
+import (
+	"sort"
+	"sync"
+
+	"v.io/jiri/profiles"
+)
+
+var (
+	registry = struct {
+		sync.Mutex
+		managers map[string]profiles.Manager
+	}{
+		managers: make(map[string]profiles.Manager),
+	}
+)
+
+// Register is used to register a profile manager. It is an error
+// to call Registerr more than once with the same name, though it
+// is possible to register the same Manager using different names.
+func Register(name string, mgr profiles.Manager) {
+	registry.Lock()
+	defer registry.Unlock()
+	if _, present := registry.managers[name]; present {
+		panic("a profile manager is already registered for: " + name)
+	}
+	registry.managers[name] = mgr
+}
+
+// Names returns the names, in lexicographic order, of all of the currently
+// available profile managers.
+func Managers() []string {
+	registry.Lock()
+	defer registry.Unlock()
+	names := make([]string, 0, len(registry.managers))
+	for name := range registry.managers {
+		names = append(names, name)
+	}
+	sort.Strings(names)
+	return names
+}
+
+// LookupManager returns the manager for the named profile or nil if one is
+// not found.
+func LookupManager(name string) profiles.Manager {
+	registry.Lock()
+	defer registry.Unlock()
+	return registry.managers[name]
+}
diff --git a/profiles/manager/manager_test.go b/profiles/manager/manager_test.go
new file mode 100644
index 0000000..7b382e3
--- /dev/null
+++ b/profiles/manager/manager_test.go
@@ -0,0 +1,140 @@
+// 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 manager_test
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"v.io/jiri/jiri"
+	"v.io/jiri/profiles"
+	"v.io/jiri/profiles/manager"
+	"v.io/jiri/tool"
+)
+
+type myNewProfileMgr struct {
+	name, root  string
+	versionInfo *profiles.VersionInfo
+	profile     *profiles.Profile
+}
+
+func newProfileMgr(name string) *myNewProfileMgr {
+	supported := map[string]interface{}{
+		"2": nil,
+		"4": nil,
+		"3": nil,
+	}
+	return &myNewProfileMgr{name: name, versionInfo: profiles.NewVersionInfo("test", supported, "3")}
+}
+
+func (p *myNewProfileMgr) Name() string {
+	return p.name
+}
+
+func (p *myNewProfileMgr) Info() string {
+	return `
+The myNewProfile is for testing purposes only
+`
+}
+
+func (p *myNewProfileMgr) VersionInfo() *profiles.VersionInfo {
+	return p.versionInfo
+}
+
+func (p *myNewProfileMgr) String() string {
+	if p.profile == nil {
+		return fmt.Sprintf("Profile: %s: not installed\n", p.name)
+	}
+	return fmt.Sprintf("Profile: %s: installed\n%s\n", p.name, p.profile.Targets())
+}
+
+func (p *myNewProfileMgr) AddFlags(*flag.FlagSet, profiles.Action) {
+}
+
+func (p *myNewProfileMgr) Install(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) error {
+	p.profile = pdb.InstallProfile(p.name, "root")
+	return pdb.AddProfileTarget(p.name, target)
+}
+
+func (p *myNewProfileMgr) Uninstall(jirix *jiri.X, pdb *profiles.DB, root jiri.RelPath, target profiles.Target) error {
+	if pdb.RemoveProfileTarget(p.name, target) {
+		p.profile = nil
+	}
+	return nil
+}
+
+func tmpFile() string {
+	dirname, err := ioutil.TempDir("", "pdb")
+	if err != nil {
+		panic(err)
+	}
+	return filepath.Join(dirname, "manifest")
+}
+
+func ExampleManager() {
+	pdb := profiles.NewDB()
+	myProfile := "myNewProfile"
+	var target profiles.Target
+
+	init := func() {
+		mgr := newProfileMgr(myProfile)
+		manager.Register(myProfile, mgr)
+		flags := flag.NewFlagSet("example", flag.ContinueOnError)
+		profiles.RegisterTargetAndEnvFlags(flags, &target)
+		flags.Parse([]string{"--target=arm-linux@1", "--env=A=B,C=D", "--env=E=F"})
+	}
+	init()
+
+	profileRoot := jiri.NewRelPath("profiles")
+	mgr := manager.LookupManager(myProfile)
+	if mgr == nil {
+		panic("manager not found for: " + myProfile)
+	}
+
+	jirix := &jiri.X{Context: tool.NewDefaultContext()}
+	// Install myNewProfile for target.
+	if err := mgr.Install(jirix, pdb, profileRoot, target); err != nil {
+		panic("failed to find manager for: " + myProfile)
+	}
+
+	fmt.Println(mgr.String())
+
+	filename := tmpFile()
+	defer os.RemoveAll(filepath.Dir(filename))
+
+	if err := pdb.Write(jirix, filename); err != nil {
+		panic(err)
+	}
+
+	// Start with a new profile data base.
+	pdb = profiles.NewDB()
+	// Read the profile database.
+	pdb.Read(jirix, filename)
+
+	mgr = manager.LookupManager(myProfile)
+	if mgr == nil {
+		panic("manager not found for: " + myProfile)
+	}
+	fmt.Println(mgr.String())
+	mgr.Uninstall(jirix, pdb, profileRoot, target)
+	fmt.Println(mgr.String())
+	fmt.Println(mgr.VersionInfo().Supported())
+	fmt.Println(mgr.VersionInfo().Default())
+
+	// Output:
+	// Profile: myNewProfile: installed
+	// [arm-linux@1]
+	//
+	// Profile: myNewProfile: installed
+	// [arm-linux@1]
+	//
+	// Profile: myNewProfile: not installed
+	//
+	// [4 3 2]
+	// 3
+}
diff --git a/profiles/manager/util.go b/profiles/manager/util.go
new file mode 100644
index 0000000..eba260c
--- /dev/null
+++ b/profiles/manager/util.go
@@ -0,0 +1,63 @@
+// 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 manager
+
+import (
+	"fmt"
+
+	"v.io/jiri/jiri"
+	"v.io/jiri/profiles"
+)
+
+// ensureAction ensures that the requested profile and target
+// is installed/uninstalled, installing/uninstalling it if and only if necessary.
+func ensureAction(jirix *jiri.X, pdb *profiles.DB, action profiles.Action, profile string, root jiri.RelPath, target profiles.Target) error {
+	verb := ""
+	switch action {
+	case profiles.Install:
+		verb = "install"
+	case profiles.Uninstall:
+		verb = "uninstall"
+	default:
+		return fmt.Errorf("unrecognised action %v", action)
+	}
+	if jirix.Verbose() || jirix.DryRun() {
+		fmt.Fprintf(jirix.Stdout(), "%s %v %s\n", verb, action, target)
+	}
+	if t := pdb.LookupProfileTarget(profile, target); t != nil {
+		if jirix.Verbose() {
+			fmt.Fprintf(jirix.Stdout(), "%v %v is already %sed as %v\n", profile, target, verb, t)
+		}
+		return nil
+	}
+	mgr := LookupManager(profile)
+	if mgr == nil {
+		return fmt.Errorf("profile %v is not supported", profile)
+	}
+	version, err := mgr.VersionInfo().Select(target.Version())
+	if err != nil {
+		return err
+	}
+	target.SetVersion(version)
+	if jirix.Verbose() || jirix.DryRun() {
+		fmt.Fprintf(jirix.Stdout(), "%s %s %s\n", verb, profile, target.DebugString())
+	}
+	if action == profiles.Install {
+		return mgr.Install(jirix, pdb, root, target)
+	}
+	return mgr.Uninstall(jirix, pdb, root, target)
+}
+
+// EnsureProfileTargetIsInstalled ensures that the requested profile and target
+// is installed, installing it if only if necessary.
+func EnsureProfileTargetIsInstalled(jirix *jiri.X, pdb *profiles.DB, profile string, root jiri.RelPath, target profiles.Target) error {
+	return ensureAction(jirix, pdb, profiles.Install, profile, root, target)
+}
+
+// EnsureProfileTargetIsUninstalled ensures that the requested profile and target
+// are no longer installed.
+func EnsureProfileTargetIsUninstalled(jirix *jiri.X, pdb *profiles.DB, profile string, root jiri.RelPath, target profiles.Target) error {
+	return ensureAction(jirix, pdb, profiles.Uninstall, profile, root, target)
+}
diff --git a/profiles/manager_test.go b/profiles/manager_test.go
deleted file mode 100644
index f24a309..0000000
--- a/profiles/manager_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// 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 profiles_test
-
-import (
-	"flag"
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"v.io/jiri/jiri"
-	"v.io/jiri/profiles"
-	"v.io/jiri/tool"
-)
-
-type myNewProfile struct {
-	name, root, status string
-	versionInfo        *profiles.VersionInfo
-}
-
-func newProfile(name string) *myNewProfile {
-	supported := map[string]interface{}{
-		"2": nil,
-		"4": nil,
-		"3": nil,
-	}
-	return &myNewProfile{name: name, versionInfo: profiles.NewVersionInfo("test", supported, "3")}
-}
-
-func (p *myNewProfile) Name() string {
-	return p.name
-}
-
-func (p *myNewProfile) Info() string {
-	return `
-The myNewProfile is for testing purposes only
-`
-}
-
-func (p *myNewProfile) VersionInfo() *profiles.VersionInfo {
-	return p.versionInfo
-}
-
-func (p *myNewProfile) String() string {
-	profile := profiles.LookupProfile(p.name)
-	if profile == nil {
-		return fmt.Sprintf("Profile: %s: %s\n", p.name, p.status)
-	}
-	return fmt.Sprintf("Profile: %s: %s\n%s\n", p.name, p.status, profile.Targets())
-}
-
-func (p *myNewProfile) AddFlags(*flag.FlagSet, profiles.Action) {
-}
-
-func (p *myNewProfile) Install(jirix *jiri.X, root jiri.RelPath, target profiles.Target) error {
-	p.status = "installed"
-	profiles.AddProfileTarget(p.name, target)
-	return nil
-}
-
-func (p *myNewProfile) Uninstall(jirix *jiri.X, root jiri.RelPath, target profiles.Target) error {
-	profiles.RemoveProfileTarget(p.name, target)
-	if profiles.LookupProfile(p.name) == nil {
-		p.status = "uninstalled"
-	}
-	return nil
-}
-
-func ExampleManager() {
-	myProfile := "myNewProfile"
-	var target profiles.Target
-
-	init := func() {
-		profiles.Register(myProfile, newProfile(myProfile))
-		flags := flag.NewFlagSet("example", flag.ContinueOnError)
-		profiles.RegisterTargetAndEnvFlags(flags, &target)
-		flags.Parse([]string{"--target=arm-linux@1", "--env=A=B,C=D", "--env=E=F"})
-	}
-	init()
-
-	profileRoot := jiri.NewRelPath("profiles")
-	mgr := profiles.LookupManager(myProfile)
-	if mgr == nil {
-		panic("manager not found for: " + myProfile)
-	}
-
-	jirix := &jiri.X{Context: tool.NewDefaultContext()}
-	// Install myNewProfile for target.
-	if err := mgr.Install(jirix, profileRoot, target); err != nil {
-		panic("failed to find manager for: " + myProfile)
-	}
-
-	fmt.Println(mgr.String())
-
-	filename := tmpFile()
-	defer os.RemoveAll(filepath.Dir(filename))
-
-	if err := profiles.Write(jirix, filename); err != nil {
-		panic(err)
-	}
-
-	// Clear the profile manifest information in order to mimic starting
-	// a new process and reading the manifest file.
-	profiles.Clear()
-
-	// Read the profile manifest.
-	profiles.Read(jirix, filename)
-
-	mgr = profiles.LookupManager(myProfile)
-	if mgr == nil {
-		panic("manager not found for: " + myProfile)
-	}
-
-	fmt.Println(mgr.String())
-	mgr.Uninstall(jirix, profileRoot, target)
-	fmt.Println(mgr.String())
-	fmt.Println(mgr.VersionInfo().Supported())
-	fmt.Println(mgr.VersionInfo().Default())
-
-	// Output:
-	// Profile: myNewProfile: installed
-	// [arm-linux@1]
-	//
-	// Profile: myNewProfile: installed
-	// [arm-linux@1]
-	//
-	// Profile: myNewProfile: uninstalled
-	//
-	// [4 3 2]
-	// 3
-}
diff --git a/profiles/manifest.go b/profiles/manifest.go
index 8528d34..d8a56e4 100644
--- a/profiles/manifest.go
+++ b/profiles/manifest.go
@@ -33,23 +33,6 @@
 	V4 Version = 4
 )
 
-// Profile represents a suite of software that is managed by an implementation
-// of profiles.Manager.
-type Profile struct {
-	Name    string
-	Root    string
-	targets OrderedTargets
-}
-
-func (p *Profile) Targets() OrderedTargets {
-	r := make(OrderedTargets, len(p.targets), len(p.targets))
-	for i, t := range p.targets {
-		tmp := *t
-		r[i] = &tmp
-	}
-	return r
-}
-
 type profilesSchema struct {
 	XMLName  xml.Name         `xml:"profiles"`
 	Version  Version          `xml:"version,attr"`
@@ -77,96 +60,40 @@
 	CommandLineEnv  Environment `xml:"command-line"`
 }
 
-type profileDB struct {
-	sync.Mutex
-	version Version
-	db      map[string]*Profile
+type DB struct {
+	mu       sync.Mutex
+	version  Version
+	filename string
+	db       map[string]*Profile
 }
 
-func newDB() *profileDB {
-	return &profileDB{db: make(map[string]*Profile), version: V4}
+// NewDB returns a new instance of a profile database.
+func NewDB() *DB {
+	return &DB{db: make(map[string]*Profile), version: V4}
 }
 
-var (
-	db = newDB()
-)
-
-// Profiles returns the names, in lexicographic order, of all of the currently
-// available profiles as read or stored in the manifest. A profile name may
-// be used to lookup a profile manager or the current state of a profile.
-func Profiles() []string {
-	return db.profiles()
+// Filename returns the filename that this database was read from.
+func (pdb *DB) Filename() string {
+	return pdb.filename
 }
 
-func SchemaVersion() Version {
-	return db.schemaVersion()
-}
-
-// LookupProfile returns the profile for the name profile or nil if one is
-// not found.
-func LookupProfile(name string) *Profile {
-	return db.profile(name)
-}
-
-// LookupProfileTarget returns the target information stored for the name
-// profile.
-func LookupProfileTarget(name string, target Target) *Target {
-	mgr := db.profile(name)
-	if mgr == nil {
-		return nil
+// InstallProfile will create a new profile to the profiles database,
+// it has no effect if the profile already exists. It returns the profile
+// that was either newly created or already installed.
+func (pdb *DB) InstallProfile(name, root string) *Profile {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
+	if p := pdb.db[name]; p == nil {
+		pdb.db[name] = &Profile{name: name, root: root}
 	}
-	return FindTarget(mgr.targets, &target)
-}
-
-// InstallProfile will create a new profile and store in the profiles manifest,
-// it has no effect if the profile already exists.
-func InstallProfile(name, root string) {
-	db.installProfile(name, root)
+	return pdb.db[name]
 }
 
 // AddProfileTarget adds the specified target to the named profile.
 // The UpdateTime of the newly installed target will be set to time.Now()
-func AddProfileTarget(name string, target Target) error {
-	return db.addProfileTarget(name, &target)
-}
-
-// RemoveProfileTarget removes the specified target from the named profile.
-// If this is the last target for the profile then the profile will be deleted
-// from the manifest. It returns true if the profile was so deleted or did
-// not originally exist.
-func RemoveProfileTarget(name string, target Target) bool {
-	return db.removeProfileTarget(name, &target)
-}
-
-// UpdateProfileTarget updates the specified target from the named profile.
-// The UpdateTime of the updated target will be set to time.Now()
-func UpdateProfileTarget(name string, target Target) error {
-	return db.updateProfileTarget(name, &target)
-}
-
-// Read reads the specified manifest file to obtain the current set of
-// installed profiles.
-func Read(jirix *jiri.X, filename string) error {
-	return db.read(jirix, filename)
-}
-
-// Write writes the current set of installed profiles to the specified manifest
-// file.
-func Write(jirix *jiri.X, filename string) error {
-	return db.write(jirix, filename)
-}
-
-func (pdb *profileDB) installProfile(name, root string) {
-	pdb.Lock()
-	defer pdb.Unlock()
-	if p := pdb.db[name]; p == nil {
-		pdb.db[name] = &Profile{Name: name, Root: root}
-	}
-}
-
-func (pdb *profileDB) addProfileTarget(name string, target *Target) error {
-	pdb.Lock()
-	defer pdb.Unlock()
+func (pdb *DB) AddProfileTarget(name string, target Target) error {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 	target.UpdateTime = time.Now()
 	if pi, present := pdb.db[name]; present {
 		for _, t := range pi.Targets() {
@@ -174,17 +101,17 @@
 				return fmt.Errorf("%s is already used by profile %s %s", target, name, pi.Targets())
 			}
 		}
-		pi.targets = InsertTarget(pi.targets, target)
+		pi.targets = InsertTarget(pi.targets, &target)
 		return nil
 	}
-	pdb.db[name] = &Profile{Name: name}
-	pdb.db[name].targets = InsertTarget(nil, target)
-	return nil
+	return fmt.Errorf("profile %v is not installed", name)
 }
 
-func (pdb *profileDB) updateProfileTarget(name string, target *Target) error {
-	pdb.Lock()
-	defer pdb.Unlock()
+// UpdateProfileTarget updates the specified target from the named profile.
+// The UpdateTime of the updated target will be set to time.Now()
+func (pdb *DB) UpdateProfileTarget(name string, target Target) error {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 	target.UpdateTime = time.Now()
 	pi, present := pdb.db[name]
 	if !present {
@@ -192,7 +119,7 @@
 	}
 	for _, t := range pi.targets {
 		if target.Match(t) {
-			*t = *target
+			*t = target
 			t.UpdateTime = time.Now()
 			return nil
 		}
@@ -200,15 +127,19 @@
 	return fmt.Errorf("profile %v does not have target: %v", name, target)
 }
 
-func (pdb *profileDB) removeProfileTarget(name string, target *Target) bool {
-	pdb.Lock()
-	defer pdb.Unlock()
+// RemoveProfileTarget removes the specified target from the named profile.
+// If this is the last target for the profile then the profile will be deleted
+// from the database. It returns true if the profile was so deleted or did
+// not originally exist.
+func (pdb *DB) RemoveProfileTarget(name string, target Target) bool {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 
 	pi, present := pdb.db[name]
 	if !present {
 		return true
 	}
-	pi.targets = RemoveTarget(pi.targets, target)
+	pi.targets = RemoveTarget(pi.targets, &target)
 	if len(pi.targets) == 0 {
 		delete(pdb.db, name)
 		return true
@@ -216,14 +147,27 @@
 	return false
 }
 
-func (pdb *profileDB) profiles() []string {
-	pdb.Lock()
-	defer pdb.Unlock()
+// Names returns the names, in lexicographic order, of all of the currently
+// available profiles.
+func (pdb *DB) Names() []string {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 	return pdb.profilesUnlocked()
-
 }
 
-func (pdb *profileDB) profilesUnlocked() []string {
+// Profiles returns all currently installed the profiles, in lexicographic order.
+func (pdb *DB) Profiles() []*Profile {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
+	names := pdb.profilesUnlocked()
+	r := make([]*Profile, len(names), len(names))
+	for i, name := range names {
+		r[i] = pdb.db[name]
+	}
+	return r
+}
+
+func (pdb *DB) profilesUnlocked() []string {
 	names := make([]string, 0, len(pdb.db))
 	for name := range pdb.db {
 		names = append(names, name)
@@ -232,15 +176,42 @@
 	return names
 }
 
-func (pdb *profileDB) profile(name string) *Profile {
-	pdb.Lock()
-	defer pdb.Unlock()
+// LookupProfile returns the profile for the name profile or nil if one is
+// not found.
+func (pdb *DB) LookupProfile(name string) *Profile {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 	return pdb.db[name]
 }
 
-func (pdb *profileDB) read(jirix *jiri.X, filename string) error {
-	pdb.Lock()
-	defer pdb.Unlock()
+// LookupProfileTarget returns the target information stored for the name
+// profile.
+func (pdb *DB) LookupProfileTarget(name string, target Target) *Target {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
+	mgr := pdb.db[name]
+	if mgr == nil {
+		return nil
+	}
+	return FindTarget(mgr.targets, &target)
+}
+
+// EnvFromProfile obtains the environment variable settings from the specified
+// profile and target. It returns nil if the target and/or profile could not
+// be found.
+func (pdb *DB) EnvFromProfile(name string, target Target) []string {
+	t := pdb.LookupProfileTarget(name, target)
+	if t == nil {
+		return nil
+	}
+	return t.Env.Vars
+}
+
+// Read reads the specified database file to obtain the current set of
+// installed profiles into the receiver database.
+func (pdb *DB) Read(jirix *jiri.X, filename string) error {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 	pdb.db = make(map[string]*Profile)
 
 	data, err := jirix.NewSeq().ReadFile(filename)
@@ -256,13 +227,14 @@
 		return fmt.Errorf("Unmarshal(%v) failed: %v", string(data), err)
 	}
 	pdb.version = schema.Version
-	for _, profile := range schema.Profiles {
-		name := profile.Name
+	pdb.filename = filename
+	for _, p := range schema.Profiles {
+		name := p.Name
 		pdb.db[name] = &Profile{
-			Name: name,
-			Root: profile.Root,
+			name: name,
+			root: p.Root,
 		}
-		for _, target := range profile.Targets {
+		for _, target := range p.Targets {
 			pdb.db[name].targets = append(pdb.db[name].targets, &Target{
 				arch:            target.Arch,
 				opsys:           target.OS,
@@ -278,9 +250,15 @@
 	return nil
 }
 
-func (pdb *profileDB) write(jirix *jiri.X, filename string) error {
-	pdb.Lock()
-	defer pdb.Unlock()
+// Write writes the current set of installed profiles to the specified
+// database file.
+func (pdb *DB) Write(jirix *jiri.X, filename string) error {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
+
+	if len(filename) == 0 {
+		return fmt.Errorf("please specify a filename")
+	}
 
 	var schema profilesSchema
 	schema.Version = V4
@@ -288,7 +266,7 @@
 		profile := pdb.db[name]
 		schema.Profiles = append(schema.Profiles, &profileSchema{
 			Name: name,
-			Root: profile.Root,
+			Root: profile.root,
 		})
 
 		for _, target := range profile.targets {
@@ -336,8 +314,10 @@
 	return nil
 }
 
-func (pdb *profileDB) schemaVersion() Version {
-	pdb.Lock()
-	defer pdb.Unlock()
+// SchemaVersion returns the version of the xml schema used to implement
+// the database.
+func (pdb *DB) SchemaVersion() Version {
+	pdb.mu.Lock()
+	defer pdb.mu.Unlock()
 	return pdb.version
 }
diff --git a/profiles/manifest_test.go b/profiles/manifest_test.go
index 822a98b..90e4cab 100644
--- a/profiles/manifest_test.go
+++ b/profiles/manifest_test.go
@@ -11,7 +11,6 @@
 	"os"
 	"path/filepath"
 	"regexp"
-	"sort"
 	"strings"
 	"testing"
 
@@ -19,14 +18,15 @@
 	"v.io/jiri/profiles"
 )
 
-func addProfileAndTargets(t *testing.T, name string) {
-	t1, _ := profiles.NewTargetWithEnv("cpu1-os1@1", "A=B,C=D")
-	t2, _ := profiles.NewTargetWithEnv("cpu2-os2@bar", "A=B,C=D")
-	if err := profiles.AddProfileTarget(name, t1); err != nil {
+func addProfileAndTargets(t *testing.T, pdb *profiles.DB, name string) {
+	t1, _ := profiles.NewTarget("cpu1-os1@1", "A=B,C=D")
+	t2, _ := profiles.NewTarget("cpu2-os2@bar", "A=B,C=D")
+	pdb.InstallProfile(name, "")
+	if err := pdb.AddProfileTarget(name, t1); err != nil {
 		t.Fatal(err)
 	}
 	t2.InstallationDir = "bar"
-	if err := profiles.AddProfileTarget(name, t2); err != nil {
+	if err := pdb.AddProfileTarget(name, t2); err != nil {
 		t.Fatal(err)
 	}
 }
@@ -51,25 +51,26 @@
 }
 
 func TestWrite(t *testing.T) {
-	profiles.Clear()
+	pdb := profiles.NewDB()
 	jirix, cleanup := jiritest.NewX(t)
 	defer cleanup()
 	filename := tmpFile()
 	defer os.RemoveAll(filepath.Dir(filename))
 
 	// test for no version being set.
-	t1, _ := profiles.NewTargetWithEnv("cpu1-os1", "A=B,C=D")
-	if err := profiles.AddProfileTarget("b", t1); err != nil {
+	t1, _ := profiles.NewTarget("cpu1-os1", "A=B,C=D")
+	pdb.InstallProfile("b", "")
+	if err := pdb.AddProfileTarget("b", t1); err != nil {
 		t.Fatal(err)
 	}
-	if err := profiles.Write(jirix, filename); err == nil || !strings.HasPrefix(err.Error(), "missing version for profile") {
+	if err := pdb.Write(jirix, filename); err == nil || !strings.HasPrefix(err.Error(), "missing version for profile") {
 		t.Fatalf("was expecing a missing version error, but got %v", err)
 	}
-	profiles.RemoveProfileTarget("b", t1)
+	pdb.RemoveProfileTarget("b", t1)
 
-	addProfileAndTargets(t, "b")
-	addProfileAndTargets(t, "a")
-	if err := profiles.Write(jirix, filename); err != nil {
+	addProfileAndTargets(t, pdb, "b")
+	addProfileAndTargets(t, pdb, "a")
+	if err := pdb.Write(jirix, filename); err != nil {
 		t.Fatal(err)
 	}
 
@@ -81,10 +82,10 @@
 }
 
 func TestRead(t *testing.T) {
-	profiles.Clear()
+	pdb := profiles.NewDB()
 	jirix, cleanup := jiritest.NewX(t)
 	defer cleanup()
-	if err := profiles.Read(jirix, "./testdata/m1.xml"); err != nil {
+	if err := pdb.Read(jirix, "./testdata/m1.xml"); err != nil {
 		t.Fatal(err)
 	}
 
@@ -99,12 +100,11 @@
 		}
 		return true
 	}
-	names := profiles.Profiles()
-	sort.Strings(names)
+	names := pdb.Names()
 	if got, want := names, []string{"a", "b"}; !cmp(got, want) {
 		t.Errorf("got %v, want %v", got, want)
 	}
-	p := profiles.LookupProfile("a")
+	p := pdb.LookupProfile("a")
 	if got, want := p.Targets()[0].OS(), "os1"; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
@@ -114,39 +114,29 @@
 }
 
 func TestInstallProfile(t *testing.T) {
-	profiles.Clear()
-	profiles.InstallProfile("a", "root1")
-	profiles.InstallProfile("a", "root2")
-	profile := profiles.LookupProfile("a")
-	if got, want := profile.Root, "root1"; got != want {
+	pdb := profiles.NewDB()
+	pdb.InstallProfile("a", "root1")
+	pdb.InstallProfile("a", "root2")
+	profile := pdb.LookupProfile("a")
+	if got, want := profile.Root(), "root1"; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
 }
 
 func TestReadingV0(t *testing.T) {
-	profiles.Clear()
+	pdb := profiles.NewDB()
 	jirix, cleanup := jiritest.NewX(t)
 	defer cleanup()
 
-	getProfiles := func() []*profiles.Profile {
-		db := []*profiles.Profile{}
-		names := profiles.Profiles()
-		sort.Strings(names)
-		for _, name := range names {
-			db = append(db, profiles.LookupProfile(name))
-		}
-		return db
-	}
-
-	if err := profiles.Read(jirix, "./testdata/legacy.xml"); err != nil {
+	if err := pdb.Read(jirix, "./testdata/legacy.xml"); err != nil {
 		t.Fatal(err)
 	}
 
-	if got, want := profiles.SchemaVersion(), profiles.Original; got != want {
+	if got, want := pdb.SchemaVersion(), profiles.Original; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
 
-	oprofiles := getProfiles()
+	oprofiles := pdb.Profiles()
 	if got, want := len(oprofiles), 5; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
@@ -155,30 +145,33 @@
 
 	var t1 profiles.Target
 	t1.Set("cpu-os@1")
-	profiles.AddProfileTarget("__first", t1)
-
-	if err := profiles.Write(jirix, filename); err != nil {
+	pdb.InstallProfile("__first", "")
+	if err := pdb.AddProfileTarget("__first", t1); err != nil {
 		t.Fatal(err)
 	}
 
-	if err := profiles.Read(jirix, filename); err != nil {
+	if err := pdb.Write(jirix, filename); err != nil {
 		t.Fatal(err)
 	}
 
-	if got, want := profiles.SchemaVersion(), profiles.V4; got != want {
+	if err := pdb.Read(jirix, filename); err != nil {
+		t.Fatal(err)
+	}
+
+	if got, want := pdb.SchemaVersion(), profiles.V4; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
-	nprofiles := getProfiles()
+	nprofiles := pdb.Profiles()
 	if got, want := len(nprofiles), 6; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
 
-	if got, want := nprofiles[0].Name, "__first"; got != want {
+	if got, want := nprofiles[0].Name(), "__first"; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
 
 	for i, v := range nprofiles[1:] {
-		if got, want := v.Name, oprofiles[i].Name; got != want {
+		if got, want := v.Name(), oprofiles[i].Name(); got != want {
 			t.Errorf("got %v, want %v", got, want)
 		}
 	}
@@ -194,32 +187,27 @@
 		{"v3.xml", "", "", profiles.V3},
 		{"v4.xml", fake.X.Root, "${JIRI_ROOT}", profiles.V4},
 	} {
-		ch, err := profiles.NewConfigHelper(fake.X, profiles.UseProfiles, filepath.Join("testdata", c.filename))
+		pdb := profiles.NewDB()
+		err := pdb.Read(fake.X, filepath.Join("testdata", c.filename))
 		if err != nil {
 			t.Fatal(err)
 		}
-		if got, want := profiles.SchemaVersion(), c.version; got != want {
+		if got, want := pdb.SchemaVersion(), c.version; got != want {
 			t.Errorf("%d: got %v, want %v", i, got, want)
 		}
-		target, err := profiles.NewTarget("cpu1-os1@1")
+		target, err := profiles.NewTarget("cpu1-os1@1", "")
 		if err != nil {
 			t.Fatal(err)
 		}
-		p := profiles.LookupProfile("a")
+		p := pdb.LookupProfile("a")
 		// We need to expand the variable here for a V4 profile if we want
 		// to get the full absolute path.
-		if got, want := p.Root, c.variable+"/an/absolute/root"; got != want {
+		if got, want := p.Root(), c.variable+"/an/absolute/root"; got != want {
 			t.Errorf("%d: got %v, want %v", i, got, want)
 		}
-		lt := profiles.LookupProfileTarget("a", target)
+		lt := pdb.LookupProfileTarget("a", target)
 		if got, want := lt.InstallationDir, c.variable+"/an/absolute/dir"; got != want {
 			t.Errorf("%d: got %v, want %v", i, got, want)
 		}
-		// The merged environment variables are expanded appropriately
-		// internally by MergeEnvFromProfiles.
-		ch.MergeEnvFromProfiles(profiles.JiriMergePolicies(), target, "a")
-		if got, want := ch.Get("ABS"), "-I"+c.prefix+"/an/absolute/path"; got != want {
-			t.Errorf("%d: got %v, want %v", i, got, want)
-		}
 	}
 }
diff --git a/profiles/profiles.go b/profiles/profiles.go
new file mode 100644
index 0000000..3c21bb3
--- /dev/null
+++ b/profiles/profiles.go
@@ -0,0 +1,113 @@
+// 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 profiles and its subdirectoris implement support for managing external
+// sofware dependencies. They offer a balance between providing no support at
+// all and a full blown package manager. A profile is a named collection of
+// software required for a given system component or application. The name of
+// the profile refers to all of the required software, which may a single
+// library or a collection of libraries or SDKs. Profiles thus refer to
+// uncompiled source code that needs to be compiled for a specific "target".
+// Targets represent compiled code and consist of:
+//
+// 1. An 'architecture' that refers to the CPU to be generate code for.
+//
+// 2. An 'operating system' that refers to the operating system to generate
+// code for.
+//
+// 3. An 'environment' which is a set of environment variables to use when
+// compiling and using the profile.
+//
+// Targets provide the essential support for cross compilation.
+//
+// The profiles package provides the data types to support its sub-packages,
+// including a database format (in XML) that is used to store the state of
+// the currently installed profiles.
+//
+// The profiles/manager package provides a registry for profile implementations
+// to register themselves (by calling manager.Register from an init function
+// for example).
+//
+// The profiles/reader package provides support for reading the profiles
+// database and performing common operations on it.
+// profiles/commandline provides an easy to use command line environment for
+// tools that need to read and/or write profiles.
+//
+// Profiles may be installed, updated or removed. When doing so, the name of
+// the profile is required, but the other components of the target are optional
+// and will default to the values of the system that the commands are run on
+// (so-called native builds). These operations are defined by the
+// profiles/manager.Manager interface.
+package profiles
+
+import (
+	"flag"
+
+	"v.io/jiri/jiri"
+)
+
+// Profile represents an installed profile and its associated targets.
+type Profile struct {
+	name, root string
+	targets    Targets
+}
+
+// Name returns the name of this profile.
+func (p *Profile) Name() string {
+	return p.name
+}
+
+// Root returns the directory, relative to the jiri root, that this
+// profile is installed at.
+func (p *Profile) Root() string {
+	return p.root
+}
+
+// Targets returns the currently installed set of targets for this profile.
+// Note that Targets is ordered by architecture, operating system and
+// descending versions.
+func (p *Profile) Targets() Targets {
+	r := make(Targets, len(p.targets), len(p.targets))
+	for i, t := range p.targets {
+		tmp := *t
+		r[i] = &tmp
+	}
+	return r
+}
+
+type Action int
+
+const (
+	Install Action = iota
+	Uninstall
+)
+
+// Manager is the interface that must be implemented in order to
+// manage (i.e. install/uninstall) and describe a profile.
+type Manager interface {
+	// Name returns the name of this profile.
+	Name() string
+
+	// Info returns an informative description of the profile.
+	Info() string
+
+	// VersionInfo returns the VersionInfo instance for this profile.
+	VersionInfo() *VersionInfo
+
+	// String returns a string representation of the profile, conventionally this
+	// is its name and version.
+	String() string
+
+	// AddFlags allows the profile manager to add profile specific flags
+	// to the supplied FlagSet for the specified Action.
+	// They should be named <profile-name>.<flag>.
+	AddFlags(*flag.FlagSet, Action)
+
+	// Install installs the profile for the specified build target.
+	Install(jirix *jiri.X, pdb *DB, root jiri.RelPath, target Target) error
+	// Uninstall uninstalls the profile for the specified build target. When
+	// the last target for any given profile is uninstalled, then the profile
+	// itself (i.e. the source code) will be uninstalled.
+	Uninstall(jirix *jiri.X, pdb *DB, root jiri.RelPath, target Target) error
+}
diff --git a/profiles/reader/.api b/profiles/reader/.api
new file mode 100644
index 0000000..25db26c
--- /dev/null
+++ b/profiles/reader/.api
@@ -0,0 +1,72 @@
+pkg reader, const Append MergeAction
+pkg reader, const AppendJiriProfile AppendJiriProfileMode
+pkg reader, const DoNotAppendJiriProfile bool
+pkg reader, const First MergeAction
+pkg reader, const Ignore MergeAction
+pkg reader, const IgnoreBaseAndAppend MergeAction
+pkg reader, const IgnoreBaseAndPrepend MergeAction
+pkg reader, const IgnoreBaseAndUseFirst MergeAction
+pkg reader, const IgnoreBaseAndUseLast MergeAction
+pkg reader, const IgnoreProfiles MergeAction
+pkg reader, const Last MergeAction
+pkg reader, const Prepend MergeAction
+pkg reader, const SkipProfiles ProfilesMode
+pkg reader, const UseProfiles ProfilesMode
+pkg reader, func GoEnvironmentFromOS() []string
+pkg reader, func InitProfilesFromFlag(string, AppendJiriProfileMode) []string
+pkg reader, func JiriMergePolicies() MergePolicies
+pkg reader, func MergeEnv(map[string]MergePolicy, *envvar.Vars, ...[]string)
+pkg reader, func NewReader(*jiri.X, ProfilesMode, string) (*Reader, error)
+pkg reader, func ProfileMergePolicies() MergePolicies
+pkg reader, func RegisterReaderFlags(*flag.FlagSet, *ProfilesMode, *string, *string, string, *MergePolicies)
+pkg reader, func UnsetGoEnvMap(map[string]string)
+pkg reader, func UnsetGoEnvVars(*envvar.Vars)
+pkg reader, func WithDefaultVersion(profiles.Target) profiles.Target
+pkg reader, method (*MergePolicy) String() string
+pkg reader, method (*ProfilesMode) Get() interface{}
+pkg reader, method (*ProfilesMode) IsBoolFlag() bool
+pkg reader, method (*ProfilesMode) Set(string) error
+pkg reader, method (*ProfilesMode) String() string
+pkg reader, method (*Reader) EnvFromProfile(string, profiles.Target) []string
+pkg reader, method (*Reader) GoPath() string
+pkg reader, method (*Reader) JiriProfile() []string
+pkg reader, method (*Reader) LookupProfile(string) *profiles.Profile
+pkg reader, method (*Reader) LookupProfileTarget(string, profiles.Target) *profiles.Target
+pkg reader, method (*Reader) MergeEnv(map[string]MergePolicy, ...[]string)
+pkg reader, method (*Reader) MergeEnvFromProfiles(map[string]MergePolicy, profiles.Target, ...string)
+pkg reader, method (*Reader) PrependToPATH(string)
+pkg reader, method (*Reader) ProfileNames() []string
+pkg reader, method (*Reader) Profiles() []*profiles.Profile
+pkg reader, method (*Reader) SchemaVersion() profiles.Version
+pkg reader, method (*Reader) SkippingProfiles() bool
+pkg reader, method (*Reader) VDLPath() string
+pkg reader, method (*Reader) ValidateRequestedProfilesAndTarget([]string, profiles.Target) error
+pkg reader, method (MergePolicies) DebugString() string
+pkg reader, method (MergePolicies) Get() interface{}
+pkg reader, method (MergePolicies) Set(string) error
+pkg reader, method (MergePolicies) String() string
+pkg reader, method (MergePolicies) Usage() string
+pkg reader, type AppendJiriProfileMode bool
+pkg reader, type MergeAction int
+pkg reader, type MergePolicies map[string]MergePolicy
+pkg reader, type MergePolicy struct
+pkg reader, type MergePolicy struct, Action MergeAction
+pkg reader, type MergePolicy struct, Separator string
+pkg reader, type ProfilesMode bool
+pkg reader, type Reader struct
+pkg reader, type Reader struct, embedded *envvar.Vars
+pkg reader, var AppendFlag MergePolicy
+pkg reader, var AppendPath MergePolicy
+pkg reader, var GoFlags []string
+pkg reader, var IgnoreBaseAppendFlag MergePolicy
+pkg reader, var IgnoreBaseAppendPath MergePolicy
+pkg reader, var IgnoreBasePrependFlag MergePolicy
+pkg reader, var IgnoreBasePrependPath MergePolicy
+pkg reader, var IgnoreBaseUseFirst MergePolicy
+pkg reader, var IgnoreBaseUseLast MergePolicy
+pkg reader, var IgnoreVariable MergePolicy
+pkg reader, var PrependFlag MergePolicy
+pkg reader, var PrependPath MergePolicy
+pkg reader, var UseBaseIgnoreProfiles MergePolicy
+pkg reader, var UseFirst MergePolicy
+pkg reader, var UseLast MergePolicy
diff --git a/profiles/reader/flags.go b/profiles/reader/flags.go
new file mode 100644
index 0000000..d7fa0f4
--- /dev/null
+++ b/profiles/reader/flags.go
@@ -0,0 +1,58 @@
+// 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 reader
+
+import (
+	"flag"
+	"path/filepath"
+	"strings"
+
+	"v.io/jiri/jiri"
+)
+
+// RegisterReaderFlags registers the flags commonly used with a profiles.Reader.
+// --profiles-manifest, --skip-profiles, --profiles and --merge-policies.
+func RegisterReaderFlags(flags *flag.FlagSet, profilesMode *ProfilesMode, manifest, profiles *string, defaultManifest string, policies *MergePolicies) {
+	flags.Var(profilesMode, "skip-profiles", "if set, no profiles will be used")
+	registerProfilesFlag(flags, profiles)
+	registerMergePoliciesFlag(flags, policies)
+	registerManifestFlag(flags, manifest, defaultManifest)
+}
+
+// RegisterProfilesFlag registers the --profiles flag
+func registerProfilesFlag(flags *flag.FlagSet, profiles *string) {
+	flags.StringVar(profiles, "profiles", "base,jiri", "a comma separated list of profiles to use")
+}
+
+// RegisterMergePoliciesFlag registers the --merge-policies flag
+func registerMergePoliciesFlag(flags *flag.FlagSet, policies *MergePolicies) {
+	flags.Var(policies, "merge-policies", "specify policies for merging environment variables")
+}
+
+// RegisterManifestFlag registers the commonly used --profiles-manifest
+// flag with the supplied FlagSet.
+func registerManifestFlag(flags *flag.FlagSet, manifest *string, defaultManifest string) {
+	root := jiri.FindRoot()
+	flags.StringVar(manifest, "profiles-manifest", filepath.Join(root, defaultManifest), "specify the profiles XML manifest filename.")
+	flags.Lookup("profiles-manifest").DefValue = filepath.Join("$JIRI_ROOT", defaultManifest)
+}
+
+type AppendJiriProfileMode bool
+
+const (
+	AppendJiriProfile      AppendJiriProfileMode = true
+	DoNotAppendJiriProfile                       = false
+)
+
+// InitProfilesFromFlag splits a comma separated list of profile names into
+// a slice and optionally appends the 'jiri' profile if it's not already
+// present.
+func InitProfilesFromFlag(flag string, appendJiriProfile AppendJiriProfileMode) []string {
+	n := strings.Split(flag, ",")
+	if appendJiriProfile == AppendJiriProfile && !strings.Contains(flag, "jiri") {
+		n = append(n, "jiri")
+	}
+	return n
+}
diff --git a/profiles/util_test.go b/profiles/reader/flags_test.go
similarity index 82%
rename from profiles/util_test.go
rename to profiles/reader/flags_test.go
index c7bdf6a..40130a7 100644
--- a/profiles/util_test.go
+++ b/profiles/reader/flags_test.go
@@ -2,15 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package profiles
+package reader
 
 import "testing"
 
-// Clear resets the current database and is intended for use from tests only.
-func Clear() {
-	db = newDB()
-}
-
 func TestAppendJiriProfile(t *testing.T) {
 	p := InitProfilesFromFlag("foo", DoNotAppendJiriProfile)
 	if got, want := p, []string{"foo"}; len(got) != 1 || got[0] != "foo" {
diff --git a/profiles/env.go b/profiles/reader/reader.go
similarity index 86%
rename from profiles/env.go
rename to profiles/reader/reader.go
index 7d5325b..605f33f 100644
--- a/profiles/env.go
+++ b/profiles/reader/reader.go
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package profiles
+// Package reader provides support for reading and processing jiri profiles.
+package reader
 
 import (
 	"bytes"
@@ -14,6 +15,7 @@
 	"strings"
 
 	"v.io/jiri/jiri"
+	"v.io/jiri/profiles"
 	"v.io/jiri/project"
 	"v.io/jiri/util"
 	"v.io/x/lib/envvar"
@@ -92,24 +94,25 @@
 	return vars
 }
 
-// ConfigHelper wraps the various sources of configuration and profile
+// Reader wraps the various sources of configuration and profile
 // information to provide convenient methods for determing the environment
 // variables to use for a given situation. It creates an initial copy of the OS
 // environment that is mutated by its various methods.
-type ConfigHelper struct {
+type Reader struct {
 	*envvar.Vars
 	profilesMode bool
 	jirix        *jiri.X
 	config       *util.Config
 	projects     project.Projects
 	tools        project.Tools
+	pdb          *profiles.DB
 }
 
-// NewConfigHelper creates a new config helper. If filename is of non-zero
+// NewReader creates a new profiles reader. If filename is of non-zero
 // length then that file will be read as a profiles manifest file, if not, the
 // existing, if any, in-memory profiles information will be used. If SkipProfiles
 // is specified for profilesMode, then no profiles are used.
-func NewConfigHelper(jirix *jiri.X, profilesMode ProfilesMode, filename string) (*ConfigHelper, error) {
+func NewReader(jirix *jiri.X, profilesMode ProfilesMode, filename string) (*Reader, error) {
 	config, err := util.LoadConfig(jirix)
 	if err != nil {
 		return nil, err
@@ -118,38 +121,62 @@
 	if err != nil {
 		return nil, err
 	}
+	pdb := profiles.NewDB()
 	if profilesMode == UseProfiles && len(filename) > 0 {
-		if err := Read(jirix, filename); err != nil {
+		if err := pdb.Read(jirix, filename); err != nil {
 			return nil, err
 		}
 	}
-	ch := &ConfigHelper{
+	rd := &Reader{
 		jirix:        jirix,
 		config:       config,
 		projects:     projects,
 		tools:        tools,
 		profilesMode: bool(profilesMode),
+		pdb:          pdb,
 	}
-	ch.Vars = envvar.VarsFromOS()
+	rd.Vars = envvar.VarsFromOS()
 	if profilesMode == SkipProfiles {
-		return ch, nil
+		return rd, nil
 	}
 	if len(os.Getenv("JIRI_PROFILE")) > 0 {
 		return nil, fmt.Errorf(`old style profiles are no longer supported. Please
 do not set JIRI_PROFILE.`)
 	}
-	return ch, nil
+	return rd, nil
 }
 
-// Root returns the root of the jiri universe.
-func (ch *ConfigHelper) Root() string {
-	return ch.jirix.Root
+func (rd *Reader) SchemaVersion() profiles.Version {
+	return rd.pdb.SchemaVersion()
+}
+
+func (rd *Reader) ProfileNames() []string {
+	return rd.pdb.Names()
+}
+
+func (rd *Reader) Profiles() []*profiles.Profile {
+	return rd.pdb.Profiles()
+}
+
+func (rd *Reader) LookupProfile(name string) *profiles.Profile {
+	return rd.pdb.LookupProfile(name)
+}
+
+func (rd *Reader) LookupProfileTarget(name string, target profiles.Target) *profiles.Target {
+	return rd.pdb.LookupProfileTarget(name, target)
 }
 
 // MergeEnv merges the embedded environment with the environment
 // variables provided by the vars parameter according to the policies parameter.
-func (ch *ConfigHelper) MergeEnv(policies map[string]MergePolicy, vars ...[]string) {
-	MergeEnv(policies, ch.Vars, vars...)
+func (rd *Reader) MergeEnv(policies map[string]MergePolicy, vars ...[]string) {
+	MergeEnv(policies, rd.Vars, vars...)
+}
+
+// EnvFromProfile obtains the environment variable settings from the specified
+// profile and target. It returns nil if the target and/or profile could not
+// be found.
+func (rd *Reader) EnvFromProfile(name string, target profiles.Target) []string {
+	return rd.pdb.EnvFromProfile(name, target)
 }
 
 // MergeEnvFromProfiles merges the embedded environment with the environment
@@ -157,42 +184,42 @@
 // the manifest and in addition the 'jiri' profile may be used which refers to
 // the environment variables maintained by the jiri tool itself. It will also
 // expand all instances of ${JIRI_ROOT} in the returned environment.
-func (ch *ConfigHelper) MergeEnvFromProfiles(policies map[string]MergePolicy, target Target, profileNames ...string) {
+func (rd *Reader) MergeEnvFromProfiles(policies map[string]MergePolicy, target profiles.Target, profileNames ...string) {
 	envs := [][]string{}
 	for _, profile := range profileNames {
 		var e []string
 		if profile == "jiri" {
-			e = ch.JiriProfile()
+			e = rd.JiriProfile()
 		} else {
-			e = EnvFromProfile(target, profile)
+			e = rd.pdb.EnvFromProfile(profile, target)
 		}
 		if e == nil {
 			continue
 		}
 		envs = append(envs, e)
 	}
-	MergeEnv(policies, ch.Vars, envs...)
-	jiri.ExpandEnv(ch.jirix, ch.Vars)
+	MergeEnv(policies, rd.Vars, envs...)
+	jiri.ExpandEnv(rd.jirix, rd.Vars)
 }
 
 // SkippingProfiles returns true if no profiles are being used.
-func (ch *ConfigHelper) SkippingProfiles() bool {
-	return ch.profilesMode == bool(SkipProfiles)
+func (rd *Reader) SkippingProfiles() bool {
+	return rd.profilesMode == bool(SkipProfiles)
 }
 
 // ValidateRequestProfilesAndTarget checks that the supplied slice of profiles
 // names is supported (including the 'jiri' profile) and that each has
 // the specified target installed taking account if running using profiles
 // at all or if using old-style profiles.
-func (ch *ConfigHelper) ValidateRequestedProfilesAndTarget(profileNames []string, target Target) error {
-	if ProfilesMode(ch.profilesMode) == SkipProfiles {
+func (rd *Reader) ValidateRequestedProfilesAndTarget(profileNames []string, target profiles.Target) error {
+	if ProfilesMode(rd.profilesMode) == SkipProfiles {
 		return nil
 	}
 	for _, n := range profileNames {
 		if n == "jiri" {
 			continue
 		}
-		if LookupProfileTarget(n, target) == nil {
+		if rd.pdb.LookupProfileTarget(n, target) == nil {
 			return fmt.Errorf("%q for %q is not available or not installed, use the \"list\" command to see the installed/available profiles.", target, n)
 		}
 	}
@@ -200,30 +227,30 @@
 }
 
 // PrependToPath prepends its argument to the PATH environment variable.
-func (ch *ConfigHelper) PrependToPATH(path string) {
-	existing := ch.GetTokens("PATH", ":")
-	ch.SetTokens("PATH", append([]string{path}, existing...), ":")
+func (rd *Reader) PrependToPATH(path string) {
+	existing := rd.GetTokens("PATH", ":")
+	rd.SetTokens("PATH", append([]string{path}, existing...), ":")
 }
 
 // JiriProfile returns a pseudo profile that is maintained by the Jiri
 // tool itself, this currently consists of the GoPath and VDLPath variables.
 // It will generally be used as the last profile in the set of profiles
 // passed to MergeEnv.
-func (ch *ConfigHelper) JiriProfile() []string {
-	return []string{ch.GoPath(), ch.VDLPath()}
+func (rd *Reader) JiriProfile() []string {
+	return []string{rd.GoPath(), rd.VDLPath()}
 }
 
 // GoPath computes and returns the GOPATH environment variable based on the
 // current jiri configuration.
-func (ch *ConfigHelper) GoPath() string {
-	path := pathHelper(ch.jirix, ch.projects, ch.config.GoWorkspaces(), "")
+func (rd *Reader) GoPath() string {
+	path := pathHelper(rd.jirix, rd.projects, rd.config.GoWorkspaces(), "")
 	return "GOPATH=" + envvar.JoinTokens(path, ":")
 }
 
 // VDLPath computes and returns the VDLPATH environment variable based on the
 // current jiri configuration.
-func (ch *ConfigHelper) VDLPath() string {
-	path := pathHelper(ch.jirix, ch.projects, ch.config.VDLWorkspaces(), "src")
+func (rd *Reader) VDLPath() string {
+	path := pathHelper(rd.jirix, rd.projects, rd.config.VDLWorkspaces(), "src")
 	return "VDLPATH=" + envvar.JoinTokens(path, ":")
 }
 
@@ -406,20 +433,9 @@
 	}
 }
 
-// EnvFromProfile obtains the environment variable settings from the specified
-// profile and target. It returns nil if the target and/or profile could not
-// be found.
-func EnvFromProfile(target Target, profileName string) []string {
-	t := LookupProfileTarget(profileName, target)
-	if t == nil {
-		return nil
-	}
-	return t.Env.Vars
-}
-
 // WithDefaultVersion returns a copy of the supplied target with its
 // version set to the default (i.e. emtpy string).
-func WithDefaultVersion(target Target) Target {
+func WithDefaultVersion(target profiles.Target) profiles.Target {
 	t := &target
 	t.SetVersion("")
 	return target
diff --git a/profiles/env_test.go b/profiles/reader/reader_test.go
similarity index 64%
rename from profiles/env_test.go
rename to profiles/reader/reader_test.go
index 501368f..f56a55e 100644
--- a/profiles/env_test.go
+++ b/profiles/reader/reader_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package profiles_test
+package reader_test
 
 import (
 	"flag"
@@ -13,69 +13,72 @@
 
 	"v.io/jiri/jiritest"
 	"v.io/jiri/profiles"
+	"v.io/jiri/profiles/reader"
 	"v.io/jiri/project"
 	"v.io/jiri/util"
 	"v.io/x/lib/envvar"
 )
 
-func TestConfigHelper(t *testing.T) {
+func TestReader(t *testing.T) {
 	fake, cleanup := jiritest.NewFakeJiriRoot(t)
 	defer cleanup()
-	ch, err := profiles.NewConfigHelper(fake.X, profiles.UseProfiles, filepath.Join("testdata", "m2.xml"))
+	rd, err := reader.NewReader(fake.X, reader.UseProfiles, filepath.Join("testdata", "m2.xml"))
 	if err != nil {
 		t.Fatal(err)
 	}
-	ch.Vars = envvar.VarsFromOS()
-	ch.Delete("CGO_CFLAGS")
-	native, err := profiles.NewTarget("amd64-darwin")
+	rd.Vars = envvar.VarsFromOS()
+	rd.Delete("CGO_CFLAGS")
+	native, err := profiles.NewTarget("amd64-darwin", "")
 	if err != nil {
 		t.Fatal(err)
 	}
-	ch.MergeEnvFromProfiles(profiles.JiriMergePolicies(), native, "go", "syncbase")
-	if got, want := ch.Get("CGO_CFLAGS"), "-IX -IY -IA -IB"; got != want {
+	rd.MergeEnvFromProfiles(reader.JiriMergePolicies(), native, "go", "syncbase")
+	if got, want := rd.Get("CGO_CFLAGS"), "-IX -IY -IA -IB"; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
 }
 
 func TestEnvFromTarget(t *testing.T) {
-	profiles.Clear()
 	fake, cleanup := jiritest.NewFakeJiriRoot(t)
 	defer cleanup()
-	profiles.InstallProfile("a", "root")
-	profiles.InstallProfile("b", "root")
+	pdb := profiles.NewDB()
+	pdb.InstallProfile("a", "root")
+	pdb.InstallProfile("b", "root")
 	t1, t2 := &profiles.Target{}, &profiles.Target{}
+
 	t1.Set("cpu1-os1@1")
 	t1.Env.Set("A=B C=D,B=C Z=Z")
 	t2.Set("cpu1-os1@1")
 	t2.Env.Set("A=Z,B=Z,Z=Z1")
-	profiles.AddProfileTarget("a", *t1)
-	profiles.AddProfileTarget("b", *t2)
+	pdb.AddProfileTarget("a", *t1)
+	pdb.AddProfileTarget("b", *t2)
+	pdb.Write(fake.X, "profile-manifest")
 	filename := filepath.Join(fake.X.Root, "profile-manifest")
-	if err := profiles.Write(fake.X, filename); err != nil {
+	if err := pdb.Write(fake.X, filename); err != nil {
 		t.Fatal(err)
 	}
-	ch, err := profiles.NewConfigHelper(fake.X, profiles.UseProfiles, filename)
+	rd, err := reader.NewReader(fake.X, reader.UseProfiles, filename)
 	if err != nil {
 		t.Fatal(err)
 	}
-	ch.Vars = envvar.VarsFromSlice([]string{})
-	t1Target, err := profiles.NewTarget("cpu1-os1@1")
+	rd.Vars = envvar.VarsFromSlice([]string{})
+	t1Target, err := profiles.NewTarget("cpu1-os1@1", "")
 	if err != nil {
 		t.Fatal(err)
 	}
-	ch.MergeEnvFromProfiles(map[string]profiles.MergePolicy{
-		"A": profiles.AppendFlag,
-		"B": profiles.UseLast,
-		"Z": profiles.IgnoreBaseUseLast},
+	rd.MergeEnvFromProfiles(map[string]reader.MergePolicy{
+		"A": reader.AppendFlag,
+		"B": reader.UseLast,
+		"Z": reader.IgnoreBaseUseLast},
 		t1Target, "a", "b")
-	vars := ch.ToMap()
+	vars := rd.ToMap()
 	if got, want := len(vars), 3; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
-	if got, want := ch.Get("A"), "B C=D Z"; got != want {
+	if got, want := rd.Get("A"), "B C=D Z"; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
-	if got, want := ch.Get("B"), "Z"; got != want {
+	if got, want := rd.Get("B"), "Z"; got != want {
 		t.Errorf("got %v, want %v", got, want)
 	}
 }
@@ -86,27 +89,27 @@
 	c := []string{"FS1=C", "FS2=C", "FS3=C", "A=BL", "B=C", "C=DL", "P=C", "V=C", "P1=C", "V1=C", "Y=ZL", "GP=XL", "IA=C", "IB=C", "IC=C", "ID=C", "IE=C", "IG3=B"}
 	env := envvar.VarsFromSlice(base)
 
-	policies := map[string]profiles.MergePolicy{
-		"GP":  profiles.UseLast,
-		"P":   profiles.PrependPath,
-		"V":   profiles.PrependFlag,
-		"P1":  profiles.AppendPath,
-		"V1":  profiles.AppendFlag,
-		"A":   profiles.IgnoreBaseUseLast,
-		"B":   profiles.UseBaseIgnoreProfiles,
-		"IA":  profiles.IgnoreBaseAppendPath,
-		"IB":  profiles.IgnoreBaseAppendFlag,
-		"IC":  profiles.IgnoreBasePrependPath,
-		"ID":  profiles.IgnoreBasePrependFlag,
-		"IE":  profiles.IgnoreBaseUseLast,
-		"IF":  profiles.IgnoreBaseUseFirst,
-		"IG1": profiles.IgnoreVariable,
-		"IG2": profiles.IgnoreVariable,
-		"IG3": profiles.IgnoreVariable,
-		"C":   profiles.UseLast,
-		"Y":   profiles.UseLast,
+	policies := map[string]reader.MergePolicy{
+		"GP":  reader.UseLast,
+		"P":   reader.PrependPath,
+		"V":   reader.PrependFlag,
+		"P1":  reader.AppendPath,
+		"V1":  reader.AppendFlag,
+		"A":   reader.IgnoreBaseUseLast,
+		"B":   reader.UseBaseIgnoreProfiles,
+		"IA":  reader.IgnoreBaseAppendPath,
+		"IB":  reader.IgnoreBaseAppendFlag,
+		"IC":  reader.IgnoreBasePrependPath,
+		"ID":  reader.IgnoreBasePrependFlag,
+		"IE":  reader.IgnoreBaseUseLast,
+		"IF":  reader.IgnoreBaseUseFirst,
+		"IG1": reader.IgnoreVariable,
+		"IG2": reader.IgnoreVariable,
+		"IG3": reader.IgnoreVariable,
+		"C":   reader.UseLast,
+		"Y":   reader.UseLast,
 	}
-	profiles.MergeEnv(policies, env, b, c)
+	reader.MergeEnv(policies, env, b, c)
 
 	expected := []string{"B=A", "A=BL", "C=DL", "GP=XL", "P1=A:B:C", "P=C:B:A",
 		"V1=A B C", "V=C B A", "W=X", "Y=ZL",
@@ -134,7 +137,7 @@
 }
 
 func testSetPathHelper(t *testing.T, name string) {
-	profiles.Clear()
+	pdb := profiles.NewDB()
 	fake, cleanup := jiritest.NewFakeJiriRoot(t)
 	defer cleanup()
 
@@ -160,7 +163,7 @@
 		config = util.NewConfig(util.VDLWorkspacesOpt([]string{"test", "does/not/exist"}))
 	}
 
-	if err := profiles.Write(fake.X, filepath.Join(fake.X.Root, "profiles-manifest")); err != nil {
+	if err := pdb.Write(fake.X, filepath.Join(fake.X.Root, "profiles-manifest")); err != nil {
 		t.Fatal(err)
 	}
 
@@ -168,7 +171,7 @@
 		t.Fatalf("%v", err)
 	}
 
-	ch, err := profiles.NewConfigHelper(fake.X, profiles.UseProfiles, filepath.Join(fake.X.Root, "profiles-manifest"))
+	rd, err := reader.NewReader(fake.X, reader.UseProfiles, filepath.Join(fake.X.Root, "profiles-manifest"))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -177,7 +180,7 @@
 	switch name {
 	case "GOPATH":
 		want = "GOPATH=" + filepath.Join(fake.X.Root, "test")
-		got = ch.GoPath()
+		got = rd.GoPath()
 	case "VDLPATH":
 		// Make a fake src directory.
 		want = filepath.Join(fake.X.Root, "test", "src")
@@ -185,7 +188,7 @@
 			t.Fatalf("%v", err)
 		}
 		want = "VDLPATH=" + want
-		got = ch.VDLPath()
+		got = rd.VDLPath()
 	}
 	if got != want {
 		t.Fatalf("unexpected value: got %v, want %v", got, want)
@@ -201,7 +204,7 @@
 }
 
 func TestMergePolicyFlags(t *testing.T) {
-	mp := profiles.MergePolicies{}
+	mp := reader.MergePolicies{}
 	fs := flag.NewFlagSet("test", flag.ContinueOnError)
 	fs.Var(mp, "p", mp.Usage())
 	all := []string{"-p=:a", "-p=+b", "-p=^c", "-p=^:d", "-p=^e:", "-p=^+f", "-p=^g+", "-p=last*", "-p=xx:", "-p=yy+", "-p=zz^"}
@@ -210,26 +213,26 @@
 	}
 	for _, c := range []struct {
 		k string
-		p profiles.MergePolicy
+		p reader.MergePolicy
 	}{
-		{"a", profiles.AppendPath},
-		{"b", profiles.AppendFlag},
-		{"c", profiles.IgnoreBaseUseFirst},
-		{"d", profiles.IgnoreBaseAppendPath},
-		{"e", profiles.IgnoreBasePrependPath},
-		{"f", profiles.IgnoreBaseAppendFlag},
-		{"g", profiles.IgnoreBasePrependFlag},
-		{"last", profiles.UseLast},
-		{"xx", profiles.PrependPath},
-		{"yy", profiles.PrependFlag},
-		{"zz", profiles.UseBaseIgnoreProfiles},
+		{"a", reader.AppendPath},
+		{"b", reader.AppendFlag},
+		{"c", reader.IgnoreBaseUseFirst},
+		{"d", reader.IgnoreBaseAppendPath},
+		{"e", reader.IgnoreBasePrependPath},
+		{"f", reader.IgnoreBaseAppendFlag},
+		{"g", reader.IgnoreBasePrependFlag},
+		{"last", reader.UseLast},
+		{"xx", reader.PrependPath},
+		{"yy", reader.PrependFlag},
+		{"zz", reader.UseBaseIgnoreProfiles},
 	} {
 		if got, want := mp[c.k], c.p; got != want {
 			t.Errorf("(%s) got %v, want %v", c.k, got, want)
 		}
 	}
 
-	mp = profiles.MergePolicies{}
+	mp = reader.MergePolicies{}
 	fs1 := flag.NewFlagSet("test1", flag.ContinueOnError)
 	fs1.Var(mp, "p", mp.Usage())
 	if err := fs1.Parse([]string{"-p=yy+,zz^"}); err != nil {
@@ -240,7 +243,7 @@
 	}
 
 	for i, cl := range append(all, "-p=+b,^c,zz^") {
-		mp := profiles.MergePolicies{}
+		mp := reader.MergePolicies{}
 		fs := flag.NewFlagSet(fmt.Sprintf("t%d", i), flag.ContinueOnError)
 		fs.Var(mp, "p", mp.Usage())
 		err := fs.Parse([]string{cl})
diff --git a/profiles/testdata/m2.xml b/profiles/reader/testdata/m2.xml
similarity index 100%
rename from profiles/testdata/m2.xml
rename to profiles/reader/testdata/m2.xml
diff --git a/profiles/target.go b/profiles/target.go
index 5bf831c..2ff4b59 100644
--- a/profiles/target.go
+++ b/profiles/target.go
@@ -89,16 +89,14 @@
 	Vars []string `xml:"var"`
 }
 
-func NewTarget(target string) (Target, error) {
+// NewTarget creates a new target using the supplied target and environment
+// parameters specified in command line format.
+func NewTarget(target string, env ...string) (Target, error) {
 	t := &Target{}
 	err := t.Set(target)
-	return *t, err
-}
-
-func NewTargetWithEnv(target, env string) (Target, error) {
-	t := &Target{}
-	err := t.Set(target)
-	t.commandLineEnv.Set(env)
+	for _, e := range env {
+		t.commandLineEnv.Set(e)
+	}
 	return *t, err
 }
 
@@ -128,7 +126,6 @@
 // Thus, (targets in <arch>-<os>[@<version>] format), are all true:
 // b-c < c-c
 // b-c@3 < b-c@2
-//
 func (pt *Target) Less(pt2 *Target) bool {
 	switch {
 	case pt.arch != pt2.arch:
@@ -236,26 +233,26 @@
 	return fmt.Sprintf("%v-%v@%s", v.arch, v.opsys, v.version)
 }
 
-// OrderderTargets is a list of *Targets ordered by architecture,
+// Targets is a list of *Target's ordered by architecture,
 // operating system and descending versions.
-type OrderedTargets []*Target
+type Targets []*Target
 
 // Implements sort.Len
-func (tl OrderedTargets) Len() int {
+func (tl Targets) Len() int {
 	return len(tl)
 }
 
 // Implements sort.Less
-func (tl OrderedTargets) Less(i, j int) bool {
+func (tl Targets) Less(i, j int) bool {
 	return tl[i].Less(tl[j])
 }
 
 // Implements sort.Swap
-func (tl OrderedTargets) Swap(i, j int) {
+func (tl Targets) Swap(i, j int) {
 	tl[i], tl[j] = tl[i], tl[j]
 }
 
-func (tl OrderedTargets) Sort() {
+func (tl Targets) Sort() {
 	sort.Sort(tl)
 }
 
@@ -292,9 +289,9 @@
 	return "specifcy an environment variable in the form: <var>=[<val>],..."
 }
 
-// InsertTarget inserts the given target into OrderedTargets if it's not
+// InsertTarget inserts the given target into Targets if it's not
 // already there and returns a new slice.
-func InsertTarget(targets OrderedTargets, target *Target) OrderedTargets {
+func InsertTarget(targets Targets, target *Target) Targets {
 	for i, t := range targets {
 		if !t.Less(target) {
 			targets = append(targets, nil)
@@ -308,7 +305,7 @@
 
 // RemoveTarget removes the given target from a slice of Target and returns
 // a slice.
-func RemoveTarget(targets OrderedTargets, target *Target) OrderedTargets {
+func RemoveTarget(targets Targets, target *Target) Targets {
 	for i, t := range targets {
 		if target.Match(t) {
 			targets, targets[len(targets)-1] = append(targets[:i], targets[i+1:]...), nil
@@ -322,7 +319,7 @@
 // the slice of Targets. If target has not been explicitly set and there is
 // only a single target available in targets then that one target is considered
 // as matching.
-func FindTarget(targets OrderedTargets, target *Target) *Target {
+func FindTarget(targets Targets, target *Target) *Target {
 	for _, t := range targets {
 		if target.Match(t) {
 			tmp := *t
@@ -335,7 +332,7 @@
 // FindTargetWithDefault is like FindTarget except that if there is only one
 // target in the slice and the requested target has not been explicitly set
 // (IsSet is false) then that one target is returned by default.
-func FindTargetWithDefault(targets OrderedTargets, target *Target) *Target {
+func FindTargetWithDefault(targets Targets, target *Target) *Target {
 	if len(targets) == 1 && !target.IsSet() {
 		tmp := *targets[0]
 		return &tmp
diff --git a/profiles/target_test.go b/profiles/target_test.go
index f0404f1..43fda01 100644
--- a/profiles/target_test.go
+++ b/profiles/target_test.go
@@ -87,7 +87,7 @@
 
 func TestCopyCommandLineEnv(t *testing.T) {
 	env := "A=B,C=D"
-	target, _ := profiles.NewTargetWithEnv("a=a-o", env)
+	target, _ := profiles.NewTarget("a=a-o", env)
 	clenv := target.CommandLineEnv()
 	if got, want := strings.Join(clenv.Vars, ","), env; got != want {
 		t.Errorf("got %v, want %v", got, want)
@@ -223,11 +223,11 @@
 		{"x-b@2", "x-b@1", true},
 		{"x-b", "x-b", false},
 	} {
-		a, err := profiles.NewTarget(c.a)
+		a, err := profiles.NewTarget(c.a, "")
 		if err != nil {
 			t.Fatal(err)
 		}
-		b, _ := profiles.NewTarget(c.b)
+		b, _ := profiles.NewTarget(c.b, "")
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -236,7 +236,7 @@
 		}
 	}
 
-	ol := profiles.OrderedTargets{}
+	ol := profiles.Targets{}
 	data := []string{"a-b@2", "x-y", "a-b@12", "a-b@3", "a-b@0", "x-y@3", "x-y@2"}
 	for _, s := range data {
 		target, err := profiles.NewTarget(s)
diff --git a/profiles/util.go b/profiles/util.go
index c80ff83..c5529c5 100644
--- a/profiles/util.go
+++ b/profiles/util.go
@@ -22,19 +22,12 @@
 )
 
 const (
+	// TODO(cnicolaou): move these to runutil
 	DefaultDirPerm  = os.FileMode(0755)
 	DefaultFilePerm = os.FileMode(0644)
 	targetDefValue  = "<runtime.GOARCH>-<runtime.GOOS>"
 )
 
-// RegisterTargetFlag registers the commonly used --target flag with
-// the supplied FlagSet.
-func RegisterTargetFlag(flags *flag.FlagSet, target *Target) {
-	*target = DefaultTarget()
-	flags.Var(target, "target", target.Usage())
-	flags.Lookup("target").DefValue = targetDefValue
-}
-
 // RegisterTargetAndEnvFlags registers the commonly used --target and --env
 // flags with the supplied FlagSet
 func RegisterTargetAndEnvFlags(flags *flag.FlagSet, target *Target) {
@@ -44,50 +37,17 @@
 	flags.Var(&target.commandLineEnv, "env", target.commandLineEnv.Usage())
 }
 
-// RegisterManifestFlag registers the commonly used --profiles-manifest
-// flag with the supplied FlagSet.
-func RegisterManifestFlag(flags *flag.FlagSet, manifest *string, defaultManifest string) {
-	root := jiri.FindRoot()
-	flags.StringVar(manifest, "profiles-manifest", filepath.Join(root, defaultManifest), "specify the profiles XML manifest filename.")
-	flags.Lookup("profiles-manifest").DefValue = filepath.Join("$JIRI_ROOT", defaultManifest)
-}
-
-// RegisterProfileFlags registers the commonly used --profiles-manifest, --profiles,
-// --target and --merge-policies flags with the supplied FlagSet.
-func RegisterProfileFlags(flags *flag.FlagSet, profilesMode *ProfilesMode, manifest, profiles *string, defaultManifest string, policies *MergePolicies, target *Target) {
-	flags.Var(profilesMode, "skip-profiles", "if set, no profiles will be used")
-	RegisterProfilesFlag(flags, profiles)
-	RegisterMergePoliciesFlag(flags, policies)
-	RegisterManifestFlag(flags, manifest, defaultManifest)
-	RegisterTargetFlag(flags, target)
-}
-
 // RegisterProfilesFlag registers the --profiles flag
 func RegisterProfilesFlag(flags *flag.FlagSet, profiles *string) {
 	flags.StringVar(profiles, "profiles", "base,jiri", "a comma separated list of profiles to use")
 }
 
-// RegisterMergePoliciesFlag registers the --merge-policies flag
-func RegisterMergePoliciesFlag(flags *flag.FlagSet, policies *MergePolicies) {
-	flags.Var(policies, "merge-policies", "specify policies for merging environment variables")
-}
-
-type AppendJiriProfileMode bool
-
-const (
-	AppendJiriProfile      AppendJiriProfileMode = true
-	DoNotAppendJiriProfile                       = false
-)
-
-// InitProfilesFromFlag splits a comma separated list of profile names into
-// a slice and optionally appends the 'jiri' profile if it's not already
-// present.
-func InitProfilesFromFlag(flag string, appendJiriProfile AppendJiriProfileMode) []string {
-	n := strings.Split(flag, ",")
-	if appendJiriProfile == AppendJiriProfile && !strings.Contains(flag, "jiri") {
-		n = append(n, "jiri")
-	}
-	return n
+// RegisterTargetFlag registers the commonly used --target flag with
+// the supplied FlagSet.
+func RegisterTargetFlag(flags *flag.FlagSet, target *Target) {
+	*target = DefaultTarget()
+	flags.Var(target, "target", target.Usage())
+	flags.Lookup("target").DefValue = targetDefValue
 }
 
 // AtomicAction performs an action 'atomically' by keeping track of successfully
@@ -95,6 +55,7 @@
 // are not successfully logged therein after deleting the entire contents of the
 // dir parameter. Consequently it does not make sense to apply AtomicAction to
 // the same directory in sequence.
+// TODO(cnicolaou): move this to runutil
 func AtomicAction(jirix *jiri.X, installFn func() error, dir, message string) error {
 	atomicFn := func() error {
 		completionLogPath := filepath.Join(dir, ".complete")
@@ -141,6 +102,7 @@
 
 // InstallPackages identifies the packages that need to be installed
 // and installs them using the OS-specific package manager.
+// TODO(cnicolaou): move these to runutil
 func InstallPackages(jirix *jiri.X, pkgs []string) error {
 	installDepsFn := func() error {
 		s := jirix.NewSeq()
@@ -192,60 +154,10 @@
 	return jirix.NewSeq().Call(installDepsFn, "Install dependencies: "+strings.Join(pkgs, ",")).Done()
 }
 
-// ensureAction ensures that the requested profile and target
-// is installed/uninstalled, installing/uninstalling it if and only if necessary.
-func ensureAction(jirix *jiri.X, action Action, profile string, root jiri.RelPath, target Target) error {
-	verb := ""
-	switch action {
-	case Install:
-		verb = "install"
-	case Uninstall:
-		verb = "uninstall"
-	default:
-		return fmt.Errorf("unrecognised action %v", action)
-	}
-	if jirix.Verbose() || jirix.DryRun() {
-		fmt.Fprintf(jirix.Stdout(), "%s %v %s\n", verb, action, target)
-	}
-	if t := LookupProfileTarget(profile, target); t != nil {
-		if jirix.Verbose() {
-			fmt.Fprintf(jirix.Stdout(), "%v %v is already %sed as %v\n", profile, target, verb, t)
-		}
-		return nil
-	}
-	mgr := LookupManager(profile)
-	if mgr == nil {
-		return fmt.Errorf("profile %v is not supported", profile)
-	}
-	version, err := mgr.VersionInfo().Select(target.Version())
-	if err != nil {
-		return err
-	}
-	target.SetVersion(version)
-	if jirix.Verbose() || jirix.DryRun() {
-		fmt.Fprintf(jirix.Stdout(), "%s %s %s\n", verb, profile, target.DebugString())
-	}
-	if action == Install {
-		return mgr.Install(jirix, root, target)
-	}
-	return mgr.Uninstall(jirix, root, target)
-}
-
-// EnsureProfileTargetIsInstalled ensures that the requested profile and target
-// is installed, installing it if only if necessary.
-func EnsureProfileTargetIsInstalled(jirix *jiri.X, profile string, root jiri.RelPath, target Target) error {
-	return ensureAction(jirix, Install, profile, root, target)
-}
-
-// EnsureProfileTargetIsUninstalled ensures that the requested profile and target
-// are no longer installed.
-func EnsureProfileTargetIsUninstalled(jirix *jiri.X, profile string, root jiri.RelPath, target Target) error {
-	return ensureAction(jirix, Uninstall, profile, root, target)
-}
-
 // Fetch downloads the specified url and saves it to dst.
 // TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
 // so it does not pollute the profile package namespace.
+// TODO(cnicolaou): move these to runutil
 func Fetch(jirix *jiri.X, dst, url string) error {
 	s := jirix.NewSeq()
 	s.Output([]string{"fetching " + url})
@@ -268,6 +180,7 @@
 // Unzip unzips the file in srcFile and puts resulting files in directory dstDir.
 // TODO(nlacasse, cnicoloau): Move this to a package for profile-implementors
 // so it does not pollute the profile package namespace.
+// TODO(cnicolaou): move these to runutil
 func Unzip(jirix *jiri.X, srcFile, dstDir string) error {
 	r, err := zip.OpenReader(srcFile)
 	if err != nil {