Merge "TBR: counterpart of v.io/c/20691"
diff --git a/data/config.v1.xml b/data/config.v1.xml
index df7939d..3862c5c 100644
--- a/data/config.v1.xml
+++ b/data/config.v1.xml
@@ -232,6 +232,9 @@
       <test>vanadium-js-vom</test>
     </group>
     <group name="mojo">
+      <!-- NOTE(caprita): this is flaky. See v.io/i/1240
+      <test>vanadium-mojo-discovery-test</test>
+      -->
       <test>vanadium-mojo-syncbase-test</test>
       <test>vanadium-mojo-v23proxy-unit-test</test>
       <!-- NOTE(caprita): this is flaky.  See v.io/i/1226
diff --git a/data/oncall.v1.xml b/data/oncall.v1.xml
index 960f50e..087f553 100644
--- a/data/oncall.v1.xml
+++ b/data/oncall.v1.xml
@@ -19,7 +19,7 @@
     <startDate>Mar 21, 2016 08:00:00 AM</startDate>
   </shift>
   <shift>
-    <primary>jyh</primary>
+    <primary>afergan</primary>
     <secondary>alexfandrianto</secondary>
     <startDate>Mar 28, 2016 08:00:00 AM</startDate>
   </shift>
@@ -50,11 +50,11 @@
   </shift>
   <shift>
     <primary>nlacasse</primary>
-    <secondary>p</secondary>
+    <secondary>suharshs</secondary>
     <startDate>May 09, 2016 08:00:00 AM</startDate>
   </shift>
   <shift>
-    <primary>p</primary>
+    <primary>suharshs</primary>
     <secondary>nlacasse</secondary>
     <startDate>May 16, 2016 08:00:00 AM</startDate>
   </shift>
diff --git a/internal/golib/go_test.go b/internal/golib/go_test.go
index 6f1cc4a..adf5dba 100644
--- a/internal/golib/go_test.go
+++ b/internal/golib/go_test.go
@@ -43,7 +43,7 @@
 	const perm = os.ModePerm
 	goFile := filepath.Join(pkgdir, "doc.go")
 	inFile := filepath.Join(pkgdir, "test.vdl")
-	outFile := inFile + ".go"
+	outFile := filepath.Join(pkgdir, "testpkg.vdl.go")
 	if err := s.MkdirAll(pkgdir, perm).
 		WriteFile(goFile, []byte("package testpkg\n"), perm).
 		WriteFile(inFile, []byte("package testpkg\n"), perm).Done(); err != nil {
diff --git a/jiri-api/api.go b/jiri-api/api.go
index 31b4687..4e1a4fb 100644
--- a/jiri-api/api.go
+++ b/jiri-api/api.go
@@ -42,7 +42,7 @@
 func init() {
 	cmdAPICheck.Flags.BoolVar(&detailedOutputFlag, "detailed", true, "If true, shows each API change in an expanded form. Otherwise, only a summary is shown.")
 	cmdAPI.Flags.StringVar(&gotoolsBinPathFlag, "gotools-bin", "", "The path to the gotools binary to use. If empty, gotools will be built if necessary.")
-	profilescmdline.RegisterReaderFlags(&cmdAPI.Flags, &readerFlags, jiri.DefaultProfilesDBPath())
+	profilescmdline.RegisterReaderFlags(&cmdAPI.Flags, &readerFlags, jiri.ProfilesDBDir)
 	tool.InitializeProjectFlags(&cmdAPI.Flags)
 	tool.InitializeRunFlags(&cmdAPI.Flags)
 }
diff --git a/jiri-api/doc.go b/jiri-api/doc.go
index 783c98c..1007005 100644
--- a/jiri-api/doc.go
+++ b/jiri-api/doc.go
@@ -29,9 +29,9 @@
    Name of the project manifest.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -72,9 +72,9 @@
    Name of the project manifest.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -105,9 +105,9 @@
    Name of the project manifest.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
diff --git a/jiri-contributors/contrib.go b/jiri-contributors/contrib.go
new file mode 100644
index 0000000..9ac4331
--- /dev/null
+++ b/jiri-contributors/contrib.go
@@ -0,0 +1,239 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $JIRI_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go -env=CMDLINE_PREFIX=jiri .
+
+package main
+
+import (
+	"encoding/xml"
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+
+	"v.io/jiri"
+	"v.io/jiri/collect"
+	"v.io/jiri/gitutil"
+	"v.io/jiri/project"
+	"v.io/jiri/tool"
+	"v.io/x/devtools/tooldata"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/set"
+)
+
+const (
+	aliasesFileName = "aliases.v1.xml"
+)
+
+var (
+	countFlag   bool
+	aliasesFlag string
+)
+
+func init() {
+	cmdContributorsList.Flags.BoolVar(&countFlag, "n", false, "Show number of contributions.")
+	cmdContributorsList.Flags.StringVar(&aliasesFlag, "aliases", "", "Path to the aliases file.")
+}
+
+// cmdContributors represents the "jiri contributors" command.
+var cmdContributors = &cmdline.Command{
+	Name:     "contributors",
+	Short:    "List project contributors",
+	Long:     "List project contributors.",
+	Children: []*cmdline.Command{cmdContributorsList},
+}
+
+// cmdContributorsList represents the "jiri contributors list" command.
+var cmdContributorsList = &cmdline.Command{
+	Runner: jiri.RunnerFunc(runContributorsList),
+	Name:   "contributors",
+	Short:  "List project contributors",
+	Long: `
+Lists project contributors. Projects to consider can be specified as
+an argument. If no projects are specified, all projects in the current
+manifest are considered by default.
+`,
+	ArgsName: "<projects>",
+	ArgsLong: "<projects> is a list of projects to consider.",
+}
+
+type contributor struct {
+	count int
+	email string
+	name  string
+}
+
+var (
+	contributorRE = regexp.MustCompile("^(.*)\t(.*) <(.*)>$")
+)
+
+type aliasesSchema struct {
+	XMLName xml.Name      `xml:"aliases"`
+	Names   []nameSchema  `xml:"name"`
+	Emails  []emailSchema `xml:"email"`
+}
+
+type nameSchema struct {
+	Canonical string   `xml:"canonical"`
+	Aliases   []string `xml:"alias"`
+}
+
+type emailSchema struct {
+	Canonical string   `xml:"canonical"`
+	Aliases   []string `xml:"alias"`
+}
+
+type aliasMaps struct {
+	emails map[string]string
+	names  map[string]string
+}
+
+func canonicalize(aliases *aliasMaps, email, name string) (string, string) {
+	canonicalEmail, canonicalName := email, name
+	if email, ok := aliases.emails[email]; ok {
+		canonicalEmail = email
+	}
+	if name, ok := aliases.names[name]; ok {
+		canonicalName = name
+	}
+	return canonicalEmail, canonicalName
+}
+
+func loadAliases(jirix *jiri.X) (*aliasMaps, error) {
+	aliasesFile := aliasesFlag
+	if aliasesFile == "" {
+		dataDir, err := tooldata.DataDirPath(jirix, tool.Name)
+		if err != nil {
+			return nil, err
+		}
+		aliasesFile = filepath.Join(dataDir, aliasesFileName)
+	}
+	bytes, err := jirix.NewSeq().ReadFile(aliasesFile)
+	if err != nil {
+		return nil, err
+	}
+	var data aliasesSchema
+	if err := xml.Unmarshal(bytes, &data); err != nil {
+		return nil, fmt.Errorf("Unmarshal(%v) failed: %v", string(bytes), err)
+	}
+	aliases := &aliasMaps{
+		emails: map[string]string{},
+		names:  map[string]string{},
+	}
+	for _, email := range data.Emails {
+		for _, alias := range email.Aliases {
+			aliases.emails[alias] = email.Canonical
+		}
+	}
+	for _, name := range data.Names {
+		for _, alias := range name.Aliases {
+			aliases.names[alias] = name.Canonical
+		}
+	}
+	return aliases, nil
+}
+
+func runContributorsList(jirix *jiri.X, args []string) error {
+	localProjects, err := project.LocalProjects(jirix, project.FastScan)
+	if err != nil {
+		return err
+	}
+	projectNames := map[string]struct{}{}
+	if len(args) != 0 {
+		projectNames = set.String.FromSlice(args)
+	} else {
+		for _, p := range localProjects {
+			projectNames[p.Name] = struct{}{}
+		}
+	}
+
+	aliases, err := loadAliases(jirix)
+	if err != nil {
+		return err
+	}
+	contributors := map[string]*contributor{}
+	for name, _ := range projectNames {
+		projects := localProjects.Find(name)
+		if len(projects) == 0 {
+			continue
+		}
+
+		for _, project := range projects {
+			if err := jirix.NewSeq().Chdir(project.Path).Done(); err != nil {
+				return err
+			}
+			switch project.Protocol {
+			case "git":
+				lines, err := listCommitters(jirix)
+				if err != nil {
+					return err
+				}
+				for _, line := range lines {
+					matches := contributorRE.FindStringSubmatch(line)
+					if got, want := len(matches), 4; got != want {
+						return fmt.Errorf("unexpected length of %v: got %v, want %v", matches, got, want)
+					}
+					count, err := strconv.Atoi(strings.TrimSpace(matches[1]))
+					if err != nil {
+						return fmt.Errorf("Atoi(%v) failed: %v", strings.TrimSpace(matches[1]), err)
+					}
+					c := &contributor{
+						count: count,
+						email: strings.TrimSpace(matches[3]),
+						name:  strings.TrimSpace(matches[2]),
+					}
+					if c.email == "jenkins.veyron@gmail.com" || c.email == "jenkins.veyron.rw@gmail.com" {
+						continue
+					}
+					c.email, c.name = canonicalize(aliases, c.email, c.name)
+					if existing, ok := contributors[c.name]; ok {
+						existing.count += c.count
+					} else {
+						contributors[c.name] = c
+					}
+				}
+			}
+		}
+	}
+	names := []string{}
+	for name, _ := range contributors {
+		names = append(names, name)
+	}
+	sort.Strings(names)
+	for _, name := range names {
+		c := contributors[name]
+		if countFlag {
+			fmt.Fprintf(jirix.Stdout(), "%4d ", c.count)
+		}
+		fmt.Fprintf(jirix.Stdout(), "%v <%v>\n", c.name, c.email)
+	}
+	return nil
+}
+
+func listCommitters(jirix *jiri.X) (_ []string, e error) {
+	branch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName()
+	if err != nil {
+		return nil, err
+	}
+	stashed, err := gitutil.New(jirix.NewSeq()).Stash()
+	if err != nil {
+		return nil, err
+	}
+	if stashed {
+		defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).StashPop() }, &e)
+	}
+	if err := gitutil.New(jirix.NewSeq()).CheckoutBranch("master"); err != nil {
+		return nil, err
+	}
+	defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).CheckoutBranch(branch) }, &e)
+	return gitutil.New(jirix.NewSeq()).Committers()
+}
+
+func main() {
+	cmdline.Main(cmdContributors)
+}
diff --git a/jiri-contributors/doc.go b/jiri-contributors/doc.go
new file mode 100644
index 0000000..5bf5512
--- /dev/null
+++ b/jiri-contributors/doc.go
@@ -0,0 +1,67 @@
+// 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.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+List project contributors.
+
+Usage:
+   jiri contributors [flags] <command>
+
+The jiri contributors commands are:
+   contributors List project contributors
+   help         Display help for commands or topics
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -time=false
+   Dump timing information to stderr before exiting the program.
+
+Jiri contributors contributors - List project contributors
+
+Lists project contributors. Projects to consider can be specified as an
+argument. If no projects are specified, all projects in the current manifest are
+considered by default.
+
+Usage:
+   jiri contributors contributors [flags] <projects>
+
+<projects> is a list of projects to consider.
+
+The jiri contributors contributors flags are:
+ -aliases=
+   Path to the aliases file.
+ -n=false
+   Show number of contributions.
+
+Jiri contributors help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   jiri contributors help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The jiri contributors help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact   - Good for compact cmdline output.
+      full      - Good for cmdline output, shows all global flags.
+      godoc     - Good for godoc processing.
+      shortonly - Only output short description.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/jiri-dockergo/doc.go b/jiri-dockergo/doc.go
index d3fee5a..8a88f3e 100644
--- a/jiri-dockergo/doc.go
+++ b/jiri-dockergo/doc.go
@@ -49,9 +49,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
diff --git a/jiri-dockergo/go.go b/jiri-dockergo/go.go
index ec5458e..96d4203 100644
--- a/jiri-dockergo/go.go
+++ b/jiri-dockergo/go.go
@@ -75,7 +75,7 @@
 
 func init() {
 	tool.InitializeRunFlags(&cmd.Flags)
-	profilescmdline.RegisterReaderFlags(&cmd.Flags, &readerFlags, jiri.DefaultProfilesDBPath())
+	profilescmdline.RegisterReaderFlags(&cmd.Flags, &readerFlags, jiri.ProfilesDBDir)
 	flag.StringVar(&imageFlag, "image", "", "Name of the docker image to use. If empty, the tool will automatically select an image based on the environment variables, possibly edited by the profile")
 	flag.StringVar(&extraLDFlags, "extra-ldflags", "", golib.ExtraLDFlagsFlagDescription)
 }
diff --git a/jiri-go/doc.go b/jiri-go/doc.go
index c99e72c..887ec39 100644
--- a/jiri-go/doc.go
+++ b/jiri-go/doc.go
@@ -23,9 +23,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
diff --git a/jiri-go/go.go b/jiri-go/go.go
index 55e37c5..d207461 100644
--- a/jiri-go/go.go
+++ b/jiri-go/go.go
@@ -46,7 +46,7 @@
 )
 
 func init() {
-	profilescmdline.RegisterReaderFlags(&cmdGo.Flags, &readerFlags, jiri.DefaultProfilesDBPath())
+	profilescmdline.RegisterReaderFlags(&cmdGo.Flags, &readerFlags, jiri.ProfilesDBDir)
 	flag.BoolVar(&systemGoFlag, "system-go", false, "use the version of go found in $PATH rather than that built by the go profile")
 	flag.StringVar(&extraLDFlags, "extra-ldflags", "", golib.ExtraLDFlagsFlagDescription)
 	flag.BoolVar(&envFlag, "print-run-env", false, "print detailed info on environment variables and the command line used")
diff --git a/jiri-goext/doc.go b/jiri-goext/doc.go
index ac6f273..8bafa9f 100644
--- a/jiri-goext/doc.go
+++ b/jiri-goext/doc.go
@@ -22,9 +22,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -59,9 +59,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
diff --git a/jiri-goext/goext.go b/jiri-goext/goext.go
index 1ebb54a..8e5a769 100644
--- a/jiri-goext/goext.go
+++ b/jiri-goext/goext.go
@@ -26,7 +26,7 @@
 )
 
 func init() {
-	profilescmdline.RegisterReaderFlags(&cmdGoExt.Flags, &readerFlags, jiri.DefaultProfilesDBPath())
+	profilescmdline.RegisterReaderFlags(&cmdGoExt.Flags, &readerFlags, jiri.ProfilesDBDir)
 	flag.BoolVar(&envFlag, "print-run-env", false, "print detailed info on environment variables and the command line used")
 	tool.InitializeRunFlags(&cmdGoExt.Flags)
 }
diff --git a/jiri-v23-profile/doc.go b/jiri-profile-v23/doc.go
similarity index 62%
rename from jiri-v23-profile/doc.go
rename to jiri-profile-v23/doc.go
index 99fef76..500261e 100644
--- a/jiri-v23-profile/doc.go
+++ b/jiri-profile-v23/doc.go
@@ -75,19 +75,17 @@
 import _ "myprofile") by the command line tools that are to use them.
 
 Usage:
-   jiri v23-profile [flags] <command>
+   jiri profile-v23 [flags] <command>
 
-The jiri v23-profile commands are:
+The jiri profile-v23 commands are:
    install     Install the given profiles
    uninstall   Uninstall the given profiles
    update      Install the latest default version of the given profiles
    cleanup     Cleanup the locally installed profiles
    available   List the available profiles
-   list        List available or installed profiles
-   env         Display profile environment variables
    help        Display help for commands or topics
 
-The jiri v23-profile flags are:
+The jiri profile-v23 flags are:
  -color=true
    Use color to format output.
  -v=false
@@ -99,16 +97,16 @@
  -time=false
    Dump timing information to stderr before exiting the program.
 
-Jiri v23-profile install - Install the given profiles
+Jiri profile-v23 install - Install the given profiles
 
 Install the given profiles.
 
 Usage:
-   jiri v23-profile install [flags] <profiles>
+   jiri profile-v23 install [flags] <profiles>
 
 <profiles> is a list of profiles to install.
 
-The jiri v23-profile install flags are:
+The jiri profile-v23 install flags are:
  -env=
    specify an environment variable in the form: <var>=[<val>],...
  -force=false
@@ -119,9 +117,9 @@
    a colon separated list of directories to use from the sysroot image
  -mojodev.dir=
    Path of mojo repo checkout.
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
+ -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
  -target=<runtime.GOARCH>-<runtime.GOOS>
    specifies a profile target in the following form: <arch>-<os>[@<version>]
@@ -131,25 +129,25 @@
  -v=false
    Print verbose output.
 
-Jiri v23-profile uninstall - Uninstall the given profiles
+Jiri profile-v23 uninstall - Uninstall the given profiles
 
 Uninstall the given profiles.
 
 Usage:
-   jiri v23-profile uninstall [flags] <profiles>
+   jiri profile-v23 uninstall [flags] <profiles>
 
 <profiles> is a list of profiles to uninstall.
 
-The jiri v23-profile uninstall flags are:
+The jiri profile-v23 uninstall flags are:
  -all-targets=false
    apply to all targets for the specified profile(s)
  -go.sysroot-image=
    sysroot image for cross compiling to the currently specified target
  -go.sysroot-image-dirs-to-use=/lib:/usr/lib:/usr/include
    a colon separated list of directories to use from the sysroot image
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
+ -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
  -target=<runtime.GOARCH>-<runtime.GOOS>
    specifies a profile target in the following form: <arch>-<os>[@<version>]
@@ -159,19 +157,19 @@
  -color=true
    Use color to format output.
 
-Jiri v23-profile update - Install the latest default version of the given profiles
+Jiri profile-v23 update - Install the latest default version of the given profiles
 
 Install the latest default version of the given profiles.
 
 Usage:
-   jiri v23-profile update [flags] <profiles>
+   jiri profile-v23 update [flags] <profiles>
 
 <profiles> is a list of profiles to update, if omitted all profiles are updated.
 
-The jiri v23-profile update flags are:
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+The jiri profile-v23 update flags are:
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
+ -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
  -v=false
    print more detailed information
@@ -179,24 +177,24 @@
  -color=true
    Use color to format output.
 
-Jiri v23-profile cleanup - Cleanup the locally installed profiles
+Jiri profile-v23 cleanup - Cleanup the locally installed profiles
 
 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.
 
 Usage:
-   jiri v23-profile cleanup [flags] <profiles>
+   jiri profile-v23 cleanup [flags] <profiles>
 
 <profiles> is a list of profiles to cleanup, if omitted all profiles are
 cleaned.
 
-The jiri v23-profile cleanup flags are:
+The jiri profile-v23 cleanup flags are:
  -gc=false
    uninstall profile targets that are older than the current default
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
+ -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
  -rewrite-profiles-db=false
    rewrite the profiles database to use the latest schema version
@@ -208,14 +206,14 @@
  -color=true
    Use color to format output.
 
-Jiri v23-profile available - List the available profiles
+Jiri profile-v23 available - List the available profiles
 
 List the available profiles.
 
 Usage:
-   jiri v23-profile available [flags]
+   jiri profile-v23 available [flags]
 
-The jiri v23-profile available flags are:
+The jiri profile-v23 available flags are:
  -describe=false
    print the profile description
  -v=false
@@ -224,88 +222,7 @@
  -color=true
    Use color to format output.
 
-Jiri v23-profile list - List available or installed profiles
-
-List available or installed profiles.
-
-Usage:
-   jiri v23-profile list [flags] [<profiles>]
-
-<profiles> is a list of profiles to list, defaulting to all profiles if none are
-specifically requested. List can also be used to test for the presence of a
-specific target for the requested profiles. If the target is not installed, it
-will exit with an error.
-
-The jiri v23-profile list flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -info=
-   The following fields for use with -info are available:
-   	SchemaVersion - the version of the profiles implementation.
-   	DBPath - the path for the profiles database.
-   	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.Root - the root directory of the requested profile.
-   	Profile.Name - the qualified name of the profile.
-   	Profile.Installer - the name of the profile installer.
-   	Profile.DBPath - the path to the database file for this profile.
-   	Note: if no profiles are specified then the requested field will be displayed for all profiles.
- -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
-   specify policies for merging environment variables
- -profiles=base,jiri
-   a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -skip-profiles=false
-   if set, no profiles will be used
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile env - Display profile environment variables
-
-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.
-
-Usage:
-   jiri v23-profile env [flags] [<environment variable names>]
-
-[<environment variable names>] is an optional list of environment variables to
-display
-
-The jiri v23-profile env flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
-   specify policies for merging environment variables
- -profiles=base,jiri
-   a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -skip-profiles=false
-   if set, no profiles will be used
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile help - Display help for commands or topics
+Jiri profile-v23 help - Display help for commands or topics
 
 Help with no args displays the usage of the parent command.
 
@@ -314,11 +231,11 @@
 "help ..." recursively displays help for all commands and topics.
 
 Usage:
-   jiri v23-profile help [flags] [command/topic ...]
+   jiri profile-v23 help [flags] [command/topic ...]
 
 [command/topic ...] optionally identifies a specific sub-command or help topic.
 
-The jiri v23-profile help flags are:
+The jiri profile-v23 help flags are:
  -style=compact
    The formatting style for help output:
       compact   - Good for compact cmdline output.
diff --git a/jiri-profile-v23/go_profile/go.go b/jiri-profile-v23/go_profile/go.go
index 47c307b..6ff3ad7 100644
--- a/jiri-profile-v23/go_profile/go.go
+++ b/jiri-profile-v23/go_profile/go.go
@@ -129,7 +129,7 @@
 		s = s.Run("tar", "-C", tmpDir, "-xzf", local)
 	}
 	if err := s.Remove(local).
-		MkdirAll(dir, profilesutil.DefaultDirPerm).
+		MkdirAll(filepath.Dir(dir), profilesutil.DefaultDirPerm).
 		Rename(filepath.Join(tmpDir, "go"), dir).
 		Done(); err != nil {
 		return err
diff --git a/jiri-run/doc.go b/jiri-run/doc.go
index 6b4382e..d03219a 100644
--- a/jiri-run/doc.go
+++ b/jiri-run/doc.go
@@ -21,9 +21,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
diff --git a/jiri-run/run.go b/jiri-run/run.go
index 03b71e4..353efbb 100644
--- a/jiri-run/run.go
+++ b/jiri-run/run.go
@@ -27,7 +27,7 @@
 
 func init() {
 	tool.InitializeRunFlags(&cmdRun.Flags)
-	profilescmdline.RegisterReaderFlags(&cmdRun.Flags, &readerFlags, jiri.DefaultProfilesDBPath())
+	profilescmdline.RegisterReaderFlags(&cmdRun.Flags, &readerFlags, jiri.ProfilesDBDir)
 	flag.BoolVar(&envFlag, "print-run-env", false, "print detailed info on environment variables and the command line used")
 }
 
diff --git a/jiri-test/doc.go b/jiri-test/doc.go
index ee6cda6..d83368b 100644
--- a/jiri-test/doc.go
+++ b/jiri-test/doc.go
@@ -12,6 +12,7 @@
    jiri test [flags] <command>
 
 The jiri test commands are:
+   poll        Poll existing jiri projects
    project     Run tests for a vanadium project
    run         Run vanadium tests
    list        List vanadium tests
@@ -24,9 +25,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -41,6 +42,38 @@
  -time=false
    Dump timing information to stderr before exiting the program.
 
+Jiri test poll - Poll existing jiri projects
+
+Poll jiri projects that can affect the outcome of the given tests and report
+whether any new changes in these projects exist. If no tests are specified, all
+projects are polled by default.
+
+Usage:
+   jiri test poll [flags] <test ...>
+
+<test ...> is a list of tests that determine what projects to poll.
+
+The jiri test poll flags are:
+ -manifest=
+   Name of the project manifest.
+
+ -color=true
+   Use color to format output.
+ -env=
+   specify an environment variable in the form: <var>=[<val>],...
+ -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
+   specify policies for merging environment variables
+ -profiles=v23:base,jiri
+   a comma separated list of profiles to use
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   the path, relative to JIRI_ROOT, that contains the profiles database.
+ -skip-profiles=false
+   if set, no profiles will be used
+ -target=<runtime.GOARCH>-<runtime.GOOS>
+   specifies a profile target in the following form: <arch>-<os>[@<version>]
+ -v=false
+   Print verbose output.
+
 Jiri test project - Run tests for a vanadium project
 
 Runs tests for a vanadium project that is by the remote URL specified as the
@@ -60,9 +93,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -113,9 +146,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -138,9 +171,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
diff --git a/jiri-test/internal/test/common.go b/jiri-test/internal/test/common.go
index 13ad764..4daf061 100644
--- a/jiri-test/internal/test/common.go
+++ b/jiri-test/internal/test/common.go
@@ -19,7 +19,7 @@
 var (
 	// The name of the profile database file to use. It is intended
 	// to be set via a command line flag.
-	ProfilesDBFilename = jiri.DefaultProfilesDBPath()
+	ProfilesDBFilename = jiri.ProfilesDBDir
 
 	// cleanGo is used to control whether the initTest function removes
 	// all stale Go object files and binaries. It is used to prevent the
diff --git a/jiri-test/internal/test/mojo.go b/jiri-test/internal/test/mojo.go
index 28c60b2..dc8db62 100644
--- a/jiri-test/internal/test/mojo.go
+++ b/jiri-test/internal/test/mojo.go
@@ -16,8 +16,17 @@
 	defaultMojoTestTimeout = 10 * time.Minute
 )
 
-// vanadiumMojoSyncbaseTest runs the tests for the Vanadium Mojo Syncbase
-// service.
+// vanadiumMojoDiscoveryTest runs the tests for the Vanadium Mojo Discovery service.
+func vanadiumMojoDiscoveryTest(jirix *jiri.X, testName string, _ ...Opt) (*test.Result, error) {
+	testDir := filepath.Join(jirix.Root, "release", "mojo", "discovery")
+	if r, err := runMakefileTest(jirix, testName, testDir, "test", nil, []string{"v23:mojo", "v23:dart"}, defaultMojoTestTimeout); err != nil {
+		return r, err
+	}
+	// For android, just make sure that everything builds.
+	return runMakefileTest(jirix, testName, testDir, "build", map[string]string{"ANDROID": "1"}, []string{"v23:mojo", "v23:android"}, defaultMojoTestTimeout)
+}
+
+// vanadiumMojoSyncbaseTest runs the tests for the Vanadium Mojo Syncbase service.
 func vanadiumMojoSyncbaseTest(jirix *jiri.X, testName string, _ ...Opt) (*test.Result, error) {
 	testDir := filepath.Join(jirix.Root, "release", "mojo", "syncbase")
 	return runMakefileTest(jirix, testName, testDir, "test", nil, []string{"v23:dart", "v23:mojo"}, defaultMojoTestTimeout)
diff --git a/jiri-test/internal/test/prod.go b/jiri-test/internal/test/prod.go
index 7dd9b19..0daa78a 100644
--- a/jiri-test/internal/test/prod.go
+++ b/jiri-test/internal/test/prod.go
@@ -219,7 +219,7 @@
 	s := jirix.NewSeq()
 	dir := filepath.Join(tmpdir, "credentials")
 	bin := filepath.Join(jirix.Root, "release", "go", "bin", "principal")
-	if err := s.Timeout(test.DefaultTimeout).Last(bin, "create", dir, "prod-services-tester"); err != nil {
+	if err := s.Timeout(test.DefaultTimeout).Last(bin, "create", "-with-passphrase=false", dir, "prod-services-tester"); err != nil {
 		fmt.Fprintf(jirix.Stderr(), "principal create failed: %v\n", err)
 		return "", err
 	}
diff --git a/jiri-test/internal/test/release.go b/jiri-test/internal/test/release.go
index 44bda1f..375b9cc 100644
--- a/jiri-test/internal/test/release.go
+++ b/jiri-test/internal/test/release.go
@@ -7,7 +7,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -44,6 +43,14 @@
 	defaultReleaseTestTimeout = time.Minute * 5
 	manifestRE                = regexp.MustCompile(`^devmgr/.*<manifest snapshotpath="manifest/(.*)">`)
 
+	toolsPackages = []string{
+		"v.io/x/ref/services/agent/gcreds/",
+		"v.io/x/ref/services/agent/vbecome/",
+		"v.io/x/ref/services/debug/debug/",
+		"v.io/x/ref/services/device/device/",
+		"v.io/x/devtools/vbinary/",
+	}
+
 	serviceBinaries = []string{
 		"applicationd",
 		"binaryd",
@@ -85,16 +92,16 @@
 	}
 }
 
-// buildVanadiumBinaries builds all vanadium binaries.
-func (u *updater) buildVanadiumBinaries() error {
+// buildBinaries builds binaries for the given package pattern.
+func (u *updater) buildBinaries(pkgs ...string) error {
 	s := u.jirix.NewSeq()
 	args := []string{
 		"jiri",
 		"go",
 		"install",
 		"-tags=leveldb",
-		"v.io/...",
 	}
+	args = append(args, pkgs...)
 	u.outputCmd(args)
 	return s.Last(args[0], args[1:]...)
 }
@@ -138,11 +145,13 @@
 func (u *updater) downloadReleaseBinaries(binDir string) error {
 	s := u.jirix.NewSeq()
 	args := []string{
+		u.bin("vbinary"),
 		"--release",
 		"download",
 		"--output-dir=" + binDir,
 	}
-	return s.Last(u.bin("vbinary"), args...)
+	u.outputCmd(args)
+	return s.Last(args[0], args[1:]...)
 }
 
 // checkReleaseCandidateStatus checks whether the "latest" file in
@@ -153,11 +162,10 @@
 	s := u.jirix.NewSeq()
 	args := []string{
 		"cat",
-		fmt.Sprintf("%s/latest"),
+		fmt.Sprintf("%s/latest", bucket),
 	}
 	var out bytes.Buffer
-	stdout := io.MultiWriter(u.jirix.Stdout(), &out)
-	if err := s.Capture(stdout, nil).Last("gsutil", args...); err != nil {
+	if err := s.Capture(&out, nil).Last("gsutil", args...); err != nil {
 		return "", err
 	}
 	t, err := time.Parse(rcTimeFormat, out.String())
@@ -168,6 +176,7 @@
 	if t.Year() != now.Year() || t.Month() != now.Month() || t.Day() != now.Day() {
 		return "", fmt.Errorf("Release candidate (%v) not done for today", t)
 	}
+	fmt.Fprintf(u.jirix.Stdout(), "Snapshot timestamp: %s\n", out.String())
 	return out.String(), nil
 }
 
@@ -383,7 +392,7 @@
 		step{
 			msg: "Prepare binaries",
 			fn: func() error {
-				if err := u.buildVanadiumBinaries(); err != nil {
+				if err := u.buildBinaries("v.io/..."); err != nil {
 					return err
 				}
 				return u.uploadVanadiumBinaries(rcTimestamp)
@@ -406,7 +415,7 @@
 
 // vanadiumReleaseProduction updates binaries of production cloud services and runs tests for them.
 func vanadiumReleaseProduction(jirix *jiri.X, testName string, opts ...Opt) (_ *test.Result, e error) {
-	cleanup, err := initTest(jirix, testName, []string{"base"})
+	cleanup, err := initTest(jirix, testName, []string{"v23:base"})
 	if err != nil {
 		return nil, newInternalError(err, "Init")
 	}
@@ -421,13 +430,20 @@
 	defer u.jirix.NewSeq().RemoveAll(binDir)
 
 	// Make sure we got a release candidate today.
-	rcTimestamp, err := u.checkReleaseCandidateStatus()
-	if err != nil {
-		return nil, newInternalError(err, "Check release candidate")
+	rcTimestamp := ""
+	if result, err := invoker(jirix, "Check release candidate status", func() error {
+		rcTimestamp, err = u.checkReleaseCandidateStatus()
+		return err
+	}); result != nil || err != nil {
+		return result, err
 	}
 
 	steps := []step{
 		step{
+			msg: "Prepare tools",
+			fn:  func() error { return u.buildBinaries(toolsPackages...) },
+		},
+		step{
 			msg: "Download release binaries",
 			fn:  func() error { return u.downloadReleaseBinaries(binDir) },
 		},
diff --git a/jiri-test/internal/test/release_kube.go b/jiri-test/internal/test/release_kube.go
new file mode 100644
index 0000000..cf7cd00
--- /dev/null
+++ b/jiri-test/internal/test/release_kube.go
@@ -0,0 +1,52 @@
+// 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 test
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"v.io/jiri"
+	"v.io/jiri/collect"
+	"v.io/x/devtools/internal/test"
+)
+
+func vanadiumReleaseKubeStaging(jirix *jiri.X, testName string, opts ...Opt) (_ *test.Result, e error) {
+	manifestPath := os.Getenv("SNAPSHOT_MANIFEST")
+	if manifestPath == "" {
+		return nil, fmt.Errorf("SNAPSHOT_MANIFEST environment variable not set")
+	}
+	return vanadiumReleaseKubeCommon(jirix, testName, "staging", filepath.Base(manifestPath))
+}
+
+func vanadiumReleaseKubeProduction(jirix *jiri.X, testName string, opts ...Opt) (_ *test.Result, e error) {
+	return vanadiumReleaseKubeCommon(jirix, testName, "production", "")
+}
+
+func vanadiumReleaseKubeCommon(jirix *jiri.X, testName, updateType, version string) (_ *test.Result, e error) {
+	cleanup, err := initTest(jirix, testName, []string{"v23:base"})
+	if err != nil {
+		return nil, newInternalError(err, "Init")
+	}
+	defer collect.Error(func() error { return cleanup() }, &e)
+
+	// Build and run vprodupdater.
+	s := jirix.NewSeq()
+	if err := s.Last("jiri", "go", "install", "v.io/infrastructure/vprodupdater/"); err != nil {
+		return nil, newInternalError(err, "Build vprodupdater")
+	}
+	vprodupdaterBin := filepath.Join(jirix.Root, "infrastructure", "go", "bin", "vprodupdater")
+	args := []string{
+		fmt.Sprintf("-type=%s", updateType),
+	}
+	if version != "" {
+		args = append(args, fmt.Sprintf("-tag=%s", version))
+	}
+	if err := s.Last(vprodupdaterBin, args...); err != nil {
+		return nil, newInternalError(err, "Run vprodupdater")
+	}
+	return &test.Result{Status: test.Passed}, nil
+}
diff --git a/jiri-test/internal/test/run.go b/jiri-test/internal/test/run.go
index b670b69..d9ec6b7 100644
--- a/jiri-test/internal/test/run.go
+++ b/jiri-test/internal/test/run.go
@@ -119,6 +119,7 @@
 	"vanadium-js-vdl":                         vanadiumJSVdl,
 	"vanadium-js-vdl-audit":                   vanadiumJSVdlAudit,
 	"vanadium-js-vom":                         vanadiumJSVom,
+	"vanadium-mojo-discovery-test":            vanadiumMojoDiscoveryTest,
 	"vanadium-mojo-syncbase-test":             vanadiumMojoSyncbaseTest,
 	"vanadium-mojo-v23proxy-unit-test":        vanadiumMojoV23ProxyUnitTest,
 	"vanadium-mojo-v23proxy-integration-test": vanadiumMojoV23ProxyIntegrationTest,
@@ -137,6 +138,8 @@
 	"vanadium-release-candidate":              vanadiumReleaseCandidate,
 	"vanadium-release-candidate-snapshot":     vanadiumReleaseCandidateSnapshot,
 	"vanadium-release-production":             vanadiumReleaseProduction,
+	"vanadium-release-kube-staging":           vanadiumReleaseKubeStaging,
+	"vanadium-release-kube-production":        vanadiumReleaseKubeProduction,
 	"vanadium-signup-github":                  vanadiumSignupGithub,
 	"vanadium-signup-github-new":              vanadiumSignupGithubNew,
 	"vanadium-signup-group":                   vanadiumSignupGroup,
diff --git a/jiri-test/test.go b/jiri-test/test.go
index 3de63f7..a844362 100644
--- a/jiri-test/test.go
+++ b/jiri-test/test.go
@@ -8,16 +8,20 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"runtime"
 	"strings"
 
 	"v.io/jiri"
 	"v.io/jiri/profiles/profilescmdline"
+	"v.io/jiri/project"
 	"v.io/jiri/tool"
 	"v.io/x/devtools/internal/test"
 	jiriTest "v.io/x/devtools/jiri-test/internal/test"
+	"v.io/x/devtools/tooldata"
 	"v.io/x/lib/cmdline"
+	"v.io/x/lib/set"
 )
 
 var (
@@ -48,7 +52,8 @@
 	cmdTestRun.Flags.StringVar(&mockTestFilePaths, "mock-file-paths", "", "Colon-separated file paths to read when testing presubmit test. This flag is only used when running presubmit end-to-end test.")
 	cmdTestRun.Flags.StringVar(&mockTestFileContents, "mock-file-contents", "", "Colon-separated file contents to check when testing presubmit test. This flag is only used when running presubmit end-to-end test.")
 	tool.InitializeRunFlags(&cmdTest.Flags)
-	profilescmdline.RegisterReaderFlags(&cmdTest.Flags, &readerFlags, jiri.DefaultProfilesDBPath())
+	tool.InitializeProjectFlags(&cmdProjectPoll.Flags)
+	profilescmdline.RegisterReaderFlags(&cmdTest.Flags, &readerFlags, jiri.ProfilesDBDir)
 }
 
 // cmdTest represents the "jiri test" command.
@@ -56,7 +61,7 @@
 	Name:     "test",
 	Short:    "Manage vanadium tests",
 	Long:     "Manage vanadium tests.",
-	Children: []*cmdline.Command{cmdTestProject, cmdTestRun, cmdTestList},
+	Children: []*cmdline.Command{cmdProjectPoll, cmdTestProject, cmdTestRun, cmdTestList},
 }
 
 // cmdTestProject represents the "jiri test project" command.
@@ -121,6 +126,68 @@
 	return nil
 }
 
+// cmdProjectPoll represents the "jiri project poll" command.
+var cmdProjectPoll = &cmdline.Command{
+	Runner: jiri.RunnerFunc(runProjectPoll),
+	Name:   "poll",
+	Short:  "Poll existing jiri projects",
+	Long: `
+Poll jiri projects that can affect the outcome of the given tests
+and report whether any new changes in these projects exist. If no
+tests are specified, all projects are polled by default.
+`,
+	ArgsName: "<test ...>",
+	ArgsLong: "<test ...> is a list of tests that determine what projects to poll.",
+}
+
+// runProjectPoll generates a description of changes that exist
+// remotely but do not exist locally.
+func runProjectPoll(jirix *jiri.X, args []string) error {
+	projectSet := map[string]struct{}{}
+	if len(args) > 0 {
+		config, err := tooldata.LoadConfig(jirix)
+		if err != nil {
+			return err
+		}
+		// Compute a map from tests to projects that can change the
+		// outcome of the test.
+		testProjects := map[string][]string{}
+		for _, project := range config.Projects() {
+			for _, test := range config.ProjectTests([]string{project}) {
+				testProjects[test] = append(testProjects[test], project)
+			}
+		}
+		for _, arg := range args {
+			projects, ok := testProjects[arg]
+			if !ok {
+				return fmt.Errorf("failed to find any projects for test %q", arg)
+			}
+			set.String.Union(projectSet, set.String.FromSlice(projects))
+		}
+	}
+	update, err := project.PollProjects(jirix, projectSet)
+	if err != nil {
+		return err
+	}
+
+	// Remove projects with empty changes.
+	for project := range update {
+		if changes := update[project]; len(changes) == 0 {
+			delete(update, project)
+		}
+	}
+
+	// Print update if it is not empty.
+	if len(update) > 0 {
+		bytes, err := json.MarshalIndent(update, "", "  ")
+		if err != nil {
+			return fmt.Errorf("MarshalIndent() failed: %v", err)
+		}
+		fmt.Fprintf(jirix.Stdout(), "%s\n", bytes)
+	}
+	return nil
+}
+
 func optsFromFlags() (opts []jiriTest.Opt) {
 	if partFlag >= 0 {
 		opt := jiriTest.PartOpt(partFlag)
diff --git a/jiri-v23-profile/.api b/jiri-v23-profile/.api
deleted file mode 100644
index ad99453..0000000
--- a/jiri-v23-profile/.api
+++ /dev/null
@@ -1 +0,0 @@
-pkg main, const DefaultDBFilename ideal-string
diff --git a/jiri-v23-profile/profile.go b/jiri-v23-profile/profile.go
index 95d2ba9..557d24e 100644
--- a/jiri-v23-profile/profile.go
+++ b/jiri-v23-profile/profile.go
@@ -2,57 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// The following enables go generate to generate the doc.go file.
-//go:generate go run $JIRI_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go -env=CMDLINE_PREFIX=jiri .
-
 package main
 
-import (
-	"v.io/jiri/profiles/profilescmdline"
-	"v.io/jiri/tool"
-	"v.io/x/lib/cmdline"
-
-	// Add profile manager implementations here.
-	"v.io/x/devtools/jiri-profile-v23/android_profile"
-	"v.io/x/devtools/jiri-profile-v23/base_profile"
-	"v.io/x/devtools/jiri-profile-v23/dart_profile"
-	"v.io/x/devtools/jiri-profile-v23/go_profile"
-	"v.io/x/devtools/jiri-profile-v23/java_profile"
-	"v.io/x/devtools/jiri-profile-v23/mojo_dev_profile"
-	"v.io/x/devtools/jiri-profile-v23/mojo_profile"
-	"v.io/x/devtools/jiri-profile-v23/nacl_profile"
-	"v.io/x/devtools/jiri-profile-v23/nodejs_profile"
-	"v.io/x/devtools/jiri-profile-v23/syncbase_profile"
-	"v.io/x/devtools/jiri-profile-v23/terraform_profile"
-)
-
-// commandLineDriver implements the command line for the 'v23-profile'
-// subcommand.
-var commandLineDriver = &cmdline.Command{
-	Name:  "v23-profile",
-	Short: "Manage profiles",
-	Long:  profilescmdline.HelpMsg(),
-}
-
-// DefaultDBFilename is the default filename used for the profiles database
-// by the jiri-v23-profile subcommand.
-const DefaultDBFilename = ".jiri_v23_profiles"
+import "os"
 
 func main() {
-	android_profile.Register("", "android")
-	base_profile.Register("", "base")
-	dart_profile.Register("", "dart")
-	go_profile.Register("", "go")
-	java_profile.Register("", "java")
-	mojo_profile.Register("", "mojo")
-	mojo_dev_profile.Register("", "mojodev")
-	nacl_profile.Register("", "nacl")
-	nodejs_profile.Register("", "nodejs")
-	syncbase_profile.Register("", "syncbase")
-	terraform_profile.Register("", "terraform")
-
-	profilescmdline.RegisterManagementCommands(commandLineDriver, false, "", DefaultDBFilename, "profiles")
-	profilescmdline.RegisterReaderCommands(commandLineDriver, DefaultDBFilename)
-	tool.InitializeRunFlags(&commandLineDriver.Flags)
-	cmdline.Main(commandLineDriver)
+	os.Exit(1)
 }
diff --git a/jiridoc/doc.go b/jiridoc/doc.go
index 1a7d089..a9b649c 100644
--- a/jiridoc/doc.go
+++ b/jiridoc/doc.go
@@ -34,7 +34,7 @@
                 environment
    swift        Compile the Swift framework
    test         Manage vanadium tests
-   v23-profile  Manage profiles
+   v23-profile  No description available
 
 The jiri additional help topics are:
    filesystem  Description of jiri file system layout
@@ -309,9 +309,9 @@
    	Note: if no profiles are specified then the requested field will be displayed for all profiles.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -344,9 +344,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -372,7 +372,7 @@
    specify an environment variable in the form: <var>=[<val>],...
  -force=false
    force install the profile even if it is already installed
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
@@ -396,7 +396,7 @@
 The jiri profile uninstall flags are:
  -all-targets=false
    apply to all targets for the specified profile(s)
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
@@ -418,7 +418,7 @@
 <profiles> is a list of profiles to update, if omitted all profiles are updated.
 
 The jiri profile update flags are:
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
@@ -443,7 +443,7 @@
 The jiri profile cleanup flags are:
  -gc=false
    uninstall profile targets that are older than the current default
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -profiles-dir=.jiri_root/profiles
    the directory, relative to JIRI_ROOT, that profiles are installed in
@@ -800,9 +800,9 @@
    Name of the project manifest.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -837,9 +837,9 @@
    Name of the project manifest.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -870,9 +870,9 @@
    Name of the project manifest.
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -987,9 +987,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1017,9 +1017,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1045,9 +1045,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1075,9 +1075,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1347,9 +1347,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1436,6 +1436,7 @@
    jiri test [flags] <command>
 
 The jiri test commands are:
+   poll        Poll existing jiri projects
    project     Run tests for a vanadium project
    run         Run vanadium tests
    list        List vanadium tests
@@ -1447,9 +1448,41 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
+   the path, relative to JIRI_ROOT, that contains the profiles database.
+ -skip-profiles=false
+   if set, no profiles will be used
+ -target=<runtime.GOARCH>-<runtime.GOOS>
+   specifies a profile target in the following form: <arch>-<os>[@<version>]
+ -v=false
+   Print verbose output.
+
+Jiri test poll - Poll existing jiri projects
+
+Poll jiri projects that can affect the outcome of the given tests and report
+whether any new changes in these projects exist. If no tests are specified, all
+projects are polled by default.
+
+Usage:
+   jiri test poll [flags] <test ...>
+
+<test ...> is a list of tests that determine what projects to poll.
+
+The jiri test poll flags are:
+ -manifest=
+   Name of the project manifest.
+
+ -color=true
+   Use color to format output.
+ -env=
+   specify an environment variable in the form: <var>=[<val>],...
+ -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
+   specify policies for merging environment variables
+ -profiles=v23:base,jiri
+   a comma separated list of profiles to use
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1477,9 +1510,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1530,9 +1563,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1555,9 +1588,9 @@
    specify an environment variable in the form: <var>=[<val>],...
  -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
    specify policies for merging environment variables
- -profiles=base,jiri
+ -profiles=v23:base,jiri
    a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
+ -profiles-db=$JIRI_ROOT/.jiri_root/profile_db
    the path, relative to JIRI_ROOT, that contains the profiles database.
  -skip-profiles=false
    if set, no profiles will be used
@@ -1566,300 +1599,7 @@
  -v=false
    Print verbose output.
 
-Jiri v23-profile - Manage profiles
-
-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. A profile is a named
-collection of software required for a given system component or application.
-Current example profiles include 'syncbase' which consists of the leveldb and
-snappy libraries or 'android' which consists of all of the android components
-and downloads needed to build android applications. Profiles are built for
-specific targets.
-
-Targets
-
-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. 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. A lexicographically orderd set of supported versions, one of which is
-designated as the default.
-
-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.
-
-Targets are versioned and multiple versions may be installed and used
-simultaneously. Versions are ordered lexicographically and each target specifies
-a 'default' version to be used when a specific version is not explicitly
-requested. A request to 'upgrade' the profile will result in the installation of
-the default version of the targets currently installed if that default version
-is not already installed.
-
-The Supported Commands
-
-Profiles, or more correctly, targets for specific profiles may be installed 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) and the default
-version for that target. Once a profile is installed it may be referred to by
-its tag for subsequent removals.
-
-The are also update and cleanup commands. Update installs the default version of
-the requested profile or for all profiles for the already installed targets.
-Cleanup will uninstall targets whose version is older than the default.
-
-Finally, there are commands to list the available and installed profiles and to
-access the environment variables specified and stored in each profile
-installation and a command (recreate) to generate a list of commands that can be
-run to recreate the currently installed profiles.
-
-The Profiles Database
-
-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 database via the
-profiles package. The profile command line tools support displaying the 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
-
-Profiles are intended to be provided as go packages that register themselves
-with the profile command line tools via the *v.io/jiri/profiles* package. They
-must implement the interfaces defined by that package and be imported (e.g.
-import _ "myprofile") by the command line tools that are to use them.
-
-Usage:
-   jiri v23-profile [flags] <command>
-
-The jiri v23-profile commands are:
-   install     Install the given profiles
-   uninstall   Uninstall the given profiles
-   update      Install the latest default version of the given profiles
-   cleanup     Cleanup the locally installed profiles
-   available   List the available profiles
-   list        List available or installed profiles
-   env         Display profile environment variables
-
-The jiri v23-profile flags are:
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri v23-profile install - Install the given profiles
-
-Install the given profiles.
-
-Usage:
-   jiri v23-profile install [flags] <profiles>
-
-<profiles> is a list of profiles to install.
-
-The jiri v23-profile install flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -force=false
-   force install the profile even if it is already installed
- -go.sysroot-image=
-   sysroot image for cross compiling to the currently specified target
- -go.sysroot-image-dirs-to-use=/lib:/usr/lib:/usr/include
-   a colon separated list of directories to use from the sysroot image
- -mojodev.dir=
-   Path of mojo repo checkout.
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
-
- -color=true
-   Use color to format output.
- -v=false
-   Print verbose output.
-
-Jiri v23-profile uninstall - Uninstall the given profiles
-
-Uninstall the given profiles.
-
-Usage:
-   jiri v23-profile uninstall [flags] <profiles>
-
-<profiles> is a list of profiles to uninstall.
-
-The jiri v23-profile uninstall flags are:
- -all-targets=false
-   apply to all targets for the specified profile(s)
- -go.sysroot-image=
-   sysroot image for cross compiling to the currently specified target
- -go.sysroot-image-dirs-to-use=/lib:/usr/lib:/usr/include
-   a colon separated list of directories to use from the sysroot image
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile update - Install the latest default version of the given
-profiles
-
-Install the latest default version of the given profiles.
-
-Usage:
-   jiri v23-profile update [flags] <profiles>
-
-<profiles> is a list of profiles to update, if omitted all profiles are updated.
-
-The jiri v23-profile update flags are:
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile cleanup - Cleanup the locally installed profiles
-
-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.
-
-Usage:
-   jiri v23-profile cleanup [flags] <profiles>
-
-<profiles> is a list of profiles to cleanup, if omitted all profiles are
-cleaned.
-
-The jiri v23-profile cleanup flags are:
- -gc=false
-   uninstall profile targets that are older than the current default
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -profiles-dir=profiles
-   the directory, relative to JIRI_ROOT, that profiles are installed in
- -rewrite-profiles-db=false
-   rewrite the profiles database to use the latest schema version
- -rm-all=false
-   remove profiles database and all profile generated output files.
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile available - List the available profiles
-
-List the available profiles.
-
-Usage:
-   jiri v23-profile available [flags]
-
-The jiri v23-profile available flags are:
- -describe=false
-   print the profile description
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile list - List available or installed profiles
-
-List available or installed profiles.
-
-Usage:
-   jiri v23-profile list [flags] [<profiles>]
-
-<profiles> is a list of profiles to list, defaulting to all profiles if none are
-specifically requested. List can also be used to test for the presence of a
-specific target for the requested profiles. If the target is not installed, it
-will exit with an error.
-
-The jiri v23-profile list flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -info=
-   The following fields for use with -info are available:
-   	SchemaVersion - the version of the profiles implementation.
-   	DBPath - the path for the profiles database.
-   	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.Root - the root directory of the requested profile.
-   	Profile.Name - the qualified name of the profile.
-   	Profile.Installer - the name of the profile installer.
-   	Profile.DBPath - the path to the database file for this profile.
-   	Note: if no profiles are specified then the requested field will be displayed for all profiles.
- -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
-   specify policies for merging environment variables
- -profiles=base,jiri
-   a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -skip-profiles=false
-   if set, no profiles will be used
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
-
-Jiri v23-profile env - Display profile environment variables
-
-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.
-
-Usage:
-   jiri v23-profile env [flags] [<environment variable names>]
-
-[<environment variable names>] is an optional list of environment variables to
-display
-
-The jiri v23-profile env flags are:
- -env=
-   specify an environment variable in the form: <var>=[<val>],...
- -merge-policies=+CCFLAGS,+CGO_CFLAGS,+CGO_CXXFLAGS,+CGO_LDFLAGS,+CXXFLAGS,GOARCH,GOOS,GOPATH:,^GOROOT*,+LDFLAGS,:PATH,VDLPATH:
-   specify policies for merging environment variables
- -profiles=base,jiri
-   a comma separated list of profiles to use
- -profiles-db=$JIRI_ROOT/.jiri_v23_profiles
-   the path, relative to JIRI_ROOT, that contains the profiles database.
- -skip-profiles=false
-   if set, no profiles will be used
- -target=<runtime.GOARCH>-<runtime.GOOS>
-   specifies a profile target in the following form: <arch>-<os>[@<version>]
- -v=false
-   print more detailed information
-
- -color=true
-   Use color to format output.
+Jiri v23-profile - No description available
 
 Jiri filesystem - Description of jiri file system layout
 
diff --git a/madb/clear_data.go b/madb/clear_data.go
index d058afb..4b9aa4a 100644
--- a/madb/clear_data.go
+++ b/madb/clear_data.go
@@ -22,6 +22,9 @@
 	Long: `
 Clears your app data from all devices.
 
+To specify which user's data should be cleared, use 'madb user set' command to set the default user
+ID for that device.  (See 'madb help user' for more details.)
+
 `,
 	ArgsName: "[<application_id>]",
 	ArgsLong: `
@@ -53,9 +56,16 @@
 		appID := args[0]
 
 		// TODO(youngseokyoon): maybe do something equivalent for flutter?
-		cmdArgs := []string{"-s", d.Serial, "shell", "pm", "clear", appID}
+		cmdArgs := []string{"-s", d.Serial, "shell", "pm", "clear"}
+
+		// Specify the user ID if applicable.
+		if d.UserID != "" {
+			cmdArgs = append(cmdArgs, "--user", d.UserID)
+		}
+		cmdArgs = append(cmdArgs, appID)
+
 		cmd := sh.Cmd("adb", cmdArgs...)
-		return runGoshCommandForDevice(cmd, d)
+		return runGoshCommandForDevice(cmd, d, true)
 	}
 
 	return fmt.Errorf("No arguments are provided and failed to extract the id from the build scripts.")
diff --git a/madb/doc.go b/madb/doc.go
index 21df511..601b647 100644
--- a/madb/doc.go
+++ b/madb/doc.go
@@ -22,6 +22,7 @@
    start       Launch your app on all devices
    stop        Stop your app on all devices
    uninstall   Uninstall your app from all devices
+   user        Manage default user settings for each device
    help        Display help for commands or topics
 
 The madb flags are:
@@ -45,6 +46,10 @@
 
 Clears your app data from all devices.
 
+To specify which user's data should be cleared, use 'madb user set' command to
+set the default user ID for that device.  (See 'madb help user' for more
+details.)
+
 Usage:
    madb clear-data [flags] [<application_id>]
 
@@ -148,8 +153,8 @@
 Sets a human-friendly nickname that can be used when specifying the device in
 any madb commands.
 
-The device serial can be obtain using the 'adb devices -l' command. For example,
-consider the following example output:
+The device serial can be obtained using the 'adb devices -l' command. For
+example, consider the following example output:
 
     HT4BVWV00023           device usb:3-3.4.2 product:volantisg model:Nexus_9 device:flounder_lte
 
@@ -173,7 +178,7 @@
    madb name set [flags] <device_serial> <nickname>
 
 <device_serial> is a device serial (e.g., 'HT4BVWV00023') or an alternative
-device specifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
+device qualifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
 <nickname> is an alpha-numeric string with no special characters or spaces.
 
 The madb name set flags are:
@@ -249,6 +254,10 @@
 
 Launches your app on all devices.
 
+To run your app as a specific user on a particular device, use 'madb user set'
+command to set the default user ID for that device.  (See 'madb help user' for
+more details.)
+
 Usage:
    madb start [flags] [<application_id> <activity_name>]
 
@@ -307,6 +316,10 @@
 
 Stops your app on all devices.
 
+To stop your app for a specific user on a particular device, use 'madb user set'
+command to set the default user ID for that device.  (See 'madb help user' for
+more details.)
+
 Usage:
    madb stop [flags] [<application_id>]
 
@@ -354,6 +367,10 @@
 
 Uninstall your app from all devices.
 
+To uninstall your app for a specific user on a particular device, use 'madb user
+set' command to set the default user ID for that device.  (See 'madb help user'
+for more details.)
+
 Usage:
    madb uninstall [flags] [<application_id>]
 
@@ -396,6 +413,170 @@
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1.  Command will be run only on specified devices.
 
+Madb user - Manage default user settings for each device
+
+Manages default user settings for each device.
+
+An Android device can have multiple user accounts, and each user account has a
+numeric ID associated with it.  Certain adb commands accept '--user <user_id>'
+as a parameter to allow specifying which of the Android user account should be
+used when running the command.  The default behavior when the user ID is not
+provided varies by the adb command being run.
+
+Some madb commands internally run these adb commands which accept the '--user'
+flag.  You can let madb use different user IDs for different devices by storing
+the default user ID for each device using 'madb user set' command.  If the
+default user ID is not set for a particular device, madb will not provide the
+'--user' flag to the underlying adb command, and the current user will be used
+for that device as a result.
+
+Below is the list of madb commands which are affected by the default user ID
+settings:
+
+    madb clear-data
+    madb start
+    madb stop
+    madb uninstall
+
+For more details on how to obtain the user ID from an Android device, see 'madb
+user help set'.
+
+NOTE: Device specifier flags (-d, -e, -n) are ignored in all 'madb name'
+commands.
+
+Usage:
+   madb user [flags] <command>
+
+The madb user commands are:
+   set         Set a default user ID to be used for the given device.
+   unset       Unset the default user ID set by the 'madb user set' command.
+   list        List all the existing default user IDs.
+   clear-all   Clear all the existing default user settings.
+
+The madb user flags are:
+ -d=false
+   Restrict the command to only run on real devices.
+ -e=false
+   Restrict the command to only run on emulators.
+ -n=
+   Comma-separated device serials, qualifiers, device indices (e.g., '@1',
+   '@2'), or nicknames (set by 'madb name').  A device index is specified by an
+   '@' sign followed by the index of the device in the output of 'adb devices'
+   command, starting from 1.  Command will be run only on specified devices.
+
+Madb user set
+
+Sets a default user ID to be used for the specified device, when there are
+multiple user accounts on a single device.
+
+The user IDs can be obtained using the 'adb [<device_serial>] shell pm list
+users' command. Alternatively, you can use 'madb exec' if you want to specify
+the device with a nickname. For example, running the following command:
+
+    madb -n=MyPhone exec shell pm list users
+
+will list the available users and their IDs on the MyPhone device. Consider the
+following example output:
+
+    [MyPhone]       Users:
+    [MyPhone]               UserInfo{0:John Doe:13} running
+    [MyPhone]               UserInfo{10:Work profile:30} running
+
+There are two available users, "John Doe" and "Work profile". Each user is
+assigned a "user ID", which appears on the left of the user name. In this case,
+the user ID of "John Doe" is "0", and the user ID of the "Work profile" is "10".
+
+To use the "Work profile" as the default user when running madb commands on this
+device, run the following command:
+
+    madb user set MyPhone 10
+
+and then madb will use "Work profile" as the default user for device "MyPhone"
+in any of the subsequence madb commands.
+
+Usage:
+   madb user set [flags] <device_serial> <user_id>
+
+<device_serial> is the unique serial number for the device, which can be
+obtained from 'adb devices'. <user_id> is one of the user IDs obtained from 'adb
+shell pm list users' command.
+
+The madb user set flags are:
+ -d=false
+   Restrict the command to only run on real devices.
+ -e=false
+   Restrict the command to only run on emulators.
+ -n=
+   Comma-separated device serials, qualifiers, device indices (e.g., '@1',
+   '@2'), or nicknames (set by 'madb name').  A device index is specified by an
+   '@' sign followed by the index of the device in the output of 'adb devices'
+   command, starting from 1.  Command will be run only on specified devices.
+
+Madb user unset
+
+Unsets the default user ID assigned by the 'madb user set' command for the
+specified device.
+
+Running this command without any device specifiers will unset the default users
+only for the currently available devices and emulators, while keeping the
+default user IDs for the other devices.
+
+Usage:
+   madb user unset [flags] <device_serial>
+
+<device_serial> is the unique serial number for the device, which can be
+obtained from 'adb devices'.
+
+The madb user unset flags are:
+ -d=false
+   Restrict the command to only run on real devices.
+ -e=false
+   Restrict the command to only run on emulators.
+ -n=
+   Comma-separated device serials, qualifiers, device indices (e.g., '@1',
+   '@2'), or nicknames (set by 'madb name').  A device index is specified by an
+   '@' sign followed by the index of the device in the output of 'adb devices'
+   command, starting from 1.  Command will be run only on specified devices.
+
+Madb user list
+
+Lists all the currently stored default user IDs for devices.
+
+Usage:
+   madb user list [flags]
+
+The madb user list flags are:
+ -d=false
+   Restrict the command to only run on real devices.
+ -e=false
+   Restrict the command to only run on emulators.
+ -n=
+   Comma-separated device serials, qualifiers, device indices (e.g., '@1',
+   '@2'), or nicknames (set by 'madb name').  A device index is specified by an
+   '@' sign followed by the index of the device in the output of 'adb devices'
+   command, starting from 1.  Command will be run only on specified devices.
+
+Madb user clear-all
+
+Clears all the currently stored default user IDs for devices.
+
+This command clears the default user IDs regardless of whether the device is
+currently connected or not.
+
+Usage:
+   madb user clear-all [flags]
+
+The madb user clear-all flags are:
+ -d=false
+   Restrict the command to only run on real devices.
+ -e=false
+   Restrict the command to only run on emulators.
+ -n=
+   Comma-separated device serials, qualifiers, device indices (e.g., '@1',
+   '@2'), or nicknames (set by 'madb name').  A device index is specified by an
+   '@' sign followed by the index of the device in the output of 'adb devices'
+   command, starting from 1.  Command will be run only on specified devices.
+
 Madb help - Display help for commands or topics
 
 Help with no args displays the usage of the parent command.
diff --git a/madb/exec.go b/madb/exec.go
index 72c0b56..42b182b 100644
--- a/madb/exec.go
+++ b/madb/exec.go
@@ -38,5 +38,5 @@
 
 	cmdArgs := append([]string{"-s", d.Serial}, args...)
 	cmd := sh.Cmd("adb", cmdArgs...)
-	return runGoshCommandForDevice(cmd, d)
+	return runGoshCommandForDevice(cmd, d, false)
 }
diff --git a/madb/madb.go b/madb/madb.go
index 7078841..5b1d77d 100644
--- a/madb/madb.go
+++ b/madb/madb.go
@@ -9,6 +9,7 @@
 
 import (
 	"bytes"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -58,9 +59,17 @@
 }
 
 var cmdMadb = &cmdline.Command{
-	Children: []*cmdline.Command{cmdMadbClearData, cmdMadbExec, cmdMadbName, cmdMadbStart, cmdMadbStop, cmdMadbUninstall},
-	Name:     "madb",
-	Short:    "Multi-device Android Debug Bridge",
+	Children: []*cmdline.Command{
+		cmdMadbClearData,
+		cmdMadbExec,
+		cmdMadbName,
+		cmdMadbStart,
+		cmdMadbStop,
+		cmdMadbUninstall,
+		cmdMadbUser,
+	},
+	Name:  "madb",
+	Short: "Multi-device Android Debug Bridge",
 	Long: `
 Multi-device Android Debug Bridge
 
@@ -97,6 +106,7 @@
 	Qualifiers []string
 	Nickname   string
 	Index      int
+	UserID     string
 }
 
 // Returns the display name which is intended to be used as the console output prefix.
@@ -110,23 +120,28 @@
 }
 
 // Runs "adb devices -l" command, and parses the result to get all the device serial numbers.
-func getDevices(nicknameFile string) ([]device, error) {
+func getDevices(nicknameFile string, userFile string) ([]device, error) {
 	sh := gosh.NewShell(nil)
 	defer sh.Cleanup()
 
 	output := sh.Cmd("adb", "devices", "-l").Stdout()
 
-	nsm, err := readNicknameSerialMap(nicknameFile)
+	nicknameSerialMap, err := readMapFromFile(nicknameFile)
 	if err != nil {
 		return nil, err
 	}
 
-	return parseDevicesOutput(output, nsm)
+	serialUserMap, err := readMapFromFile(userFile)
+	if err != nil {
+		return nil, err
+	}
+
+	return parseDevicesOutput(output, nicknameSerialMap, serialUserMap)
 }
 
 // Parses the output generated from "adb devices -l" command and return the list of device serial numbers
 // Devices that are currently offline are excluded from the returned list.
-func parseDevicesOutput(output string, nsm map[string]string) ([]device, error) {
+func parseDevicesOutput(output string, nicknameSerialMap map[string]string, serialUserMap map[string]string) ([]device, error) {
 	lines := strings.Split(output, "\n")
 
 	result := []device{}
@@ -161,7 +176,7 @@
 		// Determine whether there is a nickname defined for this device,
 		// so that the console output prefix can display the nickname instead of the serial.
 	NSMLoop:
-		for nickname, serial := range nsm {
+		for nickname, serial := range nicknameSerialMap {
 			if d.Serial == serial {
 				d.Nickname = nickname
 				break
@@ -175,6 +190,11 @@
 			}
 		}
 
+		// Determine whether there is a default user ID set by 'madb user'.
+		if userID, ok := serialUserMap[d.Serial]; ok {
+			d.UserID = userID
+		}
+
 		result = append(result, d)
 	}
 
@@ -189,7 +209,12 @@
 		return nil, err
 	}
 
-	allDevices, err := getDevices(nicknameFile)
+	userFile, err := getDefaultUserFilePath()
+	if err != nil {
+		return nil, err
+	}
+
+	allDevices, err := getDevices(nicknameFile, userFile)
 	if err != nil {
 		return nil, err
 	}
@@ -369,9 +394,16 @@
 	return nil
 }
 
-func runGoshCommandForDevice(cmd *gosh.Cmd, d device) error {
-	stdout := textutil.PrefixLineWriter(os.Stdout, "["+d.displayName()+"]\t")
-	stderr := textutil.PrefixLineWriter(os.Stderr, "["+d.displayName()+"]\t")
+func runGoshCommandForDevice(cmd *gosh.Cmd, d device, printUserID bool) error {
+	var prefix string
+	if printUserID && d.UserID != "" {
+		prefix = "[" + d.displayName() + ":" + d.UserID + "]\t"
+	} else {
+		prefix = "[" + d.displayName() + "]\t"
+	}
+
+	stdout := textutil.PrefixLineWriter(os.Stdout, prefix)
+	stderr := textutil.PrefixLineWriter(os.Stderr, prefix)
 	cmd.AddStdoutWriter(stdout)
 	cmd.AddStderrWriter(stderr)
 	cmd.Run()
@@ -574,3 +606,68 @@
 	ids = projectIds{lines[0], lines[1]}
 	return
 }
+
+// readMapFromFile reads the provided file and reconstructs the string => string map.
+// When the file does not exist, it returns an empty map (instead of nil), so that callers can safely add new entries.
+func readMapFromFile(filename string) (map[string]string, error) {
+	result := make(map[string]string)
+
+	// The file may not exist or be empty when there are no stored data.
+	if stat, err := os.Stat(filename); os.IsNotExist(err) || (err == nil && stat.Size() == 0) {
+		return result, nil
+	}
+
+	f, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	decoder := json.NewDecoder(f)
+
+	// Decoding might fail when the file is somehow corrupted, or when the schema is updated.
+	// In such cases, move on after resetting the cache file instead of exiting the app.
+	if err := decoder.Decode(&result); err != nil {
+		fmt.Fprintf(os.Stderr, "WARNING: Could not decode the file: %q.  Resetting the file.\n", err)
+		if err := os.Remove(f.Name()); err != nil {
+			return nil, err
+		}
+
+		return make(map[string]string), nil
+	}
+
+	return result, nil
+}
+
+// writeMapToFile takes a string => string map and writes it into the provided file name.
+func writeMapToFile(data map[string]string, filename string) error {
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	encoder := json.NewEncoder(f)
+	return encoder.Encode(data)
+}
+
+type pathProvider func() (string, error)
+
+// subCommandRunnerWithFilepath is an adapter that turns the madb name/user subcommand functions into cmdline.Runners.
+type subCommandRunnerWithFilepath struct {
+	subCmd func(*cmdline.Env, []string, string) error
+	pp     pathProvider
+}
+
+var _ cmdline.Runner = (*subCommandRunnerWithFilepath)(nil)
+
+// Run implements the cmdline.Runner interface by providing the default name file path
+// as the third string argument of the underlying run function.
+func (f subCommandRunnerWithFilepath) Run(env *cmdline.Env, args []string) error {
+	p, err := f.pp()
+	if err != nil {
+		return err
+	}
+
+	return f.subCmd(env, args, p)
+}
diff --git a/madb/madb_test.go b/madb/madb_test.go
index 6c4ec79..daf797f 100644
--- a/madb/madb_test.go
+++ b/madb/madb_test.go
@@ -32,7 +32,7 @@
 
 `
 
-	got, err := parseDevicesOutput(output, nil)
+	got, err := parseDevicesOutput(output, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
@@ -44,6 +44,7 @@
 			Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
 			Nickname:   "",
 			Index:      1,
+			UserID:     "",
 		},
 		device{
 			Serial:     "emulator-5554",
@@ -51,6 +52,7 @@
 			Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
 			Nickname:   "",
 			Index:      2,
+			UserID:     "",
 		},
 	}
 
@@ -63,7 +65,7 @@
 
 `
 
-	got, err = parseDevicesOutput(output, nil)
+	got, err = parseDevicesOutput(output, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
@@ -77,7 +79,7 @@
 deviceid02       device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
 
 `
-	got, err = parseDevicesOutput(output, nil)
+	got, err = parseDevicesOutput(output, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
@@ -89,6 +91,7 @@
 			Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
 			Nickname:   "",
 			Index:      2,
+			UserID:     "",
 		},
 	}
 
@@ -103,12 +106,16 @@
 
 	`
 
-	nsm := map[string]string{
+	nicknameSerialMap := map[string]string{
 		"MyPhone": "deviceid01",
 		"ARMv7":   "model:sdk_phone_armv7",
 	}
 
-	got, err = parseDevicesOutput(output, nsm)
+	serialUserMap := map[string]string{
+		"deviceid01": "10",
+	}
+
+	got, err = parseDevicesOutput(output, nicknameSerialMap, serialUserMap)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
@@ -120,6 +127,7 @@
 			Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
 			Nickname:   "MyPhone",
 			Index:      1,
+			UserID:     "10",
 		},
 		device{
 			Serial:     "emulator-5554",
@@ -127,6 +135,7 @@
 			Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
 			Nickname:   "ARMv7",
 			Index:      2,
+			UserID:     "",
 		},
 	}
 
@@ -143,6 +152,7 @@
 		Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
 		Nickname:   "MyPhone",
 		Index:      1,
+		UserID:     "",
 	}
 
 	d2 := device{
@@ -151,6 +161,7 @@
 		Qualifiers: []string{"usb:3-3.4.1", "product:volantisg", "model:Nexus_9", "device:flounder_lte"},
 		Nickname:   "",
 		Index:      2,
+		UserID:     "",
 	}
 
 	e1 := device{
@@ -159,6 +170,7 @@
 		Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
 		Nickname:   "ARMv7",
 		Index:      3,
+		UserID:     "",
 	}
 
 	d3 := device{
@@ -167,6 +179,7 @@
 		Qualifiers: []string{"usb:3-3.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
 		Nickname:   "SecondPhone",
 		Index:      4,
+		UserID:     "",
 	}
 
 	e2 := device{
@@ -175,6 +188,7 @@
 		Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
 		Nickname:   "",
 		Index:      5,
+		UserID:     "",
 	}
 
 	allDevices := []device{d1, d2, e1, d3, e2}
diff --git a/madb/name.go b/madb/name.go
index b5fc2b4..3fce437 100644
--- a/madb/name.go
+++ b/madb/name.go
@@ -5,7 +5,6 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -27,14 +26,14 @@
 }
 
 var cmdMadbNameSet = &cmdline.Command{
-	Runner: runnerFuncWithFilepath(runMadbNameSet),
+	Runner: subCommandRunnerWithFilepath{runMadbNameSet, getDefaultNameFilePath},
 	Name:   "set",
 	Short:  "Set a nickname to be used in place of the device serial.",
 	Long: `
 Sets a human-friendly nickname that can be used when specifying the device in
 any madb commands.
 
-The device serial can be obtain using the 'adb devices -l' command.
+The device serial can be obtained using the 'adb devices -l' command.
 For example, consider the following example output:
 
     HT4BVWV00023           device usb:3-3.4.2 product:volantisg model:Nexus_9 device:flounder_lte
@@ -57,7 +56,7 @@
 `,
 	ArgsName: "<device_serial> <nickname>",
 	ArgsLong: `
-<device_serial> is a device serial (e.g., 'HT4BVWV00023') or an alternative device specifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
+<device_serial> is a device serial (e.g., 'HT4BVWV00023') or an alternative device qualifier (e.g., 'usb:3-3.4.2') obtained from 'adb devices -l' command
 <nickname> is an alpha-numeric string with no special characters or spaces.
 `,
 }
@@ -77,30 +76,30 @@
 		return env.UsageErrorf("Not a valid nickname: %v", nickname)
 	}
 
-	nsm, err := readNicknameSerialMap(filename)
+	nicknameSerialMap, err := readMapFromFile(filename)
 	if err != nil {
 		return err
 	}
 
 	// If the nickname is already in use, don't allow it at all.
-	if _, present := nsm[nickname]; present {
+	if _, present := nicknameSerialMap[nickname]; present {
 		return fmt.Errorf("The provided nickname %q is already in use.", nickname)
 	}
 
 	// If the serial number already has an assigned nickname, delete it first.
 	// Need to do this check, because the nickname-serial map should be a one-to-one mapping.
-	if nn, present := reverseMap(nsm)[serial]; present {
-		delete(nsm, nn)
+	if nickname, present := reverseMap(nicknameSerialMap)[serial]; present {
+		delete(nicknameSerialMap, nickname)
 	}
 
 	// Add the nickname serial mapping.
-	nsm[nickname] = serial
+	nicknameSerialMap[nickname] = serial
 
-	return writeNicknameSerialMap(nsm, filename)
+	return writeMapToFile(nicknameSerialMap, filename)
 }
 
 var cmdMadbNameUnset = &cmdline.Command{
-	Runner: runnerFuncWithFilepath(runMadbNameUnset),
+	Runner: subCommandRunnerWithFilepath{runMadbNameUnset, getDefaultNameFilePath},
 	Name:   "unset",
 	Short:  "Unset a nickname set by the 'madb name set' command.",
 	Long: `
@@ -124,15 +123,15 @@
 		return env.UsageErrorf("Not a valid device serial or name: %v", name)
 	}
 
-	nsm, err := readNicknameSerialMap(filename)
+	nicknameSerialMap, err := readMapFromFile(filename)
 	if err != nil {
 		return err
 	}
 
 	found := false
-	for nickname, serial := range nsm {
+	for nickname, serial := range nicknameSerialMap {
 		if nickname == name || serial == name {
-			delete(nsm, nickname)
+			delete(nicknameSerialMap, nickname)
 			found = true
 			break
 		}
@@ -142,11 +141,11 @@
 		return fmt.Errorf("The provided argument is neither a known nickname nor a device serial.")
 	}
 
-	return writeNicknameSerialMap(nsm, filename)
+	return writeMapToFile(nicknameSerialMap, filename)
 }
 
 var cmdMadbNameList = &cmdline.Command{
-	Runner: runnerFuncWithFilepath(runMadbNameList),
+	Runner: subCommandRunnerWithFilepath{runMadbNameList, getDefaultNameFilePath},
 	Name:   "list",
 	Short:  "List all the existing nicknames.",
 	Long: `
@@ -155,7 +154,7 @@
 }
 
 func runMadbNameList(env *cmdline.Env, args []string, filename string) error {
-	nsm, err := readNicknameSerialMap(filename)
+	nicknameSerialMap, err := readMapFromFile(filename)
 	if err != nil {
 		return err
 	}
@@ -164,7 +163,7 @@
 	fmt.Println("Serial          Nickname")
 	fmt.Println("========================")
 
-	for nickname, serial := range nsm {
+	for nickname, serial := range nicknameSerialMap {
 		fmt.Printf("%v\t%v\n", serial, nickname)
 	}
 
@@ -172,7 +171,7 @@
 }
 
 var cmdMadbNameClearAll = &cmdline.Command{
-	Runner: runnerFuncWithFilepath(runMadbNameClearAll),
+	Runner: subCommandRunnerWithFilepath{runMadbNameClearAll, getDefaultNameFilePath},
 	Name:   "clear-all",
 	Short:  "Clear all the existing nicknames.",
 	Long: `
@@ -217,62 +216,3 @@
 
 	return reversed
 }
-
-// readNicknameSerialMap reads the provided file and reconstructs the nickname => serial map.
-// The mapping is written one per each line, in the form of "<nickname> <serial>".
-func readNicknameSerialMap(filename string) (map[string]string, error) {
-	result := make(map[string]string)
-
-	f, err := os.Open(filename)
-	if err != nil {
-		// Nickname file may not exist when there are no nicknames assigned, and it is not an error.
-		if os.IsNotExist(err) {
-			return result, nil
-		}
-
-		return nil, err
-	}
-	defer f.Close()
-
-	decoder := json.NewDecoder(f)
-
-	// Decoding might fail when the nickname file is somehow corrupted, or when the schema is updated.
-	// In such cases, move on after resetting the cache file instead of exiting the app.
-	if err := decoder.Decode(&result); err != nil {
-		fmt.Fprintf(os.Stderr, "WARNING: Could not decode the nickname file: %q.  Resetting the file.\n", err)
-		if err := os.Remove(f.Name()); err != nil {
-			return nil, err
-		}
-
-		return make(map[string]string), nil
-	}
-
-	return result, nil
-}
-
-// writeNicknameSerialmap takes a nickname => serial map and writes it into the provided file name.
-// The mapping is written one per each line, in the form of "<nickname> <serial>".
-func writeNicknameSerialMap(nsm map[string]string, filename string) error {
-	f, err := os.Create(filename)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	encoder := json.NewEncoder(f)
-	return encoder.Encode(nsm)
-}
-
-// runnerFuncWithFilepath is an adapter that turns the madb name subcommand functions into cmdline.Runners.
-type runnerFuncWithFilepath func(*cmdline.Env, []string, string) error
-
-// Run implements the cmdline.Runner interface by providing the default name file path
-// as the third string argument of the underlying run function.
-func (f runnerFuncWithFilepath) Run(env *cmdline.Env, args []string) error {
-	p, err := getDefaultNameFilePath()
-	if err != nil {
-		return err
-	}
-
-	return f(env, args, p)
-}
diff --git a/madb/name_test.go b/madb/name_test.go
index 9a39c27..57038d4 100644
--- a/madb/name_test.go
+++ b/madb/name_test.go
@@ -10,11 +10,6 @@
 	"testing"
 )
 
-type stringBoolPair struct {
-	s string
-	b bool
-}
-
 func TestMadbNameSet(t *testing.T) {
 	filename := tempFilename(t)
 	defer os.Remove(filename)
@@ -27,7 +22,7 @@
 		t.Fatal(err)
 	}
 
-	if got, err = readNicknameSerialMap(filename); err != nil {
+	if got, err = readMapFromFile(filename); err != nil {
 		t.Fatal(err)
 	}
 	want = map[string]string{"NICKNAME1": "SERIAL1"}
@@ -40,7 +35,7 @@
 		t.Fatal(err)
 	}
 
-	if got, err = readNicknameSerialMap(filename); err != nil {
+	if got, err = readMapFromFile(filename); err != nil {
 		t.Fatal(err)
 	}
 	want = map[string]string{"NICKNAME1": "SERIAL1", "NICKNAME2": "SERIAL2"}
@@ -53,7 +48,7 @@
 		t.Fatal(err)
 	}
 
-	if got, err = readNicknameSerialMap(filename); err != nil {
+	if got, err = readMapFromFile(filename); err != nil {
 		t.Fatal(err)
 	}
 	want = map[string]string{"NN1": "SERIAL1", "NICKNAME2": "SERIAL2"}
@@ -83,7 +78,7 @@
 	if err = runMadbNameUnset(nil, []string{"SERIAL1"}, filename); err != nil {
 		t.Fatal(err)
 	}
-	if got, err = readNicknameSerialMap(filename); err != nil {
+	if got, err = readMapFromFile(filename); err != nil {
 		t.Fatal(err)
 	}
 	want = map[string]string{"NICKNAME2": "SERIAL2", "NICKNAME3": "SERIAL3"}
@@ -95,7 +90,7 @@
 	if err = runMadbNameUnset(nil, []string{"NICKNAME2"}, filename); err != nil {
 		t.Fatal(err)
 	}
-	if got, err = readNicknameSerialMap(filename); err != nil {
+	if got, err = readMapFromFile(filename); err != nil {
 		t.Fatal(err)
 	}
 	want = map[string]string{"NICKNAME3": "SERIAL3"}
@@ -132,7 +127,10 @@
 }
 
 func TestIsValidDeviceSerial(t *testing.T) {
-	testCases := []stringBoolPair{
+	tests := []struct {
+		input string
+		want  bool
+	}{
 		// The following strings should be accepted
 		{"HT4BVWV00023", true},
 		{"01023f5e2fd2accf", true},
@@ -148,15 +146,18 @@
 		{"#not_allowed_chars~", false},
 	}
 
-	for _, tc := range testCases {
-		if got, want := isValidDeviceSerial(tc.s), tc.b; got != want {
-			t.Fatalf("unmatched results for serial '%v': got %v, want %v", tc.s, got, want)
+	for _, test := range tests {
+		if got := isValidDeviceSerial(test.input); got != test.want {
+			t.Fatalf("unmatched results for serial '%v': got %v, want %v", test.input, got, test.want)
 		}
 	}
 }
 
 func TestIsValidNickname(t *testing.T) {
-	testCases := []stringBoolPair{
+	tests := []struct {
+		input string
+		want  bool
+	}{
 		// The following strings should be accepted
 		{"Nexus5X", true},
 		{"Nexus9", true},
@@ -170,9 +171,9 @@
 		{"#not_allowed_chars~", false},
 	}
 
-	for _, tc := range testCases {
-		if got, want := isValidNickname(tc.s), tc.b; got != want {
-			t.Fatalf("unmatched results for nickname '%v': got %v, want %v", tc.s, got, want)
+	for _, test := range tests {
+		if got := isValidNickname(test.input); got != test.want {
+			t.Fatalf("unmatched results for nickname '%v': got %v, want %v", test.input, got, test.want)
 		}
 	}
 }
diff --git a/madb/start.go b/madb/start.go
index 297c077..96a3c59 100644
--- a/madb/start.go
+++ b/madb/start.go
@@ -28,6 +28,9 @@
 	Long: `
 Launches your app on all devices.
 
+To run your app as a specific user on a particular device, use 'madb user set' command to set the
+default user ID for that device.  (See 'madb help user' for more details.)
+
 `,
 	ArgsName: "[<application_id> <activity_name>]",
 	ArgsLong: `
@@ -80,9 +83,15 @@
 		if forceStopFlag {
 			cmdArgs = append(cmdArgs, "-S")
 		}
+
+		// Specify the user ID if applicable.
+		if d.UserID != "" {
+			cmdArgs = append(cmdArgs, "--user", d.UserID)
+		}
+
 		cmdArgs = append(cmdArgs, "-n", appID+"/"+activity)
 		cmd := sh.Cmd("adb", cmdArgs...)
-		return runGoshCommandForDevice(cmd, d)
+		return runGoshCommandForDevice(cmd, d, true)
 	}
 
 	// In case of flutter, the application ID is not even needed.
@@ -90,7 +99,7 @@
 	if isFlutterProject(wd) {
 		cmdArgs := []string{"start", "--android-device-id", d.Serial}
 		cmd := sh.Cmd("flutter", cmdArgs...)
-		return runGoshCommandForDevice(cmd, d)
+		return runGoshCommandForDevice(cmd, d, false)
 	}
 
 	return fmt.Errorf("No arguments are provided and failed to extract the ids from the build scripts.")
diff --git a/madb/stop.go b/madb/stop.go
index 37fa188..72c9245 100644
--- a/madb/stop.go
+++ b/madb/stop.go
@@ -22,6 +22,9 @@
 	Long: `
 Stops your app on all devices.
 
+To stop your app for a specific user on a particular device, use 'madb user set' command to set the
+default user ID for that device.  (See 'madb help user' for more details.)
+
 `,
 	ArgsName: "[<application_id>]",
 	ArgsLong: `
@@ -56,9 +59,16 @@
 		appID := args[0]
 
 		// More details on the "adb shell am" command can be found at: http://developer.android.com/tools/help/shell.html#am
-		cmdArgs := []string{"-s", d.Serial, "shell", "force-stop", appID}
+		cmdArgs := []string{"-s", d.Serial, "shell", "am", "force-stop"}
+
+		// Specify the user ID if applicable.
+		if d.UserID != "" {
+			cmdArgs = append(cmdArgs, "--user", d.UserID)
+		}
+
+		cmdArgs = append(cmdArgs, appID)
 		cmd := sh.Cmd("adb", cmdArgs...)
-		return runGoshCommandForDevice(cmd, d)
+		return runGoshCommandForDevice(cmd, d, true)
 	}
 
 	// In case of flutter, the application ID is not even needed.
@@ -66,7 +76,7 @@
 	if isFlutterProject(wd) {
 		cmdArgs := []string{"stop", "--android-device-id", d.Serial}
 		cmd := sh.Cmd("flutter", cmdArgs...)
-		return runGoshCommandForDevice(cmd, d)
+		return runGoshCommandForDevice(cmd, d, false)
 	}
 
 	return fmt.Errorf("No arguments are provided and failed to extract the id from the build scripts.")
diff --git a/madb/uninstall.go b/madb/uninstall.go
index cf2beb7..2589e88 100644
--- a/madb/uninstall.go
+++ b/madb/uninstall.go
@@ -27,6 +27,9 @@
 	Long: `
 Uninstall your app from all devices.
 
+To uninstall your app for a specific user on a particular device, use 'madb user set' command to set
+the default user ID for that device.  (See 'madb help user' for more details.)
+
 `,
 	ArgsName: "[<application_id>]",
 	ArgsLong: `
@@ -61,9 +64,15 @@
 		if keepDataFlag {
 			cmdArgs = append(cmdArgs, "-k")
 		}
+
+		// Specify the user ID if applicable.
+		if d.UserID != "" {
+			cmdArgs = append(cmdArgs, "--user", d.UserID)
+		}
+
 		cmdArgs = append(cmdArgs, appID)
 		cmd := sh.Cmd("adb", cmdArgs...)
-		return runGoshCommandForDevice(cmd, d)
+		return runGoshCommandForDevice(cmd, d, true)
 	}
 
 	return fmt.Errorf("No arguments are provided and failed to extract the id from the build scripts.")
diff --git a/madb/user.go b/madb/user.go
new file mode 100644
index 0000000..10041f3
--- /dev/null
+++ b/madb/user.go
@@ -0,0 +1,207 @@
+// 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 main
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	"v.io/x/lib/cmdline"
+)
+
+// TODO(youngseokyoon): add a helper command that wraps "madb exec shell pm list users" to show all available users.
+var cmdMadbUser = &cmdline.Command{
+	Children: []*cmdline.Command{cmdMadbUserSet, cmdMadbUserUnset, cmdMadbUserList, cmdMadbUserClearAll},
+	Name:     "user",
+	Short:    "Manage default user settings for each device",
+	Long: `
+Manages default user settings for each device.
+
+An Android device can have multiple user accounts, and each user account has a numeric ID associated
+with it.  Certain adb commands accept '--user <user_id>' as a parameter to allow specifying which of
+the Android user account should be used when running the command.  The default behavior when the
+user ID is not provided varies by the adb command being run.
+
+Some madb commands internally run these adb commands which accept the '--user' flag.  You can let
+madb use different user IDs for different devices by storing the default user ID for each device
+using 'madb user set' command.  If the default user ID is not set for a particular device, madb will
+not provide the '--user' flag to the underlying adb command, and the current user will be used for
+that device as a result.
+
+Below is the list of madb commands which are affected by the default user ID settings:
+
+    madb clear-data
+    madb start
+    madb stop
+    madb uninstall
+
+For more details on how to obtain the user ID from an Android device, see 'madb user help set'.
+
+NOTE: Device specifier flags (-d, -e, -n) are ignored in all 'madb name' commands.
+`,
+}
+
+var cmdMadbUserSet = &cmdline.Command{
+	Runner: subCommandRunnerWithFilepath{runMadbUserSet, getDefaultUserFilePath},
+	Name:   "set",
+	Short:  "Set a default user ID to be used for the given device.",
+	Long: `
+Sets a default user ID to be used for the specified device, when there are multiple user accounts on
+a single device.
+
+The user IDs can be obtained using the 'adb [<device_serial>] shell pm list users' command.
+Alternatively, you can use 'madb exec' if you want to specify the device with a nickname.
+For example, running the following command:
+
+    madb -n=MyPhone exec shell pm list users
+
+will list the available users and their IDs on the MyPhone device.
+Consider the following example output:
+
+    [MyPhone]       Users:
+    [MyPhone]               UserInfo{0:John Doe:13} running
+    [MyPhone]               UserInfo{10:Work profile:30} running
+
+There are two available users, "John Doe" and "Work profile". Each user is assigned a "user ID",
+which appears on the left of the user name. In this case, the user ID of "John Doe" is "0", and the
+user ID of the "Work profile" is "10".
+
+To use the "Work profile" as the default user when running madb commands on this device, run the
+following command:
+
+    madb user set MyPhone 10
+
+and then madb will use "Work profile" as the default user for device "MyPhone" in any of the
+subsequence madb commands.
+`,
+	ArgsName: "<device_serial> <user_id>",
+	ArgsLong: `
+<device_serial> is the unique serial number for the device, which can be obtained from 'adb devices'.
+<user_id> is one of the user IDs obtained from 'adb shell pm list users' command.
+`,
+}
+
+func runMadbUserSet(env *cmdline.Env, args []string, filename string) error {
+	// Check if the arguments are valid.
+	if len(args) != 2 {
+		return fmt.Errorf("There must be exactly two arguments.")
+	}
+
+	// TODO(youngseokyoon): make it possible to specify the device using its nickname or index.
+	// Validate the device serial
+	serial := args[0]
+	if !isValidDeviceSerial(serial) {
+		return fmt.Errorf("Not a valid device serial: %v", serial)
+	}
+
+	// Validate the user ID.
+	userID := args[1]
+	if id, err := strconv.Atoi(userID); err != nil || id < 0 {
+		return fmt.Errorf("Not a valid user ID: %v", userID)
+	}
+
+	// Get the <device_serial, user_id> mapping.
+	serialUserMap, err := readMapFromFile(filename)
+	if err != nil {
+		return err
+	}
+
+	// Add the <device_serial, user_id> mapping for the specified device.
+	serialUserMap[serial] = userID
+	return writeMapToFile(serialUserMap, filename)
+}
+
+var cmdMadbUserUnset = &cmdline.Command{
+	Runner: subCommandRunnerWithFilepath{runMadbUserUnset, getDefaultUserFilePath},
+	Name:   "unset",
+	Short:  "Unset the default user ID set by the 'madb user set' command.",
+	Long: `
+Unsets the default user ID assigned by the 'madb user set' command for the specified device.
+
+Running this command without any device specifiers will unset the default users only for the
+currently available devices and emulators, while keeping the default user IDs for the other devices.
+`,
+	ArgsName: "<device_serial>",
+	ArgsLong: `
+<device_serial> is the unique serial number for the device, which can be obtained from 'adb devices'.
+`,
+}
+
+func runMadbUserUnset(env *cmdline.Env, args []string, filename string) error {
+	// Check if the arguments are valid.
+	if len(args) != 1 {
+		return fmt.Errorf("There must be exactly one argument.")
+	}
+
+	// TODO(youngseokyoon): make it possible to specify the device using its nickname or index.
+	// Validate the device serial
+	serial := args[0]
+	if !isValidDeviceSerial(serial) {
+		return fmt.Errorf("Not a valid device serial: %v", serial)
+	}
+
+	// Get the <device_serial, user_id> mapping.
+	serialUserMap, err := readMapFromFile(filename)
+	if err != nil {
+		return err
+	}
+
+	// Delete the <device_serial, user_id> mapping for the specified device.
+	delete(serialUserMap, serial)
+	return writeMapToFile(serialUserMap, filename)
+}
+
+var cmdMadbUserList = &cmdline.Command{
+	Runner: subCommandRunnerWithFilepath{runMadbUserList, getDefaultUserFilePath},
+	Name:   "list",
+	Short:  "List all the existing default user IDs.",
+	Long: `
+Lists all the currently stored default user IDs for devices.
+`,
+}
+
+func runMadbUserList(env *cmdline.Env, args []string, filename string) error {
+	// Get the <device_serial, user_id> mapping.
+	serialUserMap, err := readMapFromFile(filename)
+	if err != nil {
+		return err
+	}
+
+	// TODO(youngseokyoon): pretty print this.
+	fmt.Println("Device Serial    User ID")
+	fmt.Println("========================")
+
+	for s, u := range serialUserMap {
+		fmt.Printf("%v\t%v\n", s, u)
+	}
+
+	return nil
+}
+
+var cmdMadbUserClearAll = &cmdline.Command{
+	Runner: subCommandRunnerWithFilepath{runMadbUserClearAll, getDefaultUserFilePath},
+	Name:   "clear-all",
+	Short:  "Clear all the existing default user settings.",
+	Long: `
+Clears all the currently stored default user IDs for devices.
+
+This command clears the default user IDs regardless of whether the device is currently connected or not.
+`,
+}
+
+func runMadbUserClearAll(env *cmdline.Env, args []string, filename string) error {
+	return os.Remove(filename)
+}
+
+func getDefaultUserFilePath() (string, error) {
+	configDir, err := getConfigDir()
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.Join(configDir, "users"), nil
+}
diff --git a/madb/user_test.go b/madb/user_test.go
new file mode 100644
index 0000000..91497d8
--- /dev/null
+++ b/madb/user_test.go
@@ -0,0 +1,109 @@
+// 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 main
+
+import (
+	"os"
+	"reflect"
+	"testing"
+)
+
+func TestMadbUserSet(t *testing.T) {
+	filename := tempFilename(t)
+	defer os.Remove(filename)
+
+	var got, want map[string]string
+	var err error
+
+	// Set a new nickname
+	if err = runMadbUserSet(nil, []string{"SERIAL1", "0"}, filename); err != nil {
+		t.Fatal(err)
+	}
+
+	if got, err = readMapFromFile(filename); err != nil {
+		t.Fatal(err)
+	}
+	want = map[string]string{"SERIAL1": "0"}
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("unmatched results: got %v, want %v", got, want)
+	}
+
+	// Set a second nickname
+	if err = runMadbUserSet(nil, []string{"SERIAL2", "10"}, filename); err != nil {
+		t.Fatal(err)
+	}
+
+	if got, err = readMapFromFile(filename); err != nil {
+		t.Fatal(err)
+	}
+	want = map[string]string{"SERIAL1": "0", "SERIAL2": "10"}
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("unmatched results: got %v, want %v", got, want)
+	}
+
+	// Override an existing nickname to another
+	if err = runMadbUserSet(nil, []string{"SERIAL1", "20"}, filename); err != nil {
+		t.Fatal(err)
+	}
+
+	if got, err = readMapFromFile(filename); err != nil {
+		t.Fatal(err)
+	}
+	want = map[string]string{"SERIAL1": "20", "SERIAL2": "10"}
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("unmatched results: got %v, want %v", got, want)
+	}
+
+	// Try some invalid ids and see if they fail.
+	invalidIds := []string{"-1", "NAME"}
+	for _, id := range invalidIds {
+		if err = runMadbUserSet(nil, []string{"SERIAL", id}, filename); err == nil {
+			t.Fatalf("expected an error but succeeded.")
+		}
+	}
+}
+
+func TestMadbUserUnset(t *testing.T) {
+	filename := tempFilename(t)
+	defer os.Remove(filename)
+
+	// Set up some default users first.
+	runMadbUserSet(nil, []string{"SERIAL1", "0"}, filename)
+	runMadbUserSet(nil, []string{"SERIAL2", "0"}, filename)
+	runMadbUserSet(nil, []string{"SERIAL3", "10"}, filename)
+
+	var got, want map[string]string
+	var err error
+
+	// Unset by serial number.
+	if err = runMadbUserUnset(nil, []string{"SERIAL1"}, filename); err != nil {
+		t.Fatal(err)
+	}
+	if got, err = readMapFromFile(filename); err != nil {
+		t.Fatal(err)
+	}
+	want = map[string]string{"SERIAL2": "0", "SERIAL3": "10"}
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("unmatched results: got %v, want %v", got, want)
+	}
+}
+
+func TestMadbUserClearAll(t *testing.T) {
+	filename := tempFilename(t)
+	defer os.Remove(filename)
+
+	// Set up some default users first.
+	runMadbUserSet(nil, []string{"SERIAL1", "0"}, filename)
+	runMadbUserSet(nil, []string{"SERIAL2", "0"}, filename)
+	runMadbUserSet(nil, []string{"SERIAL3", "10"}, filename)
+
+	// Run the clear-all command. The file should be empty after running the command.
+	runMadbUserClearAll(nil, []string{}, filename)
+
+	// Check if the file is successfully deleted.
+	if _, err := os.Stat(filename); !os.IsNotExist(err) {
+		t.Fatalf("failed to delete file %q", filename)
+	}
+}
diff --git a/tooldata/.api b/tooldata/.api
new file mode 100644
index 0000000..56f4e19
--- /dev/null
+++ b/tooldata/.api
@@ -0,0 +1,45 @@
+pkg tooldata, func ConfigFilePath(*jiri.X) (string, error)
+pkg tooldata, func DataDirPath(*jiri.X, string) (string, error)
+pkg tooldata, func LoadConfig(*jiri.X) (*Config, error)
+pkg tooldata, func LoadOncallRotation(*jiri.X) (*OncallRotation, error)
+pkg tooldata, func NewConfig(...ConfigOpt) *Config
+pkg tooldata, func Oncall(*jiri.X, time.Time) (*OncallShift, error)
+pkg tooldata, func OncallRotationPath(*jiri.X) (string, error)
+pkg tooldata, func SaveConfig(*jiri.X, *Config) error
+pkg tooldata, func ThirdPartyBinPath(*jiri.X, string) (string, error)
+pkg tooldata, method (Config) APICheckProjects() map[string]struct{}
+pkg tooldata, method (Config) CopyrightCheckProjects() map[string]struct{}
+pkg tooldata, method (Config) GoPath(*jiri.X) string
+pkg tooldata, method (Config) GoWorkspaces() []string
+pkg tooldata, method (Config) GroupTests([]string) []string
+pkg tooldata, method (Config) JenkinsMatrixJobs() map[string]JenkinsMatrixJobInfo
+pkg tooldata, method (Config) ProjectTests([]string) []string
+pkg tooldata, method (Config) Projects() []string
+pkg tooldata, method (Config) TestDependencies(string) []string
+pkg tooldata, method (Config) TestParts(string) []string
+pkg tooldata, method (Config) VDLPath(*jiri.X) string
+pkg tooldata, method (Config) VDLWorkspaces() []string
+pkg tooldata, type APICheckProjectsOpt map[string]struct{}
+pkg tooldata, type Config struct
+pkg tooldata, type ConfigOpt interface, unexported methods
+pkg tooldata, type CopyrightCheckProjectsOpt map[string]struct{}
+pkg tooldata, type GoWorkspacesOpt []string
+pkg tooldata, type JenkinsMatrixJobInfo struct
+pkg tooldata, type JenkinsMatrixJobInfo struct, HasArch bool
+pkg tooldata, type JenkinsMatrixJobInfo struct, HasOS bool
+pkg tooldata, type JenkinsMatrixJobInfo struct, HasParts bool
+pkg tooldata, type JenkinsMatrixJobInfo struct, Name string
+pkg tooldata, type JenkinsMatrixJobInfo struct, ShowOS bool
+pkg tooldata, type JenkinsMatrixJobsOpt map[string]JenkinsMatrixJobInfo
+pkg tooldata, type OncallRotation struct
+pkg tooldata, type OncallRotation struct, Shifts []OncallShift
+pkg tooldata, type OncallRotation struct, XMLName xml.Name
+pkg tooldata, type OncallShift struct
+pkg tooldata, type OncallShift struct, Date string
+pkg tooldata, type OncallShift struct, Primary string
+pkg tooldata, type OncallShift struct, Secondary string
+pkg tooldata, type ProjectTestsOpt map[string][]string
+pkg tooldata, type TestDependenciesOpt map[string][]string
+pkg tooldata, type TestGroupsOpt map[string][]string
+pkg tooldata, type TestPartsOpt map[string][]string
+pkg tooldata, type VDLWorkspacesOpt []string
diff --git a/tooldata/config.go b/tooldata/config.go
new file mode 100644
index 0000000..25a5ee2
--- /dev/null
+++ b/tooldata/config.go
@@ -0,0 +1,458 @@
+// 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 tooldata
+
+import (
+	"encoding/xml"
+	"fmt"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"v.io/jiri"
+	"v.io/jiri/project"
+	"v.io/x/lib/envvar"
+	"v.io/x/lib/set"
+)
+
+// Config holds configuration common to jiri tools.
+type Config struct {
+	// apiCheckProjects identifies the set of project names for which
+	// the API check is required.
+	apiCheckProjects map[string]struct{}
+	// copyrightCheckProjects identifies the set of project names for
+	// which the copyright check is required.
+	copyrightCheckProjects map[string]struct{}
+	// goWorkspaces identifies JIRI_ROOT subdirectories that contain a
+	// Go workspace.
+	goWorkspaces []string
+	// jenkinsMatrixJobs identifies the set of matrix (multi-configutation) jobs
+	// in Jenkins.
+	jenkinsMatrixJobs map[string]JenkinsMatrixJobInfo
+	// projectTests maps jiri projects to sets of tests that should be
+	// executed to test changes in the given project.
+	projectTests map[string][]string
+	// testDependencies maps tests to sets of tests that the given test
+	// depends on.
+	testDependencies map[string][]string
+	// testGroups maps test group labels to sets of tests that the label
+	// identifies.
+	testGroups map[string][]string
+	// testParts maps test names to lists of strings that identify
+	// different parts of a test. If a list L has n elements, then the
+	// corresponding test has n+1 parts: the first n parts are identified
+	// by L[0] to L[n-1]. The last part is whatever is left.
+	testParts map[string][]string
+	// vdlWorkspaces identifies JIRI_ROOT subdirectories that contain
+	// a VDL workspace.
+	vdlWorkspaces []string
+}
+
+// ConfigOpt is an interface for Config factory options.
+type ConfigOpt interface {
+	configOpt()
+}
+
+// APICheckProjectsOpt is the type that can be used to pass the Config
+// factory a API check projects option.
+type APICheckProjectsOpt map[string]struct{}
+
+func (APICheckProjectsOpt) configOpt() {}
+
+// CopyrightCheckProjectsOpt is the type that can be used to pass the
+// Config factory a copyright check projects option.
+type CopyrightCheckProjectsOpt map[string]struct{}
+
+func (CopyrightCheckProjectsOpt) configOpt() {}
+
+// GoWorkspacesOpt is the type that can be used to pass the Config
+// factory a Go workspace option.
+type GoWorkspacesOpt []string
+
+func (GoWorkspacesOpt) configOpt() {}
+
+// JenkinsMatrixJobsOpt is the type that can be used to pass the Config factory
+// a Jenkins matrix jobs option.
+type JenkinsMatrixJobsOpt map[string]JenkinsMatrixJobInfo
+
+func (JenkinsMatrixJobsOpt) configOpt() {}
+
+// ProjectTestsOpt is the type that can be used to pass the Config
+// factory a project tests option.
+type ProjectTestsOpt map[string][]string
+
+func (ProjectTestsOpt) configOpt() {}
+
+// TestDependenciesOpt is the type that can be used to pass the Config
+// factory a test dependencies option.
+type TestDependenciesOpt map[string][]string
+
+func (TestDependenciesOpt) configOpt() {}
+
+// TestGroupsOpt is the type that can be used to pass the Config
+// factory a test groups option.
+type TestGroupsOpt map[string][]string
+
+func (TestGroupsOpt) configOpt() {}
+
+// TestPartsOpt is the type that can be used to pass the Config
+// factory a test parts option.
+type TestPartsOpt map[string][]string
+
+func (TestPartsOpt) configOpt() {}
+
+// VDLWorkspacesOpt is the type that can be used to pass the Config
+// factory a VDL workspace option.
+type VDLWorkspacesOpt []string
+
+func (VDLWorkspacesOpt) configOpt() {}
+
+// NewConfig is the Config factory.
+func NewConfig(opts ...ConfigOpt) *Config {
+	var c Config
+	for _, opt := range opts {
+		switch typedOpt := opt.(type) {
+		case APICheckProjectsOpt:
+			c.apiCheckProjects = map[string]struct{}(typedOpt)
+		case CopyrightCheckProjectsOpt:
+			c.copyrightCheckProjects = map[string]struct{}(typedOpt)
+		case GoWorkspacesOpt:
+			c.goWorkspaces = []string(typedOpt)
+		case JenkinsMatrixJobsOpt:
+			c.jenkinsMatrixJobs = map[string]JenkinsMatrixJobInfo(typedOpt)
+		case ProjectTestsOpt:
+			c.projectTests = map[string][]string(typedOpt)
+		case TestDependenciesOpt:
+			c.testDependencies = map[string][]string(typedOpt)
+		case TestGroupsOpt:
+			c.testGroups = map[string][]string(typedOpt)
+		case TestPartsOpt:
+			c.testParts = map[string][]string(typedOpt)
+		case VDLWorkspacesOpt:
+			c.vdlWorkspaces = []string(typedOpt)
+		}
+	}
+	return &c
+}
+
+// APICheckProjects returns the set of project names for which the API
+// check is required.
+func (c Config) APICheckProjects() map[string]struct{} {
+	return c.apiCheckProjects
+}
+
+// CopyrightCheckProjects returns the set of project names for which
+// the copyright check is required.
+func (c Config) CopyrightCheckProjects() map[string]struct{} {
+	return c.copyrightCheckProjects
+}
+
+// GroupTests returns a list of Jenkins tests associated with the
+// given test groups.
+func (c Config) GroupTests(groups []string) []string {
+	testSet := map[string]struct{}{}
+	testGroups := c.testGroups
+	for _, group := range groups {
+		if testGroup, ok := testGroups[group]; ok {
+			set.String.Union(testSet, set.String.FromSlice(testGroup))
+		}
+	}
+	tests := set.String.ToSlice(testSet)
+	sort.Strings(tests)
+	return tests
+}
+
+// GoWorkspaces returns the Go workspaces included in the config.
+func (c Config) GoWorkspaces() []string {
+	return c.goWorkspaces
+}
+
+// JenkinsMatrixJobs returns the set of Jenkins matrix jobs.
+func (c Config) JenkinsMatrixJobs() map[string]JenkinsMatrixJobInfo {
+	return c.jenkinsMatrixJobs
+}
+
+// Projects returns a list of projects included in the config.
+func (c Config) Projects() []string {
+	var projects []string
+	for project, _ := range c.projectTests {
+		projects = append(projects, project)
+	}
+	sort.Strings(projects)
+	return projects
+}
+
+// ProjectTests returns a list of Jenkins tests associated with the
+// given projects by the config.
+func (c Config) ProjectTests(projects []string) []string {
+	testSet := map[string]struct{}{}
+	testGroups := c.testGroups
+	for _, project := range projects {
+		for _, test := range c.projectTests[project] {
+			if testGroup, ok := testGroups[test]; ok {
+				set.String.Union(testSet, set.String.FromSlice(testGroup))
+			} else {
+				testSet[test] = struct{}{}
+			}
+		}
+	}
+	tests := set.String.ToSlice(testSet)
+	sort.Strings(tests)
+	return tests
+}
+
+// TestDependencies returns a list of dependencies for the given test.
+func (c Config) TestDependencies(test string) []string {
+	return c.testDependencies[test]
+}
+
+// TestParts returns a list of strings that identify different test parts.
+func (c Config) TestParts(test string) []string {
+	return c.testParts[test]
+}
+
+// VDLWorkspaces returns the VDL workspaces included in the config.
+func (c Config) VDLWorkspaces() []string {
+	return c.vdlWorkspaces
+}
+
+// GoPath computes and returns the GOPATH environment variable based on the
+// current jiri configuration.
+func (c Config) GoPath(jirix *jiri.X) string {
+	projects, err := project.LocalProjects(jirix, project.FastScan)
+	if err != nil {
+		return ""
+	}
+	path := pathHelper(jirix, projects, c.goWorkspaces, "")
+	return "GOPATH=" + envvar.JoinTokens(path, ":")
+}
+
+// VDLPath computes and returns the VDLPATH environment variable based on the
+// current jiri configuration.
+func (c Config) VDLPath(jirix *jiri.X) string {
+	projects, err := project.LocalProjects(jirix, project.FastScan)
+	if err != nil {
+		return ""
+	}
+	path := pathHelper(jirix, projects, c.vdlWorkspaces, "src")
+	return "VDLPATH=" + envvar.JoinTokens(path, ":")
+}
+
+// pathHelper is a utility function for determining paths for project workspaces.
+func pathHelper(jirix *jiri.X, projects project.Projects, workspaces []string, suffix string) []string {
+	path := []string{}
+	for _, workspace := range workspaces {
+		absWorkspace := filepath.Join(jirix.Root, workspace, suffix)
+		// Only append an entry to the path if the workspace is rooted
+		// under a jiri project that exists locally or vice versa.
+		for _, project := range projects {
+			// We check if <project.Path> is a prefix of <absWorkspace> to
+			// account for Go workspaces nested under a single jiri project,
+			// such as: $JIRI_ROOT/release/projects/chat/go.
+			//
+			// We check if <absWorkspace> is a prefix of <project.Path> to
+			// account for Go workspaces that span multiple jiri projects,
+			// such as: $JIRI_ROOT/release/go.
+			if strings.HasPrefix(absWorkspace, project.Path) || strings.HasPrefix(project.Path, absWorkspace) {
+				if _, err := jirix.NewSeq().Stat(filepath.Join(absWorkspace)); err == nil {
+					path = append(path, absWorkspace)
+					break
+				}
+			}
+		}
+	}
+	return path
+}
+
+type configSchema struct {
+	APICheckProjects       []string                `xml:"apiCheckProjects>project"`
+	CopyrightCheckProjects []string                `xml:"copyrightCheckProjects>project"`
+	GoWorkspaces           []string                `xml:"goWorkspaces>workspace"`
+	JenkinsMatrixJobs      jenkinsMatrixJobsSchema `xml:"jenkinsMatrixJobs>job"`
+	ProjectTests           testGroupSchemas        `xml:"projectTests>project"`
+	TestDependencies       dependencyGroupSchemas  `xml:"testDependencies>test"`
+	TestGroups             testGroupSchemas        `xml:"testGroups>group"`
+	TestParts              partGroupSchemas        `xml:"testParts>test"`
+	VDLWorkspaces          []string                `xml:"vdlWorkspaces>workspace"`
+	XMLName                xml.Name                `xml:"config"`
+}
+
+type dependencyGroupSchema struct {
+	Name         string   `xml:"name,attr"`
+	Dependencies []string `xml:"dependency"`
+}
+
+type dependencyGroupSchemas []dependencyGroupSchema
+
+func (d dependencyGroupSchemas) Len() int           { return len(d) }
+func (d dependencyGroupSchemas) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
+func (d dependencyGroupSchemas) Less(i, j int) bool { return d[i].Name < d[j].Name }
+
+type JenkinsMatrixJobInfo struct {
+	HasArch  bool `xml:"arch,attr"`
+	HasOS    bool `xml:"OS,attr"`
+	HasParts bool `xml:"parts,attr"`
+	// ShowOS determines whether to show OS label in job summary.
+	// It is possible that a job (e.g. jiri-go-race) has an OS axis but
+	// the axis only has a single value in order to constrain where its
+	// sub-builds run. In such cases, we do not want to show the OS label.
+	ShowOS bool   `xml:"showOS,attr"`
+	Name   string `xml:",chardata"`
+}
+
+type jenkinsMatrixJobsSchema []JenkinsMatrixJobInfo
+
+func (jobs jenkinsMatrixJobsSchema) Len() int           { return len(jobs) }
+func (jobs jenkinsMatrixJobsSchema) Swap(i, j int)      { jobs[i], jobs[j] = jobs[j], jobs[i] }
+func (jobs jenkinsMatrixJobsSchema) Less(i, j int) bool { return jobs[i].Name < jobs[j].Name }
+
+type partGroupSchema struct {
+	Name  string   `xml:"name,attr"`
+	Parts []string `xml:"part"`
+}
+
+type partGroupSchemas []partGroupSchema
+
+func (p partGroupSchemas) Len() int           { return len(p) }
+func (p partGroupSchemas) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+func (p partGroupSchemas) Less(i, j int) bool { return p[i].Name < p[j].Name }
+
+type testGroupSchema struct {
+	Name  string   `xml:"name,attr"`
+	Tests []string `xml:"test"`
+}
+
+type testGroupSchemas []testGroupSchema
+
+func (p testGroupSchemas) Len() int           { return len(p) }
+func (p testGroupSchemas) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+func (p testGroupSchemas) Less(i, j int) bool { return p[i].Name < p[j].Name }
+
+// LoadConfig returns the configuration stored in the tools
+// configuration file.
+func LoadConfig(jirix *jiri.X) (*Config, error) {
+	configPath, err := ConfigFilePath(jirix)
+	if err != nil {
+		return nil, err
+	}
+	return loadConfig(jirix, configPath)
+}
+
+func loadConfig(jirix *jiri.X, path string) (*Config, error) {
+	configBytes, err := jirix.NewSeq().ReadFile(path)
+	if err != nil {
+		return nil, err
+	}
+	var data configSchema
+	if err := xml.Unmarshal(configBytes, &data); err != nil {
+		return nil, fmt.Errorf("Unmarshal(%v) failed: %v", string(configBytes), err)
+	}
+	config := &Config{
+		apiCheckProjects:       map[string]struct{}{},
+		copyrightCheckProjects: map[string]struct{}{},
+		goWorkspaces:           []string{},
+		jenkinsMatrixJobs:      map[string]JenkinsMatrixJobInfo{},
+		projectTests:           map[string][]string{},
+		testDependencies:       map[string][]string{},
+		testGroups:             map[string][]string{},
+		testParts:              map[string][]string{},
+		vdlWorkspaces:          []string{},
+	}
+	config.apiCheckProjects = set.String.FromSlice(data.APICheckProjects)
+	config.copyrightCheckProjects = set.String.FromSlice(data.CopyrightCheckProjects)
+	for _, workspace := range data.GoWorkspaces {
+		config.goWorkspaces = append(config.goWorkspaces, workspace)
+	}
+	sort.Strings(config.goWorkspaces)
+	for _, job := range data.JenkinsMatrixJobs {
+		config.jenkinsMatrixJobs[job.Name] = job
+	}
+	for _, project := range data.ProjectTests {
+		config.projectTests[project.Name] = project.Tests
+	}
+	for _, test := range data.TestDependencies {
+		config.testDependencies[test.Name] = test.Dependencies
+	}
+	for _, group := range data.TestGroups {
+		config.testGroups[group.Name] = group.Tests
+	}
+	for _, test := range data.TestParts {
+		config.testParts[test.Name] = test.Parts
+	}
+	for _, workspace := range data.VDLWorkspaces {
+		config.vdlWorkspaces = append(config.vdlWorkspaces, workspace)
+	}
+	sort.Strings(config.vdlWorkspaces)
+	return config, nil
+}
+
+// SaveConfig writes the given configuration to the tools
+// configuration file.
+func SaveConfig(jirix *jiri.X, config *Config) error {
+	configPath, err := ConfigFilePath(jirix)
+	if err != nil {
+		return err
+	}
+	return saveConfig(jirix, config, configPath)
+}
+
+func saveConfig(jirix *jiri.X, config *Config, path string) error {
+	var data configSchema
+	data.APICheckProjects = set.String.ToSlice(config.apiCheckProjects)
+	sort.Strings(data.APICheckProjects)
+	data.CopyrightCheckProjects = set.String.ToSlice(config.copyrightCheckProjects)
+	sort.Strings(data.CopyrightCheckProjects)
+	for _, workspace := range config.goWorkspaces {
+		data.GoWorkspaces = append(data.GoWorkspaces, workspace)
+	}
+	sort.Strings(data.GoWorkspaces)
+	for _, job := range config.jenkinsMatrixJobs {
+		data.JenkinsMatrixJobs = append(data.JenkinsMatrixJobs, job)
+	}
+	sort.Sort(data.JenkinsMatrixJobs)
+	for name, tests := range config.projectTests {
+		data.ProjectTests = append(data.ProjectTests, testGroupSchema{
+			Name:  name,
+			Tests: tests,
+		})
+	}
+	sort.Sort(data.ProjectTests)
+	for name, dependencies := range config.testDependencies {
+		data.TestDependencies = append(data.TestDependencies, dependencyGroupSchema{
+			Name:         name,
+			Dependencies: dependencies,
+		})
+	}
+	sort.Sort(data.TestDependencies)
+	for name, tests := range config.testGroups {
+		data.TestGroups = append(data.TestGroups, testGroupSchema{
+			Name:  name,
+			Tests: tests,
+		})
+	}
+	sort.Sort(data.TestGroups)
+	for name, parts := range config.testParts {
+		data.TestParts = append(data.TestParts, partGroupSchema{
+			Name:  name,
+			Parts: parts,
+		})
+	}
+	sort.Sort(data.TestParts)
+	for _, workspace := range config.vdlWorkspaces {
+		data.VDLWorkspaces = append(data.VDLWorkspaces, workspace)
+	}
+	sort.Strings(data.VDLWorkspaces)
+	bytes, err := xml.MarshalIndent(data, "", "  ")
+	if err != nil {
+		return fmt.Errorf("MarshalIndent(%v) failed: %v", data, err)
+	}
+	s := jirix.NewSeq()
+	if err := s.MkdirAll(filepath.Dir(path), os.FileMode(0755)).
+		WriteFile(path, bytes, os.FileMode(0644)).Done(); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/tooldata/config_test.go b/tooldata/config_test.go
new file mode 100644
index 0000000..1b16de8
--- /dev/null
+++ b/tooldata/config_test.go
@@ -0,0 +1,197 @@
+// 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 tooldata_test
+
+import (
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"v.io/jiri/jiritest"
+	"v.io/jiri/project"
+	"v.io/x/devtools/tooldata"
+)
+
+var (
+	apiCheckProjects = map[string]struct{}{
+		"projectA": struct{}{},
+		"projectB": struct{}{},
+	}
+	copyrightCheckProjects = map[string]struct{}{
+		"projectC": struct{}{},
+		"projectD": struct{}{},
+	}
+	goWorkspaces      = []string{"test-go-workspace"}
+	jenkinsMatrixJobs = map[string]tooldata.JenkinsMatrixJobInfo{
+		"test-job-A": {
+			HasArch:  false,
+			HasOS:    true,
+			HasParts: true,
+			ShowOS:   false,
+			Name:     "test-job-A",
+		},
+		"test-job-B": {
+			HasArch:  true,
+			HasOS:    false,
+			HasParts: false,
+			ShowOS:   false,
+			Name:     "test-job-B",
+		},
+	}
+	projectTests = map[string][]string{
+		"test-project":  []string{"test-test-A", "test-test-group"},
+		"test-project2": []string{"test-test-D"},
+	}
+	testDependencies = map[string][]string{
+		"test-test-A": []string{"test-test-B"},
+		"test-test-B": []string{"test-test-C"},
+	}
+	testGroups = map[string][]string{
+		"test-test-group": []string{"test-test-B", "test-test-C"},
+	}
+	testParts = map[string][]string{
+		"test-test-A": []string{"p1", "p2"},
+	}
+	vdlWorkspaces = []string{"test-vdl-workspace"}
+)
+
+func testConfigAPI(t *testing.T, c *tooldata.Config) {
+	if got, want := c.APICheckProjects(), apiCheckProjects; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected results: got %v, want %v", got, want)
+	}
+	if got, want := c.CopyrightCheckProjects(), copyrightCheckProjects; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected results: got %v, want %v", got, want)
+	}
+	if got, want := c.GoWorkspaces(), goWorkspaces; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.GroupTests([]string{"test-test-group"}), []string{"test-test-B", "test-test-C"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.JenkinsMatrixJobs(), jenkinsMatrixJobs; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.Projects(), []string{"test-project", "test-project2"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.ProjectTests([]string{"test-project"}), []string{"test-test-A", "test-test-B", "test-test-C"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.ProjectTests([]string{"test-project", "test-project2"}), []string{"test-test-A", "test-test-B", "test-test-C", "test-test-D"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.TestDependencies("test-test-A"), []string{"test-test-B"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.TestDependencies("test-test-B"), []string{"test-test-C"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.TestParts("test-test-A"), []string{"p1", "p2"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+	if got, want := c.VDLWorkspaces(), vdlWorkspaces; !reflect.DeepEqual(got, want) {
+		t.Fatalf("unexpected result: got %v, want %v", got, want)
+	}
+}
+
+func TestConfigAPI(t *testing.T) {
+	config := tooldata.NewConfig(
+		tooldata.APICheckProjectsOpt(apiCheckProjects),
+		tooldata.CopyrightCheckProjectsOpt(copyrightCheckProjects),
+		tooldata.GoWorkspacesOpt(goWorkspaces),
+		tooldata.JenkinsMatrixJobsOpt(jenkinsMatrixJobs),
+		tooldata.ProjectTestsOpt(projectTests),
+		tooldata.TestDependenciesOpt(testDependencies),
+		tooldata.TestGroupsOpt(testGroups),
+		tooldata.TestPartsOpt(testParts),
+		tooldata.VDLWorkspacesOpt(vdlWorkspaces),
+	)
+
+	testConfigAPI(t, config)
+}
+
+func TestConfigSerialization(t *testing.T) {
+	fake, cleanup := jiritest.NewFakeJiriRoot(t)
+	defer cleanup()
+
+	config := tooldata.NewConfig(
+		tooldata.APICheckProjectsOpt(apiCheckProjects),
+		tooldata.CopyrightCheckProjectsOpt(copyrightCheckProjects),
+		tooldata.GoWorkspacesOpt(goWorkspaces),
+		tooldata.JenkinsMatrixJobsOpt(jenkinsMatrixJobs),
+		tooldata.ProjectTestsOpt(projectTests),
+		tooldata.TestDependenciesOpt(testDependencies),
+		tooldata.TestGroupsOpt(testGroups),
+		tooldata.TestPartsOpt(testParts),
+		tooldata.VDLWorkspacesOpt(vdlWorkspaces),
+	)
+
+	if err := tooldata.SaveConfig(fake.X, config); err != nil {
+		t.Fatalf("%v", err)
+	}
+	gotConfig, err := tooldata.LoadConfig(fake.X)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	testConfigAPI(t, gotConfig)
+}
+
+func testSetPathHelper(t *testing.T, name string) {
+	fake, cleanup := jiritest.NewFakeJiriRoot(t)
+	defer cleanup()
+
+	// Create a test project and identify it as a Go workspace.
+	if err := fake.CreateRemoteProject("test"); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := fake.AddProject(project.Project{
+		Name:   "test",
+		Path:   "test",
+		Remote: fake.Projects["test"],
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := fake.UpdateUniverse(false); err != nil {
+		t.Fatalf("%v", err)
+	}
+	var config *tooldata.Config
+	switch name {
+	case "GOPATH":
+		config = tooldata.NewConfig(tooldata.GoWorkspacesOpt([]string{"test", "does/not/exist"}))
+	case "VDLPATH":
+		config = tooldata.NewConfig(tooldata.VDLWorkspacesOpt([]string{"test", "does/not/exist"}))
+	}
+
+	if err := tooldata.SaveConfig(fake.X, config); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	var got, want string
+	switch name {
+	case "GOPATH":
+		want = "GOPATH=" + filepath.Join(fake.X.Root, "test")
+		got = config.GoPath(fake.X)
+	case "VDLPATH":
+		// Make a fake src directory.
+		want = filepath.Join(fake.X.Root, "test", "src")
+		if err := fake.X.NewSeq().MkdirAll(want, 0755).Done(); err != nil {
+			t.Fatalf("%v", err)
+		}
+		want = "VDLPATH=" + want
+		got = config.VDLPath(fake.X)
+	}
+	if got != want {
+		t.Fatalf("unexpected value: got %v, want %v", got, want)
+	}
+}
+
+func TestGoPath(t *testing.T) {
+	testSetPathHelper(t, "GOPATH")
+}
+
+func TestVDLPath(t *testing.T) {
+	testSetPathHelper(t, "VDLPATH")
+}
diff --git a/tooldata/data/AUTHORS b/tooldata/data/AUTHORS
new file mode 100644
index 0000000..574583c
--- /dev/null
+++ b/tooldata/data/AUTHORS
@@ -0,0 +1,9 @@
+# This is the official list of Vanadium authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as:
+#   Name or Organization <email address>
+# The email address is not required for organizations.
+
+# Please keep the list sorted.
diff --git a/tooldata/data/CONTRIBUTING.md b/tooldata/data/CONTRIBUTING.md
new file mode 100644
index 0000000..de03ce0
--- /dev/null
+++ b/tooldata/data/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# Contributing to Vanadium
+
+Vanadium is an open source project.
+
+It is the work of many contributors. We appreciate your help!
+
+## Filing issues
+
+We use a single GitHub repository for [tracking all
+issues](https://github.com/vanadium/issues/issues) across all Vanadium
+repositories.
+
+## Contributing code
+
+Please read the [contribution
+guidelines](https://vanadium.github.io/community/contributing.html) before
+sending patches.
+
+**We do not accept GitHub pull requests.** (We use
+[Gerrit](https://www.gerritcodereview.com/) instead for code reviews.)
+
+Unless otherwise noted, the Vanadium source files are distributed under the
+BSD-style license found in the LICENSE file.
diff --git a/tooldata/data/CONTRIBUTORS b/tooldata/data/CONTRIBUTORS
new file mode 100644
index 0000000..b294e50
--- /dev/null
+++ b/tooldata/data/CONTRIBUTORS
@@ -0,0 +1,10 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people.  For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# Names should be added to this file as:
+#     Name <email address>
diff --git a/tooldata/data/COPYRIGHT b/tooldata/data/COPYRIGHT
new file mode 100644
index 0000000..a26470e
--- /dev/null
+++ b/tooldata/data/COPYRIGHT
@@ -0,0 +1,3 @@
+Copyright [YEAR] 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.
\ No newline at end of file
diff --git a/tooldata/data/LICENSE b/tooldata/data/LICENSE
new file mode 100644
index 0000000..411db13
--- /dev/null
+++ b/tooldata/data/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2015 The Vanadium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/tooldata/data/PATENTS b/tooldata/data/PATENTS
new file mode 100644
index 0000000..d52cc55
--- /dev/null
+++ b/tooldata/data/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Vanadium project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Vanadium, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Vanadium. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Vanadium or any code incorporated within this
+implementation of Vanadium constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Vanadium
+shall terminate as of the date such litigation is filed.
diff --git a/tooldata/data/VERSION b/tooldata/data/VERSION
new file mode 100644
index 0000000..603150b
--- /dev/null
+++ b/tooldata/data/VERSION
@@ -0,0 +1 @@
+jiri-0.1
diff --git a/tooldata/data/aliases.v1.xml b/tooldata/data/aliases.v1.xml
new file mode 100644
index 0000000..d013d35
--- /dev/null
+++ b/tooldata/data/aliases.v1.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" ?>
+<aliases>
+  <email>
+    <canonical>aghassemi@google.com</canonical>
+    <alias>aghassemi@aghassemi-macbookpro.roam.corp.google.com</alias>
+  </email>
+  <email>
+    <canonical>sadovsky@google.com</canonical>
+    <alias>asadovsky@gmail.com</alias>
+  </email>
+  <email>
+    <canonical>nevenag@google.com</canonical>
+    <alias>nevena.golubovic@gmail.com</alias>
+  </email>
+  <email>
+    <canonical>jregan@google.com</canonical>
+    <alias>git-jregan.google.com</alias>
+  </email>
+  <email>
+    <canonical>rjkroege@google.com</canonical>
+    <alias>rjkroege@chromium.org</alias>
+  </email>
+  <email>
+    <canonical>sjr@google.com</canonical>
+    <alias>sjr@jdns.org</alias>
+  </email>
+  <name>
+    <canonical>Ali Ghassemi</canonical>
+    <alias>aghassemi</alias>
+  </name>
+  <name>
+    <canonical>Ankur Taly</canonical>
+    <alias>Ankur</alias>
+  </name>
+  <name>
+    <canonical>Benjamin Prosnitz</canonical>
+    <alias>Benj Prosnitz</alias>
+  </name>
+  <name>
+    <canonical>Dave Presotto</canonical>
+    <alias>David Why Use Two When One Will Do Presotto</alias>
+    <alias>David Presotto</alias>
+  </name>
+  <name>
+    <canonical>Gautham Thambidorai</canonical>
+    <alias>Gautham</alias>
+    <alias>gauthamt</alias>
+  </name>
+  <name>
+    <canonical>Elizabeth Churchill</canonical>
+    <alias>xeeliz</alias>
+  </name>
+  <name>
+    <canonical>Ellen Isaacs</canonical>
+    <alias>lnizix</alias>
+  </name>
+  <name>
+    <canonical>Jeffrey Regan</canonical>
+    <alias>jregan</alias>
+  </name>
+  <name>
+    <canonical>Nicolas LaCasse</canonical>
+    <alias>Nicolas Lacasse</alias>
+  </name>
+  <name>
+    <canonical>William Leler</canonical>
+    <alias>Wm Leler</alias>
+    <alias>wmleler</alias>
+  </name>
+</aliases>
diff --git a/tooldata/data/config.v1.xml b/tooldata/data/config.v1.xml
new file mode 100644
index 0000000..3862c5c
--- /dev/null
+++ b/tooldata/data/config.v1.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" ?>
+<config>
+  <apiCheckProjects>
+    <project>release.go.jiri</project>
+    <project>release.go.v23</project>
+    <project>release.go.x.devtools</project>
+    <project>release.go.x.lib</project>
+  </apiCheckProjects>
+  <copyrightCheckProjects>
+    <project>release.go.jiri</project>
+    <project>release.go.v23</project>
+    <project>release.go.x.devtools</project>
+    <project>release.go.x.jni</project>
+    <project>release.go.x.lib</project>
+    <project>release.go.x.ref</project>
+    <project>release.java</project>
+    <project>release.js.core</project>
+    <project>release.js.syncbase</project>
+    <project>release.mojo.discovery</project>
+    <project>release.mojo.shared</project>
+    <project>release.mojo.syncbase</project>
+    <project>release.mojo.v23proxy</project>
+    <project>release.projects.baku</project>
+    <project>release.projects.browser</project>
+    <project>release.projects.chat</project>
+    <project>release.projects.croupier</project>
+    <project>release.projects.media-sharing</project>
+    <project>release.projects.physical-lock</project>
+    <project>release.projects.pipe2browser</project>
+    <project>release.projects.playground</project>
+    <project>release.projects.reader</project>
+    <project>release.projects.sensorlog</project>
+    <project>release.projects.syncslides</project>
+    <project>release.projects.travel</project>
+    <project>release.projects.todos</project>
+    <project>website</project>
+  </copyrightCheckProjects>
+  <goWorkspaces>
+    <workspace>infrastructure/go</workspace>
+    <workspace>release/go</workspace>
+    <workspace>release/javascript/core/go</workspace>
+    <workspace>release/projects/physical-lock/go</workspace>
+    <workspace>release/projects/sensorlog/go</workspace>
+    <workspace>roadmap/go</workspace>
+    <workspace>third_party/go</workspace>
+  </goWorkspaces>
+  <jenkinsMatrixJobs>
+    <job arch="false" OS="true" parts="false" showOS="true">third_party-go-build</job>
+    <job arch="false" OS="true" parts="false" showOS="true">third_party-go-test</job>
+    <job arch="false" OS="true" parts="true"  showOS="false">third_party-go-race</job>
+    <!-- TODO(spetrovic): this is flaky due to profile issues.  Re-enable after fixing.
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-android-build</job>
+    -->
+    <job arch="false" OS="true" parts="false" showOS="false">vanadium-bootstrap</job>
+    <job arch="true"  OS="true" parts="false" showOS="true">vanadium-go-build</job>
+    <job arch="true"  OS="true" parts="false" showOS="true">vanadium-go-test</job>
+    <job arch="false" OS="true" parts="true"  showOS="false">vanadium-go-race</job>
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-integration-test</job>
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-java-test</job>
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-website-site</job>
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-website-tutorials-core</job>
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-website-tutorials-external</job>
+    <job arch="false" OS="true" parts="false" showOS="true">vanadium-website-tutorials-java</job>
+  </jenkinsMatrixJobs>
+  <projectTests>
+    <project name="release.go.jiri">
+      <test>go</test>
+      <test>java</test>
+      <test>javascript</test>
+      <test>projects</test>
+      <test>vanadium-bootstrap</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.go.v23">
+      <test>go</test>
+      <test>java</test>
+      <test>javascript</test>
+      <test>mojo</test>
+      <test>projects</test>
+      <test>vanadium-bootstrap</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.go.x.devtools">
+      <test>go</test>
+      <test>java</test>
+      <test>javascript</test>
+      <test>mojo</test>
+      <test>projects</test>
+      <test>third_party-go</test>
+      <test>vanadium-bootstrap</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.go.x.jni">
+      <test>java</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.go.x.lib">
+      <test>go</test>
+      <test>java</test>
+      <test>javascript</test>
+      <test>mojo</test>
+      <test>projects</test>
+      <test>vanadium-bootstrap</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.go.x.ref">
+      <test>go</test>
+      <test>java</test>
+      <test>javascript</test>
+      <test>mojo</test>
+      <test>projects</test>
+      <test>vanadium-bootstrap</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.java">
+      <test>java</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.js.core">
+      <test>javascript</test>
+      <test>projects</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.mojo.discovery">
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.mojo.shared">
+      <test>mojo</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.mojo.syncbase">
+      <test>mojo</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.mojo.v23proxy">
+      <test>mojo</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.projects.baku">
+      <test>vanadium-copyright</test>
+      <test>vanadium-baku-test</test>
+    </project>
+    <project name="release.projects.croupier">
+      <test>vanadium-copyright</test>
+      <test>vanadium-croupier-unit</test>
+    </project>
+    <project name="release.projects.playground">
+      <test>vanadium-copyright</test>
+      <test>vanadium-playground-test</test>
+    </project>
+    <project name="release.projects.reader">
+      <test>vanadium-copyright</test>
+      <test>vanadium-reader-test</test>
+    </project>
+    <project name="release.projects.sensorlog">
+      <test>go</test>
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.projects.syncslides">
+      <test>vanadium-copyright</test>
+    </project>
+    <project name="release.projects.travel">
+      <test>vanadium-copyright</test>
+      <test>vanadium-travel-test</test>
+    </project>
+    <project name="third_party">
+      <test>go</test>
+      <test>javascript</test>
+      <test>projects</test>
+      <test>third_party-go</test>
+    </project>
+    <project name="website">
+      <test>vanadium-bootstrap</test>
+      <test>vanadium-copyright</test>
+      <test>vanadium-website-site</test>
+      <test>vanadium-website-tutorials-core</test>
+    </project>
+  </projectTests>
+  <testDependencies>
+    <test name="third_party-go-race">
+      <dependency>third_party-go-test</dependency>
+    </test>
+    <test name="third_party-go-test">
+      <dependency>third_party-go-build</dependency>
+    </test>
+    <test name="vanadium-playground-test">
+      <dependency>vanadium-go-build</dependency>
+    </test>
+    <test name="vanadium-go-race">
+      <dependency>vanadium-go-test</dependency>
+    </test>
+    <test name="vanadium-go-test">
+      <dependency>vanadium-go-build</dependency>
+    </test>
+    <test name="vanadium-integration-test">
+      <dependency>vanadium-go-build</dependency>
+    </test>
+  </testDependencies>
+  <testGroups>
+    <group name="go">
+      <test>vanadium-go-api</test>
+      <test>vanadium-go-build</test>
+      <test>vanadium-go-depcop</test>
+      <test>vanadium-go-format</test>
+      <test>vanadium-go-generate</test>
+      <test>vanadium-go-race</test>
+      <test>vanadium-go-test</test>
+      <test>vanadium-go-vdl</test>
+      <!-- TODO(sjr): figure out what to do with go vet. It currently produces
+           false positives which we can't workaround with CLs like
+           https://vanadium.googlesource.com/third_party/+/5e07c533eb2dfa525b5d0f3782112a2a5c4b6912,
+           because go vet moved back into core go as of Go 1.5.
+      <test>vanadium-go-vet</test>
+      -->
+      <test>vanadium-integration-test</test>
+      <test>vanadium-regression-test</test>
+      <test>vanadium-prod-services-test</test>
+      <test>vanadium-website-tutorials-core</test>
+    </group>
+    <group name="java">
+      <test>baku-android-build</test>
+      <test>baku-java-test</test>
+      <!-- TODO(spetrovic): this is flaky due to profile issues.  Re-enable after fixing.
+      <test>vanadium-android-build</test>
+      -->
+      <test>vanadium-java-test</test>
+    </group>
+    <group name="javascript">
+      <test>vanadium-js-unit</test>
+      <test>vanadium-js-vdl</test>
+      <test>vanadium-js-vdl-audit</test>
+      <test>vanadium-js-vom</test>
+    </group>
+    <group name="mojo">
+      <!-- NOTE(caprita): this is flaky. See v.io/i/1240
+      <test>vanadium-mojo-discovery-test</test>
+      -->
+      <test>vanadium-mojo-syncbase-test</test>
+      <test>vanadium-mojo-v23proxy-unit-test</test>
+      <!-- NOTE(caprita): this is flaky.  See v.io/i/1226
+      <test>vanadium-mojo-v23proxy-integration-test</test>
+      -->
+    </group>
+    <group name="projects">
+      <test>vanadium-playground-test</test>
+    </group>
+    <group name="third_party-go">
+      <test>third_party-go-build</test>
+      <test>third_party-go-race</test>
+      <test>third_party-go-test</test>
+    </group>
+  </testGroups>
+  <testParts>
+    <test name="vanadium-go-race">
+      <part>v.io/x/ref/services/device/...</part>
+      <part>v.io/x/ref/services/agent/...,v.io/x/ref/services/internal/...,v.io/x/ref/services/syncbase/...</part>
+      <part>v.io/x/ref/services/...</part>
+      <part>v.io/x/ref/runtime/...</part>
+      <part>v.io/x/ref/...</part>
+      <part>v.io/x/devtools/...</part>
+    </test>
+    <test name="third_party-go-race">
+      <part>golang.org/x/tools/go/ssa/...,golang.org/x/tools/go/pointer/...</part>
+      <part>golang.org/x/tools/...</part>
+      <part>golang.org/x/crypto/...</part>
+    </test>
+  </testParts>
+  <vdlWorkspaces>
+    <workspace>release/go</workspace>
+    <workspace>release/javascript/core/go</workspace>
+    <workspace>release/projects/physical-lock/go</workspace>
+    <workspace>release/projects/sensorlog/go</workspace>
+    <workspace>roadmap/go</workspace>
+  </vdlWorkspaces>
+</config>
diff --git a/tooldata/data/crosstool-ng-1.19.0.config b/tooldata/data/crosstool-ng-1.19.0.config
new file mode 100644
index 0000000..8808fd2
--- /dev/null
+++ b/tooldata/data/crosstool-ng-1.19.0.config
@@ -0,0 +1,538 @@
+#
+# Automatically generated make config: don't edit
+# crosstool-NG 1.19.0 Configuration
+# Sat Jul 12 19:46:11 2014
+#
+CT_CONFIGURE_has_xz=y
+CT_CONFIGURE_has_cvs=y
+CT_CONFIGURE_has_svn=y
+CT_MODULES=y
+
+#
+# Paths and misc options
+#
+
+#
+# crosstool-NG behavior
+#
+# CT_OBSOLETE is not set
+# CT_EXPERIMENTAL is not set
+# CT_DEBUG_CT is not set
+
+#
+# Paths
+#
+CT_LOCAL_TARBALLS_DIR=""
+CT_WORK_DIR="${CT_TOP_DIR}/.build"
+CT_PREFIX_DIR="/usr/local/vanadium/xgcc/${CT_TARGET}"
+CT_INSTALL_DIR="${CT_PREFIX_DIR}"
+CT_RM_RF_PREFIX_DIR=y
+CT_REMOVE_DOCS=y
+CT_INSTALL_DIR_RO=y
+CT_STRIP_ALL_TOOLCHAIN_EXECUTABLES=y
+
+#
+# Downloading
+#
+# CT_FORBID_DOWNLOAD is not set
+# CT_FORCE_DOWNLOAD is not set
+CT_CONNECT_TIMEOUT=10
+# CT_ONLY_DOWNLOAD is not set
+# CT_USE_MIRROR is not set
+
+#
+# Extracting
+#
+# CT_FORCE_EXTRACT is not set
+CT_OVERIDE_CONFIG_GUESS_SUB=y
+# CT_ONLY_EXTRACT is not set
+CT_PATCH_BUNDLED=y
+# CT_PATCH_LOCAL is not set
+# CT_PATCH_BUNDLED_LOCAL is not set
+# CT_PATCH_LOCAL_BUNDLED is not set
+# CT_PATCH_BUNDLED_FALLBACK_LOCAL is not set
+# CT_PATCH_LOCAL_FALLBACK_BUNDLED is not set
+# CT_PATCH_NONE is not set
+CT_PATCH_ORDER="bundled"
+
+#
+# Build behavior
+#
+CT_PARALLEL_JOBS=0
+CT_LOAD=""
+CT_USE_PIPES=y
+CT_EXTRA_CFLAGS_FOR_BUILD=""
+CT_EXTRA_LDFLAGS_FOR_BUILD=""
+CT_EXTRA_CFLAGS_FOR_HOST=""
+CT_EXTRA_LDFLAGS_FOR_HOST=""
+# CT_CONFIG_SHELL_SH is not set
+# CT_CONFIG_SHELL_ASH is not set
+CT_CONFIG_SHELL_BASH=y
+# CT_CONFIG_SHELL_CUSTOM is not set
+CT_CONFIG_SHELL="${bash}"
+
+#
+# Logging
+#
+# CT_LOG_ERROR is not set
+# CT_LOG_WARN is not set
+CT_LOG_INFO=y
+# CT_LOG_EXTRA is not set
+# CT_LOG_ALL is not set
+# CT_LOG_DEBUG is not set
+CT_LOG_LEVEL_MAX="INFO"
+# CT_LOG_SEE_TOOLS_WARN is not set
+CT_LOG_PROGRESS_BAR=y
+CT_LOG_TO_FILE=y
+CT_LOG_FILE_COMPRESS=y
+
+#
+# Target options
+#
+CT_ARCH="arm"
+CT_ARCH_SUPPORTS_BOTH_MMU=y
+CT_ARCH_SUPPORTS_BOTH_ENDIAN=y
+CT_ARCH_SUPPORTS_32=y
+CT_ARCH_SUPPORTS_WITH_ARCH=y
+CT_ARCH_SUPPORTS_WITH_CPU=y
+CT_ARCH_SUPPORTS_WITH_TUNE=y
+CT_ARCH_SUPPORTS_WITH_FLOAT=y
+CT_ARCH_SUPPORTS_WITH_FPU=y
+CT_ARCH_SUPPORTS_SOFTFP=y
+CT_ARCH_DEFAULT_HAS_MMU=y
+CT_ARCH_DEFAULT_LE=y
+CT_ARCH_DEFAULT_32=y
+CT_ARCH_ARCH=""
+CT_ARCH_CPU=""
+CT_ARCH_TUNE=""
+CT_ARCH_FPU=""
+# CT_ARCH_BE is not set
+CT_ARCH_LE=y
+CT_ARCH_32=y
+CT_ARCH_BITNESS=32
+CT_ARCH_FLOAT_HW=y
+# CT_ARCH_FLOAT_SW is not set
+CT_TARGET_CFLAGS=""
+CT_TARGET_LDFLAGS=""
+# CT_ARCH_alpha is not set
+CT_ARCH_arm=y
+# CT_ARCH_avr32 is not set
+# CT_ARCH_blackfin is not set
+# CT_ARCH_m68k is not set
+# CT_ARCH_mips is not set
+# CT_ARCH_powerpc is not set
+# CT_ARCH_s390 is not set
+# CT_ARCH_sh is not set
+# CT_ARCH_sparc is not set
+# CT_ARCH_x86 is not set
+CT_ARCH_alpha_AVAILABLE=y
+CT_ARCH_arm_AVAILABLE=y
+CT_ARCH_avr32_AVAILABLE=y
+CT_ARCH_blackfin_AVAILABLE=y
+CT_ARCH_m68k_AVAILABLE=y
+CT_ARCH_microblaze_AVAILABLE=y
+CT_ARCH_mips_AVAILABLE=y
+CT_ARCH_powerpc_AVAILABLE=y
+CT_ARCH_s390_AVAILABLE=y
+CT_ARCH_sh_AVAILABLE=y
+CT_ARCH_sparc_AVAILABLE=y
+CT_ARCH_x86_AVAILABLE=y
+CT_ARCH_SUFFIX=""
+
+#
+# Generic target options
+#
+# CT_MULTILIB is not set
+CT_ARCH_USE_MMU=y
+CT_ARCH_ENDIAN="little"
+
+#
+# Target optimisations
+#
+# CT_ARCH_FLOAT_SOFTFP is not set
+CT_ARCH_FLOAT="hard"
+
+#
+# arm other options
+#
+CT_ARCH_ARM_MODE="arm"
+CT_ARCH_ARM_MODE_ARM=y
+# CT_ARCH_ARM_MODE_THUMB is not set
+# CT_ARCH_ARM_INTERWORKING is not set
+CT_ARCH_ARM_EABI_FORCE=y
+CT_ARCH_ARM_EABI=y
+
+#
+# Toolchain options
+#
+
+#
+# General toolchain options
+#
+CT_FORCE_SYSROOT=y
+CT_USE_SYSROOT=y
+CT_SYSROOT_NAME="sysroot"
+CT_SYSROOT_DIR_PREFIX=""
+CT_WANTS_STATIC_LINK=y
+# CT_STATIC_TOOLCHAIN is not set
+CT_TOOLCHAIN_PKGVERSION=""
+CT_TOOLCHAIN_BUGURL=""
+
+#
+# Tuple completion and aliasing
+#
+CT_TARGET_VENDOR="unknown"
+CT_TARGET_ALIAS_SED_EXPR=""
+CT_TARGET_ALIAS=""
+
+#
+# Toolchain type
+#
+CT_CROSS=y
+# CT_CANADIAN is not set
+CT_TOOLCHAIN_TYPE="cross"
+
+#
+# Build system
+#
+CT_BUILD=""
+CT_BUILD_PREFIX=""
+CT_BUILD_SUFFIX=""
+
+#
+# Misc options
+#
+# CT_TOOLCHAIN_ENABLE_NLS is not set
+
+#
+# Operating System
+#
+CT_KERNEL_SUPPORTS_SHARED_LIBS=y
+CT_KERNEL="linux"
+CT_KERNEL_VERSION="3.10.2"
+# CT_KERNEL_bare_metal is not set
+CT_KERNEL_linux=y
+CT_KERNEL_bare_metal_AVAILABLE=y
+CT_KERNEL_linux_AVAILABLE=y
+CT_KERNEL_V_3_10=y
+# CT_KERNEL_V_3_9 is not set
+# CT_KERNEL_V_3_8 is not set
+# CT_KERNEL_V_3_7 is not set
+# CT_KERNEL_V_3_6 is not set
+# CT_KERNEL_V_3_5 is not set
+# CT_KERNEL_V_3_4 is not set
+# CT_KERNEL_V_3_3 is not set
+# CT_KERNEL_V_3_2 is not set
+# CT_KERNEL_V_3_1 is not set
+# CT_KERNEL_V_3_0 is not set
+# CT_KERNEL_V_2_6_39 is not set
+# CT_KERNEL_V_2_6_38 is not set
+# CT_KERNEL_V_2_6_37 is not set
+# CT_KERNEL_V_2_6_36 is not set
+# CT_KERNEL_V_2_6_33 is not set
+# CT_KERNEL_V_2_6_32 is not set
+# CT_KERNEL_V_2_6_31 is not set
+# CT_KERNEL_V_2_6_27 is not set
+# CT_KERNEL_LINUX_CUSTOM is not set
+CT_KERNEL_windows_AVAILABLE=y
+
+#
+# Common kernel options
+#
+CT_SHARED_LIBS=y
+
+#
+# linux other options
+#
+CT_KERNEL_LINUX_VERBOSITY_0=y
+# CT_KERNEL_LINUX_VERBOSITY_1 is not set
+# CT_KERNEL_LINUX_VERBOSITY_2 is not set
+CT_KERNEL_LINUX_VERBOSE_LEVEL=0
+CT_KERNEL_LINUX_INSTALL_CHECK=y
+
+#
+# Binary utilities
+#
+CT_ARCH_BINFMT_ELF=y
+CT_BINUTILS="binutils"
+CT_BINUTILS_binutils=y
+
+#
+# GNU binutils
+#
+CT_BINUTILS_V_2_22=y
+# CT_BINUTILS_V_2_21_53 is not set
+# CT_BINUTILS_V_2_21_1a is not set
+# CT_BINUTILS_V_2_20_1a is not set
+# CT_BINUTILS_V_2_19_1a is not set
+# CT_BINUTILS_V_2_18a is not set
+CT_BINUTILS_VERSION="2.22"
+CT_BINUTILS_2_22_or_later=y
+CT_BINUTILS_2_21_or_later=y
+CT_BINUTILS_2_20_or_later=y
+CT_BINUTILS_2_19_or_later=y
+CT_BINUTILS_2_18_or_later=y
+CT_BINUTILS_HAS_HASH_STYLE=y
+CT_BINUTILS_HAS_GOLD=y
+CT_BINUTILS_GOLD_SUPPORTS_ARCH=y
+CT_BINUTILS_HAS_PLUGINS=y
+CT_BINUTILS_HAS_PKGVERSION_BUGURL=y
+CT_BINUTILS_FORCE_LD_BFD=y
+CT_BINUTILS_LINKER_LD=y
+# CT_BINUTILS_LINKER_LD_GOLD is not set
+# CT_BINUTILS_LINKER_GOLD_LD is not set
+CT_BINUTILS_LINKERS_LIST="ld"
+CT_BINUTILS_LINKER_DEFAULT="bfd"
+# CT_BINUTILS_PLUGINS is not set
+CT_BINUTILS_EXTRA_CONFIG_ARRAY=""
+# CT_BINUTILS_FOR_TARGET is not set
+
+#
+# binutils other options
+#
+
+#
+# C compiler
+#
+CT_CC="gcc"
+CT_CC_VERSION="4.8.1"
+CT_CC_CORE_PASSES_NEEDED=y
+CT_CC_gcc=y
+# CT_CC_GCC_SHOW_LINARO is not set
+CT_CC_V_4_8_1=y
+# CT_CC_V_4_8_0 is not set
+# CT_CC_V_4_7_3 is not set
+# CT_CC_V_4_7_2 is not set
+# CT_CC_V_4_7_1 is not set
+# CT_CC_V_4_7_0 is not set
+# CT_CC_V_4_6_4 is not set
+# CT_CC_V_4_6_3 is not set
+# CT_CC_V_4_6_2 is not set
+# CT_CC_V_4_6_1 is not set
+# CT_CC_V_4_6_0 is not set
+# CT_CC_V_4_5_3 is not set
+# CT_CC_V_4_5_2 is not set
+# CT_CC_V_4_5_1 is not set
+# CT_CC_V_4_5_0 is not set
+# CT_CC_V_4_4_7 is not set
+# CT_CC_V_4_4_6 is not set
+# CT_CC_V_4_4_5 is not set
+# CT_CC_V_4_4_4 is not set
+# CT_CC_V_4_4_3 is not set
+# CT_CC_V_4_4_2 is not set
+# CT_CC_V_4_4_1 is not set
+# CT_CC_V_4_4_0 is not set
+# CT_CC_V_4_3_6 is not set
+# CT_CC_V_4_3_5 is not set
+# CT_CC_V_4_3_4 is not set
+# CT_CC_V_4_3_3 is not set
+# CT_CC_V_4_3_2 is not set
+# CT_CC_V_4_3_1 is not set
+# CT_CC_V_4_2_4 is not set
+# CT_CC_V_4_2_2 is not set
+CT_CC_GCC_4_2_or_later=y
+CT_CC_GCC_4_3_or_later=y
+CT_CC_GCC_4_4_or_later=y
+CT_CC_GCC_4_5_or_later=y
+CT_CC_GCC_4_6_or_later=y
+CT_CC_GCC_4_7_or_later=y
+CT_CC_GCC_4_8=y
+CT_CC_GCC_4_8_or_later=y
+CT_CC_GCC_HAS_GRAPHITE=y
+CT_CC_GCC_USE_GRAPHITE=y
+CT_CC_GCC_HAS_LTO=y
+CT_CC_GCC_USE_LTO=y
+CT_CC_GCC_HAS_PKGVERSION_BUGURL=y
+CT_CC_GCC_HAS_BUILD_ID=y
+CT_CC_GCC_HAS_LNK_HASH_STYLE=y
+CT_CC_GCC_USE_GMP_MPFR=y
+CT_CC_GCC_USE_MPC=y
+CT_CC_GCC_HAS_LIBQUADMATH=y
+# CT_CC_LANG_FORTRAN is not set
+CT_CC_SUPPORT_CXX=y
+CT_CC_SUPPORT_FORTRAN=y
+CT_CC_SUPPORT_JAVA=y
+CT_CC_SUPPORT_ADA=y
+CT_CC_SUPPORT_OBJC=y
+CT_CC_SUPPORT_OBJCXX=y
+
+#
+# Additional supported languages:
+#
+CT_CC_LANG_CXX=y
+# CT_CC_LANG_JAVA is not set
+
+#
+# gcc other options
+#
+CT_CC_ENABLE_CXX_FLAGS=""
+CT_CC_CORE_EXTRA_CONFIG_ARRAY=""
+CT_CC_EXTRA_CONFIG_ARRAY=""
+CT_CC_STATIC_LIBSTDCXX=y
+# CT_CC_GCC_SYSTEM_ZLIB is not set
+
+#
+# Optimisation features
+#
+
+#
+# Settings for libraries running on target
+#
+CT_CC_GCC_ENABLE_TARGET_OPTSPACE=y
+# CT_CC_GCC_LIBMUDFLAP is not set
+# CT_CC_GCC_LIBGOMP is not set
+# CT_CC_GCC_LIBSSP is not set
+# CT_CC_GCC_LIBQUADMATH is not set
+
+#
+# Misc. obscure options.
+#
+CT_CC_CXA_ATEXIT=y
+# CT_CC_GCC_DISABLE_PCH is not set
+CT_CC_GCC_SJLJ_EXCEPTIONS=m
+CT_CC_GCC_LDBL_128=m
+# CT_CC_GCC_BUILD_ID is not set
+CT_CC_GCC_LNK_HASH_STYLE_DEFAULT=y
+# CT_CC_GCC_LNK_HASH_STYLE_SYSV is not set
+# CT_CC_GCC_LNK_HASH_STYLE_GNU is not set
+# CT_CC_GCC_LNK_HASH_STYLE_BOTH is not set
+CT_CC_GCC_LNK_HASH_STYLE=""
+
+#
+# C-library
+#
+CT_LIBC="eglibc"
+CT_LIBC_VERSION="2_17"
+CT_LIBC_eglibc=y
+# CT_LIBC_glibc is not set
+# CT_LIBC_uClibc is not set
+CT_LIBC_eglibc_AVAILABLE=y
+CT_LIBC_EGLIBC_V_2_17=y
+# CT_LIBC_EGLIBC_V_2_16 is not set
+# CT_LIBC_EGLIBC_V_2_15 is not set
+# CT_LIBC_EGLIBC_V_2_14 is not set
+# CT_LIBC_EGLIBC_V_2_13 is not set
+# CT_LIBC_EGLIBC_V_2_12 is not set
+# CT_LIBC_EGLIBC_V_2_11 is not set
+# CT_LIBC_EGLIBC_V_2_10 is not set
+# CT_LIBC_EGLIBC_V_2_9 is not set
+# CT_LIBC_EGLIBC_V_TRUNK is not set
+CT_LIBC_EGLIBC_2_16_or_later=y
+CT_EGLIBC_REVISION="HEAD"
+# CT_EGLIBC_HTTP is not set
+# CT_EGLIBC_CHECKOUT is not set
+# CT_EGLIBC_OPT_SIZE is not set
+# CT_EGLIBC_CUSTOM_CONFIG is not set
+CT_LIBC_glibc_AVAILABLE=y
+CT_LIBC_mingw_AVAILABLE=y
+CT_LIBC_newlib_AVAILABLE=y
+CT_LIBC_none_AVAILABLE=y
+CT_LIBC_uClibc_AVAILABLE=y
+CT_LIBC_SUPPORT_THREADS_ANY=y
+CT_LIBC_SUPPORT_NPTL=y
+CT_LIBC_SUPPORT_LINUXTHREADS=y
+CT_THREADS="nptl"
+
+#
+# Common C library options
+#
+CT_THREADS_NPTL=y
+# CT_THREADS_LINUXTHREADS is not set
+CT_LIBC_XLDD=y
+
+#
+# eglibc other options
+#
+CT_LIBC_GLIBC_MAY_FORCE_PORTS=y
+CT_LIBC_glibc_familly=y
+CT_LIBC_GLIBC_EXTRA_CONFIG_ARRAY=""
+CT_LIBC_GLIBC_CONFIGPARMS=""
+CT_LIBC_GLIBC_EXTRA_CFLAGS=""
+CT_LIBC_EXTRA_CC_ARGS=""
+# CT_LIBC_DISABLE_VERSIONING is not set
+CT_LIBC_OLDEST_ABI=""
+CT_LIBC_GLIBC_FORCE_UNWIND=y
+CT_LIBC_GLIBC_USE_PORTS=y
+CT_LIBC_ADDONS_LIST=""
+
+#
+# WARNING !!!
+#
+
+#
+#   For glibc >= 2.8, it can happen that the tarballs
+#
+
+#
+#   for the addons are not available for download.
+#
+
+#
+#   If that happens, bad luck... Try a previous version
+#
+
+#
+#   or try again later... :-(
+#
+# CT_LIBC_LOCALES is not set
+# CT_LIBC_GLIBC_KERNEL_VERSION_NONE is not set
+CT_LIBC_GLIBC_KERNEL_VERSION_AS_HEADERS=y
+# CT_LIBC_GLIBC_KERNEL_VERSION_CHOSEN is not set
+CT_LIBC_GLIBC_MIN_KERNEL="3.10.2"
+
+#
+# Debug facilities
+#
+# CT_DEBUG_dmalloc is not set
+# CT_DEBUG_duma is not set
+# CT_DEBUG_gdb is not set
+# CT_DEBUG_ltrace is not set
+# CT_DEBUG_strace is not set
+
+#
+# Companion libraries
+#
+CT_COMPLIBS_NEEDED=y
+CT_GMP_NEEDED=y
+CT_MPFR_NEEDED=y
+CT_ISL_NEEDED=y
+CT_CLOOG_NEEDED=y
+CT_MPC_NEEDED=y
+CT_COMPLIBS=y
+CT_GMP=y
+CT_MPFR=y
+CT_ISL=y
+CT_CLOOG=y
+CT_MPC=y
+CT_GMP_V_5_1_1=y
+# CT_GMP_V_5_0_2 is not set
+# CT_GMP_V_5_0_1 is not set
+# CT_GMP_V_4_3_2 is not set
+# CT_GMP_V_4_3_1 is not set
+# CT_GMP_V_4_3_0 is not set
+CT_GMP_VERSION="5.1.1"
+CT_MPFR_V_3_1_2=y
+# CT_MPFR_V_3_1_0 is not set
+# CT_MPFR_V_3_0_1 is not set
+# CT_MPFR_V_3_0_0 is not set
+# CT_MPFR_V_2_4_2 is not set
+# CT_MPFR_V_2_4_1 is not set
+# CT_MPFR_V_2_4_0 is not set
+CT_MPFR_VERSION="3.1.2"
+CT_ISL_V_0_11_1=y
+CT_ISL_VERSION="0.11.1"
+CT_CLOOG_V_0_18_0=y
+CT_CLOOG_VERSION="0.18.0"
+CT_CLOOG_0_18_or_later=y
+CT_MPC_V_1_0_1=y
+# CT_MPC_V_1_0 is not set
+# CT_MPC_V_0_9 is not set
+# CT_MPC_V_0_8_2 is not set
+# CT_MPC_V_0_8_1 is not set
+# CT_MPC_V_0_7 is not set
+CT_MPC_VERSION="1.0.1"
+
+#
+# Companion libraries common options
+#
+# CT_COMPLIBS_CHECK is not set
diff --git a/tooldata/data/crosstool-ng-1.20.0.config b/tooldata/data/crosstool-ng-1.20.0.config
new file mode 100644
index 0000000..191cfb5
--- /dev/null
+++ b/tooldata/data/crosstool-ng-1.20.0.config
@@ -0,0 +1,633 @@
+#
+# Automatically generated make config: don't edit
+# crosstool-NG 1.20.0 Configuration
+# Wed Apr 29 23:28:05 2015
+#
+CT_CONFIGURE_has_make381=y
+CT_CONFIGURE_has_xz=y
+CT_CONFIGURE_has_cvs=y
+CT_CONFIGURE_has_svn=y
+CT_MODULES=y
+
+#
+# Paths and misc options
+#
+
+#
+# crosstool-NG behavior
+#
+# CT_OBSOLETE is not set
+# CT_EXPERIMENTAL is not set
+# CT_DEBUG_CT is not set
+
+#
+# Paths
+#
+CT_LOCAL_TARBALLS_DIR=""
+CT_WORK_DIR="${CT_TOP_DIR}/.build"
+CT_PREFIX_DIR="/usr/local/vanadium/xgcc/${CT_TARGET}"
+CT_INSTALL_DIR="${CT_PREFIX_DIR}"
+CT_RM_RF_PREFIX_DIR=y
+CT_REMOVE_DOCS=y
+CT_INSTALL_DIR_RO=y
+CT_STRIP_ALL_TOOLCHAIN_EXECUTABLES=y
+
+#
+# Downloading
+#
+# CT_FORBID_DOWNLOAD is not set
+# CT_FORCE_DOWNLOAD is not set
+CT_CONNECT_TIMEOUT=10
+# CT_ONLY_DOWNLOAD is not set
+# CT_USE_MIRROR is not set
+
+#
+# Extracting
+#
+# CT_FORCE_EXTRACT is not set
+CT_OVERIDE_CONFIG_GUESS_SUB=y
+# CT_ONLY_EXTRACT is not set
+CT_PATCH_BUNDLED=y
+# CT_PATCH_LOCAL is not set
+# CT_PATCH_BUNDLED_LOCAL is not set
+# CT_PATCH_LOCAL_BUNDLED is not set
+# CT_PATCH_BUNDLED_FALLBACK_LOCAL is not set
+# CT_PATCH_LOCAL_FALLBACK_BUNDLED is not set
+# CT_PATCH_NONE is not set
+CT_PATCH_ORDER="bundled"
+
+#
+# Build behavior
+#
+CT_PARALLEL_JOBS=0
+CT_LOAD=""
+CT_USE_PIPES=y
+CT_EXTRA_CFLAGS_FOR_BUILD=""
+CT_EXTRA_LDFLAGS_FOR_BUILD=""
+CT_EXTRA_CFLAGS_FOR_HOST=""
+CT_EXTRA_LDFLAGS_FOR_HOST=""
+# CT_CONFIG_SHELL_SH is not set
+# CT_CONFIG_SHELL_ASH is not set
+CT_CONFIG_SHELL_BASH=y
+# CT_CONFIG_SHELL_CUSTOM is not set
+CT_CONFIG_SHELL="${bash}"
+
+#
+# Logging
+#
+# CT_LOG_ERROR is not set
+# CT_LOG_WARN is not set
+# CT_LOG_INFO is not set
+# CT_LOG_EXTRA is not set
+CT_LOG_ALL=y
+# CT_LOG_DEBUG is not set
+CT_LOG_LEVEL_MAX="ALL"
+# CT_LOG_SEE_TOOLS_WARN is not set
+CT_LOG_TO_FILE=y
+CT_LOG_FILE_COMPRESS=y
+
+#
+# Target options
+#
+CT_ARCH="arm"
+CT_ARCH_SUPPORTS_BOTH_MMU=y
+CT_ARCH_SUPPORTS_BOTH_ENDIAN=y
+CT_ARCH_SUPPORTS_32=y
+CT_ARCH_SUPPORTS_64=y
+CT_ARCH_SUPPORTS_WITH_ARCH=y
+CT_ARCH_SUPPORTS_WITH_CPU=y
+CT_ARCH_SUPPORTS_WITH_TUNE=y
+CT_ARCH_SUPPORTS_WITH_FLOAT=y
+CT_ARCH_SUPPORTS_WITH_FPU=y
+CT_ARCH_SUPPORTS_SOFTFP=y
+CT_ARCH_DEFAULT_HAS_MMU=y
+CT_ARCH_DEFAULT_LE=y
+CT_ARCH_DEFAULT_32=y
+CT_ARCH_ARCH=""
+CT_ARCH_CPU=""
+CT_ARCH_TUNE=""
+CT_ARCH_FPU=""
+# CT_ARCH_BE is not set
+CT_ARCH_LE=y
+CT_ARCH_32=y
+# CT_ARCH_64 is not set
+CT_ARCH_BITNESS=32
+CT_ARCH_FLOAT_HW=y
+# CT_ARCH_FLOAT_SW is not set
+CT_TARGET_CFLAGS=""
+CT_TARGET_LDFLAGS=""
+# CT_ARCH_alpha is not set
+CT_ARCH_arm=y
+# CT_ARCH_avr32 is not set
+# CT_ARCH_blackfin is not set
+# CT_ARCH_m68k is not set
+# CT_ARCH_mips is not set
+# CT_ARCH_powerpc is not set
+# CT_ARCH_s390 is not set
+# CT_ARCH_sh is not set
+# CT_ARCH_sparc is not set
+# CT_ARCH_x86 is not set
+CT_ARCH_alpha_AVAILABLE=y
+CT_ARCH_arm_AVAILABLE=y
+CT_ARCH_avr32_AVAILABLE=y
+CT_ARCH_blackfin_AVAILABLE=y
+CT_ARCH_m68k_AVAILABLE=y
+CT_ARCH_microblaze_AVAILABLE=y
+CT_ARCH_mips_AVAILABLE=y
+CT_ARCH_powerpc_AVAILABLE=y
+CT_ARCH_s390_AVAILABLE=y
+CT_ARCH_sh_AVAILABLE=y
+CT_ARCH_sparc_AVAILABLE=y
+CT_ARCH_x86_AVAILABLE=y
+CT_ARCH_SUFFIX=""
+
+#
+# Generic target options
+#
+# CT_MULTILIB is not set
+CT_ARCH_USE_MMU=y
+CT_ARCH_ENDIAN="little"
+
+#
+# Target optimisations
+#
+# CT_ARCH_FLOAT_AUTO is not set
+# CT_ARCH_FLOAT_SOFTFP is not set
+CT_ARCH_FLOAT="hard"
+
+#
+# arm other options
+#
+CT_ARCH_ARM_MODE="arm"
+CT_ARCH_ARM_MODE_ARM=y
+# CT_ARCH_ARM_MODE_THUMB is not set
+# CT_ARCH_ARM_INTERWORKING is not set
+CT_ARCH_ARM_EABI_FORCE=y
+CT_ARCH_ARM_EABI=y
+
+#
+# Toolchain options
+#
+
+#
+# General toolchain options
+#
+CT_FORCE_SYSROOT=y
+CT_USE_SYSROOT=y
+CT_SYSROOT_NAME="sysroot"
+CT_SYSROOT_DIR_PREFIX=""
+CT_WANTS_STATIC_LINK=y
+# CT_STATIC_TOOLCHAIN is not set
+CT_TOOLCHAIN_PKGVERSION=""
+CT_TOOLCHAIN_BUGURL=""
+
+#
+# Tuple completion and aliasing
+#
+CT_TARGET_VENDOR="unknown"
+CT_TARGET_ALIAS_SED_EXPR=""
+CT_TARGET_ALIAS=""
+
+#
+# Toolchain type
+#
+CT_CROSS=y
+# CT_CANADIAN is not set
+CT_TOOLCHAIN_TYPE="cross"
+
+#
+# Build system
+#
+CT_BUILD=""
+CT_BUILD_PREFIX=""
+CT_BUILD_SUFFIX=""
+
+#
+# Misc options
+#
+# CT_TOOLCHAIN_ENABLE_NLS is not set
+
+#
+# Operating System
+#
+CT_KERNEL_SUPPORTS_SHARED_LIBS=y
+CT_KERNEL="linux"
+CT_KERNEL_VERSION="3.10.47"
+# CT_KERNEL_bare_metal is not set
+CT_KERNEL_linux=y
+CT_KERNEL_bare_metal_AVAILABLE=y
+CT_KERNEL_linux_AVAILABLE=y
+# CT_KERNEL_V_3_15 is not set
+# CT_KERNEL_V_3_14 is not set
+# CT_KERNEL_V_3_13 is not set
+# CT_KERNEL_V_3_12 is not set
+# CT_KERNEL_V_3_11 is not set
+CT_KERNEL_V_3_10=y
+# CT_KERNEL_V_3_9 is not set
+# CT_KERNEL_V_3_8 is not set
+# CT_KERNEL_V_3_7 is not set
+# CT_KERNEL_V_3_6 is not set
+# CT_KERNEL_V_3_5 is not set
+# CT_KERNEL_V_3_4 is not set
+# CT_KERNEL_V_3_3 is not set
+# CT_KERNEL_V_3_2 is not set
+# CT_KERNEL_V_3_1 is not set
+# CT_KERNEL_V_3_0 is not set
+# CT_KERNEL_V_2_6_39 is not set
+# CT_KERNEL_V_2_6_38 is not set
+# CT_KERNEL_V_2_6_37 is not set
+# CT_KERNEL_V_2_6_36 is not set
+# CT_KERNEL_V_2_6_33 is not set
+# CT_KERNEL_V_2_6_32 is not set
+# CT_KERNEL_V_2_6_31 is not set
+# CT_KERNEL_V_2_6_27 is not set
+# CT_KERNEL_LINUX_CUSTOM is not set
+CT_KERNEL_windows_AVAILABLE=y
+
+#
+# Common kernel options
+#
+CT_SHARED_LIBS=y
+
+#
+# linux other options
+#
+CT_KERNEL_LINUX_VERBOSITY_0=y
+# CT_KERNEL_LINUX_VERBOSITY_1 is not set
+# CT_KERNEL_LINUX_VERBOSITY_2 is not set
+CT_KERNEL_LINUX_VERBOSE_LEVEL=0
+CT_KERNEL_LINUX_INSTALL_CHECK=y
+
+#
+# Binary utilities
+#
+CT_ARCH_BINFMT_ELF=y
+CT_BINUTILS="binutils"
+CT_BINUTILS_binutils=y
+
+#
+# GNU binutils
+#
+CT_BINUTILS_V_2_22=y
+# CT_BINUTILS_V_2_21_53 is not set
+# CT_BINUTILS_V_2_21_1a is not set
+# CT_BINUTILS_V_2_20_1a is not set
+# CT_BINUTILS_V_2_19_1a is not set
+# CT_BINUTILS_V_2_18a is not set
+CT_BINUTILS_VERSION="2.22"
+CT_BINUTILS_2_22_or_later=y
+CT_BINUTILS_2_21_or_later=y
+CT_BINUTILS_2_20_or_later=y
+CT_BINUTILS_2_19_or_later=y
+CT_BINUTILS_2_18_or_later=y
+CT_BINUTILS_HAS_HASH_STYLE=y
+CT_BINUTILS_HAS_GOLD=y
+CT_BINUTILS_GOLD_SUPPORTS_ARCH=y
+CT_BINUTILS_HAS_PLUGINS=y
+CT_BINUTILS_HAS_PKGVERSION_BUGURL=y
+CT_BINUTILS_FORCE_LD_BFD=y
+CT_BINUTILS_LINKER_LD=y
+# CT_BINUTILS_LINKER_LD_GOLD is not set
+# CT_BINUTILS_LINKER_GOLD_LD is not set
+CT_BINUTILS_LINKERS_LIST="ld"
+CT_BINUTILS_LINKER_DEFAULT="bfd"
+# CT_BINUTILS_PLUGINS is not set
+CT_BINUTILS_EXTRA_CONFIG_ARRAY=""
+# CT_BINUTILS_FOR_TARGET is not set
+
+#
+# binutils other options
+#
+
+#
+# C-library
+#
+CT_LIBC="glibc"
+CT_LIBC_VERSION="2.19"
+# CT_LIBC_eglibc is not set
+CT_LIBC_glibc=y
+# CT_LIBC_musl is not set
+# CT_LIBC_uClibc is not set
+CT_LIBC_eglibc_AVAILABLE=y
+CT_THREADS="nptl"
+CT_LIBC_glibc_AVAILABLE=y
+CT_LIBC_GLIBC_V_2_19=y
+# CT_LIBC_GLIBC_V_2_18 is not set
+# CT_LIBC_GLIBC_V_2_17 is not set
+# CT_LIBC_GLIBC_V_2_16_0 is not set
+# CT_LIBC_GLIBC_V_2_15 is not set
+# CT_LIBC_GLIBC_V_2_14_1 is not set
+# CT_LIBC_GLIBC_V_2_14 is not set
+# CT_LIBC_GLIBC_V_2_13 is not set
+# CT_LIBC_GLIBC_V_2_12_2 is not set
+# CT_LIBC_GLIBC_V_2_12_1 is not set
+# CT_LIBC_GLIBC_V_2_11_1 is not set
+# CT_LIBC_GLIBC_V_2_11 is not set
+# CT_LIBC_GLIBC_V_2_10_1 is not set
+# CT_LIBC_GLIBC_V_2_9 is not set
+# CT_LIBC_GLIBC_V_2_8 is not set
+CT_LIBC_mingw_AVAILABLE=y
+CT_LIBC_musl_AVAILABLE=y
+CT_LIBC_newlib_AVAILABLE=y
+CT_LIBC_none_AVAILABLE=y
+CT_LIBC_uClibc_AVAILABLE=y
+CT_LIBC_SUPPORT_THREADS_ANY=y
+CT_LIBC_SUPPORT_THREADS_NATIVE=y
+
+#
+# Common C library options
+#
+CT_THREADS_NATIVE=y
+CT_LIBC_XLDD=y
+# CT_LIBC_GLIBC_PORTS_EXTERNAL is not set
+CT_LIBC_GLIBC_MAY_FORCE_PORTS=y
+CT_LIBC_glibc_familly=y
+CT_LIBC_GLIBC_EXTRA_CONFIG_ARRAY=""
+CT_LIBC_GLIBC_CONFIGPARMS=""
+CT_LIBC_GLIBC_EXTRA_CFLAGS=""
+CT_LIBC_EXTRA_CC_ARGS=""
+# CT_LIBC_DISABLE_VERSIONING is not set
+CT_LIBC_OLDEST_ABI=""
+CT_LIBC_GLIBC_FORCE_UNWIND=y
+CT_LIBC_GLIBC_USE_PORTS=y
+CT_LIBC_ADDONS_LIST=""
+# CT_LIBC_LOCALES is not set
+# CT_LIBC_GLIBC_KERNEL_VERSION_NONE is not set
+CT_LIBC_GLIBC_KERNEL_VERSION_AS_HEADERS=y
+# CT_LIBC_GLIBC_KERNEL_VERSION_CHOSEN is not set
+CT_LIBC_GLIBC_MIN_KERNEL="3.10.47"
+
+#
+# glibc other options
+#
+
+#
+# WARNING !!!                                            
+#
+
+#
+#   For glibc >= 2.8, it can happen that the tarballs    
+#
+
+#
+#   for the addons are not available for download.       
+#
+
+#
+#   If that happens, bad luck... Try a previous version  
+#
+
+#
+#   or try again later... :-(                            
+#
+
+#
+# C compiler
+#
+CT_CC="gcc"
+CT_CC_VERSION="4.8.1"
+CT_CC_CORE_PASSES_NEEDED=y
+CT_CC_CORE_PASS_1_NEEDED=y
+CT_CC_CORE_PASS_2_NEEDED=y
+CT_CC_gcc=y
+# CT_CC_GCC_SHOW_LINARO is not set
+# CT_CC_V_4_9_1 is not set
+# CT_CC_V_4_9_0 is not set
+# CT_CC_V_4_8_3 is not set
+# CT_CC_V_4_8_2 is not set
+CT_CC_V_4_8_1=y
+# CT_CC_V_4_8_0 is not set
+# CT_CC_V_4_7_4 is not set
+# CT_CC_V_4_7_3 is not set
+# CT_CC_V_4_7_2 is not set
+# CT_CC_V_4_7_1 is not set
+# CT_CC_V_4_7_0 is not set
+# CT_CC_V_4_6_4 is not set
+# CT_CC_V_4_6_3 is not set
+# CT_CC_V_4_6_2 is not set
+# CT_CC_V_4_6_1 is not set
+# CT_CC_V_4_6_0 is not set
+# CT_CC_V_4_5_3 is not set
+# CT_CC_V_4_5_2 is not set
+# CT_CC_V_4_5_1 is not set
+# CT_CC_V_4_5_0 is not set
+# CT_CC_V_4_4_7 is not set
+# CT_CC_V_4_4_6 is not set
+# CT_CC_V_4_4_5 is not set
+# CT_CC_V_4_4_4 is not set
+# CT_CC_V_4_4_3 is not set
+# CT_CC_V_4_4_2 is not set
+# CT_CC_V_4_4_1 is not set
+# CT_CC_V_4_4_0 is not set
+# CT_CC_V_4_3_6 is not set
+# CT_CC_V_4_3_5 is not set
+# CT_CC_V_4_3_4 is not set
+# CT_CC_V_4_3_3 is not set
+# CT_CC_V_4_3_2 is not set
+# CT_CC_V_4_3_1 is not set
+# CT_CC_V_4_2_4 is not set
+# CT_CC_V_4_2_2 is not set
+CT_CC_GCC_4_2_or_later=y
+CT_CC_GCC_4_3_or_later=y
+CT_CC_GCC_4_4_or_later=y
+CT_CC_GCC_4_5_or_later=y
+CT_CC_GCC_4_6_or_later=y
+CT_CC_GCC_4_7_or_later=y
+CT_CC_GCC_4_8=y
+CT_CC_GCC_4_8_or_later=y
+CT_CC_GCC_HAS_GRAPHITE=y
+CT_CC_GCC_USE_GRAPHITE=y
+CT_CC_GCC_HAS_LTO=y
+CT_CC_GCC_USE_LTO=y
+CT_CC_GCC_HAS_PKGVERSION_BUGURL=y
+CT_CC_GCC_HAS_BUILD_ID=y
+CT_CC_GCC_HAS_LNK_HASH_STYLE=y
+CT_CC_GCC_USE_GMP_MPFR=y
+CT_CC_GCC_USE_MPC=y
+CT_CC_GCC_HAS_LIBQUADMATH=y
+CT_CC_GCC_HAS_LIBSANITIZER=y
+# CT_CC_LANG_FORTRAN is not set
+CT_CC_SUPPORT_CXX=y
+CT_CC_SUPPORT_FORTRAN=y
+CT_CC_SUPPORT_JAVA=y
+CT_CC_SUPPORT_ADA=y
+CT_CC_SUPPORT_OBJC=y
+CT_CC_SUPPORT_OBJCXX=y
+CT_CC_SUPPORT_GOLANG=y
+
+#
+# Additional supported languages:
+#
+CT_CC_LANG_CXX=y
+# CT_CC_LANG_JAVA is not set
+
+#
+# gcc other options
+#
+CT_CC_ENABLE_CXX_FLAGS=""
+CT_CC_CORE_EXTRA_CONFIG_ARRAY=""
+CT_CC_EXTRA_CONFIG_ARRAY=""
+CT_CC_STATIC_LIBSTDCXX=y
+# CT_CC_GCC_SYSTEM_ZLIB is not set
+
+#
+# Optimisation features
+#
+
+#
+# Settings for libraries running on target
+#
+CT_CC_GCC_ENABLE_TARGET_OPTSPACE=y
+# CT_CC_GCC_LIBMUDFLAP is not set
+# CT_CC_GCC_LIBGOMP is not set
+# CT_CC_GCC_LIBSSP is not set
+# CT_CC_GCC_LIBQUADMATH is not set
+# CT_CC_GCC_LIBSANITIZER is not set
+
+#
+# Misc. obscure options.
+#
+CT_CC_CXA_ATEXIT=y
+# CT_CC_GCC_DISABLE_PCH is not set
+CT_CC_GCC_SJLJ_EXCEPTIONS=m
+CT_CC_GCC_LDBL_128=m
+# CT_CC_GCC_BUILD_ID is not set
+CT_CC_GCC_LNK_HASH_STYLE_DEFAULT=y
+# CT_CC_GCC_LNK_HASH_STYLE_SYSV is not set
+# CT_CC_GCC_LNK_HASH_STYLE_GNU is not set
+# CT_CC_GCC_LNK_HASH_STYLE_BOTH is not set
+CT_CC_GCC_LNK_HASH_STYLE=""
+CT_CC_GCC_DEC_FLOAT_AUTO=y
+# CT_CC_GCC_DEC_FLOAT_BID is not set
+# CT_CC_GCC_DEC_FLOAT_DPD is not set
+# CT_CC_GCC_DEC_FLOATS_NO is not set
+
+#
+# Debug facilities
+#
+CT_DEBUG_dmalloc=y
+CT_DMALLOC_V_5_5_2=y
+CT_DMALLOC_VERSION="5.5.2"
+CT_DEBUG_duma=y
+CT_DUMA_A=y
+CT_DUMA_SO=y
+CT_DUMA_V_2_5_15=y
+CT_DUMA_VERSION="2_5_15"
+CT_DEBUG_gdb=y
+CT_GDB_CROSS=y
+# CT_GDB_CROSS_STATIC is not set
+# CT_GDB_CROSS_SIM is not set
+# CT_GDB_CROSS_PYTHON is not set
+CT_GDB_CROSS_EXTRA_CONFIG_ARRAY=""
+CT_GDB_NATIVE=y
+# CT_GDB_NATIVE_STATIC is not set
+CT_GDB_GDBSERVER=y
+CT_GDB_GDBSERVER_HAS_IPA_LIB=y
+CT_GDB_GDBSERVER_STATIC=y
+
+#
+# gdb version
+#
+# CT_DEBUG_GDB_SHOW_LINARO is not set
+CT_GDB_V_7_8=y
+# CT_GDB_V_7_7_1 is not set
+# CT_GDB_V_7_7 is not set
+# CT_GDB_V_7_6_1 is not set
+# CT_GDB_V_7_5_1 is not set
+# CT_GDB_V_7_4_1 is not set
+# CT_GDB_V_7_4 is not set
+# CT_GDB_V_7_3_1 is not set
+# CT_GDB_V_7_3a is not set
+# CT_GDB_V_7_2a is not set
+# CT_GDB_V_7_1a is not set
+# CT_GDB_V_7_0_1a is not set
+# CT_GDB_V_7_0a is not set
+# CT_GDB_V_6_8a is not set
+CT_GDB_7_2_or_later=y
+CT_GDB_7_0_or_later=y
+CT_GDB_HAS_PKGVERSION_BUGURL=y
+CT_GDB_HAS_PYTHON=y
+CT_GDB_INSTALL_GDBINIT=y
+CT_GDB_VERSION="7.8"
+CT_DEBUG_ltrace=y
+CT_LTRACE_V_0_7_3=y
+# CT_LTRACE_V_0_5_3 is not set
+# CT_LTRACE_V_0_5_2 is not set
+CT_LTRACE_VERSION="0.7.3"
+CT_DEBUG_strace=y
+CT_STRACE_V_4_8=y
+# CT_STRACE_V_4_7 is not set
+# CT_STRACE_V_4_6 is not set
+# CT_STRACE_V_4_5_20 is not set
+# CT_STRACE_V_4_5_19 is not set
+# CT_STRACE_V_4_5_18 is not set
+CT_STRACE_VERSION="4.8"
+
+#
+# Companion libraries
+#
+CT_COMPLIBS_NEEDED=y
+CT_GMP_NEEDED=y
+CT_MPFR_NEEDED=y
+CT_ISL_NEEDED=y
+CT_CLOOG_NEEDED=y
+CT_MPC_NEEDED=y
+CT_COMPLIBS=y
+CT_GMP=y
+CT_MPFR=y
+CT_ISL=y
+CT_CLOOG=y
+CT_MPC=y
+CT_LIBELF_TARGET=y
+# CT_GMP_V_5_1_3 is not set
+CT_GMP_V_5_1_1=y
+# CT_GMP_V_5_0_2 is not set
+# CT_GMP_V_5_0_1 is not set
+# CT_GMP_V_4_3_2 is not set
+# CT_GMP_V_4_3_1 is not set
+# CT_GMP_V_4_3_0 is not set
+CT_GMP_VERSION="5.1.1"
+CT_MPFR_V_3_1_2=y
+# CT_MPFR_V_3_1_0 is not set
+# CT_MPFR_V_3_0_1 is not set
+# CT_MPFR_V_3_0_0 is not set
+# CT_MPFR_V_2_4_2 is not set
+# CT_MPFR_V_2_4_1 is not set
+# CT_MPFR_V_2_4_0 is not set
+CT_MPFR_VERSION="3.1.2"
+# CT_ISL_V_0_12_2 is not set
+CT_ISL_V_0_11_1=y
+CT_ISL_VERSION="0.11.1"
+# CT_CLOOG_V_0_18_1 is not set
+CT_CLOOG_V_0_18_0=y
+CT_CLOOG_VERSION="0.18.0"
+CT_CLOOG_0_18_or_later=y
+# CT_MPC_V_1_0_2 is not set
+CT_MPC_V_1_0_1=y
+# CT_MPC_V_1_0 is not set
+# CT_MPC_V_0_9 is not set
+# CT_MPC_V_0_8_2 is not set
+# CT_MPC_V_0_8_1 is not set
+# CT_MPC_V_0_7 is not set
+CT_MPC_VERSION="1.0.1"
+
+#
+# libelf version needed to build for target
+#
+CT_LIBELF_V_0_8_13=y
+# CT_LIBELF_V_0_8_12 is not set
+CT_LIBELF_VERSION="0.8.13"
+
+#
+# Companion libraries common options
+#
+# CT_COMPLIBS_CHECK is not set
+
+#
+# Companion tools
+#
+
+#
+# READ HELP before you say 'Y' below !!!
+#
+# CT_COMP_TOOLS is not set
diff --git a/tooldata/data/oncall.v1.xml b/tooldata/data/oncall.v1.xml
new file mode 100644
index 0000000..087f553
--- /dev/null
+++ b/tooldata/data/oncall.v1.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" ?>
+<!--
+  This file has been auto generated. Please don't edit manually.
+-->
+<rotation>
+  <shift>
+    <primary>jlodhia</primary>
+    <secondary>herbertc</secondary>
+    <startDate>Mar 07, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>razvanm</primary>
+    <secondary>hpucha</secondary>
+    <startDate>Mar 14, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>mattr</primary>
+    <secondary>jasoncampbell</secondary>
+    <startDate>Mar 21, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>afergan</primary>
+    <secondary>alexfandrianto</secondary>
+    <startDate>Mar 28, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>kash</primary>
+    <secondary>jingjin</secondary>
+    <startDate>Apr 04, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>herbertc</primary>
+    <secondary>razvanm</secondary>
+    <startDate>Apr 11, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>ivanpi</primary>
+    <secondary>kash</secondary>
+    <startDate>Apr 18, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>jasoncampbell</primary>
+    <secondary>m3b</secondary>
+    <startDate>Apr 25, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>caprita</primary>
+    <secondary>jlodhia</secondary>
+    <startDate>May 02, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>nlacasse</primary>
+    <secondary>suharshs</secondary>
+    <startDate>May 09, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>suharshs</primary>
+    <secondary>nlacasse</secondary>
+    <startDate>May 16, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>rdaoud</primary>
+    <secondary>ribrdb</secondary>
+    <startDate>May 23, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>cnicolaou</primary>
+    <secondary>caprita</secondary>
+    <startDate>May 30, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>ashankar</primary>
+    <secondary>mattr</secondary>
+    <startDate>Jun 06, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>jhahn</primary>
+    <secondary>rdaoud</secondary>
+    <startDate>Jun 13, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>ribrdb</primary>
+    <secondary>aghassemi</secondary>
+    <startDate>Jun 20, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>afergan</primary>
+    <secondary>cierniak</secondary>
+    <startDate>Jun 27, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>gauthamt</primary>
+    <secondary>jyh</secondary>
+    <startDate>Jul 04, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>rosswang</primary>
+    <secondary>rthellend</secondary>
+    <startDate>Jul 11, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>hpucha</primary>
+    <secondary>ivanpi</secondary>
+    <startDate>Jul 18, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>rthellend</primary>
+    <secondary>rosswang</secondary>
+    <startDate>Jul 25, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>sadovsky</primary>
+    <secondary>aghassemi</secondary>
+    <startDate>Aug 01, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>bprosnitz</primary>
+    <secondary>sadovsky</secondary>
+    <startDate>Aug 08, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>spetrovic</primary>
+    <secondary>suharshs</secondary>
+    <startDate>Aug 15, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>suharshs</primary>
+    <secondary>spetrovic</secondary>
+    <startDate>Aug 22, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>toddw</primary>
+    <secondary>youngseokyoon</secondary>
+    <startDate>Aug 29, 2016 08:00:00 AM</startDate>
+  </shift>
+  <shift>
+    <primary>youngseokyoon</primary>
+    <secondary>toddw</secondary>
+    <startDate>Sep 05, 2016 08:00:00 AM</startDate>
+  </shift>
+</rotation>
\ No newline at end of file
diff --git a/tooldata/datadir.go b/tooldata/datadir.go
new file mode 100644
index 0000000..edada0b
--- /dev/null
+++ b/tooldata/datadir.go
@@ -0,0 +1,16 @@
+// 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 tooldata
+
+import (
+	"path/filepath"
+
+	"v.io/jiri"
+)
+
+// DataDirPath returns the path to the data directory of the given tool.
+func DataDirPath(jirix *jiri.X, toolName string) (string, error) {
+	return filepath.Join(jirix.Root, "release", "go", "src", "v.io", "x", "devtools", "tooldata", "data"), nil
+}
diff --git a/tooldata/doc.go b/tooldata/doc.go
new file mode 100644
index 0000000..aa20543
--- /dev/null
+++ b/tooldata/doc.go
@@ -0,0 +1,7 @@
+// 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 tooldata provides routines for locating and reading tool
+// specific data.
+package tooldata
diff --git a/tooldata/oncall.go b/tooldata/oncall.go
new file mode 100644
index 0000000..b1dd61b
--- /dev/null
+++ b/tooldata/oncall.go
@@ -0,0 +1,66 @@
+// 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 tooldata
+
+import (
+	"encoding/xml"
+	"fmt"
+	"io/ioutil"
+	"time"
+
+	"v.io/jiri"
+)
+
+type OncallRotation struct {
+	Shifts  []OncallShift `xml:"shift"`
+	XMLName xml.Name      `xml:"rotation"`
+}
+
+type OncallShift struct {
+	Primary   string `xml:"primary"`
+	Secondary string `xml:"secondary"`
+	Date      string `xml:"startDate"`
+}
+
+// LoadOncallRotation parses the default oncall schedule file.
+func LoadOncallRotation(jirix *jiri.X) (*OncallRotation, error) {
+	oncallRotationsFile, err := OncallRotationPath(jirix)
+	if err != nil {
+		return nil, err
+	}
+	content, err := ioutil.ReadFile(oncallRotationsFile)
+	if err != nil {
+		return nil, fmt.Errorf("ReadFile(%q) failed: %v", oncallRotationsFile, err)
+	}
+	rotation := OncallRotation{}
+	if err := xml.Unmarshal(content, &rotation); err != nil {
+		return nil, fmt.Errorf("Unmarshal(%q) failed: %v", string(content), err)
+	}
+	return &rotation, nil
+}
+
+// Oncall finds the oncall shift at the given time from the
+// oncall configuration file by comparing timestamps.
+func Oncall(jirix *jiri.X, targetTime time.Time) (*OncallShift, error) {
+	// Parse oncall configuration file.
+	rotation, err := LoadOncallRotation(jirix)
+	if err != nil {
+		return nil, err
+	}
+
+	// Find the oncall at targetTime.
+	layout := "Jan 2, 2006 3:04:05 PM"
+	for i := len(rotation.Shifts) - 1; i >= 0; i-- {
+		shift := rotation.Shifts[i]
+		t, err := time.ParseInLocation(layout, shift.Date, targetTime.Location())
+		if err != nil {
+			return nil, fmt.Errorf("Parse(%q, %v) failed: %v", layout, shift.Date, err)
+		}
+		if targetTime.Unix() >= t.Unix() {
+			return &shift, nil
+		}
+	}
+	return nil, nil
+}
diff --git a/tooldata/oncall_test.go b/tooldata/oncall_test.go
new file mode 100644
index 0000000..32c1e56
--- /dev/null
+++ b/tooldata/oncall_test.go
@@ -0,0 +1,102 @@
+// 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 tooldata_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/jiri"
+	"v.io/jiri/jiritest"
+	"v.io/x/devtools/tooldata"
+)
+
+func createOncallFile(t *testing.T, jirix *jiri.X) {
+	content := `<?xml version="1.0" ?>
+<rotation>
+  <shift>
+    <primary>spetrovic</primary>
+    <secondary>suharshs</secondary>
+    <startDate>Nov 5, 2014 12:00:00 PM</startDate>
+  </shift>
+  <shift>
+    <primary>suharshs</primary>
+    <secondary>jingjin</secondary>
+    <startDate>Nov 12, 2014 12:00:00 PM</startDate>
+  </shift>
+  <shift>
+    <primary>jsimsa</primary>
+    <secondary>toddw</secondary>
+    <startDate>Nov 19, 2014 12:00:00 PM</startDate>
+  </shift>
+</rotation>`
+	oncallRotationsFile, err := tooldata.OncallRotationPath(jirix)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	dir := filepath.Dir(oncallRotationsFile)
+	dirMode := os.FileMode(0700)
+	if err := jirix.NewSeq().MkdirAll(dir, dirMode).Done(); err != nil {
+		t.Fatalf("MkdirAll(%q, %v) failed: %v", dir, dirMode, err)
+	}
+	fileMode := os.FileMode(0644)
+	if err := ioutil.WriteFile(oncallRotationsFile, []byte(content), fileMode); err != nil {
+		t.Fatalf("WriteFile(%q, %q, %v) failed: %v", oncallRotationsFile, content, fileMode, err)
+	}
+}
+
+func TestOncall(t *testing.T) {
+	fake, cleanup := jiritest.NewFakeJiriRoot(t)
+	defer cleanup()
+
+	// Create a oncall.v1.xml file.
+	createOncallFile(t, fake.X)
+	testCases := []struct {
+		targetTime    time.Time
+		expectedShift *tooldata.OncallShift
+	}{
+		{
+			time.Date(2013, time.November, 5, 12, 0, 0, 0, time.Local),
+			nil,
+		},
+		{
+			time.Date(2014, time.November, 5, 12, 0, 0, 0, time.Local),
+			&tooldata.OncallShift{
+				Primary:   "spetrovic",
+				Secondary: "suharshs",
+				Date:      "Nov 5, 2014 12:00:00 PM",
+			},
+		},
+		{
+			time.Date(2014, time.November, 5, 14, 0, 0, 0, time.Local),
+			&tooldata.OncallShift{
+				Primary:   "spetrovic",
+				Secondary: "suharshs",
+				Date:      "Nov 5, 2014 12:00:00 PM",
+			},
+		},
+		{
+			time.Date(2014, time.November, 20, 14, 0, 0, 0, time.Local),
+			&tooldata.OncallShift{
+				Primary:   "jsimsa",
+				Secondary: "toddw",
+				Date:      "Nov 19, 2014 12:00:00 PM",
+			},
+		},
+	}
+	for _, test := range testCases {
+		got, err := tooldata.Oncall(fake.X, test.targetTime)
+		if err != nil {
+			t.Fatalf("want no errors, got: %v", err)
+		}
+		if !reflect.DeepEqual(test.expectedShift, got) {
+			t.Fatalf("want %#v, got %#v", test.expectedShift, got)
+		}
+	}
+}
diff --git a/tooldata/paths.go b/tooldata/paths.go
new file mode 100644
index 0000000..14fffa3
--- /dev/null
+++ b/tooldata/paths.go
@@ -0,0 +1,49 @@
+// 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 tooldata
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+
+	"v.io/jiri"
+	"v.io/jiri/tool"
+	"v.io/x/lib/host"
+)
+
+// ConfigFilePath returns the path to the tools configuration file.
+func ConfigFilePath(jirix *jiri.X) (string, error) {
+	dataDir, err := DataDirPath(jirix, tool.Name)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(dataDir, "config.v1.xml"), nil
+}
+
+// OncallRotationPath returns the path to the oncall rotation file.
+func OncallRotationPath(jirix *jiri.X) (string, error) {
+	dataDir, err := DataDirPath(jirix, tool.Name)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(dataDir, "oncall.v1.xml"), nil
+}
+
+// ThirdPartyBinPath returns the path to the given third-party tool
+// taking into account the host and the target Go architecture.
+func ThirdPartyBinPath(jirix *jiri.X, name string) (string, error) {
+	bin := filepath.Join(jirix.Root, "third_party", "go", "bin", name)
+	goArch := os.Getenv("GOARCH")
+	machineArch, err := host.Arch()
+	if err != nil {
+		return "", err
+	}
+	if goArch != "" && goArch != machineArch {
+		bin = filepath.Join(jirix.Root, "third_party", "go", "bin", fmt.Sprintf("%s_%s", runtime.GOOS, goArch), name)
+	}
+	return bin, nil
+}