Android-new-build-system教程
step 1. make/core/main.mk
ifndef kati host_prebuilts := linux-x86 ifeq ($(shell uname),darwin) host_prebuilts := darwin-x86 endif .phony: run_soong_ui run_soong_ui: +@prebuilts/build-tools/$(host_prebuilts)/bin/makeparallel --ninja build/soong/soong_ui.bash --make-mode $(makecmdgoals) .phony: $(makecmdgoals) $(sort $(makecmdgoals)) : run_soong_ui @#empty
step 2. soong/soong_ui.bash
# bootstrap microfactory from source if necessary and use it to build the # soong_ui binary, then run soong_ui. function run_go { # increment when microfactory changes enough that it cannot rebuild itself. # for example, if we use a new command line argument that doesn't work on older versions. local mf_version=2 local mf_src="${top}/build/soong/cmd/microfactory" local out_dir="${out_dir-}" if [ -z "${out_dir}" ]; then if [ "${out_dir_common_base-}" ]; then out_dir="${out_dir_common_base}/$(basename ${top})" else out_dir="${top}/out" fi fi local mf_bin="${out_dir}/microfactory_$(uname)" local mf_version_file="${out_dir}/.microfactory_$(uname)_version" local soong_ui_bin="${out_dir}/soong_ui" local from_src=1 if [ -f "${mf_bin}" ] && [ -f "${mf_version_file}" ]; then if [ "${mf_version}" -eq "$(cat "${mf_version_file}")" ]; then from_src=0 fi fi local mf_cmd if [ $from_src -eq 1 ]; then mf_cmd="${goroot}/bin/go run ${mf_src}/microfactory.go" else mf_cmd="${mf_bin}" fi ${mf_cmd} -s "${mf_src}" -b "${mf_bin}" \ -pkg-path "android/soong=${top}/build/soong" -trimpath "${top}/build/soong" \ -o "${soong_ui_bin}" android/soong/cmd/soong_ui if [ $from_src -eq 1 ]; then echo "${mf_version}" >"${mf_version_file}" fi exec "${out_dir}/soong_ui" "$@" }
第一次执行, from_src为1:
varable | value |
---|---|
mf_cmd | /home/kelvin/os/android-8.0.0_r4/prebuilts/go/linux-x86//bin/go run /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go |
mf_src | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory |
mf_bin | /home/kelvin/os/android-8.0.0_r4/out/microfactory_linux |
soong_ui_bin | /home/kelvin/os/android-8.0.0_r4/out/soong_ui |
现在分析mf_cmd的执行过程:
func main() { var output, mysrc, mybin, trimpath string var pkgmap pkgpathmapping flags := flag.newflagset("", flag.exitonerror) flags.boolvar(&race, "race", false, "enable data race detection.") flags.boolvar(&verbose, "v", false, "verbose") flags.stringvar(&output, "o", "", "output file") flags.stringvar(&mysrc, "s", "", "microfactory source directory (for rebuilding microfactory if necessary)") flags.stringvar(&mybin, "b", "", "microfactory binary location") flags.stringvar(&trimpath, "trimpath", "", "remove prefix from recorded source file paths") flags.var(&pkgmap, "pkg-path", "mapping of package prefixes to file paths") err := flags.parse(os.args[1:]) if err == flag.errhelp || flags.narg() != 1 || output == "" { fmt.fprintln(os.stderr, "usage:", os.args[0], "-o out/binary ") flags.printdefaults() os.exit(1) } if mybin != "" && mysrc != "" { rebuildmicrofactory(mybin, mysrc, &pkgmap) } mainpackage := &gopackage{ name: "main", } if path, ok, err := pkgmap.path(flags.arg(0)); err != nil { fmt.fprintln(os.stderr, "error finding main path:", err) os.exit(1) } else if !ok { fmt.fprintln(os.stderr, "cannot find path for", flags.arg(0)) } else { if err := mainpackage.finddeps(path, &pkgmap); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } } intermediates := filepath.join(filepath.dir(output), "."+filepath.base(output)+"_intermediates") err = os.mkdirall(intermediates, 0777) if err != nil { fmt.fprintln(os.stderr, "failed to create intermediates directory: %ve", err) os.exit(1) } err = mainpackage.compile(intermediates, trimpath) if err != nil { fmt.fprintln(os.stderr, "failed to compile:", err) os.exit(1) } err = mainpackage.link(output) if err != nil { fmt.fprintln(os.stderr, "failed to link:", err) os.exit(1) } }
解析传入的参数,然后调用流程如下:
// rebuildmicrofactory checks to see if microfactory itself needs to be rebuilt, // and if does, it will launch a new copy instead of returning. func rebuildmicrofactory(mybin, mysrc string, pkgmap *pkgpathmapping) { intermediates := filepath.join(filepath.dir(mybin), "."+filepath.base(mybin)+"_intermediates") err := os.mkdirall(intermediates, 0777) if err != nil { fmt.fprintln(os.stderr, "failed to create intermediates directory: %v", err) os.exit(1) } pkg := &gopackage{ name: "main", } if err := pkg.finddeps(mysrc, pkgmap); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } if err := pkg.compile(intermediates, mysrc); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } if err := pkg.link(mybin); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } if !pkg.rebuilt { return } cmd := exec.command(mybin, os.args[1:]...) cmd.stdin = os.stdin cmd.stdout = os.stdout cmd.stderr = os.stderr if err := cmd.run(); err == nil { os.exit(0) } else if e, ok := err.(*exec.exiterror); ok { os.exit(e.processstate.sys().(syscall.waitstatus).exitstatus()) } os.exit(1) }
注意,还函数会进入两次,该函数中一些重要的变量:
varable | value |
---|---|
intermediates | /home/kelvin/os/android-8.0.0_r4/out/.microfactory_linux_intermediates |
寻找依赖:
// finddeps is the recursive version of finddeps. allpackages is the map of // all locally defined packages so that the same dependency of two different // packages is only resolved once. func (p *gopackage) finddeps(path string, pkgmap *pkgpathmapping, allpackages map[string]*gopackage) error { // if this ever becomes too slow, we can look at reading the files once instead of twice // but that just complicates things today, and we're already really fast. foundpkgs, err := parser.parsedir(token.newfileset(), path, func(fi os.fileinfo) bool { name := fi.name() if fi.isdir() || strings.hassuffix(name, "_test.go") || name[0] == '.' || name[0] == '_' { return false } if runtime.goos != "darwin" && strings.hassuffix(name, "_darwin.go") { return false } if runtime.goos != "linux" && strings.hassuffix(name, "_linux.go") { return false } return true }, parser.importsonly) if err != nil { return fmt.errorf("error parsing directory %q: %v", path, err) } var foundpkg *ast.package // foundpkgs is a map[string]*ast.package, but we only want one package if len(foundpkgs) != 1 { return fmt.errorf("expected one package in %q, got %d", path, len(foundpkgs)) } // extract the first (and only) entry from the map. for _, pkg := range foundpkgs { foundpkg = pkg } var deps []string localdeps := make(map[string]bool) for filename, astfile := range foundpkg.files { p.files = append(p.files, filename) for _, importspec := range astfile.imports { name, err := strconv.unquote(importspec.path.value) if err != nil { return fmt.errorf("%s: invalid quoted string: <%s> %v", filename, importspec.path.value, err) } if pkg, ok := allpackages[name]; ok && pkg != nil { if pkg != nil { if _, ok := localdeps[name]; !ok { deps = append(deps, name) localdeps[name] = true } } continue } var pkgpath string if path, ok, err := pkgmap.path(name); err != nil { return err } else if !ok { // probably in the stdlib, compiler will fail we a reasonable error message otherwise. // mark it as such so that we don't try to decode its path again. allpackages[name] = nil continue } else { pkgpath = path } pkg := &gopackage{ name: name, } deps = append(deps, name) allpackages[name] = pkg localdeps[name] = true if err := pkg.finddeps(pkgpath, pkgmap, allpackages); err != nil { return err } } } sort.strings(p.files) if verbose { fmt.fprintf(os.stderr, "package %q depends on %v\n", p.name, deps) } for _, dep := range deps { p.deps = append(p.deps, allpackages[dep]) } return nil }
那么还该函数怎么寻找依赖的呢?
func parsedir(fset *token.fileset, path string, filter func(os.fileinfo) bool, mode mode) (pkgs map[string]*ast.package, first error)
parsedir calls parsefile for all files with names ending in “.go” in the directory specified by path and returns a map of package name -> package ast with all the packages found.
if filter != nil, only the files with os.fileinfo entries passing through the filter (and ending in “.go”) are considered. the mode bits are passed to parsefile unchanged. position information is recorded in fset, which must not be nil.
if the directory couldn’t be read, a nil map and the respective error are returned. if a parse error occurred, a non-nil but incomplete map and the first error encountered are returned.
这里传入的path为 mysrc,该值由soong/soong_ui.bash传入
varable | value |
---|---|
mf_cmd | /home/kelvin/os/android-8.0.0_r4/prebuilts/go/linux-x86//bin/go run /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go |
mf_src | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory |
mf_bin | /home/kelvin/os/android-8.0.0_r4/out/microfactory_linux |
soong_ui_bin | /home/kelvin/os/android-8.0.0_r4/out/soong_ui |
我们打印其中的filename的名字:
第一次调用:
intermediates | /home/kelvin/os/android-8.0.0_r4/out/.microfactory_linux_intermediates |
---|---|
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go |
依赖就是import语句所import的包。对其中每一个import的包会调用pkgmap.path(name);
// path takes a package name, applies the path mappings and returns the resulting path. // // if the package isn't mapped, we'll return false to prevent compilation attempts. func (p *pkgpathmapping) path(pkg string) (string, bool, error) { if p.paths == nil { return "", false, fmt.errorf("no package mappings") } for _, pkgprefix := range p.pkgs { if pkg == pkgprefix { return p.paths[pkgprefix], true, nil } else if strings.hasprefix(pkg, pkgprefix+"/") { return filepath.join(p.paths[pkgprefix], strings.trimprefix(pkg, pkgprefix+"/")), true, nil } } return "", false, nil } ```` 对应的类型为: ```go // pkgpathmapping can be used with flag.var to parse -pkg-path arguments of //= mappings. type pkgpathmapping struct { pkgs []string paths map[string]string }
其值由soong/soong_ui.bash传递,参数为”android/soong=${top}/build/soong”
在解析时,会调用其set函数:
func (p *pkgpathmapping) set(value string) error { equalpos := strings.index(value, "=") if equalpos == -1 { return fmt.errorf("argument must be in the form of: %q", p.string()) } pkgprefix := strings.trimsuffix(value[:equalpos], "/") pathprefix := strings.trimsuffix(value[equalpos+1:], "/") if p.paths == nil { p.paths = make(map[string]string) } if _, ok := p.paths[pkgprefix]; ok { return fmt.errorf("duplicate package prefix: %q", pkgprefix) } p.pkgs = append(p.pkgs, pkgprefix) p.paths[pkgprefix] = pathprefix return nil }
pkgprefix | android/soong |
---|---|
pathprefix | /home/kelvin/os/android-8.0.0_r4/build/soong |
soong/cmd/microfactory/microfactory.go import的包有:
import ( "bytes" "crypto/sha1" "flag" "fmt" "go/ast" "go/parser" "go/token" "io" "io/ioutil" "os" "os/exec" "path/filepath" "runtime" "sort" "strconv" "strings" "sync" "syscall" )
第一次执行该函数后:
package “main” depends on []
然后执行第二阶段:rebuildmicrofactory的pkg.compile(intermediates, mysrc)
func rebuildmicrofactory(mybin, mysrc string, pkgmap *pkgpathmapping) { ...... fmt.fprintln(os.stderr, "@@@finddeps") if err := pkg.finddeps(mysrc, pkgmap); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } fmt.fprintln(os.stderr, "@@@@compile" ) if err := pkg.compile(intermediates, mysrc); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } ...... }
传入的参数:
intermediates | /home/kelvin/os/android-8.0.0_r4/out/.microfactory_linux_intermediates |
---|---|
mysrc | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory |
compile的实现如下,并行的编译所有的依赖文件,第一次进来的时候没有依赖文件。
然后调用命令/home/kelvin/os/android-8.0.0_r4/prebuilts/go/linux-x86/pkg/tool/linux_amd64/compile进行编译。
函数说明:
func command(name string, arg ...string) *cmd
command returns the cmd struct to execute the named program with the given arguments.
it sets only the path and args in the returned structure.
if name contains no path separators, command uses lookpath to resolve name to a complete path if possible. otherwise it uses name directly as path.
the returned cmd’s args field is constructed from the command name followed by the elements of arg, so arg should not include the command name itself. for example, command(“echo”, “hello”). args[0] is always name, not the possibly resolved path.
func (c *cmd) run() error
run starts the specified command and waits for it to complete.
the returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and exits with a zero exit status.
if the command starts but does not complete successfully, the error is of type *exiterror. other error types may be returned for other situations.
传入的参数:
p.output | /home/kelvin/os/android-8.0.0_r4/out/.microfactory_linux_intermediates/main/main.a |
---|---|
p.name | main |
func (p *gopackage) compile(outdir, trimpath string) error { p.mutex.lock() defer p.mutex.unlock() if p.compiled { return p.failed } p.compiled = true // build all dependencies in parallel, then fail if any of them failed. var wg sync.waitgroup for _, dep := range p.deps { wg.add(1) go func(dep *gopackage) { defer wg.done() dep.compile(outdir, trimpath) }(dep) } wg.wait() for _, dep := range p.deps { if dep.failed != nil { p.failed = dep.failed return p.failed } } p.pkgdir = filepath.join(outdir, p.name) p.output = filepath.join(p.pkgdir, p.name) + ".a" shafile := p.output + ".hash" hash := sha1.new() fmt.fprintln(hash, runtime.goos, runtime.goarch, goversion) cmd := exec.command(filepath.join(gotooldir, "compile"), "-o", p.output, "-p", p.name, "-complete", "-pack", "-nolocalimports") if race { cmd.args = append(cmd.args, "-race") fmt.fprintln(hash, "-race") } if trimpath != "" { cmd.args = append(cmd.args, "-trimpath", trimpath) fmt.fprintln(hash, trimpath) } for _, dep := range p.deps { cmd.args = append(cmd.args, "-i", dep.pkgdir) hash.write(dep.hashresult) } for _, filename := range p.files { cmd.args = append(cmd.args, filename) fmt.fprintln(hash, filename) // hash the contents of the input files f, err := os.open(filename) if err != nil { f.close() err = fmt.errorf("%s: %v", filename, err) p.failed = err return err } _, err = io.copy(hash, f) if err != nil { f.close() err = fmt.errorf("%s: %v", filename, err) p.failed = err return err } f.close() } p.hashresult = hash.sum(nil) var rebuild bool if _, err := os.stat(p.output); err != nil { rebuild = true } if !rebuild { if oldsha, err := ioutil.readfile(shafile); err == nil { rebuild = !bytes.equal(oldsha, p.hashresult) } else { rebuild = true } } if !rebuild { return nil } err := os.removeall(p.pkgdir) if err != nil { err = fmt.errorf("%s: %v", p.name, err) p.failed = err return err } err = os.mkdirall(filepath.dir(p.output), 0777) if err != nil { err = fmt.errorf("%s: %v", p.name, err) p.failed = err return err } cmd.stdin = nil cmd.stdout = os.stdout cmd.stderr = os.stderr if verbose { fmt.fprintln(os.stderr, cmd.args) } err = cmd.run() if err != nil { err = fmt.errorf("%s: %v", p.name, err) p.failed = err return err } err = ioutil.writefile(shafile, p.hashresult, 0666) if err != nil { err = fmt.errorf("%s: %v", p.name, err) p.failed = err return err } p.rebuilt = true return nil }
第三步是rebuildmicrofactory.pkg.link(mybin)
func rebuildmicrofactory(mybin, mysrc string, pkgmap *pkgpathmapping) { ...... fmt.fprintln(os.stderr, "@@@@link" ) if err := pkg.link(mybin); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } ...... } ```` link的实现如下: ```go func (p *gopackage) link(out string) error { if p.name != "main" { return fmt.errorf("can only link main package") } shafile := filepath.join(filepath.dir(out), "."+filepath.base(out)+"_hash") if !p.rebuilt { if _, err := os.stat(out); err != nil { p.rebuilt = true } else if oldsha, err := ioutil.readfile(shafile); err != nil { p.rebuilt = true } else { p.rebuilt = !bytes.equal(oldsha, p.hashresult) } } if !p.rebuilt { return nil } err := os.remove(shafile) if err != nil && !os.isnotexist(err) { return err } err = os.remove(out) if err != nil && !os.isnotexist(err) { return err } cmd := exec.command(filepath.join(gotooldir, "link"), "-o", out) if race { cmd.args = append(cmd.args, "-race") } for _, dep := range p.deps { cmd.args = append(cmd.args, "-l", dep.pkgdir) } cmd.args = append(cmd.args, p.output) cmd.stdin = nil cmd.stdout = os.stdout cmd.stderr = os.stderr if verbose { fmt.fprintln(os.stderr, cmd.args) } err = cmd.run() if err != nil { return err } return ioutil.writefile(shafile, p.hashresult, 0666) }
与compile类似。
最后一步:
func rebuildmicrofactory(mybin, mysrc string, pkgmap *pkgpathmapping) { ....... cmd := exec.command(mybin, os.args[1:]...) cmd.stdin = os.stdin cmd.stdout = os.stdout cmd.stderr = os.stderr if err := cmd.run(); err == nil { os.exit(0) } else if e, ok := err.(*exec.exiterror); ok { os.exit(e.processstate.sys().(syscall.waitstatus).exitstatus()) } os.exit(1) }
此处执行的为mybin:
/home/kelvin/os/android-8.0.0_r4/out/microfactory_linux
该文件由上一步的link()生成。
所以,microfacrory.go又会执行一遍。那么第二次执行和第一次执行有什么不同呢?
第二次执行完后,会继续mainpackage.finddeps
func main() { ...... mainpackage := &gopackage{ name: "main", } if path, ok, err := pkgmap.path(flags.arg(0)); err != nil { fmt.fprintln(os.stderr, "error finding main path:", err) os.exit(1) } else if !ok { fmt.fprintln(os.stderr, "cannot find path for", flags.arg(0)) } else { if err := mainpackage.finddeps(path, &pkgmap); err != nil { fmt.fprintln(os.stderr, err) os.exit(1) } }
这次传入的参数为是:
flags.arg(0): android/soong/cmd/soong_ui,该参数同样由soong/soong_ui.bash传递。
path: /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/soong_ui
那些会由依赖,import的包以android/soong开始,这些目录下的包都会被依赖。
下面soong/cmd/soong_ui/main.go
import ( "context" "os" "path/filepath" "strconv" "strings" "android/soong/ui/build" "android/soong/ui/logger" "android/soong/ui/tracer" ) ```` 结果如下: package | depends on --------|-------------------------------- "android/soong/ui/logger" | [] "android/soong/ui/tracer" | [android/soong/ui/logger] "android/soong/ui/build" | [android/soong/ui/logger android/soong/ui/tracer] "main" | [android/soong/ui/build android/soong/ui/logger android/soong/ui/tracer] 第二次调用: intermediates | /home/kelvin/os/android-8.0.0_r4/out/.microfactory_linux_intermediates --------------|------------------------------------------------ filename | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/soong_ui/main.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/util_linux.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/kati.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/context.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/logger/logger.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/tracer/tracer.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/tracer/ninja.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/ninja.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/signal.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/environment.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/soong.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/config.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/make.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/build.go filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/util.go 下一步: ```go func main() { ... ... err = mainpackage.compile(intermediates, trimpath) if err != nil { fmt.fprintln(os.stderr, "failed to compile:", err) os.exit(1) } err = mainpackage.link(output) if err != nil { fmt.fprintln(os.stderr, "failed to link:", err) os.exit(1) } }
传入的参数:
intermediates | /home/kelvin/os/android-8.0.0_r4/out/.soong_ui_intermediates |
---|---|
trimpath | /home/kelvin/os/android-8.0.0_r4/build/soong |
最后一步:
func main() { ...... err = mainpackage.link(output) if err != nil { fmt.fprintln(os.stderr, "failed to link:", err) os.exit(1) } }
这里需要关注的传入的参数:为 out/soong_ui, 这个即为生成的可执行文件。
该路径由soong/soong_ui.bash传入。
step 3. soong_ui的执行
soong/soog_ui.bash
# bootstrap microfactory from source if necessary and use it to build the # soong_ui binary, then run soong_ui. function run_go { ..... ${mf_cmd} -s "${mf_src }" -b "${mf_bin}" \ -pkg-path "android/soong=${top}/build/soong" -trimpath "${top}/build/soong" \ -o "${soong_ui_bin}" android/soong/cmd/soong_ui if [ $from_src -eq 1 ]; then echo "${mf_version}" >"${mf_version_file}" fi exec "${out_dir}/soong_ui" "$@" }
soong/cmd/soong_ui/main.go
func main() { log := logger.new(os.stderr) defer log.cleanup() if len(os.args) < 2 || !inlist("--make-mode", os.args) { log.fatalln("the `soong` native ui is not yet available.") } ctx, cancel := context.withcancel(context.background()) defer cancel() trace := tracer.new(log) defer trace.close() build.setupsignals(log, cancel, func() { trace.close() log.cleanup() }) buildctx := build.context{&build.contextimpl{ context: ctx, logger: log, tracer: trace, stdiointerface: build.stdioimpl{}, }} config := build.newconfig(buildctx, os.args[1:]...) log.setverbose(config.isverbose()) build.setupoutdir(buildctx, config) if config.dist() { logsdir := filepath.join(config.distdir(), "logs") os.mkdirall(logsdir, 0777) log.setoutput(filepath.join(logsdir, "soong.log")) trace.setoutput(filepath.join(logsdir, "build.trace")) } else { log.setoutput(filepath.join(config.outdir(), "soong.log")) trace.setoutput(filepath.join(config.outdir(), "build.trace")) } if start, ok := os.lookupenv("trace_begin_soong"); ok { if !strings.hassuffix(start, "n") { if start_time, err := strconv.parseuint(start, 10, 64); err == nil { log.verbosef("took %dms to start up.", time.since(time.unix(0, int64(start_time))).nanoseconds()/time.millisecond.nanoseconds()) buildctx.completetrace("startup", start_time, uint64(time.now().unixnano())) } } } build.build(buildctx, config, build.buildall) }
首先会注册信号处理函数:
build.setupsignals(log, cancel, func() { trace.close() log.cleanup() })
最后比较关键:
build.build(buildctx, config, build.buildall)
build/soong/ui/build/build.go
// build the tree. the 'what' argument can be used to chose which components of // the build to run. func build(ctx context, config config, what int) { ctx.verboseln("starting build with args:", config.arguments()) ctx.verboseln("environment:", config.environment().environ()) if inlist("help", config.arguments()) { cmd := exec.commandcontext(ctx.context, "make", "-f", "build/core/help.mk") cmd.env = config.environment().environ() cmd.stdout = ctx.stdout() cmd.stderr = ctx.stderr() if err := cmd.run(); err != nil { ctx.fatalln("failed to run make:", err) } return } setupoutdir(ctx, config) if what&buildproductconfig != 0 { // run make for product config runmakeproductconfig(ctx, config) } if what&buildsoong != 0 { // run soong runsoongbootstrap(ctx, config) runsoong(ctx, config) } if what&buildkati != 0 { // run ckati runkati(ctx, config) } if what&buildninja != 0 { // write combined ninja file createcombinedbuildninjafile(ctx, config) // run ninja runninja(ctx, config) } }
runmakeproductconfig分析:[build/soong/ui/build/make.go]
func runmakeproductconfig(ctx context, config config) { // variables to export into the environment of kati/ninja exportenvvars := []string{ // so that we can use the correct target_product if it's been // modified by product-* arguments "target_product", // compiler wrappers set up by make "cc_wrapper", "cxx_wrapper", // ccache settings "ccache_compilercheck", "ccache_sloppiness", "ccache_basedir", "ccache_cpp2", } // variables to print out in the top banner bannervars := []string{ "platform_version_codename", "platform_version", "target_product", "target_build_variant", "target_build_type", "target_build_apps", "target_arch", "target_arch_variant", "target_cpu_variant", "target_2nd_arch", "target_2nd_arch_variant", "target_2nd_cpu_variant", "host_arch", "host_2nd_arch", "host_os", "host_os_extra", "host_cross_os", "host_cross_arch", "host_cross_2nd_arch", "host_build_type", "build_id", "out_dir", "aux_os_variant_list", "target_build_pdk", "pdk_fusion_platform_zip", } allvars := append(append([]string{ // used to execute kati and ninja "ninja_goals", "kati_goals", }, exportenvvars...), bannervars...) make_vars, err := dumpmakevars(ctx, config, config.arguments(), []string{ filepath.join(config.soongoutdir(), "soong.variables"), }, allvars) if err != nil { ctx.fatalln("error dumping make vars:", err) } // print the banner like make does fmt.fprintln(ctx.stdout(), "============================================") for _, name := range bannervars { if make_vars[name] != "" { fmt.fprintf(ctx.stdout(), "%s=%s\n", name, make_vars[name]) } } fmt.fprintln(ctx.stdout(), "============================================") // populate the environment env := config.environment() for _, name := range exportenvvars { if make_vars[name] == "" { env.unset(name) } else { env.set(name, make_vars[name]) } } config.setkatiargs(strings.fields(make_vars["kati_goals"])) config.setninjaargs(strings.fields(make_vars["ninja_goals"])) }
dumpmakevars分析:
// dumpmakevars can be used to extract the values of make variables after the // product configurations are loaded. this is roughly equivalent to the // `get_build_var` bash function. // // goals can be used to set makecmdgoals, which emulates passing arguments to // make without actually building them. so all the variables based on // makecmdgoals can be read. // // extra_targets adds real arguments to the make command, in case other targets // actually need to be run (like the soong config generator). // // vars is the list of variables to read. the values will be put in the // returned map. func dumpmakevars(ctx context, config config, goals, extra_targets, vars []string) (map[string]string, error) { ctx.begintrace("dumpvars") defer ctx.endtrace() cmd := exec.commandcontext(ctx.context, "make", "--no-print-directory", "-f", "build/core/config.mk", "dump-many-vars", "called_from_setup=true", "build_system=build/core", "makecmdgoals="+strings.join(goals, " "), "dump_many_vars="+strings.join(vars, " "), "out_dir="+config.outdir()) cmd.env = config.environment().environ() cmd.args = append(cmd.args, extra_targets...) // todo: error out when stderr contains any content cmd.stderr = ctx.stderr() ctx.verboseln(cmd.path, cmd.args) output, err := cmd.output() if err != nil { return nil, err } ret := make(map[string]string, len(vars)) for _, line := range strings.split(string(output), "\n") { if len(line) == 0 { continue } if key, value, ok := decodekeyvalue(line); ok { if value, ok = singleunquote(value); ok { ret[key] = value ctx.verboseln(key, value) } else { return nil, fmt.errorf("failed to parse make line: %q", line) } } else { return nil, fmt.errorf("failed to parse make line: %q", line) } } return ret, nil }
go相关:
func (c *cmd) output() ([]byte, error)
output runs the command and returns its standard output. any returned error will usually be of type *exiterror.
if c.stderr was nil, output populates exiterror.stderr.
分析cofig.mk
include
include
ifndef kati
include
endif
include $(build_system)/dumpvar.mk
$(build_system) 为build/core/ 但是用的是/build/make/core/
core -> /home/kelvin/os/android-8.0.0_r4/build/make/core
下一步,执行build/soong/ui/build/build.go
func build(ctx context, config config, what int) { ...... if what&buildproductconfig != 0 { // run make for product config runmakeproductconfig(ctx, config) } if what&buildsoong != 0 { // run soong runsoongbootstrap(ctx, config) runsoong(ctx, config) } ...... }
runsoongbootstrap soong/ui/build/soong.go
func runsoongbootstrap(ctx context, config config) { ctx.begintrace("bootstrap soong") defer ctx.endtrace() cmd := exec.commandcontext(ctx.context, "./bootstrap.bash") env := config.environment().copy() env.set("builddir", config.soongoutdir()) cmd.env = env.environ() cmd.stdout = ctx.stdout() cmd.stderr = ctx.stderr() ctx.verboseln(cmd.path, cmd.args) if err := cmd.run(); err != nil { if e, ok := err.(*exec.exiterror); ok { ctx.fatalln("soong bootstrap failed with:", e.processstate.string()) } else { ctx.fatalln("failed to run soong bootstrap:", err) } } }
这里的bootstrap.bash是哪一个? build/soong/bootstrap.bash
if [[ $# -eq 0 ]]; then mkdir -p $builddir if [[ $(find $builddir -maxdepth 1 -name android.bp) ]]; then echo "failed: the build directory must not be a source directory" exit 1 fi export srcdir_from_builddir=$(build/soong/scripts/reverse_path.py "$builddir") sed -e "s|@@builddir@@|${builddir}|" \ -e "s|@@srcdirfrombuilddir@@|${srcdir_from_builddir}|" \ -e "s|@@prebuiltos@@|${prebuiltos}|" \ "$srcdir/build/soong/soong.bootstrap.in" > $builddir/.soong.bootstrap ln -sf "${srcdir_from_builddir}/build/soong/soong.bash" $builddir/soong fi "$srcdir/build/blueprint/bootstrap.bash" "$@"
这个片段会执行两次。 区别$#:number of arguments passed to script
生成的文件:
builddir=”out/soong”
srcdir_from_builddir=”../..”
prebuiltos=”linux-x86”
创建软链接:out/soong/soong -> ../../build/soong/soong.bash
最后执行
"$srcdir/build/blueprint/bootstrap.bash" "$@"
注意两个脚本会执行多次,每次执行时,其中的某些参数会有些变化。
在这个脚本文件里,生成out/soong/.minibootstrap/目录,并生成相应的文件。
$in > $builddir/.minibootstrap/build.ninja
第一次执行时,$in为build/soong/build.ninja.in
echo "bootstrap=\"${bootstrap}\"" > $builddir/.blueprint.bootstrap echo "bootstrap_manifest=\"${bootstrap_manifest}\"" >> $builddir/.blueprint.bootstrap
生成 out/soong/.blueprint.bootstrap
sed -e "s|@@srcdir@@|$srcdir|g" \ -e "s|@@builddir@@|$builddir|g" \ -e "s|@@goroot@@|$goroot|g" \ -e "s|@@gocompile@@|$gocompile|g" \ -e "s|@@golink@@|$golink|g" \ -e "s|@@bootstrap@@|$bootstrap|g" \ -e "s|@@bootstrapmanifest@@|$bootstrap_manifest|g" \ $in > $builddir/.minibootstrap/build.ninja
这段脚本的,将$builddir/.minibootstrap/build.ninja里的某些内容进行替换:
ninja_required_version = 1.7.0
g.bootstrap.builddir = out/soong
g.bootstrap.bindir = ${g.bootstrap.builddir}/.bootstrap/bin
g.bootstrap.bootstrapcmd = ./bootstrap.bash
g.bootstrap.compilecmd = ./prebuilts/go/linux-x86//pkg/tool/linux_amd64/compile
g.bootstrap.goroot = ./prebuilts/go/linux-x86/
g.bootstrap.gotestmaincmd = ${g.bootstrap.builddir}/.bootstrap/bin/gotestmain
g.bootstrap.gotestrunnercmd = ${g.bootstrap.builddir}/.bootstrap/bin/gotestrunner
g.bootstrap.linkcmd = ./prebuilts/go/linux-x86//pkg/tool/linux_amd64/link
g.bootstrap.srcdir = .
builddir = ${g.bootstrap.builddir}/.minibootstrap
回过头来,执行下面一句 runsoong(ctx, config)
func build(ctx context, config config, what int) { ...... if what&buildsoong != 0 { // run soong runsoongbootstrap(ctx, config) runsoong(ctx, config) } if what&buildkati != 0 { // run ckati runkati(ctx, config) } if what&buildninja != 0 { // write combined ninja file createcombinedbuildninjafile(ctx, config) // run ninja runninja(ctx, config) } }
其执行过程如下:build/soong/ui/build/soong.go
func runsoong(ctx context, config config) { ctx.begintrace("soong") defer ctx.endtrace() cmd := exec.commandcontext(ctx.context, filepath.join(config.soongoutdir(), "soong"), "-w", "dupbuild=err") if config.isverbose() { cmd.args = append(cmd.args, "-v") } env := config.environment().copy() env.set("skip_ninja", "true") cmd.env = env.environ() cmd.stdin = ctx.stdin() cmd.stdout = ctx.stdout() cmd.stderr = ctx.stderr() ctx.verboseln(cmd.path, cmd.args) if err := cmd.run(); err != nil { if e, ok := err.(*exec.exiterror); ok { ctx.fatalln("soong bootstrap failed with:", e.processstate.string()) } else { ctx.fatalln("failed to run soong bootstrap:", err) } } }
其中,filepath.join(config.soongoutdir(), “soong”)为out/soong/soong
而out/soong/soong为软链接,../../build/soong/soong.bash
所以,会执行该脚本。
soog/soog.bash:
builddir="${builddir}" ninja="prebuilts/build-tools/${prebuiltos}/bin/ninja" build/blueprint/blueprint.bash "$@"
所以,又会执行build/blueprint/blueprint.bash
关键代码如下:
# build minibp and the primary build.ninja "${ninja}" -w dupbuild=err -f "${builddir}/.minibootstrap/build.ninja" # build the primary builder and the main build.ninja "${ninja}" -w dupbuild=err -f "${builddir}/.bootstrap/build.ninja" # skip_ninja can be used by wrappers that wish to run ninja themselves. if [ -z "$skip_ninja" ]; then "${ninja}" -w dupbuild=err -f "${builddir}/build.ninja" "$@" else exit 0 fi
这里开始编译前面生成的build.ninja文件.
这里重点回顾下这两个build.ninja文件是如何生成的。
out/soong/.bootstrap/bin/soong_build out/soong/build.ninja则由上一步生成。
再回到build.go继续分析:,执行runkati
func build(ctx context, config config, what int) { ... ... if what&buildsoong != 0 { // run soong runsoongbootstrap(ctx, config) runsoong(ctx, config) } if what&buildkati != 0 { // run ckati runkati(ctx, config) } if what&buildninja != 0 { // write combined ninja file createcombinedbuildninjafile(ctx, config) // run ninja runninja(ctx, config) } }
build/soong/ui/kati.go
func runkati(ctx context, config config) { ctx.begintrace("kati") defer ctx.endtrace() genkatisuffix(ctx, config) executable := "prebuilts/build-tools/" + config.hostprebuilttag() + "/bin/ckati" args := []string{ "--ninja", "--ninja_dir=" + config.outdir(), "--ninja_suffix=" + config.katisuffix(), "--regen", "--ignore_optional_include=" + filepath.join(config.outdir(), "%.p"), "--detect_android_echo", "--color_warnings", "--gen_all_targets", "-f", "build/core/main.mk", } if !config.environment().isfalse("kati_emulate_find") { args = append(args, "--use_find_emulator") } args = append(args, config.katiargs()...) args = append(args, "building_with_ninja=true", "soong_android_mk="+config.soongandroidmk(), "soong_makevars_mk="+config.soongmakevarsmk()) if config.usegoma() { args = append(args, "-j"+strconv.itoa(config.parallel())) } cmd := exec.commandcontext(ctx.context, executable, args...) cmd.env = config.environment().environ() pipe, err := cmd.stdoutpipe() if err != nil { ctx.fatalln("error getting output pipe for ckati:", err) } cmd.stderr = cmd.stdout ctx.verboseln(cmd.path, cmd.args) if err := cmd.start(); err != nil { ctx.fatalln("failed to run ckati:", err) } katirewriteoutput(ctx, pipe) if err := cmd.wait(); err != nil { if e, ok := err.(*exec.exiterror); ok { ctx.fatalln("ckati failed with:", e.processstate.string()) } else { ctx.fatalln("failed to run ckati:", err) } } }
这里config.hostprebuilttag()为linux-x86
这一次指定的makefile为build/core/main.mk, 再次进入该函数,但是会有一些区别:
第一次进入的时候,没有定义kati, 这一次进入的时候则定义定kati. 所以会进入到不同的分支。
这其中有一个很关键的函数:
var katiincludere = regexp.mustcompile(`^(\[\d+/\d+] )?including [^ ]+ ...$`) verbose := katiincludere.matchstring(line)
在terminal看到的include xx.mk进度的语句:
if smartterminal && verbose { ... ... // move to the beginning on the line, print the output, then clear // the rest of the line. fmt.fprint(ctx.stdout(), "\r", line, "\x1b[k") haveblankline = false continue }
这里涉及到的go相关的信息,
func (c *cmd) stdoutpipe() (io.readcloser, error) func (c *cmd) start() error func newscanner(r io.reader) *scanner func (s *scanner) scan() h
回到build.go继续下面的分析:
func build(ctx context, config config, what int) { ... ... if what&buildkati != 0 { // run ckati runkati(ctx, config) } if what&buildninja != 0 { // write combined ninja file createcombinedbuildninjafile(ctx, config) // run ninja runninja(ctx, config) } }
现在分析createcombinedbuildninjafile build/soong/ui/build/build.go :
func createcombinedbuildninjafile(ctx context, config config) { file, err := os.create(config.combinedninjafile()) if err != nil { ctx.fatalln("failed to create combined ninja file:", err) } defer file.close() if err := combinedbuildninjatemplate.execute(file, config); err != nil { ctx.fatalln("failed to write combined ninja file:", err) } }
config.combinedninjafile()为 out/combined-aosp_arm.ninja
该文件的值为:
builddir = out
include out/build-aosp_arm.ninja
include out/soong/build.ninja
build out/combined-aosp_arm.ninja: phony out/soong/build.ninja
回到build.go继续下面的分析:
func build(ctx context, config config, what int) { ..... // run ninja runninja(ctx, config) } }
runninja的实现 build/soong/ui/build/ninja.go
func runninja(ctx context, config config) { ctx.begintrace("ninja") defer ctx.endtrace() executable := "prebuilts/build-tools/" + config.hostprebuilttag() + "/bin/ninja" args := []string{ "-d", "keepdepfile", } args = append(args, config.ninjaargs()...) var parallel int if config.usegoma() { parallel = config.remoteparallel() } else { parallel = config.parallel() } args = append(args, "-j", strconv.itoa(parallel)) if config.keepgoing != 1 { args = append(args, "-k", strconv.itoa(config.keepgoing)) } args = append(args, "-f", config.combinedninjafile()) if config.isverbose() { args = append(args, "-v") } args = append(args, "-w", "dupbuild=err") env := config.environment().copy() env.appendfromkati(config.katienvfile()) // allow both ninja_args and ninja_extra_args, since both have been // used in the past to specify extra ninja arguments. if extra, ok := env.get("ninja_args"); ok { args = append(args, strings.fields(extra)...) } if extra, ok := env.get("ninja_extra_args"); ok { args = append(args, strings.fields(extra)...) } if _, ok := env.get("ninja_status"); !ok { env.set("ninja_status", "[%p %f/%t] ") } cmd := exec.commandcontext(ctx.context, executable, args...) cmd.env = env.environ() cmd.stdin = ctx.stdin() cmd.stdout = ctx.stdout() cmd.stderr = ctx.stderr() ctx.verboseln(cmd.path, cmd.args) starttime := time.now() defer ctx.importninjalog(filepath.join(config.outdir(), ".ninja_log"), starttime) if err := cmd.run(); err != nil { if e, ok := err.(*exec.exiterror); ok { ctx.fatalln("ninja failed with:", e.processstate.string()) } else { ctx.fatalln("failed to run ninja:", err) } } }
这里的编译工具为
executable := "prebuilts/build-tools/" + config.hostprebuilttag() + "/bin/ninja"
其传入的参数为:
-d keepdepfile -j 8 -f out/combined-aosp_arm.ninja -w dupbuild=err
所以,ninja执行的文件的为out/combined-aosp_arm.ninja,该文件包含了其他之前的编译生成的ninja文件
builddir = out
include out/build-aosp_arm.ninja
include out/soong/build.ninja
build out/combined-aosp_arm.ninja: phony out/soong/build.ninja
那么具体的编译过程是怎样的呢?
out/build-aosp_arm.ninja这个文件是怎么生成的呢?
逆向分析:
最后/bin/ninja执行的-f 参数来自config.combinedninjafile build/soong/ui/build/config.go
func (c *configimpl) combinedninjafile() string { return filepath.join(c.outdir(), "combined"+c.katisuffix()+".ninja") }
katisuffix的设置/build/soong/ui/build/kati.go.
var combinedbuildninjatemplate = template.must(template.new("combined").parse(` builddir = {{.outdir}} include {{.katininjafile}} include {{.soongninjafile}} build {{.combinedninjafile}}: phony {{.soongninjafile}} `))
该文件的生成过程在kati.go
cmd.path == prebuilts/build-tools/linux-x86/bin/ckati
cmd.args== [prebuilts/build-tools/linux-x86/bin/ckati –ninja –ninja_dir=out
–ninja_suffix=-aosp_arm –regen –ignore_optional_include=out/%.p –detect_android_echo
–color_warnings –gen_all_targets -f build/core/main.mk –use_find_emulator building_with_ninja=true
soong_android_mk=out/soong/android-aosp_arm.mk soong_makevars_mk=out/soong/make_vars-aosp_arm.mk]
所以,也就是ckati 将makefile生成了ninjia文件。
关于该文件的生成,可以参考源代码的说明文档。kati开始由go实现,由于performace的原因,改为c++实现。
out/soong/build.ninja的生成过程
注意out/soong/soong是一个软链接。所以有会soong.bash. soong.bash会执行blueprint/blueprint.bash
# .blueprint.bootstrap provides saved values from the bootstrap.bash script: # # bootstrap # bootstrap_manifest # source "${builddir}/.blueprint.bootstrap" gen_bootstrap_manifest="${builddir}/.minibootstrap/build.ninja.in" if [ -f "${gen_bootstrap_manifest}" ]; then if [ "${bootstrap_manifest}" -nt "${gen_bootstrap_manifest}" ]; then "${bootstrap}" -i "${bootstrap_manifest}" fi else "${bootstrap}" -i "${bootstrap_manifest}" fi
需要注意的是,第一次编译和dirty build不同。
这里的值
bootstrap=”./bootstrap.bash”
bootstrap_manifest=”./build/soong/build.ninja.in”
所以,会执行代码根目录下的bootstrap.bash
所以,又会执行build/blueprint/bootstrap.bash
编译android.bp文件的命令为
out/soong/.bootstrap/bin/minibp -t -b out/soong -d out/soong/.minibootstrap/build.ninja.in.d -o out/soong/.minibootstrap/build.ninja.in android.bp
更多参考信息:build/blueprint/bootstrap/doc.go
kati生成ninja文件的build/kati/ninja.cc
void generateninja()
上一篇: jsp导出excel 解决文件名中文乱码
下一篇: 一个不错的正则