美文网首页
Android 编译系统--07:Blueprint详细分析

Android 编译系统--07:Blueprint详细分析

作者: DarcyZhou | 来源:发表于2023-12-08 10:05 被阅读0次

    本文转载自:Blueprint代码详细分析-Android10.0编译系统(七)

    本文基于Android 10.0源码分析

    1.概述

      上一节,我们介绍了blueprint的作用和执行过程,这一节我们从代码的层面来分析blueprint的具体执行流程。启用Soong以后,在Android编译最开始的准备阶段,会执行build/soong/soong.bash进行环境准备。其中会先编译、安装Blueprint到out目录下。也就是说,在编译Android项目时,Android.bp相关工具链会自动编译,无需费神。

      Soong是与Android强关联的一个项目,而Blueprint则相对比较独立,可以单独编译、使用。

    2.blueprint相关编译链生成

      通过前面的make编译,我们了解到blueprint等工具链的生成,是在runSoong()中编出来的,因此我们就从runSoong()为突破口进行分析。

    2.1 Build调用栈

      make后,走的是Soong的构建,其中会把一些编译工具给编出来,例如blueprint,然后通过runSoong找到所有的Android.bp文件,编译成out/soong/build.ninja。

      通过runKatiBuild找到所有的Android通过runKatiBuild找到所有的Android.mk文件,编译out/build-aosp_arm.ninja,最后把这些ninja文件合并成combined-aosp_arm.ninja,通过ninja完成最终编译。

      其中runSoong对工具进行编译,编译出blueprint等编译工具,把*.bp 编译成 out/soong/build.ninja。

    编译系统7-1.PNG

    2.2 runSoong调用栈

      runSoong执行bootstrap.bash和blueprint_impl.bash,最终生成minibp和bpglob进程,建立/out/soong/.minibootstrap/build.ninja和/out/soong/.bootstrap/build.ninja 两个文件。再通过out/soong/.bootstrap/bin/soong_build,编译out/.module_paths/Android.bp.list及out/soong/.bootstrap/build-globs.ninja生成out/soong/build.ninja,参与最终的ninja编译。

    编译系统7-2.PNG

    2.3 runSoong()

      runSoong()的执行过程:

    • 执行build/blueprint/bootstrap.bash生成.minibootstrap/build.ninja和.bootstrap/build.ninja;

    • 生成minibp\bpglob;

    • 通过ninja来编译.minibootstrap/build.ninja和.bootstrap/build.ninja。

    (1)首先执行build/blueprint/bootstrap.bash

    (2)bootstrap.bash的作用
      它可以引导独立的blueprint来生成minibp二进制文件,可以直接运行./build/blueprint/bootstrap.bash。也可以从另一个脚本调用它来引导基于Bleprint的自定义构建系统。

    // /build/soong/ui/build/soong.go
    func runSoong(ctx Context, config Config) {
        ctx.BeginTrace(metrics.RunSoong, "soong")
        defer ctx.EndTrace()
    
        func() {
            ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
            defer ctx.EndTrace()
    
            cmd := Command(ctx, config, "blueprint bootstrap", "build/blueprint/bootstrap.bash", "-t")
            cmd.Environment.Set("BLUEPRINTDIR", "./build/blueprint")
            cmd.Environment.Set("BOOTSTRAP", "./build/blueprint/bootstrap.bash")
            cmd.Environment.Set("BUILDDIR", config.SoongOutDir())
            cmd.Environment.Set("GOROOT", "./"+filepath.Join("prebuilts/go", config.HostPrebuiltTag()))
            cmd.Environment.Set("BLUEPRINT_LIST_FILE", filepath.Join(config.FileListDir(), "Android.bp.list"))
            cmd.Environment.Set("NINJA_BUILDDIR", config.OutDir())
            cmd.Environment.Set("SRCDIR", ".")
            cmd.Environment.Set("TOPNAME", "Android.bp")
            cmd.Sandbox = soongSandbox
    
         //执行build/blueprint/bootstrap.bash
            cmd.RunAndPrintOrFatal()
        }()
    
        func() {
            ctx.BeginTrace(metrics.RunSoong, "environment check")
            defer ctx.EndTrace()
    
            envFile := filepath.Join(config.SoongOutDir(), ".soong.environment")
            envTool := filepath.Join(config.SoongOutDir(), ".bootstrap/bin/soong_env")
            if _, err := os.Stat(envFile); err == nil {
                if _, err := os.Stat(envTool); err == nil {
                    cmd := Command(ctx, config, "soong_env", envTool, envFile)
                    cmd.Sandbox = soongSandbox
    
                    var buf strings.Builder
                    cmd.Stdout = &buf
                    cmd.Stderr = &buf
                    if err := cmd.Run(); err != nil {
                        ctx.Verboseln("soong_env failed, forcing manifest regeneration")
                        os.Remove(envFile)
                    }
    
                    if buf.Len() > 0 {
                        ctx.Verboseln(buf.String())
                    }
                } else {
                    ctx.Verboseln("Missing soong_env tool, forcing manifest regeneration")
                    os.Remove(envFile)
                }
            } else if !os.IsNotExist(err) {
                ctx.Fatalf("Failed to stat %f: %v", envFile, err)
            }
        }()
    
        var cfg microfactory.Config
        cfg.Map("github.com/google/blueprint", "build/blueprint")
    
        cfg.TrimPath = absPath(ctx, ".")
    
        func() {
            ctx.BeginTrace(metrics.RunSoong, "minibp")
            defer ctx.EndTrace()
    
            minibp := filepath.Join(config.SoongOutDir(), ".minibootstrap/minibp")
            if _, err := microfactory.Build(&cfg, minibp, "github.com/google/blueprint/bootstrap/minibp"); err != nil {
                ctx.Fatalln("Failed to build minibp:", err)
            }
        }()
    
        func() {
            ctx.BeginTrace(metrics.RunSoong, "bpglob")
            defer ctx.EndTrace()
    
            bpglob := filepath.Join(config.SoongOutDir(), ".minibootstrap/bpglob")
            if _, err := microfactory.Build(&cfg, bpglob, "github.com/google/blueprint/bootstrap/bpglob"); err != nil {
                ctx.Fatalln("Failed to build bpglob:", err)
            }
        }()
    
        ninja := func(name, file string) {
            ctx.BeginTrace(metrics.RunSoong, name)
            defer ctx.EndTrace()
    
            fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
            nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
            defer nr.Close()
    
            cmd := Command(ctx, config, "soong "+name,
                config.PrebuiltBuildTool("ninja"),
                "-d", "keepdepfile",
                "-w", "dupbuild=err",
                "-j", strconv.Itoa(config.Parallel()),
                "--frontend_file", fifo,
                "-f", filepath.Join(config.SoongOutDir(), file))
            cmd.Sandbox = soongSandbox
            cmd.RunAndPrintOrFatal()
        }
    
        ninja("minibootstrap", ".minibootstrap/build.ninja")
        ninja("bootstrap", ".bootstrap/build.ninja")
    }
    

    2.4 minibp的生成

      从build/blueprint/Blueprints中可知,需要编译的二进制文件名为minibp,源文件为:bootstrap/minibp/main.go,依赖于blueprint、blueprint-bootstrap、gotestmain-tests。

    // build/blueprint/Blueprints
    bootstrap_go_binary {
        name: "minibp",
        deps: [
            "blueprint",
            "blueprint-bootstrap",
            "gotestmain-tests",
        ],
        srcs: ["bootstrap/minibp/main.go"],
    }
    

    out/soong/.bootstrap/build.ninja中模块的相关规则如下:

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Module:  minibp
    # Variant:
    # Type:    bootstrap_go_binary
    # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
    # Defined: build/blueprint/Blueprints:132:1
    
    build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $
            g.bootstrap.compile $
            ${g.bootstrap.srcDir}/build/blueprint/bootstrap/minibp/main.go | $
            ${g.bootstrap.compileCmd} $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
            ${g.bootstrap.buildDir}/.bootstrap/gotestmain-tests/pkg/github.com/google/blueprint/gotestmain.a
        incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg -I ${g.bootstrap.buildDir}/.bootstrap/gotestmain-tests/pkg
        pkgPath = minibp
    
    build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out: g.bootstrap.link $
            ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a | $
            ${g.bootstrap.linkCmd} $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
            ${g.bootstrap.buildDir}/.bootstrap/gotestmain-tests/pkg/github.com/google/blueprint/gotestmain.a
        libDirFlags = -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg -L ${g.bootstrap.buildDir}/.bootstrap/gotestmain-tests/pkg
    
    build out/soong/.bootstrap/bin/minibp: g.bootstrap.cp $
            ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out || $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/test/test.passed $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/test/test.passed $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/test/test.passed $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint/test/test.passed $
            ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/test/test.passed $
            ${g.bootstrap.buildDir}/.bootstrap/gotestmain-tests/test/test.passed
    

    可以看到最终生成的minibp需要三部生成,第一部编译成minibp.a其中依赖于blueprint、blueprint-pathtools和main.go,第二部是link成a.out,第三部通过cp到对应的bin的目录下,完成一个module的解析。

    3.Blueprint的编译阶段

      生成Ninja文件的过程分为四个阶段。解析一个Blueprint文件需要四步:

    第一步:注册阶段准备上下文来处理包含各种类型模块的Blueprint文件。注册module也就是bootstrap_go_binary和内容处理规则,如deps等。

    第二步:解析阶段读取一个或多个Blueprint文件,并根据已注册的模块类型验证其内容。

    第三步:generate阶段分析解析的Blueprint内容,为必须执行的构建操作创建一个内部表示。此 阶段还执行模块依赖项和已解析Bleprint文件中定义的属性值的验证。

    第四步:写入阶段根据生成的构建操作生成Ninja文件。

    3.1 minibp编译入口

    编译系统7-3.png

    主要步骤:

    • 配置一个Context结构;

    • 执行command.go的Main()进行最终编译。

    // /build/blueprint/bootstrap/minibp/main.go
    func main() {
        flag.Parse()
    
        ctx := blueprint.NewContext() //配置一个Context结构
        if !runAsPrimaryBuilder {
            ctx.SetIgnoreUnknownModuleTypes(true)
        }
    
        config := Config{
            generatingPrimaryBuilder: !runAsPrimaryBuilder,
        }
    
        //执行 command.go 的Main() 进行最终编译
        bootstrap.Main(ctx, config)
    }
    

    3.2 Context配置

      返回一个Context的结构,存储了bp里面的一些字段,例如之前看到的bootstrap_core_go_binary。

    // /build/blueprint/context.go
    func newContext() *Context {
        return &Context{
            Context:            context.Background(),
            moduleFactories:    make(map[string]ModuleFactory), //存储着我们后面注册的module如“bootstrap_core_go_binary”
            nameInterface:      NewSimpleNameInterface(),
            moduleInfo:         make(map[Module]*moduleInfo),
            globs:              make(map[string]GlobPath),
            fs:                 pathtools.OsFs,
            ninjaBuildDir:      nil,
            requiredNinjaMajor: 1,
            requiredNinjaMinor: 7,
            requiredNinjaMicro: 0,
        }
    }
    

    3.3 执行 command.go Main()

    3.3.1 调用栈

    编译系统7-4.png
    // /build/blueprint/bootstrap/command.go
    package bootstrap
    func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...string) {
        ...
        ctx.RegisterBottomUpMutator("bootstrap_plugin_deps", pluginDeps)
        ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory(bootstrapConfig))
        ctx.RegisterModuleType("bootstrap_go_binary", newGoBinaryModuleFactory(bootstrapConfig, false))
        ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory(bootstrapConfig, true))
        ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig))
    
        ctx.RegisterSingletonType("glob", globSingletonFactory(ctx))
    
        deps, errs := ctx.ParseFileList(filepath.Dir(bootstrapConfig.topLevelBlueprintsFile), filesToParse)
        if len(errs) > 0 {
            fatalErrors(errs)
        }
    
        // Add extra ninja file dependencies
        deps = append(deps, extraNinjaFileDeps...)
    
        extraDeps, errs := ctx.ResolveDependencies(config)
        ...
        extraDeps, errs = ctx.PrepareBuildActions(config)
    
        ...
        err = ctx.WriteBuildFile(out)
    
        ...
    }
    

    步骤:

    1. 注册一些bp模块类型、依赖和类型;

    2. 解析Blueprints文件;

    3. 检查解析的Blueprint文件中定义的所有模块指定的依赖项是否有效;

    4. 生成需要执行的所有生成操作的内部表示;

    5. 将生成的构建操作的Ninja manifest文本写入文件。

    3.3.2 RegisterBottomUpMutator()

      RegisterBottomUpMutator注册一个mutator,它将被调用来将模块拆分为变量。每个注册的mutator按注册顺序调用(混合使用toppownmutator和BottomUpMutators)每个模块一次,在所有模块依赖项的调用返回之前,不会在模块上调用。

      此处给定的赋值函数类型名称对于上下文中所有自下而上或早期的赋值函数必须是唯一的。返回一个MutatorHandle,在这个句柄上可以调用Parallel来设置mutator在维护顺序的同时并行访问模块。

    // /build/blueprint/context.go
    func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator) MutatorHandle {
        for _, m := range c.variantMutatorNames {
            if m == name {
                panic(fmt.Errorf("mutator name %s is already registered", name))
            }
        }
    
        info := &mutatorInfo{
            bottomUpMutator: mutator,
            name:            name,
        }
        c.mutatorInfo = append(c.mutatorInfo, info)
    
        c.variantMutatorNames = append(c.variantMutatorNames, name)
    
        return info
    }
    

    3.3.3 RegisterSingletonType()

      RegisterSingletonType注册将被调用以生成生成操作的单例类型。作为生成阶段的一部分,每个注册的单例类型被实例化和调用一次。按照注册顺序调用每个已注册的单例。此处给定的单例类型名称对于上下文必须是唯一的。factory函数应该是一个命名函数,这样它的包和名称就可以包含在生成的Ninja文件中,以便进行调试。

    // /build/blueprint/context.go
    func (c *Context) RegisterSingletonType(name string, factory SingletonFactory) {
        for _, s := range c.singletonInfo {
            if s.name == name {
                panic(errors.New("singleton name is already registered"))
            }
        }
    
        c.singletonInfo = append(c.singletonInfo, &singletonInfo{
            factory:   factory,
            singleton: factory(),
            name:      name,
        })
    }
    

    3.3.4 ResolveDependencies()

      ResolveDependencies检查解析的Blueprint文件中定义的所有模块指定的依赖项是否有效。这意味着依赖的模块已经定义,并且不存在循环依赖。

    // /build/blueprint/context.go
    func (c *Context) resolveDependencies(ctx context.Context, config interface{}) (deps []string, errs []error) {
        pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) {
            c.liveGlobals = newLiveTracker(config)
    
            deps, errs = c.generateSingletonBuildActions(config, c.preSingletonInfo, c.liveGlobals)
            if len(errs) > 0 {
                return
            }
    
            errs = c.updateDependencies()
            if len(errs) > 0 {
                return
            }
    
            var mutatorDeps []string
            mutatorDeps, errs = c.runMutators(ctx, config)
            if len(errs) > 0 {
                return
            }
            deps = append(deps, mutatorDeps...)
    
            c.cloneModules()
    
            c.dependenciesReady = true
        })
    
        if len(errs) > 0 {
            return nil, errs
        }
    
        return deps, nil
    }
    

    3.3.5 PrepareBuildActions()

      PrepareBuildActions生成需要执行的所有生成操作的内部表示。这个过程包括对在解析阶段创建的每个Module对象调用GenerateBuildActions方法,然后对每个注册的单例对象调用GenerateBuildActions方法。

      如果尚未调用ResolveDependencies方法,则此方法会自动调用该方法。通过传递给GenerateBuildActions的ModuleContext和SingletonContext对象上的config方法,config参数可用于所有Module和Singleton对象。它还传递给通过PoolFunc、RuleFunc和VariableFunc指定的函数,以便它们可以计算特定配置的值。返回的deps是由模块和singleton通过ModuleContext添加的ninja文件依赖项的列表。AddNinjaFileDeps(),SingletonContext.AddNinjaFileDeps(),和PackageContext.AddNinjaFileDeps()方法。

    // /build/blueprint/context.go
    func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs []error) {
        pprof.Do(c.Context, pprof.Labels("blueprint", "PrepareBuildActions"), func(ctx context.Context) {
            c.buildActionsReady = false
    
            if !c.dependenciesReady {
                var extraDeps []string
                extraDeps, errs = c.resolveDependencies(ctx, config)
                if len(errs) > 0 {
                    return
                }
                deps = append(deps, extraDeps...)
            }
    
            var depsModules []string
            depsModules, errs = c.generateModuleBuildActions(config, c.liveGlobals)
            if len(errs) > 0 {
                return
            }
    
            var depsSingletons []string
            depsSingletons, errs = c.generateSingletonBuildActions(config, c.singletonInfo, c.liveGlobals)
            if len(errs) > 0 {
                return
            }
    
            deps = append(deps, depsModules...)
            deps = append(deps, depsSingletons...)
    
            if c.ninjaBuildDir != nil {
                err := c.liveGlobals.addNinjaStringDeps(c.ninjaBuildDir)
                if err != nil {
                    errs = []error{err}
                    return
                }
            }
    
            pkgNames, depsPackages := c.makeUniquePackageNames(c.liveGlobals)
    
            deps = append(deps, depsPackages...)
    
            // This will panic if it finds a problem since it's a programming error.
            c.checkForVariableReferenceCycles(c.liveGlobals.variables, pkgNames)
    
            c.pkgNames = pkgNames
            c.globalVariables = c.liveGlobals.variables
            c.globalPools = c.liveGlobals.pools
            c.globalRules = c.liveGlobals.rules
    
            c.buildActionsReady = true
        })
    
        if len(errs) > 0 {
            return nil, errs
        }
    
        return deps, nil
    }
    

    3.3.6 WriteBuildFile()

      WriteBuildFile将生成的构建操作的Ninja manifest文本写入文件。如果在PrepareBuildActions成功完成之前调用此函数,则返回errBuildActionsOnTready。

    // /build/blueprint/context.go
    func (c *Context) WriteBuildFile(w io.Writer) error {
        var err error
        pprof.Do(c.Context, pprof.Labels("blueprint", "WriteBuildFile"), func(ctx context.Context) {
            if !c.buildActionsReady {
                err = ErrBuildActionsNotReady
                return
            }
    
            nw := newNinjaWriter(w)
    
            err = c.writeBuildFileHeader(nw)
            if err != nil {
                return
            }
    
            err = c.writeNinjaRequiredVersion(nw)
            if err != nil {
                return
            }
    
            err = c.writeSubninjas(nw)
            if err != nil {
                return
            }
    
            // TODO: Group the globals by package.
    
            err = c.writeGlobalVariables(nw)
            if err != nil {
                return
            }
    
            err = c.writeGlobalPools(nw)
            if err != nil {
                return
            }
    
            err = c.writeBuildDir(nw)
            if err != nil {
                return
            }
    
            err = c.writeGlobalRules(nw)
            if err != nil {
                return
            }
    
            err = c.writeAllModuleActions(nw)
            if err != nil {
                return
            }
    
            err = c.writeAllSingletonActions(nw)
            if err != nil {
                return
            }
        })
    
        if err != nil {
            return err
        }
    
        return nil
    }
    

    4.总结

    1. 根据上面的总结,我们明白了Blueprint到ninja的流程,详细流程如下:

    2. 执行build/blueprint/bootstrap.bash 生成.minibootstrap/build.ninja 和.bootstrap/build.ninja;

    3. 编译生成minibp\bpglob 两个可执行程序;

    4. 通过minibp 解析并生成out/soong/.bootstrap/build.ninja;

      1)注册一些bp模块类型、依赖和类型

      2)解析Blueprints文件;

      3)检查解析的Blueprint文件中定义的所有模块指定的依赖项是否有效;

      4)生成需要执行的所有生成操作的内部表示;

      5)将生成的构建操作的Ninja manifest文本写入文件。

    5. 通过ninja来编译.minibootstrap/build.ninja 和.bootstrap/build.ninja,最终生成out/soong/build.ninja。

      下面我们再进一步探讨一些Android.bp的配置,以及ninja的编译过程。

    相关文章

      网友评论

          本文标题:Android 编译系统--07:Blueprint详细分析

          本文链接:https://www.haomeiwen.com/subject/xoyxidtx.html