1. 程式人生 > >Android-new-build-system

Android-new-build-system

1. make/core/main.mk

ifndef KATI

host_prebuilts := linux-x86  
ifeq ($(shell uname),Darwin)  
host_prebuilts := darwin-x86  
endif  

.PHONY: run_soong_ui  
run_soong_ui:  
    [email protected]/build-tools/$(host_prebuilts)/bin/makeparallel --ninja build/soong/soong_ui.bash --make-mode $(MAKECMDGOALS)  

.PHONY
: $(MAKECMDGOALS)
$(sort $(MAKECMDGOALS)) : run_soong_ui @#empty

2. soong/soong_ui.bash

# Bootstrap microfactory from source if necessary and use it to build the
# soong_ui binary, then run soong_ui.
function run_go
{
    # Increment when microfactory changes enough that it cannot rebuild itself.
# For example, if we use a new command line argument that doesn't work on older versions. local mf_version=2 local mf_src="${TOP}/build/soong/cmd/microfactory" local out_dir="${OUT_DIR-}" if [ -z "${out_dir}" ]; then if [ "${OUT_DIR_COMMON_BASE-}" ]; then out_dir="${OUT_DIR_COMMON_BASE}
/$(basename ${TOP})"
else out_dir="${TOP}/out" fi fi local mf_bin="${out_dir}/microfactory_$(uname)" local mf_version_file="${out_dir}/.microfactory_$(uname)_version" local soong_ui_bin="${out_dir}/soong_ui" local from_src=1 if [ -f "${mf_bin}" ] && [ -f "${mf_version_file}" ]; then if [ "${mf_version}" -eq "$(cat "${mf_version_file}")" ]; then from_src=0 fi fi local mf_cmd if [ $from_src -eq 1 ]; then mf_cmd="${GOROOT}/bin/go run ${mf_src}/microfactory.go" else mf_cmd="${mf_bin}" fi ${mf_cmd} -s "${mf_src}" -b "${mf_bin}" \ -pkg-path "android/soong=${TOP}/build/soong" -trimpath "${TOP}/build/soong" \ -o "${soong_ui_bin}" android/soong/cmd/soong_ui if [ $from_src -eq 1 ]; then echo "${mf_version}" >"${mf_version_file}" fi exec "${out_dir}/soong_ui" "[email protected]" }

第一次執行, from_src為1:

varable value
mf_cmd /home/kelvin/os/android-8.0.0_r4/prebuilts/go/linux-x86//bin/go run /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go
mf_src /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory
mf_bin /home/kelvin/os/android-8.0.0_r4/out/microfactory_Linux
soong_ui_bin /home/kelvin/os/android-8.0.0_r4/out/soong_ui

現在分析mf_cmd的執行過程[microfactory.go]:

func main() {
    var output, mysrc, mybin, trimPath string
    var pkgMap pkgPathMapping

    flags := flag.NewFlagSet("", flag.ExitOnError)
    flags.BoolVar(&race, "race", false, "enable data race detection.")
    flags.BoolVar(&verbose, "v", false, "Verbose")
    flags.StringVar(&output, "o", "", "Output file")
    flags.StringVar(&mysrc, "s", "", "Microfactory source directory (for rebuilding microfactory if necessary)")
    flags.StringVar(&mybin, "b", "", "Microfactory binary location")
    flags.StringVar(&trimPath, "trimpath", "", "remove prefix from recorded source file paths")
    flags.Var(&pkgMap, "pkg-path", "Mapping of package prefixes to file paths")
    err := flags.Parse(os.Args[1:])

    if err == flag.ErrHelp || flags.NArg() != 1 || output == "" {
        fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "-o out/binary <main-package>")
        flags.PrintDefaults()
        os.Exit(1)
    }

    if mybin != "" && mysrc != "" {
        rebuildMicrofactory(mybin, mysrc, &pkgMap)
    }

    mainPackage := &GoPackage{
        Name: "main",
    }

    if path, ok, err := pkgMap.Path(flags.Arg(0)); err != nil {
        fmt.Fprintln(os.Stderr, "Error finding main path:", err)
        os.Exit(1)
    } else if !ok {
        fmt.Fprintln(os.Stderr, "Cannot find path for", flags.Arg(0))
    } else {
        if err := mainPackage.FindDeps(path, &pkgMap); err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }

    intermediates := filepath.Join(filepath.Dir(output), "."+filepath.Base(output)+"_intermediates")

    err = os.MkdirAll(intermediates, 0777)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to create intermediates directory: %ve", err)
        os.Exit(1)
    }

    err = mainPackage.Compile(intermediates, trimPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to compile:", err)
        os.Exit(1)
    }

    err = mainPackage.Link(output)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to link:", err)
        os.Exit(1)
    }
}

