blob: e0f8caac23f56252f29ca75344f8ad5733e18de8 [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 (
Dan Willemsenc2af0be2017-01-20 14:10:01 -080018 "context"
19 "flag"
20 "fmt"
Dan Willemsen41538382018-08-31 19:51:35 -070021 "io"
Dan Willemsenf624fb92017-05-19 16:39:04 -070022 "io/ioutil"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080023 "os"
24 "path/filepath"
25 "runtime"
26 "strings"
27 "sync"
Dan Willemsen22de2162017-12-11 14:35:23 -080028 "syscall"
Steven Moreland552432e2017-03-29 19:26:09 -070029 "time"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080030
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -070031 "android/soong/finder"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080032 "android/soong/ui/build"
33 "android/soong/ui/logger"
Dan Willemsenb82471a2018-05-17 16:37:09 -070034 "android/soong/ui/status"
35 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070036 "android/soong/ui/tracer"
Dan Willemsene3480762017-11-07 11:23:27 -080037 "android/soong/zip"
Dan Willemsenc2af0be2017-01-20 14:10:01 -080038)
39
Dan Willemsen1bdbdec2019-12-27 09:54:11 -080040var numJobs = flag.Int("j", 0, "number of parallel jobs [0=autodetect]")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080041
Dan Willemsene3480762017-11-07 11:23:27 -080042var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
Dan Willemsen41538382018-08-31 19:51:35 -070043var incremental = flag.Bool("incremental", false, "run in incremental mode (saving intermediates)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080044
45var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
Dan Willemsenf624fb92017-05-19 16:39:04 -070046var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
Dan Willemsenc2af0be2017-01-20 14:10:01 -080047
48var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
49var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
50
Dan Willemsen5ed900b2017-05-07 11:40:30 -070051var buildVariant = flag.String("variant", "eng", "build variant to use")
52
Dan Willemsen9609ad92019-12-05 15:22:41 -080053var shardCount = flag.Int("shard-count", 1, "split the products into multiple shards (to spread the build onto multiple machines, etc)")
54var shard = flag.Int("shard", 1, "1-indexed shard to execute")
55
Colin Crossf2f3d312020-12-17 11:29:31 -080056var skipProducts multipleStringArg
57var includeProducts multipleStringArg
58
59func init() {
60 flag.Var(&skipProducts, "skip-products", "comma-separated list of products to skip (known failures, etc)")
61 flag.Var(&includeProducts, "products", "comma-separated list of products to build")
62}
63
64// multipleStringArg is a flag.Value that takes comma separated lists and converts them to a
65// []string. The argument can be passed multiple times to append more values.
66type multipleStringArg []string
67
68func (m *multipleStringArg) String() string {
69 return strings.Join(*m, `, `)
70}
71
72func (m *multipleStringArg) Set(s string) error {
73 *m = append(*m, strings.Split(s, ",")...)
74 return nil
75}
76
Dan Willemsenf624fb92017-05-19 16:39:04 -070077const errorLeadingLines = 20
78const errorTrailingLines = 20
79
Dan Willemsenb82471a2018-05-17 16:37:09 -070080func errMsgFromLog(filename string) string {
81 if filename == "" {
82 return ""
Dan Willemsena4e43a72017-05-06 16:58:26 -070083 }
84
Dan Willemsenb82471a2018-05-17 16:37:09 -070085 data, err := ioutil.ReadFile(filename)
86 if err != nil {
87 return ""
Dan Willemsenf624fb92017-05-19 16:39:04 -070088 }
89
Dan Willemsenb82471a2018-05-17 16:37:09 -070090 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
91 if len(lines) > errorLeadingLines+errorTrailingLines+1 {
92 lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
93 len(lines)-errorLeadingLines-errorTrailingLines)
Dan Willemsena4e43a72017-05-06 16:58:26 -070094
Dan Willemsenb82471a2018-05-17 16:37:09 -070095 lines = append(lines[:errorLeadingLines+1],
96 lines[len(lines)-errorTrailingLines:]...)
Dan Willemsena4e43a72017-05-06 16:58:26 -070097 }
Dan Willemsenb82471a2018-05-17 16:37:09 -070098 var buf strings.Builder
99 for _, line := range lines {
100 buf.WriteString("> ")
101 buf.WriteString(line)
102 buf.WriteString("\n")
Dan Willemsena4e43a72017-05-06 16:58:26 -0700103 }
Dan Willemsenb82471a2018-05-17 16:37:09 -0700104 return buf.String()
Dan Willemsena4e43a72017-05-06 16:58:26 -0700105}
106
Dan Willemsen22de2162017-12-11 14:35:23 -0800107// TODO(b/70370883): This tool uses a lot of open files -- over the default
108// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
109// the algorithm.
110func setMaxFiles(log logger.Logger) {
111 var limits syscall.Rlimit
112
113 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
114 if err != nil {
115 log.Println("Failed to get file limit:", err)
116 return
117 }
118
119 log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
120 if limits.Cur == limits.Max {
121 return
122 }
123
124 limits.Cur = limits.Max
125 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
126 if err != nil {
127 log.Println("Failed to increase file limit:", err)
128 }
129}
130
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700131func inList(str string, list []string) bool {
132 for _, other := range list {
133 if str == other {
134 return true
135 }
136 }
137 return false
138}
139
Dan Willemsen41538382018-08-31 19:51:35 -0700140func copyFile(from, to string) error {
141 fromFile, err := os.Open(from)
142 if err != nil {
143 return err
144 }
145 defer fromFile.Close()
146
147 toFile, err := os.Create(to)
148 if err != nil {
149 return err
150 }
151 defer toFile.Close()
152
153 _, err = io.Copy(toFile, fromFile)
154 return err
155}
156
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700157type mpContext struct {
158 Context context.Context
159 Logger logger.Logger
160 Status status.ToolStatus
161 Tracer tracer.Tracer
162 Finder *finder.Finder
163 Config build.Config
164
165 LogsDir string
166}
167
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800168func main() {
Colin Cross097ed2a2019-06-08 21:48:58 -0700169 stdio := terminal.StdioImpl{}
Dan Willemsenb82471a2018-05-17 16:37:09 -0700170
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700171 output := terminal.NewStatusOutput(stdio.Stdout(), "", false,
Colin Crosse0df1a32019-06-09 19:40:08 -0700172 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
173
174 log := logger.New(output)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800175 defer log.Cleanup()
176
177 flag.Parse()
178
179 ctx, cancel := context.WithCancel(context.Background())
180 defer cancel()
181
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700182 trace := tracer.New(log)
183 defer trace.Close()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800184
Dan Willemsenb82471a2018-05-17 16:37:09 -0700185 stat := &status.Status{}
186 defer stat.Finish()
Colin Crosse0df1a32019-06-09 19:40:08 -0700187 stat.AddOutput(output)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700188
Dan Willemsen34218ec2018-07-19 22:57:18 -0700189 var failures failureCount
190 stat.AddOutput(&failures)
191
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700192 build.SetupSignals(log, cancel, func() {
193 trace.Close()
194 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700195 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700196 })
197
Dan Willemsen59339a22018-07-22 21:18:45 -0700198 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700199 Context: ctx,
200 Logger: log,
201 Tracer: trace,
Colin Crosse0df1a32019-06-09 19:40:08 -0700202 Writer: output,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700203 Status: stat,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700204 }}
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800205
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000206 args := ""
207 if *alternateResultDir {
208 args = "dist"
209 }
210 config := build.NewConfig(buildCtx, args)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800211 if *outDir == "" {
Dan Willemsen41538382018-08-31 19:51:35 -0700212 name := "multiproduct"
213 if !*incremental {
214 name += "-" + time.Now().Format("20060102150405")
215 }
Steven Moreland552432e2017-03-29 19:26:09 -0700216
217 *outDir = filepath.Join(config.OutDir(), name)
218
Dan Willemsenf624fb92017-05-19 16:39:04 -0700219 // Ensure the empty files exist in the output directory
220 // containing our output directory too. This is mostly for
221 // safety, but also triggers the ninja_build file so that our
222 // build servers know that they can parse the output as if it
223 // was ninja output.
224 build.SetupOutDir(buildCtx, config)
225
Steven Moreland552432e2017-03-29 19:26:09 -0700226 if err := os.MkdirAll(*outDir, 0777); err != nil {
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800227 log.Fatalf("Failed to create tempdir: %v", err)
228 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800229 }
230 config.Environment().Set("OUT_DIR", *outDir)
231 log.Println("Output directory:", *outDir)
232
Dan Willemsene3480762017-11-07 11:23:27 -0800233 logsDir := filepath.Join(config.OutDir(), "logs")
234 os.MkdirAll(logsDir, 0777)
235
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800236 build.SetupOutDir(buildCtx, config)
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000237
238 os.MkdirAll(config.LogsDir(), 0777)
239 log.SetOutput(filepath.Join(config.LogsDir(), "soong.log"))
240 trace.SetOutput(filepath.Join(config.LogsDir(), "build.trace"))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800241
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800242 var jobs = *numJobs
243 if jobs < 1 {
244 jobs = runtime.NumCPU() / 4
245
246 ramGb := int(config.TotalRAM() / 1024 / 1024 / 1024)
Colin Crossf04fe9a2021-01-26 14:03:21 -0800247 if ramJobs := ramGb / 25; ramGb > 0 && jobs > ramJobs {
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800248 jobs = ramJobs
249 }
250
251 if jobs < 1 {
252 jobs = 1
253 }
254 }
255 log.Verbosef("Using %d parallel jobs", jobs)
256
Dan Willemsen22de2162017-12-11 14:35:23 -0800257 setMaxFiles(log)
258
Dan Willemsen04d76ef2018-05-15 00:52:29 -0700259 finder := build.NewSourceFinder(buildCtx, config)
260 defer finder.Shutdown()
261
262 build.FindSources(buildCtx, config, finder)
263
Dan Willemsenb2e6c2e2017-07-13 17:24:44 -0700264 vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800265 if err != nil {
266 log.Fatal(err)
267 }
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700268 var productsList []string
269 allProducts := strings.Fields(vars["all_named_products"])
270
Colin Crossf2f3d312020-12-17 11:29:31 -0800271 if len(includeProducts) > 0 {
272 var missingProducts []string
273 for _, product := range includeProducts {
Jeff Gastonb61e3f72017-10-25 15:02:45 -0700274 if inList(product, allProducts) {
275 productsList = append(productsList, product)
276 } else {
277 missingProducts = append(missingProducts, product)
278 }
279 }
280 if len(missingProducts) > 0 {
281 log.Fatalf("Products don't exist: %s\n", missingProducts)
282 }
283 } else {
284 productsList = allProducts
285 }
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700286
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700287 finalProductsList := make([]string, 0, len(productsList))
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700288 skipProduct := func(p string) bool {
Colin Crossf2f3d312020-12-17 11:29:31 -0800289 for _, s := range skipProducts {
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700290 if p == s {
291 return true
292 }
293 }
294 return false
295 }
296 for _, product := range productsList {
297 if !skipProduct(product) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700298 finalProductsList = append(finalProductsList, product)
Dan Willemsen9957b9c2017-10-06 15:05:05 -0700299 } else {
300 log.Verbose("Skipping: ", product)
301 }
302 }
303
Dan Willemsen9609ad92019-12-05 15:22:41 -0800304 if *shard < 1 {
305 log.Fatalf("--shard value must be >= 1, not %d\n", *shard)
306 } else if *shardCount < 1 {
307 log.Fatalf("--shard-count value must be >= 1, not %d\n", *shardCount)
308 } else if *shard > *shardCount {
309 log.Fatalf("--shard (%d) must not be greater than --shard-count (%d)\n", *shard,
310 *shardCount)
311 } else if *shardCount > 1 {
312 finalProductsList = splitList(finalProductsList, *shardCount)[*shard-1]
313 }
314
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700315 log.Verbose("Got product list: ", finalProductsList)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800316
Dan Willemsenb82471a2018-05-17 16:37:09 -0700317 s := buildCtx.Status.StartTool()
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700318 s.SetTotalActions(len(finalProductsList))
Dan Willemsena4e43a72017-05-06 16:58:26 -0700319
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700320 mpCtx := &mpContext{
321 Context: ctx,
322 Logger: log,
323 Status: s,
324 Tracer: trace,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800325
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700326 Finder: finder,
327 Config: config,
Dan Willemsenf624fb92017-05-19 16:39:04 -0700328
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700329 LogsDir: logsDir,
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800330 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700331
332 products := make(chan string, len(productsList))
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800333 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700334 defer close(products)
335 for _, product := range finalProductsList {
336 products <- product
337 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800338 }()
339
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700340 var wg sync.WaitGroup
Dan Willemsen1bdbdec2019-12-27 09:54:11 -0800341 for i := 0; i < jobs; i++ {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700342 wg.Add(1)
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800343 go func() {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700344 defer wg.Done()
345 for {
346 select {
347 case product := <-products:
348 if product == "" {
349 return
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800350 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700351 buildProduct(mpCtx, product)
352 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800353 }
354 }()
355 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700356 wg.Wait()
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800357
Dan Willemsene3480762017-11-07 11:23:27 -0800358 if *alternateResultDir {
359 args := zip.ZipArgs{
360 FileArgs: []zip.FileArg{
361 {GlobDir: logsDir, SourcePrefixToStrip: logsDir},
362 },
Rupert Shuttleworth3c9f5ac2020-12-10 11:32:38 +0000363 OutputFilePath: filepath.Join(config.RealDistDir(), "logs.zip"),
Dan Willemsene3480762017-11-07 11:23:27 -0800364 NumParallelJobs: runtime.NumCPU(),
365 CompressionLevel: 5,
366 }
Colin Cross05518bc2018-09-27 15:06:19 -0700367 if err := zip.Zip(args); err != nil {
Dan Willemsene3480762017-11-07 11:23:27 -0800368 log.Fatalf("Error zipping logs: %v", err)
369 }
370 }
371
Dan Willemsenb82471a2018-05-17 16:37:09 -0700372 s.Finish()
Dan Willemsen34218ec2018-07-19 22:57:18 -0700373
374 if failures == 1 {
375 log.Fatal("1 failure")
376 } else if failures > 1 {
377 log.Fatalf("%d failures", failures)
378 } else {
Colin Crosse0df1a32019-06-09 19:40:08 -0700379 fmt.Fprintln(output, "Success")
Dan Willemsen34218ec2018-07-19 22:57:18 -0700380 }
Dan Willemsenc2af0be2017-01-20 14:10:01 -0800381}
Dan Willemsen34218ec2018-07-19 22:57:18 -0700382
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700383func buildProduct(mpctx *mpContext, product string) {
384 var stdLog string
385
386 outDir := filepath.Join(mpctx.Config.OutDir(), product)
387 logsDir := filepath.Join(mpctx.LogsDir, product)
388
389 if err := os.MkdirAll(outDir, 0777); err != nil {
390 mpctx.Logger.Fatalf("Error creating out directory: %v", err)
391 }
392 if err := os.MkdirAll(logsDir, 0777); err != nil {
393 mpctx.Logger.Fatalf("Error creating log directory: %v", err)
394 }
395
396 stdLog = filepath.Join(logsDir, "std.log")
397 f, err := os.Create(stdLog)
398 if err != nil {
399 mpctx.Logger.Fatalf("Error creating std.log: %v", err)
400 }
401 defer f.Close()
402
403 log := logger.New(f)
404 defer log.Cleanup()
405 log.SetOutput(filepath.Join(logsDir, "soong.log"))
406
407 action := &status.Action{
408 Description: product,
409 Outputs: []string{product},
410 }
411 mpctx.Status.StartAction(action)
412 defer logger.Recover(func(err error) {
413 mpctx.Status.FinishAction(status.ActionResult{
414 Action: action,
415 Error: err,
416 Output: errMsgFromLog(stdLog),
417 })
418 })
419
420 ctx := build.Context{ContextImpl: &build.ContextImpl{
421 Context: mpctx.Context,
422 Logger: log,
423 Tracer: mpctx.Tracer,
Colin Cross097ed2a2019-06-08 21:48:58 -0700424 Writer: f,
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700425 Thread: mpctx.Tracer.NewThread(product),
426 Status: &status.Status{},
427 }}
Colin Crossc0b9f6b2019-09-23 12:44:54 -0700428 ctx.Status.AddOutput(terminal.NewStatusOutput(ctx.Writer, "", false,
Sasha Smundakc0c9ef92019-01-23 09:52:57 -0800429 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700430
Colin Cross00a8a3f2020-10-29 14:08:31 -0700431 args := append([]string(nil), flag.Args()...)
432 args = append(args, "--skip-soong-tests")
433 config := build.NewConfig(ctx, args...)
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700434 config.Environment().Set("OUT_DIR", outDir)
Dan Willemsenf99915f2018-10-25 22:04:42 -0700435 if !*keepArtifacts {
436 config.Environment().Set("EMPTY_NINJA_FILE", "true")
437 }
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700438 build.FindSources(ctx, config, mpctx.Finder)
439 config.Lunch(ctx, product, *buildVariant)
440
441 defer func() {
442 if *keepArtifacts {
443 args := zip.ZipArgs{
444 FileArgs: []zip.FileArg{
445 {
446 GlobDir: outDir,
447 SourcePrefixToStrip: outDir,
448 },
449 },
450 OutputFilePath: filepath.Join(mpctx.Config.OutDir(), product+".zip"),
451 NumParallelJobs: runtime.NumCPU(),
452 CompressionLevel: 5,
453 }
Colin Cross05518bc2018-09-27 15:06:19 -0700454 if err := zip.Zip(args); err != nil {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700455 log.Fatalf("Error zipping artifacts: %v", err)
456 }
457 }
Dan Willemsenf99915f2018-10-25 22:04:42 -0700458 if !*incremental {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700459 os.RemoveAll(outDir)
460 }
461 }()
462
463 buildWhat := build.BuildProductConfig
464 if !*onlyConfig {
465 buildWhat |= build.BuildSoong
466 if !*onlySoong {
467 buildWhat |= build.BuildKati
468 }
469 }
470
471 before := time.Now()
472 build.Build(ctx, config, buildWhat)
473
474 // Save std_full.log if Kati re-read the makefiles
475 if buildWhat&build.BuildKati != 0 {
Dan Willemsen29971232018-09-26 14:58:30 -0700476 if after, err := os.Stat(config.KatiBuildNinjaFile()); err == nil && after.ModTime().After(before) {
Dan Willemsenbcc1dbf2018-09-04 18:09:47 -0700477 err := copyFile(stdLog, filepath.Join(filepath.Dir(stdLog), "std_full.log"))
478 if err != nil {
479 log.Fatalf("Error copying log file: %s", err)
480 }
481 }
482 }
483
484 mpctx.Status.FinishAction(status.ActionResult{
485 Action: action,
486 })
487}
488
Dan Willemsen34218ec2018-07-19 22:57:18 -0700489type failureCount int
490
491func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
492
493func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
494 if result.Error != nil {
495 *f += 1
496 }
497}
498
499func (f *failureCount) Message(level status.MsgLevel, message string) {
500 if level >= status.ErrorLvl {
501 *f += 1
502 }
503}
504
505func (f *failureCount) Flush() {}
Colin Crosse0df1a32019-06-09 19:40:08 -0700506
507func (f *failureCount) Write(p []byte) (int, error) {
508 // discard writes
509 return len(p), nil
510}
Dan Willemsen9609ad92019-12-05 15:22:41 -0800511
512func splitList(list []string, shardCount int) (ret [][]string) {
513 each := len(list) / shardCount
514 extra := len(list) % shardCount
515 for i := 0; i < shardCount; i++ {
516 count := each
517 if extra > 0 {
518 count += 1
519 extra -= 1
520 }
521 ret = append(ret, list[:count])
522 list = list[count:]
523 }
524 return
525}