OpenHarmony编译链之GN


在学习OpenHarmony时,看他她使用GN将整个项目组织起来,于是学习了一下GN,这里将学习的东西记录下

1. GN有什么作用

在学习GN之前我们先来了解下什么是GN,对于一套编译工具,要将源代码编译成最终可以执行文件,需要三个部分构件系统:GN、Ninja、GCC(这里也可以是其他的编译器)

GN作为编译逻辑部分,我理解他就像一个穿糖葫芦的竹签一样,将整个项目穿起来。通过gn gen这个命令,会将这些线索连在一起,生成build.ninja文件。

Ninja作为构建中间部分,他用最简单最快速的方式将编译文件编译参数传递给编译系统,其作用有点像Makefile,编译时,不同的是由Ninja负责执行编译过程。

GCC作为编译部分,通常随编译系统而变化。

好了,有了整体的认识,也知道为啥要学,下面我们就找些资料学习下吧。这里主要参考 Chromium-based projects的文档,相关文档我会放在我的gitee上,方便后续的同学学习。学习资料链接

2. GN学习—组成元素

gn的内部不是很多,即使不学习语法,可以仿写将自己的源文件加入编译。所以这里我们先介绍GN的组成元素,在介绍GN的基本使用,在最后介绍GN的简单语法。在完成这些介绍后,给大家一个使用的案例,这样子比较容易掌握。

  • Targets
    在大多数GN文件中都需要有一编译的目标生成文件,他可以是executable(可执行文件),static_library(静态库),shared_library(动态库)等,除了这些Targets之外还有:

●executable, shared_library, static_library

●loadable_module: 运行时加载模块

●source_set: 被编译的源文件集合,这种编译不会生成任何库文件

●group: 声明一组target,你可以写一个group,里面放不同的库target,使用的时候就用group的名字代表这些targets

●copy: 拷贝文件

●action, action _foreach: 运行一个脚本时所用

●bundle_data, create_bundle: Mac & iOS

●component: shared library or source set depending on mode

●test

●app: executable or iOS application + bundle

●android_apk, generate_jni, etc.: Lots of Android ones!

  • config
    这个关键字的目的是声明一个配置,配置信息可以包括flags,defines,include_dirs等,但是不包括sources和deps/public_deps等依赖性文件。
    config配置应紧挨着使用它的相应target目标之前出现。
    与单个target目标关联的config配置应与target目标同名,并用​​_config​​跟在它后面。如 target名称为foo,则对应的config名称为foo_config。

例子Example for the src/foo/BUILD.gn file:

# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Config for foo is named foo_config and immediately precedes it in the file.
config("foo_config") {
}

# Target matching path name is the first target.
executable("foo") {      #这里是Targets
}

# Test for foo follows it.
test("foo_unittests") {
}

config("bar_config") {
}

source_set("bar") {
}
  • Sources
    这个标签的意思是列出来需要编译的源文件,当然,可以在其中使用条件语句进行条件编译
sources = [
    "main.cc",
  ]
  if (use_aura) {
    sources += [ "thing_aura.cc" ]
  }
  if (use_gtk) {
    sources += [ "thing_gtk.cc" ]
  }
  • Deps
    即编译Targets所用到的依赖,依赖应按字母顺序排列。
    当前文件中的 Deps 应首先写入,并且不能使用文件名限定(仅需要:foo )。
    其他 deps 应始终使用完全限定的路径名,除非出于某种原因需要相对路径名。
 deps = [
    ":a_thing",
    ":mystatic",
    "//foo/bar:other_thing",
    "//foo/baz:that_thing",
  ]
  • Import
    导入gni文件,这和include类似
import("//foo/bar/baz.gni")  # Even if this file is in the foo/bar directory
  • Variables
    在.gni文件中的顶级局部变量前面加上下划线前缀。此前缀会导致变量无法被导入到其他构建文件。
_this_var_will_not_be_exported = 1
but_this_one_will = 2

3. GN学习—快速使用

运行GN(Generate Ninja),生成了一个构建目录,ninja文件将被自动生成,如果你在该目录下进行构建时,文件已经过期,ninja则会自动重新生成,所以你不必重新运行gn

gn gen out/my_build
gn args out/my_build(如:gn is_debug = false out/my_build)

