blob: 2846387721d1cbd077e66de657ca5f04d3fdd74b [file] [log] [blame]
Dan Willemsenc2af0be2017-01-20 14:10:01 -08001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
Lukacs T. Berkicef87b62021-08-10 15:01:13 +020018 "bufio"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080019 "context"
20 "flag"
21 "fmt"
Dan Willemsen41538382018-08-31 19:51:35 -070022 "io"
Lukacs T. Berkicef87b62021-08-10 15:01:13 +020023 "log"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080024 "os"
Lukacs T. Berkicef87b62021-08-10 15:01:13 +020025 "os/exec"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080026 "path/filepath"
Lukacs T. Berki2c405692021-08-11 09:19:27 +020027 "regexp"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080028 "runtime"
29 "strings"
30 "sync"
Dan Willemsen22de2162017-12-11 14:35:23 -080031 "syscall"
Steven Moreland552432e2017-03-29 19:26:09 -070032 "time"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080033
Dan Willemsenc2af0be2017-01-20 14:10:01 -080034 "android/soong/ui/logger"
Lukacs T. Berkif656b842021-08-11 11:10:28 +020035 "android/soong/ui/signal"
Dan Willemsenb82471a2018-05-17 16:37:09 -070036 "android/soong/ui/status"
37 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070038 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080039 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080040)
41
Dan Willemsen1bdbdec2019-12-27 09:54:11 -080042var numJobs = flag.Int("j", 0, "number of parallel jobs [0=autodetect]")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080043
Dan Willemsene3480762017-11-07 11:23:27 -080044var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsen41538382018-08-31 19:51:35 -070045var incremental = flag.Bool("incremental", false, "run in incremental mode (saving intermediates)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080046
47var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070048var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080049
50var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
51var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
52
Dan Willemsen5ed900b2017-05-07 11:40:30 -070053var buildVariant = flag.String("variant", "eng", "build variant to use")
54
Dan Willemsen9609ad92019-12-05 15:22:41 -080055var shardCount = flag.Int("shard-count", 1, "split the products into multiple shards (to spread the build onto multiple machines, etc)")
56var shard = flag.Int("shard", 1, "1-indexed shard to execute")
57
Colin Crossf2f3d312020-12-17 11:29:31 -080058var skipProducts multipleStringArg
59var includeProducts multipleStringArg
60
61func init() {
62 flag.Var(&skipProducts, "skip-products", "comma-separated list of products to skip (known failures, etc)")
63 flag.Var(&includeProducts, "products", "comma-separated list of products to build")
64}
65
66// multipleStringArg is a flag.Value that takes comma separated lists and converts them to a
67// []string. The argument can be passed multiple times to append more values.
68type multipleStringArg []string
69
70func (m *multipleStringArg) String() string {
71 return strings.Join(*m, `, `)
72}
73
74func (m *multipleStringArg) Set(s string) error {
75 *m = append(*m, strings.Split(s, ",")...)
76 return nil
77}
78
Dan Willemsen22de2162017-12-11 14:35:23 -080079// TODO(b/70370883): This tool uses a lot of open files -- over the default
80// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
81// the algorithm.
82func setMaxFiles(log logger.Logger) {
83 var limits syscall.Rlimit
84
85 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
86 if err != nil {
87 log.Println("Failed to get file limit:", err)
88 return
89 }
90
91 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
92 if limits.Cur == limits.Max {
93 return
94 }
95
96 limits.Cur = limits.Max
97 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
98 if err != nil {
99 log.Println("Failed to increase file limit:", err)
100 }
101}
102
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700103func inList(str string, list []string) bool {
104 for _, other := range list {
105 if str == other {
106 return true
107 }
108 }
109 return false
110}
111
Dan Willemsen41538382018-08-31 19:51:35 -0700112func copyFile(from, to string) error {
113 fromFile, err := os.Open(from)
114 if err != nil {
115 return err
116 }
117 defer fromFile.Close()
118
119 toFile, err := os.Create(to)
120 if err != nil {
121 return err
122 }
123 defer toFile.Close()
124
125 _, err = io.Copy(toFile, fromFile)
126 return err
127}
128
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700129type mpContext struct {
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200130 Logger logger.Logger
131 Status status.ToolStatus
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700132
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200133 SoongUi string
134 MainOutDir string
135 MainLogsDir string
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700136}
137
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200138func detectTotalRAM() uint64 {
139 var info syscall.Sysinfo_t
140 err := syscall.Sysinfo(&info)
141 if err != nil {
142 panic(err)
143 }
144 return info.Totalram * uint64(info.Unit)
145}
146
147func findNamedProducts(soongUi string, log logger.Logger) []string {
148 cmd := exec.Command(soongUi, "--dumpvars-mode", "--vars=all_named_products")
149 output, err := cmd.Output()
150 if err != nil {
151 log.Fatalf("Cannot determine named products: %v", err)
152 }
153
154 rx := regexp.MustCompile(`^all_named_products='(.*)'$`)
155 match := rx.FindStringSubmatch(strings.TrimSpace(string(output)))
156 return strings.Fields(match[1])
157}
158
159// ensureEmptyFileExists ensures that the containing directory exists, and the
160// specified file exists. If it doesn't exist, it will write an empty file.
161func ensureEmptyFileExists(file string, log logger.Logger) {
162 if _, err := os.Stat(file); os.IsNotExist(err) {
163 f, err := os.Create(file)
164 if err != nil {
165 log.Fatalf("Error creating %s: %q\n", file, err)
166 }
167 f.Close()
168 } else if err != nil {
169 log.Fatalf("Error checking %s: %q\n", file, err)
170 }
171}
172
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800173func main() {
Colin Cross097ed2a2019-06-08 21:48:58 -0700174 stdio := terminal.StdioImpl{}
Dan Willemsenb82471a2018-05-17 16:37:09 -0700175
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200176 output := terminal.NewStatusOutput(stdio.Stdout(), "", false, false)
Colin Crosse0df1a32019-06-09 19:40:08 -0700177 log := logger.New(output)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800178 defer log.Cleanup()
179
180 flag.Parse()
181
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200182 _, cancel := context.WithCancel(context.Background())
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800183 defer cancel()
184
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700185 trace := tracer.New(log)
186 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800187
Dan Willemsenb82471a2018-05-17 16:37:09 -0700188 stat := &status.Status{}
189 defer stat.Finish()
Colin Crosse0df1a32019-06-09 19:40:08 -0700190 stat.AddOutput(output)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700191
Dan Willemsen34218ec2018-07-19 22:57:18 -0700192 var failures failureCount
193 stat.AddOutput(&failures)
194
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200195 signal.SetupSignals(log, cancel, func() {
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700196 trace.Close()
197 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700198 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700199 })
200
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200201 soongUi := "build/soong/soong_ui.bash"
202
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200203 var outputDir string
204 if *outDir != "" {
205 outputDir = *outDir
206 } else {
Dan Willemsen41538382018-08-31 19:51:35 -0700207 name := "multiproduct"
208 if !*incremental {
209 name += "-" + time.Now().Format("20060102150405")
210 }
Steven Moreland552432e2017-03-29 19:26:09 -0700211
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200212 outDirBase := os.Getenv("OUT_DIR")
213 if outDirBase == "" {
214 outDirBase = "out"
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800215 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800216
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200217 outputDir = filepath.Join(outDirBase, name)
218 }
219
220 log.Println("Output directory:", outputDir)
221
222 // The ninja_build file is used by our buildbots to understand that the output
223 // can be parsed as ninja output.
224 if err := os.MkdirAll(outputDir, 0777); err != nil {
225 log.Fatalf("Failed to create output directory: %v", err)
226 }
227 ensureEmptyFileExists(filepath.Join(outputDir, "ninja_build"), log)
228
229 logsDir := filepath.Join(outputDir, "logs")
Dan Willemsene3480762017-11-07 11:23:27 -0800230 os.MkdirAll(logsDir, 0777)
231
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200232 var configLogsDir string
233 if *alternateResultDir {
234 configLogsDir = filepath.Join(outputDir, "dist/logs")
235 } else {
236 configLogsDir = outputDir
237 }
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000238
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200239 os.MkdirAll(configLogsDir, 0777)
240 log.SetOutput(filepath.Join(configLogsDir, "soong.log"))
241 trace.SetOutput(filepath.Join(configLogsDir, "build.trace"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800242
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800243 var jobs = *numJobs
244 if jobs < 1 {
245 jobs = runtime.NumCPU() / 4
246
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200247 ramGb := int(detectTotalRAM() / (1024 * 1024 * 1024))
Colin Crossf04fe9a2021-01-26 14:03:21 -0800248 if ramJobs := ramGb / 25; ramGb > 0 && jobs > ramJobs {
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800249 jobs = ramJobs
250 }
251
252 if jobs < 1 {
253 jobs = 1
254 }
255 }
256 log.Verbosef("Using %d parallel jobs", jobs)
257
Dan Willemsen22de2162017-12-11 14:35:23 -0800258 setMaxFiles(log)
259
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200260 allProducts := findNamedProducts(soongUi, log)
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700261 var productsList []string
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700262
Colin Crossf2f3d312020-12-17 11:29:31 -0800263 if len(includeProducts) > 0 {
264 var missingProducts []string
265 for _, product := range includeProducts {
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700266 if inList(product, allProducts) {
267 productsList = append(productsList, product)
268 } else {
269 missingProducts = append(missingProducts, product)
270 }
271 }
272 if len(missingProducts) > 0 {
273 log.Fatalf("Products don't exist: %s\n", missingProducts)
274 }
275 } else {
276 productsList = allProducts
277 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700278
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700279 finalProductsList := make([]string, 0, len(productsList))
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700280 skipProduct := func(p string) bool {
Colin Crossf2f3d312020-12-17 11:29:31 -0800281 for _, s := range skipProducts {
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700282 if p == s {
283 return true
284 }
285 }
286 return false
287 }
288 for _, product := range productsList {
289 if !skipProduct(product) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700290 finalProductsList = append(finalProductsList, product)
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700291 } else {
292 log.Verbose("Skipping: ", product)
293 }
294 }
295
Dan Willemsen9609ad92019-12-05 15:22:41 -0800296 if *shard < 1 {
297 log.Fatalf("--shard value must be >= 1, not %d\n", *shard)
298 } else if *shardCount < 1 {
299 log.Fatalf("--shard-count value must be >= 1, not %d\n", *shardCount)
300 } else if *shard > *shardCount {
301 log.Fatalf("--shard (%d) must not be greater than --shard-count (%d)\n", *shard,
302 *shardCount)
303 } else if *shardCount > 1 {
304 finalProductsList = splitList(finalProductsList, *shardCount)[*shard-1]
305 }
306
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700307 log.Verbose("Got product list: ", finalProductsList)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800308
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200309 s := stat.StartTool()
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700310 s.SetTotalActions(len(finalProductsList))
Dan Willemsena4e43a72017-05-06 16:58:26 -0700311
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700312 mpCtx := &mpContext{
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200313 Logger: log,
314 Status: s,
315 SoongUi: soongUi,
316 MainOutDir: outputDir,
317 MainLogsDir: logsDir,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800318 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700319
320 products := make(chan string, len(productsList))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800321 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700322 defer close(products)
323 for _, product := range finalProductsList {
324 products <- product
325 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800326 }()
327
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700328 var wg sync.WaitGroup
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800329 for i := 0; i < jobs; i++ {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700330 wg.Add(1)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800331 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700332 defer wg.Done()
333 for {
334 select {
335 case product := <-products:
336 if product == "" {
337 return
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800338 }
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200339 runSoongUiForProduct(mpCtx, product)
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700340 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800341 }
342 }()
343 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700344 wg.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800345
Dan Willemsene3480762017-11-07 11:23:27 -0800346 if *alternateResultDir {
347 args := zip.ZipArgs{
348 FileArgs: []zip.FileArg{
349 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
350 },
Lukacs T. Berki2c405692021-08-11 09:19:27 +0200351 OutputFilePath: filepath.Join(outputDir, "dist/logs.zip"),
Dan Willemsene3480762017-11-07 11:23:27 -0800352 NumParallelJobs: runtime.NumCPU(),
353 CompressionLevel: 5,
354 }
Colin Cross05518bc2018-09-27 15:06:19 -0700355 if err := zip.Zip(args); err != nil {
Dan Willemsene3480762017-11-07 11:23:27 -0800356 log.Fatalf("Error zipping logs: %v", err)
357 }
358 }
359
Dan Willemsenb82471a2018-05-17 16:37:09 -0700360 s.Finish()
Dan Willemsen34218ec2018-07-19 22:57:18 -0700361
362 if failures == 1 {
363 log.Fatal("1 failure")
364 } else if failures > 1 {
365 log.Fatalf("%d failures", failures)
366 } else {
Colin Crosse0df1a32019-06-09 19:40:08 -0700367 fmt.Fprintln(output, "Success")
Dan Willemsen34218ec2018-07-19 22:57:18 -0700368 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800369}
Dan Willemsen34218ec2018-07-19 22:57:18 -0700370
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200371func cleanupAfterProduct(outDir, productZip string) {
372 if *keepArtifacts {
373 args := zip.ZipArgs{
374 FileArgs: []zip.FileArg{
375 {
376 GlobDir: outDir,
377 SourcePrefixToStrip: outDir,
378 },
379 },
380 OutputFilePath: productZip,
381 NumParallelJobs: runtime.NumCPU(),
382 CompressionLevel: 5,
383 }
384 if err := zip.Zip(args); err != nil {
385 log.Fatalf("Error zipping artifacts: %v", err)
386 }
387 }
388 if !*incremental {
389 os.RemoveAll(outDir)
390 }
391}
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700392
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200393func runSoongUiForProduct(mpctx *mpContext, product string) {
394 outDir := filepath.Join(mpctx.MainOutDir, product)
395 logsDir := filepath.Join(mpctx.MainLogsDir, product)
396 productZip := filepath.Join(mpctx.MainOutDir, product+".zip")
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200397 consoleLogPath := filepath.Join(logsDir, "std.log")
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700398
399 if err := os.MkdirAll(outDir, 0777); err != nil {
400 mpctx.Logger.Fatalf("Error creating out directory: %v", err)
401 }
402 if err := os.MkdirAll(logsDir, 0777); err != nil {
403 mpctx.Logger.Fatalf("Error creating log directory: %v", err)
404 }
405
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200406 consoleLogFile, err := os.Create(consoleLogPath)
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700407 if err != nil {
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200408 mpctx.Logger.Fatalf("Error creating console log file: %v", err)
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700409 }
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200410 defer consoleLogFile.Close()
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700411
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200412 consoleLogWriter := bufio.NewWriter(consoleLogFile)
413 defer consoleLogWriter.Flush()
414
415 args := []string{"--make-mode", "--skip-soong-tests", "--skip-ninja"}
416
417 if !*keepArtifacts {
418 args = append(args, "--empty-ninja-file")
419 }
420
421 if *onlyConfig {
422 args = append(args, "--config-only")
423 } else if *onlySoong {
424 args = append(args, "--soong-only")
425 }
426
427 if *alternateResultDir {
428 args = append(args, "dist")
429 }
430
Lukacs T. Berkif656b842021-08-11 11:10:28 +0200431 cmd := exec.Command(mpctx.SoongUi, args...)
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200432 cmd.Stdout = consoleLogWriter
433 cmd.Stderr = consoleLogWriter
434 cmd.Env = append(os.Environ(),
435 "OUT_DIR="+outDir,
436 "TARGET_PRODUCT="+product,
437 "TARGET_BUILD_VARIANT="+*buildVariant,
438 "TARGET_BUILD_TYPE=release",
439 "TARGET_BUILD_APPS=",
440 "TARGET_BUILD_UNBUNDLED=")
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700441
442 action := &status.Action{
443 Description: product,
444 Outputs: []string{product},
445 }
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200446
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700447 mpctx.Status.StartAction(action)
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200448 defer cleanupAfterProduct(outDir, productZip)
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700449
450 before := time.Now()
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200451 err = cmd.Run()
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700452
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200453 if !*onlyConfig && !*onlySoong {
454 katiBuildNinjaFile := filepath.Join(outDir, "build-"+product+".ninja")
455 if after, err := os.Stat(katiBuildNinjaFile); err == nil && after.ModTime().After(before) {
456 err := copyFile(consoleLogPath, filepath.Join(filepath.Dir(consoleLogPath), "std_full.log"))
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700457 if err != nil {
458 log.Fatalf("Error copying log file: %s", err)
459 }
460 }
461 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700462 mpctx.Status.FinishAction(status.ActionResult{
463 Action: action,
Lukacs T. Berkicef87b62021-08-10 15:01:13 +0200464 Error: err,
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700465 })
466}
467
Dan Willemsen34218ec2018-07-19 22:57:18 -0700468type failureCount int
469
470func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
471
472func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
473 if result.Error != nil {
474 *f += 1
475 }
476}
477
478func (f *failureCount) Message(level status.MsgLevel, message string) {
479 if level >= status.ErrorLvl {
480 *f += 1
481 }
482}
483
484func (f *failureCount) Flush() {}
Colin Crosse0df1a32019-06-09 19:40:08 -0700485
486func (f *failureCount) Write(p []byte) (int, error) {
487 // discard writes
488 return len(p), nil
489}
Dan Willemsen9609ad92019-12-05 15:22:41 -0800490
491func splitList(list []string, shardCount int) (ret [][]string) {
492 each := len(list) / shardCount
493 extra := len(list) % shardCount
494 for i := 0; i < shardCount; i++ {
495 count := each
496 if extra > 0 {
497 count += 1
498 extra -= 1
499 }
500 ret = append(ret, list[:count])
501 list = list[count:]
502 }
503 return
504}