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 (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.ninja來自build/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. 所以會進入到不同的分支。
這其中有一個很關鍵的函數:

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

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *