在chromium项目中添加新的helper app
为了创建一个生命周期独立于主进程的进程,在OS X系统上,我们需要创建一个helper app来实现这一点。在使用Xcode开发时,我们可以很轻松的创建一个子app,但是对于使用gn构建的chromium项目,这一步就显得有些麻烦了。
查看gn的官方文档,它告诉我们可以利用bundle_data
和create_bundle
来创建一个的应用程序包
下面是gn的文档中提供的一个关于怎样使用bundle_data
创建一个IOS/MacOS应用的例子:
# Defines a template to create an application. On most platform, this is just
# an alias for an "executable" target, but on iOS/macOS, it builds an
# application bundle.
template("app") {
if (!is_ios && !is_mac) {
executable(target_name) {
forward_variables_from(invoker, "*")
}
} else {
app_name = target_name
gen_path = target_gen_dir
#
action("${app_name}_generate_info_plist") {
script = [ "//build/ios/ios_gen_plist.py" ]
sources = [ "templates/Info.plist" ]
outputs = [ "$gen_path/Info.plist" ]
args = rebase_path(sources, root_build_dir) +
rebase_path(outputs, root_build_dir)
}
bundle_data("${app_name}_bundle_info_plist") {
deps = [ ":${app_name}_generate_info_plist" ]
sources = [ "$gen_path/Info.plist" ]
outputs = [ "{{bundle_contents_dir}}/Info.plist" ]
}
executable("${app_name}_generate_executable") {
forward_variables_from(invoker, "*", [
"output_name",
"visibility",
])
output_name =
rebase_path("$gen_path/$app_name", root_build_dir)
}
code_signing =
defined(invoker.code_signing) && invoker.code_signing
if (is_ios && !code_signing) {
bundle_data("${app_name}_bundle_executable") {
deps = [ ":${app_name}_generate_executable" ]
sources = [ "$gen_path/$app_name" ]
outputs = [ "{{bundle_executable_dir}}/$app_name" ]
}
}
create_bundle("${app_name}.app") {
product_type = "com.apple.product-type.application"
if (is_ios) {
bundle_root_dir = "${root_build_dir}/$target_name"
bundle_contents_dir = bundle_root_dir
bundle_resources_dir = bundle_contents_dir
bundle_executable_dir = bundle_contents_dir
bundle_plugins_dir = "${bundle_contents_dir}/Plugins"
extra_attributes = {
ONLY_ACTIVE_ARCH = "YES"
DEBUG_INFORMATION_FORMAT = "dwarf"
}
} else {
bundle_root_dir = "${root_build_dir}/target_name"
bundle_contents_dir = "${bundle_root_dir}/Contents"
bundle_resources_dir = "${bundle_contents_dir}/Resources"
bundle_executable_dir = "${bundle_contents_dir}/MacOS"
bundle_plugins_dir = "${bundle_contents_dir}/Plugins"
}
deps = [ ":${app_name}_bundle_info_plist" ]
if (is_ios && code_signing) {
deps += [ ":${app_name}_generate_executable" ]
code_signing_script = "//build/config/ios/codesign.py"
code_signing_sources = [
invoker.entitlements_path,
"$target_gen_dir/$app_name",
]
code_signing_outputs = [
"$bundle_root_dir/$app_name",
"$bundle_root_dir/_CodeSignature/CodeResources",
"$bundle_root_dir/embedded.mobileprovision",
"$target_gen_dir/$app_name.xcent",
]
code_signing_args = [
"-i=" + ios_code_signing_identity,
"-b=" + rebase_path(
"$target_gen_dir/$app_name", root_build_dir),
"-e=" + rebase_path(
invoker.entitlements_path, root_build_dir),
"-e=" + rebase_path(
"$target_gen_dir/$app_name.xcent", root_build_dir),
rebase_path(bundle_root_dir, root_build_dir),
]
} else {
deps += [ ":${app_name}_bundle_executable" ]
}
}
}
}
嗯,可以看到gn对生成app类型的软件包的支持并不直接,要生成一个这样的软件包,需要自己调用python脚本生成plist
文件,手动创建app中的文件结构,再调用python脚本生成签名文件,要自己写一套这样的流程无疑是非常麻烦的,chrome在编译后可以自动生成app类型的软件包,我们不妨看一下能否利用chromium中的逻辑,在/chrome/BUILD.gn
中:
# /chrome/BUILD.gn
...
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]
# Merge all helper apps needed by //content and //chrome.
chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]
# Create all helper apps required by //content.
foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}
# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/helper-alerts-Info.plist" ]
}
# Create and bundle an InfoPlist.strings for the alert helper app.
# TODO(crbug.com/1182393): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/helper-alerts-InfoPlist.strings" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name = "$target_gen_dir/helper_alerts_infoplist_strings/base.lproj/InfoPlist.strings"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/base.lproj/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}
chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}
...
foreach(helper_params, chrome_mac_helpers) {
action("verify_libraries_chrome_helper_app_${_helper_target}") {
...
}
}
bundle_data("chrome_framework_helpers") {
foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
...
}
}
...
好吧,这里的处理更加复杂了,不过我们注意到,chrome将所有的helper app加入到了一个content_mac_helpers
列表中,然后依次对这里面的内容进行了处理,再调用content_mac_helpers
生成了app软件包,后面的内容主要是将helper app放到指定的位置,并使用链接的方式让它们和主程序共享了动态库。比较有意思的是,helper app的sources中并不包含程序入口(main()
),而是在调用chrome_helper_app
时加入了同一个函数入口chrome_exe_main_mac.cc
。(试验了一下,每启动一种helper,就会进入一次main函数)
在这里的列表中加入一个自己要创建的helper app,我们发现在编译后的helper文件夹中确实出现了一个新的helper app(如上面所说,chromium为它们添加了统一的程序入口,即便我什么文件都不添加也能编译)。但我希望能创建一个相对独立的程序,不需要包含现在的这些库,也希望程序能有一个独立的入口,那么直接在这里的列表里添加是不太合适的,我们可以再研究一下这里面有哪些template是可以利用的。
看一下这里使用的几个主要的template
tweak_info_plist
:让一个plist文件,或者将多个plist文件合并运行tweak_info_plist.py
,生成一个新的plist文件,可以在args
中设置要添加的属性。compile_plist
:作用和上面那个差不多,只不过输入的文件是以string的形式,它似乎可以替换文件中的一些变量,再写入plist文件中mac_app_bundle
:打包mac应用包,提供了多种类型的mac应用包的打包功能
重点在于mac_app_bundle
,plist可以调用前面两个template进行配置,也可以完全自己写一份,调用方式类似于这样:
mac_app_bundle("my_app") {
output_name = "my_app"
package_type = "app"
info_plist_target = ":my_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=my_app",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=my_app"
]
no_default_deps = true
deps = [
":my_app_src",
]
}
然后将生成的app包拷贝到目标目录下
action("copy_my_app_app") {
script = "//app/build/copy_floder.py"
args = [
rebase_path("$root_out_dir/my_app.app"),
rebase_path(
"$root_out_dir/$name.app/Contents/MacOS/${chrome_product_full_name} Helper (my_app).app"),
]
public_deps = [
":my_app",
]
inputs = []
outputs = [
"$root_out_dir/$name.app/Contents/MacOS/${chrome_product_full_name} Helper (my_app).app",
]
}
添加一个.mm
文件,获取上面的app路径并调用launchApplication
启动它:
int StartupMyHelperApp(pid_t parent_pid) {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSArray *p = [path pathComponents];
NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:p];
[pathComponents addObject:@"Contents"];
[pathComponents addObject:@"MacOS"];
[pathComponents addObject:@"App Helper (my_app).app"];
NSString *app_path = [NSString pathWithComponents:pathComponents];
bool ret = [[NSWorkspace sharedWorkspace] launchApplication:app_path];
return ret;
}
编译后启动主程序,然后关闭主进程,使用ps命令查看现有的进程:
可以看到现在我们创建的helper app仍在运行,成功通过这样的方法创建了一个独立进程