针对指定操作系统或架构交叉编译
运行 gn args out/Default (替换成你的编译目录), 然后为常用的交叉编译选项提供如下参数

target_os = "chromeos"
target_os = "android"

target_cpu = "arm"
target_cpu = "x86"
target_cpu = "x64"
更多信息见 GN cross compiles.

简单例子
添加一个构建文件
跳转到 examples/simple_build. 这是一个最小化 GN 仓库的根目录.

(PS: 该目录在 GN 源码仓库下 )

在该目录下有一个 tutorial 目录. 下有还没加入构建的 tutorial.cc 文件. 在该目录下创建一个新的 BUILD.gn 文件, 来为该文件生成新的可执行 target .

executable("tutorial") {
  sources = [
    "tutorial.cc"
  ]
}

现在我们需要加入该 target. 打开父目录(simple_build)下的BUILD.gn. GN从这个根文件进行加载, 之后加入该文件的所有依赖, 因此我们需要从该文件依赖新的 target.

你可以将新 target 作为依赖添加到simple_build/BUILD.gn文件里现存的 target 上, 但通常将可执行 target 作为另一个可执行 target 的依赖是不合理的(它们不能被链接). 因此我们新建一个 “tools”组, 一个“组”表示一系列不被编译或链接的依赖.

group("tools") {
  deps = [
    # 这里也可以写全名 “://tutorial:tutorial”. 
    # 更多信息可见 “gn help labels”
    "//tutorial",
  ]
}

测试添加后的效果
在 simple_build 目录运行:

gn gen out
ninja -C out tutorial
out/tutorial
"Hello, world" 应该正确输出在命令行中

