Android-new-build-system
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:
[email protected]/build-tools/$(host_prebuilts)/bin/makeparallel --ninja build/soong/soong_ui.bash --make-mode $(MAKECMDGOALS)
.PHONY : $(MAKECMDGOALS)
$(sort $(MAKECMDGOALS)) : run_soong_ui
@#empty
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" "[email protected]"
}
第一次執行, 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的執行過程[microfactory.go]:
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 <main-package>")
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
}
````
對應的型別為:
<div class="se-preview-section-delimiter"></div>
```go
// pkgPathMapping can be used with flag.Var to parse -pkg-path arguments of
// <package-prefix>=<path-prefix> 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的實現如下:
<div class="se-preview-section-delimiter"></div>
```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
下一步:
<div class="se-preview-section-delimiter"></div>
```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" "[email protected]"
}
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