解析傳入的引數,然後呼叫流程如下:

// rebuildMicrofactory checks to see if microfactory itself needs to be rebuilt,
// and if does, it will launch a new copy instead of returning.
func rebuildMicrofactory(mybin, mysrc string, pkgMap *pkgPathMapping) {
    intermediates := filepath.Join(filepath.Dir(mybin), "."+filepath.Base(mybin)+"_intermediates")

    err := os.MkdirAll(intermediates, 0777)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to create intermediates directory: %v", err)
        os.Exit(1)
    }

    pkg := &GoPackage{
        Name: "main",
    }

    if err := pkg.FindDeps(mysrc, pkgMap); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    if err := pkg.Compile(intermediates, mysrc); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    if err := pkg.Link(mybin); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    if !pkg.rebuilt {
        return
    }

    cmd := exec.Command(mybin, os.Args[1:]...)
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err == nil {
        os.Exit(0)
    } else if e, ok := err.(*exec.ExitError); ok {
        os.Exit(e.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
    }
    os.Exit(1)
}

注意,還函式會進入兩次,該函式中一些重要的變數:

varable value
intermediates /home/kelvin/os/android-8.0.0_r4/out/.microfactory_Linux_intermediates

尋找依賴:

// findDeps is the recursive version of FindDeps. allPackages is the map of
// all locally defined packages so that the same dependency of two different
// packages is only resolved once.
func (p *GoPackage) findDeps(path string, pkgMap *pkgPathMapping, allPackages map[string]*GoPackage) error {
    // If this ever becomes too slow, we can look at reading the files once instead of twice
    // But that just complicates things today, and we're already really fast.
    foundPkgs, err := parser.ParseDir(token.NewFileSet(), path, func(fi os.FileInfo) bool {
        name := fi.Name()
        if fi.IsDir() || strings.HasSuffix(name, "_test.go") || name[0] == '.' || name[0] == '_' {
            return false
        }
        if runtime.GOOS != "darwin" && strings.HasSuffix(name, "_darwin.go") {
            return false
        }
        if runtime.GOOS != "linux" && strings.HasSuffix(name, "_linux.go") {
            return false
        }
        return true
    }, parser.ImportsOnly)
    if err != nil {
        return fmt.Errorf("Error parsing directory %q: %v", path, err)
    }

    var foundPkg *ast.Package
    // foundPkgs is a map[string]*ast.Package, but we only want one package
    if len(foundPkgs) != 1 {
        return fmt.Errorf("Expected one package in %q, got %d", path, len(foundPkgs))
    }
    // Extract the first (and only) entry from the map.
    for _, pkg := range foundPkgs {
        foundPkg = pkg
    }

    var deps []string
    localDeps := make(map[string]bool)

    for filename, astFile := range foundPkg.Files {
        p.files = append(p.files, filename)

        for _, importSpec := range astFile.Imports {
            name, err := strconv.Unquote(importSpec.Path.Value)
            if err != nil {
                return fmt.Errorf("%s: invalid quoted string: <%s> %v", filename, importSpec.Path.Value, err)
            }

            if pkg, ok := allPackages[name]; ok && pkg != nil {
                if pkg != nil {
                    if _, ok := localDeps[name]; !ok {
                        deps = append(deps, name)
                        localDeps[name] = true
                    }
                }
                continue
            }

            var pkgPath string
            if path, ok, err := pkgMap.Path(name); err != nil {
                return err
            } else if !ok {
                // Probably in the stdlib, compiler will fail we a reasonable error message otherwise.
                // Mark it as such so that we don't try to decode its path again.
                allPackages[name] = nil
                continue
            } else {
                pkgPath = path
            }

            pkg := &GoPackage{
                Name: name,
            }
            deps = append(deps, name)
            allPackages[name] = pkg
            localDeps[name] = true

            if err := pkg.findDeps(pkgPath, pkgMap, allPackages); err != nil {
                return err
            }
        }
    }

    sort.Strings(p.files)

    if verbose {
        fmt.Fprintf(os.Stderr, "Package %q depends on %v\n", p.Name, deps)
    }

    for _, dep := range deps {
        p.deps = append(p.deps, allPackages[dep])
    }

    return nil
}

