[init] Use CIPD parallelism by default

Adds `-cipd-max-threads` flag to `jiri init` whose value will be used as
the `-max-threads` flag to the `cipd ensure` command. This instructs
CIPD how many threads to use for parallel extraction of packages. It
defaults to 0, which causes CIPD to use as many threads as there are
CPUs.

Bug: 38616
Change-Id: Ief604f873544d8ac72d2924ef6c748fd86e358b0
diff --git a/cipd/cipd.go b/cipd/cipd.go
index 1b338f2..c26a0a4 100644
--- a/cipd/cipd.go
+++ b/cipd/cipd.go
@@ -20,6 +20,7 @@
 	"path/filepath"
 	"regexp"
 	"runtime"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -396,7 +397,13 @@
 	}
 	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Minute)
 	defer cancel()
-	args := []string{"ensure", "-ensure-file", file, "-root", projectRoot, "-log-level", "warning"}
+	args := []string{
+		"ensure",
+		"-ensure-file", file,
+		"-root", projectRoot,
+		"-log-level", "warning",
+		"-max-threads", strconv.Itoa(jirix.CipdMaxThreads),
+	}
 
 	task := jirix.Logger.AddTaskMsg("Fetching CIPD packages")
 	defer task.Done()
diff --git a/cmd/jiri/init.go b/cmd/jiri/init.go
index 9d757ef..72e63d4 100644
--- a/cmd/jiri/init.go
+++ b/cmd/jiri/init.go
@@ -48,6 +48,7 @@
 	optionalAttrs         string
 	partialFlag           bool
 	cipdParanoidFlag      string
+	cipdMaxThreads        int
 )
 
 const (
@@ -70,6 +71,8 @@
 	cmdInit.Flags.StringVar(&optionalAttrs, "fetch-optional", optionalAttrsNotSet, "Set up attributes of optional projects and packages that should be fetched by jiri.")
 	cmdInit.Flags.BoolVar(&partialFlag, "partial", false, "Whether to use a partial checkout.")
 	cmdInit.Flags.StringVar(&cipdParanoidFlag, "cipd-paranoid-mode", "", "Whether to use paranoid mode in cipd.")
+	// Default (0) causes CIPD to use as many threads as there are CPUs.
+	cmdInit.Flags.IntVar(&cipdMaxThreads, "cipd-max-threads", 0, "Number of threads to use for unpacking CIPD packages. If zero, uses all CPUs.")
 }
 
 func runInit(env *cmdline.Env, args []string) error {
@@ -191,6 +194,8 @@
 		config.CipdParanoidMode = cipdParanoidFlag
 	}
 
+	config.CipdMaxThreads = cipdMaxThreads
+
 	if analyticsOptFlag != "" {
 		if val, err := strconv.ParseBool(analyticsOptFlag); err != nil {
 			return fmt.Errorf("'analytics-opt' flag should be true or false")
diff --git a/x.go b/x.go
index f5786b3..157ab91 100644
--- a/x.go
+++ b/x.go
@@ -49,6 +49,7 @@
 type Config struct {
 	CachePath         string `xml:"cache>path,omitempty"`
 	CipdParanoidMode  string `xml:"cipd_paranoid_mode,omitempty"`
+	CipdMaxThreads    int    `xml:"cipd_max_threads,omitempty"`
 	Shared            bool   `xml:"cache>shared,omitempty"`
 	RewriteSsoToHttps bool   `xml:"rewriteSsoToHttps,omitempty"`
 	SsoCookiePath     string `xml:"SsoCookiePath,omitempty"`
@@ -105,6 +106,7 @@
 	config              *Config
 	Cache               string
 	CipdParanoidMode    bool
+	CipdMaxThreads      int
 	Shared              bool
 	Jobs                uint
 	KeepGitHooks        bool
@@ -273,6 +275,7 @@
 				x.CipdParanoidMode = val
 			}
 		}
+		x.CipdMaxThreads = x.config.CipdMaxThreads
 		x.LockfileName = x.config.LockfileName
 		x.PrebuiltJSON = x.config.PrebuiltJSON
 		x.FetchingAttrs = x.config.FetchingAttrs