注: GN 支持静态库的 target 名不全局唯一. 可以对 ninja 传递带有路径(不带“//”)的标签来构建:
ninja -C out some/path/to/target:my_target

声明依赖
在examples/simple_build/BUILD.gn定义的 target 中, 有一个被静态库定义的函数GetStaticText().

static_library("hello_static") {
  sources = [
    "hello_static.cc",
    "hello_static.h",
  ]
}

共享库也定义了一个函数GetSharedText():

shared_library("hello_stared") {
  sources = [
    "hello_shared.cc",
    "hello_shared.h",
  ]

  defines = [ "HELLO_SHARED_IMPLEMENTATION" ]
}

这里展示了怎样在预处理器中为 target 添加 define. 如果需要多于一个的 define 或赋值 define , 参照如下形式:

defines = [
  “HELLO_SHARED_IMPLEMENTATION",
  "ENABLE_DOOM_MELON=0",
]

现在来看依赖这两个库的可执行文件

executable("hello") {
  sources = [
    "hello.cc",
  ]

  deps = [
    ":hello_shared",
    ":hello_static",
  ]
}

测试二进制
在simple_build目录运行:

ninja -C out hello
out/hello

注意你不需要重新运行 GN, 当构建文件变化时, GN 会自动重新构建 ninja 文件. 可以通过 ninja 在执行的开始时,在命令行中的输出 [1/1] Regenerating ninja files 来确认.

将设置放到 config 里
库的用户通常需要 complier flags , defines 和应用它们的 include 目录. “config” 命名的设定集合(没有源文件和依赖), 通过将这些设置放到 “config” 中来达到这个目的.

config("my_lib_config") {
  defines = [ "ENABLE_DOOM_MELON" ]
  include_dirs = [ "//third_party/something" ]
}

要把 config 加到 configs 列表里, 来使 config 中的设定在 target 中生效.

static_library("hello_shared") {
  ...
  # 注 这里通常需要 "+=", 见下文的 "默认 configs”
  configs += [:my_lib_config",
  ]
}
config 放在 public_configs 列表中则可在全部依赖它的 target 中生效.

static_library("hello_shared") {
  ...
  public_configs = [
    ":my_lib_config",
  ]
}

public_configs 同样会在当前 target 生效, 因此不需要在两个列表中同时声明.

默认 configs
构建的配置会有一些默认应用到所有 target 的设置. 它们会被设置成 configs 的默认值. 你可以使用 “print” 命令来打印它们(该命令在 debug 中非常使用).

executable("hello") {
  print(configs)
}

运行 GN 将打印一些类似的结果

$ gn gen out
["//build:compiler_defaults", "//build:executable_ldconfig"]
Done. Made 5 targets from 5 files in 9ms
target 可以修改这些默认值. 例如, 构建需要通过no_exceptions config 关闭异常, 但 target 可能需要重新启用它们, 为此需要替换默认的 configs 列表.

executable("hello") {
  ...
  configs -= [ "//build:no_exceptions" ]  # Remove global default.
  configs += [ "//build:exceptions" ]  # Replace with a different one.
}

print 命令也支持字符串差值, 它通过“$“将变量名替换成字符串

print(“The configs for the target $target_name are $configs”)

添加一个新的构建参数
在 declare_args 函数中可以定义需要的参数和它的默认值.

declare_args() {
  enable_teleporter = true
  enable_doom_melon = false
}

查看gn help buildargs来总览它是如何生效的, gn help declare_args来查看声明它们的细节.

在一个作用域中重复定义参数是不允许的, 因此定义参数前需要考虑它的名字和作用域

4. GN学习—语法

GN使用非常简单的动态类型语言。类型是:

布尔(true,false)。
64位有符号整数。
字符串。
列表(任何其他类型)。
范围(Scopes)(有点像字典,仅是内置的东西(built-in stuff))。
有一些内置变量的值取决于当前的环境。了解gn help更多信息。

语言中故意有许多遗漏。例如没有用户定义的函数调用,(模板是最接近的)。按照上述设计理念,如果你需要这样的东西,你可能做错了。

变量sources有一个特殊的规则:赋值给它时,将应用一个排除模式列表。这被设计成自动过滤掉某些类型的文件。见gn help set_sources_assignment_filter和gn help label_pattern了解更多。

语言书呆子的完整语法可以在gn help grammar获取到。

字符串
字符串用双引号括起来,并使用反斜杠作为转义字符。唯一支持的转义序列是:

" (用于直接引用)
$ (字面上的美元符号)
\ (用于文字反斜杠)
任何其他反斜杠的使用都被视为文字反斜杠。所以,例如,\b在模式中使用不需要转义,大多数Windows路径"C:\foo\bar.h"也不需要。

使用$支持简单的变量替换,其中美元符号后的单词被替换为变量的值。如果没有非变量名字符来终止变量名称,可以选择{}包围名称。更复杂的表达式不被支持,仅支持变量名称替换。

a = "mypath"
b = "$a/foo.cc"  # b -> "mypath/foo.cc"
c = "foo${a}bar.cc"  # c -> "foomypathbar.cc"

您可以使用 “$0xFF” 语法对8位字符进行编码,因此带有换行符(十六进制0A)的字符串会如下所示,“look$0x0Alike$0x0Athis”。

清单
没有办法得到一个列表的长度。如果你发现自己想要做这种事情,那么你就是想在构建中做太多的工作。

列表支持追加:

a = [ "first" ]
a += [ "second" ]  # [ "first", "second" ]
a += [ "third", "fourth" ]  # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ]  # [ "first", "second", "third", "fourth", "fifth" ]

将列表追加到另一个列表,是追加第二个列表中的项目,而不是将列表追加为嵌套成员。

您可以从列表中删除项目:

a = [ "first", "second", "third", "first" ]
b = a - [ "first" ]  # [ "second", "third" ]
a -= [ "second" ]  # [ "first", "third", "fourth" ]

列表中的 - 运算符搜索匹配项并删除所有匹配的项目。从另一个列表中减去一个列表将删除第二个列表中的每个项目。

如果找不到匹配的项目,将会抛出错误,因此您需要事先知道该项目在移除之前确实已经存在。鉴于没有办法测试包含,主要的用例是建立一个文件或标志的主列表,并基于各种条件删除那些不适用于当前版本的构建。

从风格上来说,最好只添加到列表,并让每个源文件或依赖项只出现一次。这与Chrome团队用于GYP的建议相反(GYP倾向于列出所有文件,然后删除条件中不需要的文件)。

列表支持从零开始的下标以提取值:

a = [ "first", "second", "third" ]
b = a[1]  # -> "second"

[]运算符是只读的,不能用来改变列表。这个主要的用例是当一个外部脚本返回几个已知的值,并且你想提取它们。

在某些情况下,如果您要添加到列表中,则很容易覆盖列表。为了帮助理解这种情况,将非空列表分配给包含现有非空列表的变量是错误的。如果您想避开此限制,请首先将目标变量分配给空列表。

a = [“one”]
a = [“two”]#错误:用非空列表覆盖非空列表。
a = []#OK
a = [“two”]#OK

请注意,构建脚本的执行没有内在知识的底层数据的意义。例如,这意味着它不知道sources是一个文件名列表。所以,如果你删除一个项目,它必须匹配文字字符串,而不是指定一个不同的名称,那将解析为相同的文件名称。

条件语句
条件看起来像C:

  if(is_linux ||(is_win && target_cpu ==“x86”)){
    sources -= [ "something.cc" ]
  } else if...{
    ...
  } else {
    ...
  }

如果只能在某些情况下声明目标,则可以在大多数地方使用它们,甚至在整个目标周围使用它们。

循环
你可以使用foreach迭代一个列表。这是不鼓励的。构建应该做的大部分事情通常都可以在不做这件事情的情况下表达出来,如果你觉得有必要的话,这可能表明你在元构建中做了太多工作。

foreach(i,mylist){
  print(i)  # Note: i is a copy of each element, not a reference to it.
}

函数调用
简单的函数调用看起来像大多数其他语言

print("hello, world")
assert(is_win, "This should only be executed on Windows")

这些功能是内置的,用户不能定义新的功能。

一些函数在它们下面接受一个由{ }组成的代码块:

static_library(“mylibrary”){
  sources = [“a.cc”]
}

其中大多数用来定义目标。用户可以使用下面讨论的模板机制来定义新的函数。

确切地说,这个表达式意味着该块成为函数执行的参数。大多数块式函数都会执行块,并将结果范围视为要读取的变量字典。

作用域和执行(Scoping and execution)
文件和函数调用后面跟着{ }块引入新的作用域。作用域是嵌套的。当您读取一个变量时,将会以相反的顺序搜索包含的作用域,直到找到匹配的名称。变量写入总是进入最内层的作用域。

除了最内层的作用域以外,没有办法修改任何封闭作用域。这意味着当你定义一个目标时,例如,你在块内部做的任何事情都不会泄露到文件的其余部分。

if/ else/ foreach语句,即使他们使用{ },不会引入新的范围,所以更改将持续在语句之外。

命名事物
文件和目录名称
文件和目录名称是字符串,并被解释为相对于当前构建文件的目录。有三种可能的形式:

相对名称:

"foo.cc"
"src/foo.cc"
"../src/foo.cc"

源代码树绝对名称:

//net/foo.cc”
“//base/test/foo.cc”

系统绝对名称(罕见,通常用于包含目录):

“/usr/local/include/”
“/C:/Program Files/Windows Kits/Include”
构建配置
目标
目标是构建图中的一个节点。它通常代表将要生成的某种类型的可执行文件或库文件。目标取决于其他目标。内置的目标类型(请参阅gn help 以获取更多帮助)是:

action:运行一个脚本来生成一个文件。
action_foreach:为每个源文件运行一次脚本。
bundle_data:声明数据加入到Mac / iOS包。
create_bundle:创建一个Mac / iOS包。
executable:生成一个可执行文件。
group:引用一个或多个其他目标的虚拟依赖关系节点。
shared_library:.dll或.so。
loadable_module:.dll或.so只能在运行时加载。
source_set:一个轻量级的虚拟静态库(通常比真正的静态库更可取,因为它的构建速度会更快)。
static_library:.lib或.a文件(通常你会想要一个source_set)。
您可以使用模板来扩展它制作自定义目标类型(请参见下文)。在Chrome中,一些更常用的模板是:

component:源集或共享库,取决于构建类型。
test:测试可执行文件 在移动设备上,这将为测试创建适当的本机应用程序类型。
app:可执行文件或Mac / iOS应用程序。
android_apk:制作一个APK。有很多其他的Android模版,看//build/config/android/rules.gni。
CONFIGS
配置文件是命名对象,用于指定标志集,包含目录和定义。他们可以被应用到一个目标,并推到相关的目标。

要定义一个配置:

config("myconfig") {
 includes = [ "src/include" ]
 defines = [ "ENABLE_DOOM_MELON" ]
}

要将配置应用于目标:

executable("doom_melon") {
  configs = [ ":myconfig" ]
}

构建配置文件通常指定设置默认配置列表的目标默认值。目标可以根据需要添加或删除。所以在实践中你通常会使用configs += ":myconfig"追加到默认列表。

请参阅gn help config有关如何声明和应用配置的更多信息。

公共配置
目标可以将设置应用于依赖它的其他目标。最常见的例子是一个第三方目标,它需要一些定义或包含目录头才能正确编译。您希望这些设置既适用于第三方库本身的编译,也适用于使用该库的所有目标。

要做到这一点,你写一个你想要应用的设置的配置:

config("my_external_library_config") {
  includes = "."
  defines = [ "DISABLE_JANK" ]
}

然后这个配置作为“公共”配置被添加到目标。它既适用于目标,也适用于直接依赖目标的目标。

shared_library("my_external_library") {
  ...
  # Targets that depend on this get this config applied.
  public_configs = [ ":my_external_library_config" ]
}

依赖目标又可以通过将目标作为“公共”依赖项添加到另一个级别,从而将依赖关系树转发到另一个级别。

static_library("intermediate_library") {
  ...
  # Targets that depend on this one also get the configs from "my external library".
  public_deps = [ ":my_external_library" ]
}

通过把它设置成all_dependent_config一个目标可以转发一个配置给所有的依赖者,直到达到一个链接边界为止。这是强烈不鼓励的,因为它将比必要的构建配置超出更多的标志和定义。使用public_deps来控制哪些标志适用于哪里来代替它。

在Chrome中,更喜欢build/buildflag_header.gni用于定义的构建标题头文件系统,以防止大多数编译器定义的错误。

模板
模板是GN重用代码的主要方式。通常情况下,模板会扩展到一个或多个其他目标类型。

# Declares a script that compiles IDL files to source, and then compiles those
#source files.
template("idl") {
  #Always base helper targets on target_name so they're unique。Target name
  #will be the string passed as the name when the template is invoked.
  idl_target_name =“$ {target_name} _generate”
  action_foreach(idl_target_name){
    ...
  }

  #Your template should always define a target with the name target_name.
  #When other targets depend on your template invocation, this will be the
  #destination of that dependency.
  source_set(target_name){
    ...
    deps = [ ":$idl_target_name" ]  # Require the sources to be compiled.
  }
}

通常,您的模板定义将放入.gni文件中,用户将导入该文件以查看模板定义:

import("//tools/idl_compiler.gni")

idl("my_interfaces") {
  sources = [ "a.idl", "b.idl" ]
}

当时声明一个模板会在范围内的变量周围创建一个闭包。当模板被调用时,魔术变量invoker被用来从调用范围中读取变量。模板通常会将感兴趣的值复制到自己的范围中:

template("idl") {
  source_set(target_name){
    sources = invoker.sources
  }
}

模板执行时的当前目录将是调用的构建文件的目录,而不是模板源文件。这是因为从模板调用者传入的文件是正确的(这通常是模板中大多数文件处理的原因)。但是,如果模板本身有文件(可能会生成一个运行脚本的动作),则需要使用绝对路径(“//foo/…”)来引用这些文件,以说明当前目录在调用时将不可预知。查看gn help template更多信息和更完整的例子。

其他特性
Imports
您可以使用import函数将.gni文件导入到当前作用域。这不是 C++意义上的包含。导入的文件是独立执行的,生成的作用域被复制到当前文件中(C ++在include指令出现的当前上下文中执行包含的文件)。这样可以缓存导入的结果,还可以防止包含多个包含文件在内的一些更“创造性”的用途。

通常情况下,一个.gni会定义构建参数和模板。了解gn help import更多信息。

您的.gni文件可以定义不导出到文件临时变量,通过使用名称中的前面的下划线来包含它,就像_this。

路径处理
通常情况下,您需要创建一个文件名或相对于不同目录的文件名列表。运行脚本时,这种情况尤为常见,这些脚本是以构建输出目录作为当前目录执行的,而构建文件通常是指与其包含的目录相关的文件。

您可以使用rebase_path转换目录。查看gn help rebase_path更多的帮助和例子。将相对于当前目录的文件名转换为相对于根目录的典型用法是:new_paths = rebase_path(“myfile.c”, root_build_dir)

模式
模式用于为自定义目标类型的给定输入集生成输出文件名,并自动从sources变量中移除文件(请参阅参考资料gn help set_sources_assignment_filter)。

他们就像简单的正则表达式。了解gn help label_pattern更多信息。

执行脚本
有两种方法来执行脚本。GN中的所有外部脚本都是Python。第一种方法是作为构建步骤。这样的脚本将需要一些输入,并生成一些输出作为构建的一部分。调用脚本的目标是使用“action”目标类型声明的(请参阅参考资料gn help action)。

执行脚本的第二种方法是在构建文件执行期间同步。这在某些情况下是必要的,以确定要编译的文件集合,或获取构建文件可能依赖的某些系统配置。构建文件可以读取脚本的标准输出(stdout)并以不同的方式对其执行操作。

同步脚本的执行由exec_script函数完成(详见gn help exec_script参考资料)。因为同步执行一个脚本需要暂停当前的构建文件执行,直到Python进程完成执行,依靠外部脚本是慢的,应该尽量减少。

为了防止滥用,允许调用的文件exec_script可以在顶层.gn文件中列入白名单。Chrome做到这一点需要额外的代码审查这样的补充。看gn help dotfile。

您可以同步读取和写入在同步运行脚本时不鼓励但偶尔需要的文件。典型的用例是传递一个比当前平台的命令行限制长的文件名列表。请参阅gn help read_file以及gn help write_file如何读取和写入文件。如果可能,应该避免这些功能。

超过命令行长度限制的操作可以使用响应文件绕过此限制,而不同步写入文件。看gn help response_file_contents。

与Blaze的区别和相似之处
Blaze是Google的内部构建系统,现在已经作为Bazel公开发布。它启发了一些其他系统,如Pants和Buck。

在Google的同类环境中,对条件的需求非常低,并且可以通过少量的手段(abi_deps)来获得。Chrome使用各地的条件,需要添加这些是文件看起来不同的主要原因。

GN还增加了“配置”的概念来管理一些棘手的依赖和配置问题,同样不会出现在服务器上。Blaze有一个“配置”的概念,就像一个GN工具链,但内置在工具本身。GN工具链的工作方式是试图以一种简洁的方式将这个概念分离到构建文件中的结果。

GN保留了一些GYP概念,比如“全部依赖”设置,这些设置在Blaze中有些不同。这部分是为了使现有的GYP代码更容易转换,GYP结构通常会提供更细粒度的控制(根据具体情况而定,好或坏)。

GN也使用GYP名称,比如“sources”而不是“srcs”,因为缩写似乎是不必要的,尽管它使用了Blaze的“deps”,因为“dependencies”很难打字。Chromium还在一个目标中编译多种语言,因此指定目标名称前缀的语言类型被删除(例如,从cc_library)。

<think>嗯,用户让我介绍一下OpenHarmony的编译流程。首先,我需要回忆一下自己对OpenHarmony的了解。OpenHarmony是华为推出的开源操作系统,主要面向IoT设备,支持多种终端形态。编译流程的话,可能和传统的Linux或者Android有相似之处,但也有其独特的地方。 用户可能是个开发者,想要参与OpenHarmony的开发,或者是在为项目做移植适配。他们需要了解整个编译过程,可能遇到的问题,比如环境配置、组件选择、编译错误处理等等。所以,我需要分步骤解释,从准备环境到最终生成镜像,每个环节的关键点。 首先,环境准备部分,操作系统可能支持Ubuntu等Linux发行版,需要安装依赖工具,比如Python、Node.js、hb等。这里要注意版本号,可能用户会遇到版本不兼容的问题,所以要提醒他们检查版本是否符合要求。 然后是代码获取,用repo工具同步代码,这个和Android类似,但仓库地址不同。用户可能需要配置git信息,特别是初次使用时,容易忽略这一步,导致同步失败。 接下来是选择产品和组件,产品配置文件比如ohos.build.json,用户需要根据目标设备选择正确的产品,比如Hi3516DV300。组件部分可能涉及内核、子系统、模块,这部分需要解释清楚层级关系,帮助用户理解如何裁剪组件。 配置完成后,执行编译命令,hb build可能会遇到依赖缺失或配置错误。编译过程分几个阶段,预处理、编译、链接、打包,每个阶段可能出现的问题需要简要说明,比如预处理中的宏定义错误,编译时的语法错误,链接时的库缺失,打包时的路径问题等。 生成镜像文件,不同设备需要的镜像格式不同,比如烧录到开发板的镜像可能需要特殊处理,用户需要知道如何验证镜像是否正确生成。 最后,常见问题部分要总结用户可能遇到的典型问题,比如环境配置错误、代码同步失败、依赖缺失、编译错误,以及如何解决,比如检查版本、网络、配置文件,查看日志等。 需要确保整个流程结构清晰,步骤明确,避免使用过于专业的术语,但又要准确。同时,按照用户的要求,数学公式要用$...$,独立公式用$$...$$,但在这个问题中可能涉及不多,不过如果有代码块或命令,需要用适当的格式标出,比如`hb build`。 还要注意使用中文,保持口语化,但不要太过随意。可能需要检查是否有遗漏的步骤,比如是否要解释gn和ninja的作用,或者hb工具的具体功能,比如构建系统的入口。如果用户需要更深入的信息,可能需要进一步展开,但当前问题主要是流程介绍,所以点到为止。 总结下来,回答的结构应该是分步骤,从准备到编译到生成镜像,每个步骤的关键点,加上常见问题和解决方法。确保用户能按图索骥,顺利完成编译,同时了解可能遇到的障碍和解决办法。</think>以下是对OpenHarmony编译流程的系统性分析,按核心步骤分层说明: $$ \text{OpenHarmony编译流程} = \text{环境配置} \rightarrow \text{代码获取} \rightarrow \text{产品选择} \rightarrow \text{编译构建} \rightarrow \text{镜像生成} $$ ### 一、环境准备阶段 1. **操作系统要求**:推荐Ubuntu 20.04 LTS 2. **工具链安装**: - Python 3.7+(需设置`python3`默认软链接) - Node.js 12+(用于JS应用编译) - hb工具:`pip3 install build/lite` - 编译器:如`gcc_riscv32`、`llvm`等 ### 二、代码获取 通过`repo`工具同步代码仓库: ```bash repo init -u https://gitee.com/openharmony/manifest.git -b master repo sync -c ``` ### 三、产品配置选择 1. **产品定义文件**:`productdefine/common/products/`目录下的JSON文件(如`Hi3516DV300.json`) 2. **子系统配置**:通过`subsystem_config.json`定义所需子系统 3. **组件裁剪**:使用`hb set`交互命令选择产品后,自动生成`ohos_config.json` ### 四、编译过程解析 #### 阶段1:预处理 - 执行`gn gen out/产品名`生成ninja构建文件 - 解析`BUILD.gn`文件定义的目标: $$ \text{目标} = \text{源码文件} + \text{依赖库} + \text{编译参数} $$ #### 阶段2:核心编译 通过`ninja -C out/产品名`执行: 1. C/C++代码:使用`clang`编译器链处理 2. JS应用:通过`webpack`打包为`.hap`文件 3. 内核编译:针对LiteOS-A/Linux内核单独处理 #### 阶段3:镜像打包 生成关键镜像文件: - `system.img`:基础系统镜像 - `vendor.img`:设备厂商定制组件 - `userdata.img`:用户数据分区 - `updater.img`:OTA升级专用分区 ### 五、编译产物验证 1. **文件校验**:检查`out/产品名/packages/phone/images/`目录 2. **烧录测试**:使用HiTool或fastboot工具写入设备 3. **签名验证**:对商用版本进行证书签名校验 ### 六、优化技巧 1. **增量编译**:`hb build --target-cpu arm64 --ninja-args "-j24"` 2. **缓存加速**:配置`ccache`工具 3. **并行编译**:合理设置`-j`参数(建议为CPU核心数+2) ### 七、常见问题归因 | 问题现象 | 根本原因 | 解决方向 | |---------|--------|---------| | 头文件缺失 | 组件依赖未正确声明 | 检查`bundle.json`依赖配置 | | 链接失败 | 库文件搜索路径错误 | 验证`ldflags`配置 | | 镜像过大 | 未启用组件裁剪 | 调整`subsystem_list`配置 | > **关键路径提示**:编译过程日志可通过`out/产品名/build.log`追溯完整执行链,建议使用`hb build -v`获取详细输出。 此流程体现了OpenHarmony分层化架构设计,通过`gn`+`ninja`构建系统实现跨平台支持。实际开发中需特别注意不同产品形态(轻量系统/小型系统/标准系统)的差异化处理策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶与花语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值