那麼還該函式怎麼尋找依賴的呢?

func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error)  

ParseDir calls ParseFile for all files with names ending in “.go” in the directory specified by path and returns a map of package name -> package AST with all the packages found.

If filter != nil, only the files with os.FileInfo entries passing through the filter (and ending in “.go”) are considered. The mode bits are passed to ParseFile unchanged. Position information is recorded in fset, which must not be nil.

If the directory couldn’t be read, a nil map and the respective error are returned. If a parse error occurred, a non-nil but incomplete map and the first error encountered are returned.

這裡傳入的path為 mysrc,該值由soong/soong_ui.bash傳入

varable value
mf_cmd /home/kelvin/os/android-8.0.0_r4/prebuilts/go/linux-x86//bin/go run /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go
mf_src /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory
mf_bin /home/kelvin/os/android-8.0.0_r4/out/microfactory_Linux
soong_ui_bin /home/kelvin/os/android-8.0.0_r4/out/soong_ui

我們列印其中的filename的名字:
第一次呼叫:

intermediates /home/kelvin/os/android-8.0.0_r4/out/.microfactory_Linux_intermediates
filename /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go

依賴就是import語句所import的包。對其中每一個import的包會呼叫pkgMap.Path(name);

// Path takes a package name, applies the path mappings and returns the resulting path.
//
// If the package isn't mapped, we'll return false to prevent compilation attempts.
func (p *pkgPathMapping) Path(pkg string) (string, bool, error) {
    if p.paths == nil {
        return "", false, fmt.Errorf("No package mappings")
    }

    for _, pkgPrefix := range p.pkgs {
        if pkg == pkgPrefix {
            return p.paths[pkgPrefix], true, nil
        } else if strings.HasPrefix(pkg, pkgPrefix+"/") {
            return filepath.Join(p.paths[pkgPrefix], strings.TrimPrefix(pkg, pkgPrefix+"/")), true, nil
        }
    }

    return "", false, nil
}
````
對應的型別為:




<div class="se-preview-section-delimiter"></div>

```go
// pkgPathMapping can be used with flag.Var to parse -pkg-path arguments of
// <package-prefix>=<path-prefix> mappings.
type pkgPathMapping struct {
    pkgs []string

    paths map[string]string
}

其值由soong/soong_ui.bash傳遞,引數為”android/soong=${TOP}/build/soong”
在解析時,會呼叫其Set函式:

func (p *pkgPathMapping) Set(value string) error {
    equalPos := strings.Index(value, "=")
    if equalPos == -1 {
        return fmt.Errorf("Argument must be in the form of: %q", p.String())
    }

    pkgPrefix := strings.TrimSuffix(value[:equalPos], "/")
    pathPrefix := strings.TrimSuffix(value[equalPos+1:], "/")

    if p.paths == nil {
        p.paths = make(map[string]string)
    }
    if _, ok := p.paths[pkgPrefix]; ok {
        return fmt.Errorf("Duplicate package prefix: %q", pkgPrefix)
    }

    p.pkgs = append(p.pkgs, pkgPrefix)
    p.paths[pkgPrefix] = pathPrefix

    return nil
}
pkgPrefix android/soong
pathPrefix /home/kelvin/os/android-8.0.0_r4/build/soong

soong/cmd/microfactory/microfactory.go import的包有:

import (
    "bytes"
    "crypto/sha1"
    "flag"
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
    "io"
    "io/ioutil"
    "os"
    "os/exec"
    "path/filepath"
    "runtime"
    "sort"
    "strconv"
    "strings"
    "sync"
    "syscall"
)

第一次執行該函式後:
Package “main” depends on []

然後執行第二階段:rebuildMicrofactory的pkg.Compile(intermediates, mysrc)

func rebuildMicrofactory(mybin, mysrc string, pkgMap *pkgPathMapping) {
    ......
    fmt.Fprintln(os.Stderr, "@@@FindDeps")
    if err := pkg.FindDeps(mysrc, pkgMap); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    fmt.Fprintln(os.Stderr, "@@@@Compile" )
    if err := pkg.Compile(intermediates, mysrc); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    ......
}

傳入的引數:

intermediates /home/kelvin/os/android-8.0.0_r4/out/.microfactory_Linux_intermediates
mysrc /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory

Compile的實現如下,並行的編譯所有的依賴檔案,第一次進來的時候沒有依賴檔案。
然後呼叫命令/home/kelvin/os/android-8.0.0_r4/prebuilts/go/linux-x86/pkg/tool/linux_amd64/compile進行編譯
函式說明:

func Command(name string, arg ...string) *Cmd

Command returns the Cmd struct to execute the named program with the given arguments.

It sets only the Path and Args in the returned structure.

If name contains no path separators, Command uses LookPath to resolve name to a complete path if possible. Otherwise it uses name directly as Path.

The returned Cmd’s Args field is constructed from the command name followed by the elements of arg, so arg should not include the command name itself. For example, Command(“echo”, “hello”). Args[0] is always name, not the possibly resolved Path.

func (c *Cmd) Run() error

Run starts the specified command and waits for it to complete.

The returned error is nil if the command runs, has no problems copying stdin, stdout, and stderr, and exits with a zero exit status.

If the command starts but does not complete successfully, the error is of type *ExitError. Other error types may be returned for other situations.

傳入的引數:

p.output /home/kelvin/os/android-8.0.0_r4/out/.microfactory_Linux_intermediates/main/main.a
p.name main
func (p *GoPackage) Compile(outDir, trimPath string) error {
    p.mutex.Lock()
    defer p.mutex.Unlock()
    if p.compiled {
        return p.failed
    }
    p.compiled = true

    // Build all dependencies in parallel, then fail if any of them failed.
    var wg sync.WaitGroup
    for _, dep := range p.deps {
        wg.Add(1)
        go func(dep *GoPackage) {
            defer wg.Done()
            dep.Compile(outDir, trimPath)
        }(dep)
    }
    wg.Wait()
    for _, dep := range p.deps {
        if dep.failed != nil {
            p.failed = dep.failed
            return p.failed
        }
    }

    p.pkgDir = filepath.Join(outDir, p.Name)
    p.output = filepath.Join(p.pkgDir, p.Name) + ".a"
    shaFile := p.output + ".hash"

    hash := sha1.New()
    fmt.Fprintln(hash, runtime.GOOS, runtime.GOARCH, goVersion)

    cmd := exec.Command(filepath.Join(goToolDir, "compile"),
        "-o", p.output,
        "-p", p.Name,
        "-complete", "-pack", "-nolocalimports")
    if race {
        cmd.Args = append(cmd.Args, "-race")
        fmt.Fprintln(hash, "-race")
    }
    if trimPath != "" {
        cmd.Args = append(cmd.Args, "-trimpath", trimPath)
        fmt.Fprintln(hash, trimPath)
    }
    for _, dep := range p.deps {
        cmd.Args = append(cmd.Args, "-I", dep.pkgDir)
        hash.Write(dep.hashResult)
    }
    for _, filename := range p.files {
        cmd.Args = append(cmd.Args, filename)
        fmt.Fprintln(hash, filename)

        // Hash the contents of the input files
        f, err := os.Open(filename)
        if err != nil {
            f.Close()
            err = fmt.Errorf("%s: %v", filename, err)
            p.failed = err
            return err
        }
        _, err = io.Copy(hash, f)
        if err != nil {
            f.Close()
            err = fmt.Errorf("%s: %v", filename, err)
            p.failed = err
            return err
        }
        f.Close()
    }
    p.hashResult = hash.Sum(nil)

    var rebuild bool
    if _, err := os.Stat(p.output); err != nil {
        rebuild = true
    }
    if !rebuild {
        if oldSha, err := ioutil.ReadFile(shaFile); err == nil {
            rebuild = !bytes.Equal(oldSha, p.hashResult)
        } else {
            rebuild = true
        }
    }

    if !rebuild {
        return nil
    }

    err := os.RemoveAll(p.pkgDir)
    if err != nil {
        err = fmt.Errorf("%s: %v", p.Name, err)
        p.failed = err
        return err
    }

    err = os.MkdirAll(filepath.Dir(p.output), 0777)
    if err != nil {
        err = fmt.Errorf("%s: %v", p.Name, err)
        p.failed = err
        return err
    }

    cmd.Stdin = nil
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if verbose {
        fmt.Fprintln(os.Stderr, cmd.Args)
    }
    err = cmd.Run()
    if err != nil {
        err = fmt.Errorf("%s: %v", p.Name, err)
        p.failed = err
        return err
    }

    err = ioutil.WriteFile(shaFile, p.hashResult, 0666)
    if err != nil {
        err = fmt.Errorf("%s: %v", p.Name, err)
        p.failed = err
        return err
    }

    p.rebuilt = true

    return nil
}

第三步是rebuildMicrofactory.pkg.Link(mybin)

func rebuildmicrofactory(mybin, mysrc string, pkgmap *pkgpathmapping) {
    ......
    fmt.fprintln(os.stderr, "@@@@link" )
    if err := pkg.link(mybin); err != nil {
        fmt.fprintln(os.stderr, err)
        os.exit(1)
    }
    ......
}
````

Link的實現如下:




<div class="se-preview-section-delimiter"></div>

```go
func (p *GoPackage) Link(out string) error {
    if p.Name != "main" {
        return fmt.Errorf("Can only link main package")
    }

    shaFile := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_hash")

    if !p.rebuilt {
        if _, err := os.Stat(out); err != nil {
            p.rebuilt = true
        } else if oldSha, err := ioutil.ReadFile(shaFile); err != nil {
            p.rebuilt = true
        } else {
            p.rebuilt = !bytes.Equal(oldSha, p.hashResult)
        }
    }
    if !p.rebuilt {
        return nil
    }

    err := os.Remove(shaFile)
    if err != nil && !os.IsNotExist(err) {
        return err
    }
    err = os.Remove(out)
    if err != nil && !os.IsNotExist(err) {
        return err
    }

    cmd := exec.Command(filepath.Join(goToolDir, "link"), "-o", out)
    if race {
        cmd.Args = append(cmd.Args, "-race")
    }
    for _, dep := range p.deps {
        cmd.Args = append(cmd.Args, "-L", dep.pkgDir)
    }
    cmd.Args = append(cmd.Args, p.output)
    cmd.Stdin = nil
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if verbose {
        fmt.Fprintln(os.Stderr, cmd.Args)
    }
    err = cmd.Run()
    if err != nil {
        return err
    }

    return ioutil.WriteFile(shaFile, p.hashResult, 0666)
}

與Compile類似。

最後一步:

func rebuildmicrofactory(mybin, mysrc string, pkgmap *pkgpathmapping) {
    .......
    cmd := exec.command(mybin, os.args[1:]...)
    cmd.stdin = os.stdin
    cmd.stdout = os.stdout
    cmd.stderr = os.stderr
    if err := cmd.run(); err == nil {
        os.exit(0)
    } else if e, ok := err.(*exec.exiterror); ok {
        os.exit(e.processstate.sys().(syscall.waitstatus).exitstatus())
    }
    os.exit(1)
}

此處執行的為mybin:
/home/kelvin/os/android-8.0.0_r4/out/microfactory_Linux
該檔案由上一步的Link()生成。
所以,microfacrory.go又會執行一遍。那麼第二次執行和第一次執行有什麼不同呢?

第二次執行完後,會繼續mainPackage.FindDeps

func main() {
    ......
    mainPackage := &GoPackage{
        Name: "main",
    }

    if path, ok, err := pkgMap.Path(flags.Arg(0)); err != nil {
        fmt.Fprintln(os.Stderr, "Error finding main path:", err)
        os.Exit(1)
    } else if !ok {
        fmt.Fprintln(os.Stderr, "Cannot find path for", flags.Arg(0))
    } else {
        if err := mainPackage.FindDeps(path, &pkgMap); err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }

這次傳入的引數為是:
flags.Arg(0): android/soong/cmd/soong_ui,該引數同樣由soong/soong_ui.bash傳遞。
path: /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/soong_ui

那些會由依賴,import的包以android/soong開始,這些目錄下的包都會被依賴。
下面soong/cmd/soong_ui/main.go

import (
  "context"
  "os"
  "path/filepath"
  "strconv"
  "strings"

  "android/soong/ui/build"
  "android/soong/ui/logger"
  "android/soong/ui/tracer"
)
````
結果如下:  
Package | depends on
--------|--------------------------------
"android/soong/ui/logger" | []  
"android/soong/ui/tracer" | [android/soong/ui/logger]  
"android/soong/ui/build" | [android/soong/ui/logger android/soong/ui/tracer]
"main" | [android/soong/ui/build android/soong/ui/logger android/soong/ui/tracer]  


第二次呼叫:  
intermediates |  /home/kelvin/os/android-8.0.0_r4/out/.microfactory_Linux_intermediates  
--------------|------------------------------------------------
filename      | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/microfactory/microfactory.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/cmd/soong_ui/main.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/util_linux.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/kati.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/context.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/logger/logger.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/tracer/tracer.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/tracer/ninja.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/ninja.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/signal.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/environment.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/soong.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/config.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/make.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/build.go  
filename | /home/kelvin/os/android-8.0.0_r4/build/soong/ui/build/util.go  


下一步:  




<div class="se-preview-section-delimiter"></div>

```go
func main() {
    ... ...
    err = mainPackage.Compile(intermediates, trimPath)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to compile:", err)
        os.Exit(1)
    }

    err = mainPackage.Link(output)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to link:", err)
        os.Exit(1)
    }
}

傳入的引數:

intermediates /home/kelvin/os/android-8.0.0_r4/out/.soong_ui_intermediates
trimPath /home/kelvin/os/android-8.0.0_r4/build/soong

最後一步:

func main() {
    ......
    err = mainPackage.Link(output)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Failed to link:", err)
        os.Exit(1)
    }
}

這裡需要關注的傳入的引數:為 out/soong_ui, 這個即為生成的可執行檔案。
該路徑由soong/soong_ui.bash傳入。

Step 3. soong_ui的執行

soong/soog_ui.bash

# Bootstrap microfactory from source if necessary and use it to build the
# soong_ui binary, then run soong_ui.
function run_go
{
    .....
    ${mf_cmd} -s "${mf_src }" -b "${mf_bin}" \
            -pkg-path "android/soong=${TOP}/build/soong" -trimpath "${TOP}/build/soong" \
            -o "${soong_ui_bin}" android/soong/cmd/soong_ui

    if [ $from_src -eq 1 ]; then
        echo "${mf_version}" >"${mf_version_file}"
    fi

    exec "${out_dir}/soong_ui" "[email protected]"
}

soong/cmd/soong_ui/main.go

func main() {
    log := logger.New(os.Stderr)
    defer log.Cleanup()

    if len(os.Args) < 2 || !inList("--make-mode", os.Args) {
        log.Fatalln("The `soong` native UI is not yet available.") } ctx, cancel := context.WithCancel(context.Background()) defer cancel()

    trace := tracer.New(log)
    defer trace.Close()

    build.SetupSignals(log, cancel, func() {
        trace.Close()
        log.Cleanup()
    })

    buildCtx := build.Context{&build.ContextImpl{
        Context:        ctx,
        Logger:         log,
        Tracer:         trace,
        StdioInterface: build.StdioImpl{},
    }}
    config := build.NewConfig(buildCtx, os.Args[1:]...)

    log.SetVerbose(config.IsVerbose())
    build.SetupOutDir(buildCtx, config)

    if config.Dist() {
        logsDir := filepath.Join(config.DistDir(), "logs")
        os.MkdirAll(logsDir, 0777)
        log.SetOutput(filepath.Join(logsDir, "soong.log"))
        trace.SetOutput(filepath.Join(logsDir, "build.trace"))
    } else {
        log.SetOutput(filepath.Join(config.OutDir(), "soong.log"))
        trace.SetOutput(filepath.Join(config.OutDir(), "build.trace"))
    }

    if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
        if !strings.HasSuffix(start, "N") {
            if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
                log.Verbosef("Took %dms to start up.",
                    time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
                buildCtx.CompleteTrace("startup", start_time, uint64(time.Now().UnixNano()))
            }
        }
    }

    build.Build(buildCtx, config, build.BuildAll)
}

首先會註冊訊號處理函式:

    build.SetupSignals(log, cancel, func() {
        trace.Close()
        log.Cleanup()
    })

最後比較關鍵:

    build.Build(buildCtx, config, build.BuildAll)

build/soong/ui/build/build.go

// Build the tree. The 'what' argument can be used to chose which components of
// the build to run.
func Build(ctx Context, config Config, what int) {
    ctx.Verboseln("Starting build with args:", config.Arguments())
    ctx.Verboseln("Environment:", config.Environment().Environ())

    if inList("help", config.Arguments()) {
        cmd := exec.CommandContext(ctx.Context, "make", "-f", "build/core/help.mk")
        cmd.Env = config.Environment().Environ()
        cmd.Stdout = ctx.Stdout()
        cmd.Stderr = ctx.Stderr()
        if err := cmd.Run(); err != nil {
            ctx.Fatalln("Failed to run make:", err)
        }
        return
    }

    SetupOutDir(ctx, config)

    if what&BuildProductConfig != 0 {
        // Run make for product config
        runMakeProductConfig(ctx, config)
    }

    if what&BuildSoong != 0 {
        // Run Soong
        runSoongBootstrap(ctx, config)
        runSoong(ctx, config)
    }

    if what&BuildKati != 0 {
        // Run ckati
        runKati(ctx, config)
    }

    if what&BuildNinja != 0 {
        // Write combined ninja file
        createCombinedBuildNinjaFile(ctx, config)

        // Run ninja
        runNinja(ctx, config)
    }
}

runMakeProductConfig分析:[build/soong/ui/build/make.go]

func runMakeProductConfig(ctx Context, config Config) {
    // Variables to export into the environment of Kati/Ninja
    exportEnvVars := []string{
        // So that we can use the correct TARGET_PRODUCT if it's been
        // modified by PRODUCT-* arguments
        "TARGET_PRODUCT",

        // compiler wrappers set up by make
        "CC_WRAPPER",
        "CXX_WRAPPER",

        // ccache settings
        "CCACHE_COMPILERCHECK",
        "CCACHE_SLOPPINESS",
        "CCACHE_BASEDIR",
        "CCACHE_CPP2",
    }

    // Variables to print out in the top banner
    bannerVars := []string{
        "PLATFORM_VERSION_CODENAME",
        "PLATFORM_VERSION",
        "TARGET_PRODUCT",
        "TARGET_BUILD_VARIANT",
        "TARGET_BUILD_TYPE",
        "TARGET_BUILD_APPS",
        "TARGET_ARCH",
        "TARGET_ARCH_VARIANT",
        "TARGET_CPU_VARIANT",
        "TARGET_2ND_ARCH",
        "TARGET_2ND_ARCH_VARIANT",
        "TARGET_2ND_CPU_VARIANT",
        "HOST_ARCH",
        "HOST_2ND_ARCH",
        "HOST_OS",
        "HOST_OS_EXTRA",
        "HOST_CROSS_OS",
        "HOST_CROSS_ARCH",
        "HOST_CROSS_2ND_ARCH",
        "HOST_BUILD_TYPE",
        "BUILD_ID",
        "OUT_DIR",
        "AUX_OS_VARIANT_LIST",
        "TARGET_BUILD_PDK",
        "PDK_FUSION_PLATFORM_ZIP",
    }

    allVars := append(append([]string{
        // Used to execute Kati and Ninja
        "NINJA_GOALS",
        "KATI_GOALS",
    }, exportEnvVars...), bannerVars...)

    make_vars, err := DumpMakeVars(ctx, config, config.Arguments(), []string{
        filepath.Join(config.SoongOutDir(), "soong.variables"),
    }, allVars)
    if err != nil {
        ctx.Fatalln("Error dumping make vars:", err)
    }

    // Print the banner like make does
    fmt.Fprintln(ctx.Stdout(), "============================================")
    for _, name := range bannerVars {
        if make_vars[name] != "" {
            fmt.Fprintf(ctx.Stdout(), "%s=%s\n", name, make_vars[name])
        }
    }
    fmt.Fprintln(ctx.Stdout(), "============================================")

    // Populate the environment
    env := config.Environment()
    for _, name := range exportEnvVars {
        if make_vars[name] == "" {
            env.Unset(name)
        } else {
            env.Set(name, make_vars[name])
        }
    }

    config.SetKatiArgs(strings.Fields(make_vars["KATI_GOALS"]))
    config.SetNinjaArgs(strings.Fields(make_vars["NINJA_GOALS"]))
}

DumpMakeVars分析:

// DumpMakeVars can be used to extract the values of Make variables after the
// product configurations are loaded. This is roughly equivalent to the
// `get_build_var` bash function.
//
// goals can be used to set MAKECMDGOALS, which emulates passing arguments to
// Make without actually building them. So all the variables based on
// MAKECMDGOALS can be read.
//
// extra_targets adds real arguments to the make command, in case other targets
// actually need to be run (like the Soong config generator).
//
// vars is the list of variables to read. The values will be put in the
// returned map.
func DumpMakeVars(ctx Context, config Config, goals, extra_targets, vars []string) (map[string]string, error) {
    ctx.BeginTrace("dumpvars")
    defer ctx.EndTrace()

    cmd := exec.CommandContext(ctx.Context,
        "make",
        "--no-print-directory",
        "-f", "build/core/config.mk",
        "dump-many-vars",
        "CALLED_FROM_SETUP=true",
        "BUILD_SYSTEM=build/core",
        "MAKECMDGOALS="+strings.Join(goals, " "),
        "DUMP_MANY_VARS="+strings.Join(vars, " "),
        "OUT_DIR="+config.OutDir())
    cmd.Env = config.Environment().Environ()
    cmd.Args = append(cmd.Args, extra_targets...)
    // TODO: error out when Stderr contains any content
    cmd.Stderr = ctx.Stderr()
    ctx.Verboseln(cmd.Path, cmd.Args)
    output, err := cmd.Output()
    if err != nil {
        return nil, err
    }

    ret := make(map[string]string, len(vars))
    for _, line := range strings.Split(string(output), "\n") {
        if len(line) == 0 {
            continue
        }

        if key, value, ok := decodeKeyValue(line); ok {
            if value, ok = singleUnquote(value); ok {
                ret[key] = value
                ctx.Verboseln(key, value)
            } else {
                return nil, fmt.Errorf("Failed to parse make line: %q", line)
            }
        } else {
            return nil, fmt.Errorf("Failed to parse make line: %q", line)
        }
    }

    return ret, nil
}

Go相關:

func (c *Cmd) Output() ([]byte, error)

Output runs the command and returns its standard output. Any returned error will usually be of type *ExitError.
If c.Stderr was nil, Output populates ExitError.Stderr.

分析cofig.mk
include (BUILDSYSTEM)