• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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 (
18	"bytes"
19	"context"
20	"flag"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"path/filepath"
25	"runtime"
26	"strings"
27	"sync"
28
29	"android/soong/ui/build"
30	"android/soong/ui/logger"
31	"android/soong/ui/tracer"
32)
33
34// We default to number of cpus / 4, which seems to be the sweet spot for my
35// system. I suspect this is mostly due to memory or disk bandwidth though, and
36// may depend on the size ofthe source tree, so this probably isn't a great
37// default.
38func detectNumJobs() int {
39	if runtime.NumCPU() < 4 {
40		return 1
41	}
42	return runtime.NumCPU() / 4
43}
44
45var numJobs = flag.Int("j", detectNumJobs(), "number of parallel kati jobs")
46
47var keep = flag.Bool("keep", false, "keep successful output files")
48
49var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
50
51var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
52var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
53
54type Product struct {
55	ctx    build.Context
56	config build.Config
57}
58
59func main() {
60	log := logger.New(os.Stderr)
61	defer log.Cleanup()
62
63	flag.Parse()
64
65	ctx, cancel := context.WithCancel(context.Background())
66	defer cancel()
67
68	trace := tracer.New(log)
69	defer trace.Close()
70
71	build.SetupSignals(log, cancel, func() {
72		trace.Close()
73		log.Cleanup()
74	})
75
76	buildCtx := build.Context{&build.ContextImpl{
77		Context:        ctx,
78		Logger:         log,
79		Tracer:         trace,
80		StdioInterface: build.StdioImpl{},
81	}}
82
83	failed := false
84
85	config := build.NewConfig(buildCtx)
86	if *outDir == "" {
87		var err error
88		*outDir, err = ioutil.TempDir(config.OutDir(), "multiproduct")
89		if err != nil {
90			log.Fatalf("Failed to create tempdir: %v", err)
91		}
92
93		if !*keep {
94			defer func() {
95				if !failed {
96					os.RemoveAll(*outDir)
97				}
98			}()
99		}
100	}
101	config.Environment().Set("OUT_DIR", *outDir)
102	log.Println("Output directory:", *outDir)
103
104	build.SetupOutDir(buildCtx, config)
105	log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
106	trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
107
108	vars, err := build.DumpMakeVars(buildCtx, config, nil, nil, []string{"all_named_products"})
109	if err != nil {
110		log.Fatal(err)
111	}
112	products := strings.Fields(vars["all_named_products"])
113	log.Verbose("Got product list:", products)
114
115	var wg sync.WaitGroup
116	errs := make(chan error, len(products))
117	productConfigs := make(chan Product, len(products))
118
119	// Run the product config for every product in parallel
120	for _, product := range products {
121		wg.Add(1)
122		go func(product string) {
123			defer wg.Done()
124			defer logger.Recover(func(err error) {
125				errs <- fmt.Errorf("Error building %s: %v", product, err)
126			})
127
128			productOutDir := filepath.Join(config.OutDir(), product)
129
130			if err := os.MkdirAll(productOutDir, 0777); err != nil {
131				log.Fatalf("Error creating out directory: %v", err)
132			}
133
134			f, err := os.Create(filepath.Join(productOutDir, "std.log"))
135			if err != nil {
136				log.Fatalf("Error creating std.log: %v", err)
137			}
138
139			productLog := logger.New(&bytes.Buffer{})
140			productLog.SetOutput(filepath.Join(productOutDir, "soong.log"))
141
142			productCtx := build.Context{&build.ContextImpl{
143				Context:        ctx,
144				Logger:         productLog,
145				Tracer:         trace,
146				StdioInterface: build.NewCustomStdio(nil, f, f),
147				Thread:         trace.NewThread(product),
148			}}
149
150			productConfig := build.NewConfig(productCtx)
151			productConfig.Environment().Set("OUT_DIR", productOutDir)
152			productConfig.Lunch(productCtx, product, "eng")
153
154			build.Build(productCtx, productConfig, build.BuildProductConfig)
155			productConfigs <- Product{productCtx, productConfig}
156		}(product)
157	}
158	go func() {
159		defer close(productConfigs)
160		wg.Wait()
161	}()
162
163	var wg2 sync.WaitGroup
164	// Then run up to numJobs worth of Soong and Kati
165	for i := 0; i < *numJobs; i++ {
166		wg2.Add(1)
167		go func() {
168			defer wg2.Done()
169			for product := range productConfigs {
170				func() {
171					defer logger.Recover(func(err error) {
172						errs <- fmt.Errorf("Error building %s: %v", product.config.TargetProduct(), err)
173					})
174
175					buildWhat := 0
176					if !*onlyConfig {
177						buildWhat |= build.BuildSoong
178						if !*onlySoong {
179							buildWhat |= build.BuildKati
180						}
181					}
182					build.Build(product.ctx, product.config, buildWhat)
183					if !*keep {
184						os.RemoveAll(product.config.OutDir())
185					}
186					log.Println("Finished running for", product.config.TargetProduct())
187				}()
188			}
189		}()
190	}
191	go func() {
192		wg2.Wait()
193		close(errs)
194	}()
195
196	for err := range errs {
197		failed = true
198		log.Print(err)
199	}
200
201	if failed {
202		log.Fatalln("Failed")
203	}
204}
205