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:  
    +@prebuilts/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" "$@"
}

第一次执行, 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" "$@"
}

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 (BUILDSYSTEM)/envsetup.mkinclude (BUILD_SYSTEM)/clang/versions.mk
include (BUILDSYSTEM)/combo/javac.mkJACK:= (HOST_OUT_EXECUTABLES)/jack
ifndef KATI
include (BUILDSYSTEM)/ninjaconfig.mkinclude (BUILD_SYSTEM)/soong_config.mk
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文件是如何生成的。
BUILDDIR/.minibootstrap/build.ninjabuild/soong/build.ninja.in {BUILDDIR}/.bootstrap/build.ninja则由${BUILDDIR}/.minibootstrap/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. 所以会进入到不同的分支。
这其中有一个很关键的函数:

$(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk)))

回到kati.go,继续分析:
在该函数内有一个关键点函数katiRewriteOutput()

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()  

猜你喜欢

转载自blog.csdn.net/wangkaiblog/article/details/78340104