2020-01-17 21:05:03 +08:00
|
|
|
# Copyright 2019 Google LLC
|
2019-03-19 00:21:48 +08:00
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
2022-01-28 17:38:27 +08:00
|
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
2019-03-19 00:21:48 +08:00
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
2022-03-28 20:28:42 +08:00
|
|
|
"""Starlark rules for projects using Sandboxed API."""
|
2019-03-19 00:21:48 +08:00
|
|
|
|
2022-04-05 16:12:23 +08:00
|
|
|
load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
|
2019-03-19 00:21:48 +08:00
|
|
|
load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data")
|
2022-03-29 16:06:49 +08:00
|
|
|
load(
|
|
|
|
"//sandboxed_api/bazel:proto.bzl",
|
|
|
|
_sapi_proto_library = "sapi_proto_library",
|
|
|
|
)
|
2022-06-28 13:24:23 +08:00
|
|
|
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
|
2019-03-19 00:21:48 +08:00
|
|
|
|
2022-03-29 16:06:49 +08:00
|
|
|
# Reexport symbols
|
|
|
|
sapi_proto_library = _sapi_proto_library
|
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
# Helper functions
|
|
|
|
def append_arg(arguments, name, value):
|
|
|
|
if value:
|
|
|
|
arguments.append("{}".format(name))
|
|
|
|
arguments.append(value)
|
|
|
|
|
|
|
|
def append_all(arguments, name, values):
|
|
|
|
if values:
|
|
|
|
for v in values:
|
|
|
|
append_arg(arguments, name, v)
|
|
|
|
|
|
|
|
def get_embed_dir():
|
|
|
|
return native.package_name()
|
2019-03-19 19:11:25 +08:00
|
|
|
|
2022-04-01 16:34:18 +08:00
|
|
|
def make_exec_label(label):
|
|
|
|
return attr.label(
|
|
|
|
default = label,
|
|
|
|
cfg = "exec",
|
|
|
|
allow_files = True,
|
|
|
|
executable = True,
|
|
|
|
)
|
|
|
|
|
|
|
|
# buildifier: disable=function-docstring
|
|
|
|
def select_generator(ctx):
|
|
|
|
if ctx.attr.generator_version == 1:
|
|
|
|
return ctx.executable._generator_v1
|
|
|
|
return ctx.executable._generator_v2
|
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
def sort_deps(deps):
|
2022-03-28 20:28:42 +08:00
|
|
|
"""Sorts a list of dependencies.
|
|
|
|
|
|
|
|
This does not convert absolute references targeting the current package
|
|
|
|
into relative ones.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
deps: List of labels to be sorted
|
|
|
|
Returns:
|
|
|
|
A sorted list of dependencies, with local deps (starting with ":") first.
|
|
|
|
"""
|
2022-04-01 16:34:18 +08:00
|
|
|
|
2021-01-05 23:18:13 +08:00
|
|
|
deps = depset(deps).to_list()
|
2019-03-19 00:21:48 +08:00
|
|
|
colon_deps = [x for x in deps if x.startswith(":")]
|
|
|
|
other_deps = [x for x in deps if not x.startswith(":")]
|
|
|
|
return sorted(colon_deps) + sorted(other_deps)
|
|
|
|
|
2022-11-29 18:17:03 +08:00
|
|
|
def cc_library_virtual_includes(target):
|
|
|
|
"""Checks a target for virtual includes.
|
|
|
|
|
|
|
|
Those can be created by the deprecated `cc_inc_library` rule, or by using
|
|
|
|
a combination of `cc_library()`s `includes`, `include_prefix` and
|
|
|
|
`strip_include_prefix` attributes.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
target: The Target to analyze
|
|
|
|
Returns:
|
|
|
|
A depset with include paths generated by cc_inc_library targets.
|
|
|
|
"""
|
|
|
|
cc_ctx = target[CcInfo].compilation_context
|
|
|
|
|
|
|
|
includes = []
|
|
|
|
for f in cc_ctx.headers.to_list():
|
|
|
|
p = f.path
|
|
|
|
if not p.startswith("blaze-out") and not p.startswith("bazel-out"):
|
|
|
|
continue
|
|
|
|
for path_marker in ["/_virtual_includes/", "/_/"]:
|
|
|
|
i = p.find(path_marker)
|
|
|
|
if i == -1:
|
|
|
|
continue
|
|
|
|
includes.append(p[:i] + path_marker +
|
|
|
|
p[i + len(path_marker):].split("/", 1)[0])
|
|
|
|
|
|
|
|
return depset(includes)
|
|
|
|
|
2022-04-01 16:34:18 +08:00
|
|
|
def _sapi_interface_impl(ctx):
|
2022-03-28 20:28:42 +08:00
|
|
|
cpp_toolchain = find_cpp_toolchain(ctx)
|
2022-04-01 16:34:18 +08:00
|
|
|
generator = select_generator(ctx)
|
|
|
|
use_clang_generator = ctx.attr.generator_version == 2
|
2022-03-28 20:28:42 +08:00
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
# TODO(szwl): warn if input_files is not set and we didn't find anything
|
|
|
|
input_files_paths = []
|
|
|
|
input_files = []
|
|
|
|
|
|
|
|
args = []
|
|
|
|
append_arg(args, "--sapi_name", ctx.attr.lib_name)
|
|
|
|
append_arg(args, "--sapi_out", ctx.outputs.out.path)
|
|
|
|
append_arg(args, "--sapi_embed_dir", ctx.attr.embed_dir)
|
|
|
|
append_arg(args, "--sapi_embed_name", ctx.attr.embed_name)
|
|
|
|
append_arg(args, "--sapi_functions", ",".join(ctx.attr.functions))
|
|
|
|
append_arg(args, "--sapi_ns", ctx.attr.namespace)
|
|
|
|
|
2022-03-02 19:41:10 +08:00
|
|
|
if ctx.attr.limit_scan_depth:
|
|
|
|
args.append("--sapi_limit_scan_depth")
|
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
# Parse provided files.
|
|
|
|
|
|
|
|
# The parser doesn't need the entire set of transitive headers
|
2022-03-02 19:41:10 +08:00
|
|
|
# here, just the top-level cc_library headers.
|
2019-03-19 00:21:48 +08:00
|
|
|
#
|
|
|
|
# Allow all headers through that contain the dependency's
|
|
|
|
# package path. Including extra headers is harmless except that
|
2019-05-08 22:07:01 +08:00
|
|
|
# we may hit Bazel's file-count limit, so be conservative and
|
2019-03-19 00:21:48 +08:00
|
|
|
# pass a lot through that we don't strictly need.
|
2022-03-28 20:28:42 +08:00
|
|
|
#
|
2019-03-19 00:21:48 +08:00
|
|
|
extra_flags = []
|
2022-03-02 19:41:10 +08:00
|
|
|
cc_ctx = ctx.attr.lib[CcInfo].compilation_context
|
|
|
|
|
|
|
|
# Append all headers as dependencies
|
|
|
|
input_files += cc_ctx.headers.to_list()
|
|
|
|
|
2022-11-29 18:17:03 +08:00
|
|
|
# Gather direct include paths as well as virtual ones
|
|
|
|
quote_includes = (cc_ctx.quote_includes.to_list() +
|
|
|
|
cc_library_virtual_includes(ctx.attr.lib).to_list())
|
2022-03-28 20:28:42 +08:00
|
|
|
|
|
|
|
if use_clang_generator:
|
|
|
|
input_files += cpp_toolchain.all_files.to_list()
|
|
|
|
|
|
|
|
# TODO(cblichmann): Get language standard from the toolchain
|
|
|
|
extra_flags.append("--extra-arg=-std=c++17")
|
|
|
|
|
|
|
|
# Disable warnings in parsed code
|
|
|
|
extra_flags.append("--extra-arg=-Wno-everything")
|
|
|
|
extra_flags += ["--extra-arg=-isystem{}".format(d) for d in cpp_toolchain.built_in_include_directories]
|
|
|
|
extra_flags += ["--extra-arg=-D{}".format(d) for d in cc_ctx.defines.to_list()]
|
|
|
|
extra_flags += ["--extra-arg=-isystem{}".format(i) for i in cc_ctx.system_includes.to_list()]
|
|
|
|
extra_flags += ["--extra-arg=-iquote{}".format(i) for i in quote_includes]
|
|
|
|
else:
|
|
|
|
append_all(extra_flags, "-D", cc_ctx.defines.to_list())
|
|
|
|
append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list())
|
|
|
|
append_all(extra_flags, "-iquote", quote_includes)
|
2022-03-02 19:41:10 +08:00
|
|
|
|
|
|
|
if ctx.attr.input_files:
|
2022-03-28 20:28:42 +08:00
|
|
|
for f in ctx.files.input_files:
|
|
|
|
input_files.append(f)
|
|
|
|
input_files_paths.append(f.path)
|
2019-03-19 00:21:48 +08:00
|
|
|
else:
|
2022-03-02 19:41:10 +08:00
|
|
|
# Try to find files automatically
|
|
|
|
for h in cc_ctx.direct_headers:
|
|
|
|
if h.extension != "h" or "/PROTECTED/" in h.path:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Include only headers coming from the target
|
|
|
|
# not ones that it depends on by comparing the label packages.
|
|
|
|
if (h.owner.package == ctx.attr.lib.label.package):
|
|
|
|
input_files_paths.append(h.path)
|
|
|
|
|
2022-03-28 20:28:42 +08:00
|
|
|
if use_clang_generator:
|
|
|
|
args += extra_flags + input_files_paths
|
|
|
|
else:
|
|
|
|
append_arg(args, "--sapi_in", ",".join(input_files_paths))
|
|
|
|
args += ["--"] + extra_flags
|
2019-03-19 00:21:48 +08:00
|
|
|
|
|
|
|
progress_msg = ("Generating {} from {} header files." +
|
|
|
|
"").format(ctx.outputs.out.short_path, len(input_files_paths))
|
|
|
|
ctx.actions.run(
|
|
|
|
inputs = input_files,
|
|
|
|
outputs = [ctx.outputs.out],
|
|
|
|
arguments = args,
|
2022-04-01 16:34:18 +08:00
|
|
|
mnemonic = "SapiInterfaceGen",
|
2019-03-19 00:21:48 +08:00
|
|
|
progress_message = progress_msg,
|
2022-04-01 16:34:18 +08:00
|
|
|
executable = generator,
|
2019-03-19 00:21:48 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
# Build rule that generates SAPI interface.
|
|
|
|
sapi_interface = rule(
|
2022-04-01 16:34:18 +08:00
|
|
|
implementation = _sapi_interface_impl,
|
2019-03-19 00:21:48 +08:00
|
|
|
attrs = {
|
|
|
|
"out": attr.output(mandatory = True),
|
|
|
|
"embed_dir": attr.string(),
|
|
|
|
"embed_name": attr.string(),
|
2022-03-29 20:56:49 +08:00
|
|
|
"functions": attr.string_list(
|
|
|
|
allow_empty = True,
|
|
|
|
default = [],
|
|
|
|
),
|
|
|
|
"input_files": attr.label_list(
|
|
|
|
providers = [CcInfo],
|
|
|
|
allow_files = True,
|
|
|
|
),
|
|
|
|
"lib": attr.label(
|
|
|
|
providers = [CcInfo],
|
|
|
|
mandatory = True,
|
|
|
|
),
|
2019-03-19 00:21:48 +08:00
|
|
|
"lib_name": attr.string(mandatory = True),
|
|
|
|
"namespace": attr.string(),
|
2022-03-02 19:41:10 +08:00
|
|
|
"limit_scan_depth": attr.bool(default = False),
|
2022-03-29 20:56:49 +08:00
|
|
|
"api_version": attr.int(
|
|
|
|
default = 1,
|
|
|
|
values = [1], # Only a single version is defined right now
|
|
|
|
),
|
2022-04-01 16:34:18 +08:00
|
|
|
"generator_version": attr.int(
|
|
|
|
default = 1,
|
|
|
|
values = [1, 2],
|
|
|
|
),
|
|
|
|
"_generator_v1": make_exec_label(
|
|
|
|
"//sandboxed_api/tools/generator2:sapi_generator",
|
|
|
|
),
|
|
|
|
"_generator_v2": make_exec_label(
|
|
|
|
# TODO(cblichmann): Add prebuilt version of Clang based generator
|
|
|
|
"//sandboxed_api/tools/clang_generator:generator_tool",
|
2022-03-28 20:28:42 +08:00
|
|
|
),
|
|
|
|
"_cc_toolchain": attr.label(
|
|
|
|
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
|
2019-03-19 00:21:48 +08:00
|
|
|
),
|
|
|
|
},
|
|
|
|
output_to_genfiles = True,
|
2022-06-28 13:24:23 +08:00
|
|
|
toolchains = use_cpp_toolchain(),
|
2019-03-19 00:21:48 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
def sapi_library(
|
|
|
|
name,
|
|
|
|
lib,
|
|
|
|
lib_name,
|
2023-11-17 02:00:29 +08:00
|
|
|
malloc = "@bazel_tools//tools/cpp:malloc",
|
2019-03-19 00:21:48 +08:00
|
|
|
namespace = "",
|
2022-03-29 20:56:49 +08:00
|
|
|
api_version = 1,
|
2019-07-12 19:06:39 +08:00
|
|
|
embed = True,
|
2019-03-19 00:21:48 +08:00
|
|
|
add_default_deps = True,
|
2022-03-28 20:28:42 +08:00
|
|
|
limit_scan_depth = False,
|
2019-03-19 00:21:48 +08:00
|
|
|
srcs = [],
|
2022-04-05 16:12:23 +08:00
|
|
|
data = [],
|
2019-03-19 00:21:48 +08:00
|
|
|
hdrs = [],
|
2022-04-05 16:12:23 +08:00
|
|
|
copts = sapi_platform_copts(),
|
|
|
|
defines = [],
|
2019-03-19 00:21:48 +08:00
|
|
|
functions = [],
|
|
|
|
header = "",
|
|
|
|
input_files = [],
|
|
|
|
deps = [],
|
|
|
|
tags = [],
|
2022-04-01 16:34:18 +08:00
|
|
|
generator_version = 1,
|
2022-10-13 21:00:42 +08:00
|
|
|
visibility = None,
|
2023-07-10 20:06:47 +08:00
|
|
|
compatible_with = None,
|
2022-10-13 21:00:42 +08:00
|
|
|
default_copts = []):
|
2022-03-28 20:28:42 +08:00
|
|
|
"""Provides the implementation of a Sandboxed API library.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: Name of the sandboxed library
|
|
|
|
lib: Label of the library target to sandbox
|
|
|
|
lib_name: Name of the class which will proxy the library functions from
|
|
|
|
the functions list
|
|
|
|
malloc: Override the default dependency on malloc
|
|
|
|
namespace: A C++ namespace identifier to place the API class into
|
|
|
|
embed: Whether the SAPI library should be embedded inside the host code
|
|
|
|
add_default_deps: Add SAPI dependencies to target (deprecated)
|
|
|
|
limit_scan_depth: Limit include depth for header generator (deprecated)
|
2022-03-29 20:56:49 +08:00
|
|
|
api_version: Which version of the Sandboxed API to generate. Currently,
|
|
|
|
only version 1 is defined.
|
2022-03-28 20:28:42 +08:00
|
|
|
srcs: Any additional sources to include with the sandboxed library
|
2022-04-05 16:12:23 +08:00
|
|
|
data: To be used with srcs, any additional data files to make available
|
|
|
|
to the sandboxed library.
|
2022-03-28 20:28:42 +08:00
|
|
|
hdrs: Like srcs, any additional headers to include with the sandboxed
|
|
|
|
library
|
2022-04-05 16:12:23 +08:00
|
|
|
copts: Add these options to the C++ compilation command. See
|
|
|
|
cc_library.copts.
|
|
|
|
defines: List of defines to add to the compile line. See
|
|
|
|
cc_library.defines.
|
2022-03-28 20:28:42 +08:00
|
|
|
functions: A list for function to use from host code
|
|
|
|
header: If set, do not generate a header, but use the specified one
|
|
|
|
(deprecated).
|
|
|
|
input_files: List of source files which the SAPI interface generator
|
2022-03-29 20:56:49 +08:00
|
|
|
should scan for function declarations
|
2022-03-28 20:28:42 +08:00
|
|
|
deps: Extra dependencies to add to the SAPI library
|
|
|
|
tags: Extra tags to associate with the target
|
2022-04-01 16:34:18 +08:00
|
|
|
generator_version: Which version the the interface generator to use
|
|
|
|
(experimental). Version 1 uses the Python/libclang based `generator2`,
|
|
|
|
version 2 uses the newer C++ implementation that uses the full clang
|
|
|
|
compiler front-end for parsing. Both emit equivalent Sandboxed APIs.
|
2022-03-28 20:28:42 +08:00
|
|
|
visibility: Target visibility
|
2023-07-10 20:06:47 +08:00
|
|
|
compatible_with: The list of environments this target can be built for,
|
|
|
|
in addition to default-supported environments.
|
2022-10-13 21:00:42 +08:00
|
|
|
default_copts: List of package level default copts, an additional
|
|
|
|
attribute since copts already has default value.
|
2022-03-28 20:28:42 +08:00
|
|
|
"""
|
2019-03-19 00:21:48 +08:00
|
|
|
|
|
|
|
common = {
|
|
|
|
"tags": tags,
|
|
|
|
}
|
|
|
|
if visibility:
|
|
|
|
common["visibility"] = visibility
|
|
|
|
|
2023-07-10 20:06:47 +08:00
|
|
|
if compatible_with != None:
|
|
|
|
common["compatible_with"] = compatible_with
|
|
|
|
|
2019-03-19 00:21:48 +08:00
|
|
|
generated_header = name + ".sapi.h"
|
|
|
|
|
|
|
|
# Reference (pull into the archive) required functions only. If the functions'
|
2019-05-08 22:07:01 +08:00
|
|
|
# array is empty, pull in the whole archive (may not compile with MSAN).
|
2022-03-28 20:28:42 +08:00
|
|
|
exported_funcs = ["-Wl,-u," + s for s in functions]
|
2019-03-19 00:21:48 +08:00
|
|
|
if (not exported_funcs):
|
|
|
|
exported_funcs = [
|
|
|
|
"-Wl,--whole-archive",
|
|
|
|
"-Wl,--allow-multiple-definition",
|
|
|
|
]
|
|
|
|
|
|
|
|
lib_hdrs = hdrs or []
|
|
|
|
if header:
|
|
|
|
lib_hdrs += [header]
|
|
|
|
else:
|
|
|
|
lib_hdrs += [generated_header]
|
|
|
|
|
2022-03-28 20:28:42 +08:00
|
|
|
default_deps = ["//sandboxed_api/sandbox2"]
|
2019-03-19 00:21:48 +08:00
|
|
|
|
|
|
|
# Library that contains generated interface and sandboxed binary as a data
|
|
|
|
# dependency. Add this as a dependency instead of original library.
|
|
|
|
native.cc_library(
|
|
|
|
name = name,
|
|
|
|
srcs = srcs,
|
2022-04-05 16:12:23 +08:00
|
|
|
data = [":" + name + ".bin"] + data,
|
2019-03-19 00:21:48 +08:00
|
|
|
hdrs = lib_hdrs,
|
2022-10-13 21:00:42 +08:00
|
|
|
copts = default_copts + copts,
|
2022-04-05 16:12:23 +08:00
|
|
|
defines = defines,
|
2019-03-19 00:21:48 +08:00
|
|
|
deps = sort_deps(
|
|
|
|
[
|
2022-03-28 20:28:42 +08:00
|
|
|
"@com_google_absl//absl/base:core_headers",
|
2021-01-05 23:18:13 +08:00
|
|
|
"@com_google_absl//absl/status",
|
|
|
|
"@com_google_absl//absl/status:statusor",
|
2022-03-28 20:28:42 +08:00
|
|
|
"//sandboxed_api:sapi",
|
|
|
|
"//sandboxed_api/util:status",
|
|
|
|
"//sandboxed_api:vars",
|
2019-03-19 00:21:48 +08:00
|
|
|
] + deps +
|
|
|
|
([":" + name + "_embed"] if embed else []) +
|
|
|
|
(default_deps if add_default_deps else []),
|
|
|
|
),
|
|
|
|
**common
|
|
|
|
)
|
|
|
|
|
|
|
|
native.cc_binary(
|
|
|
|
name = name + ".bin",
|
|
|
|
linkopts = [
|
2019-11-21 17:36:09 +08:00
|
|
|
"-ldl", # For dlopen(), dlsym()
|
2022-03-28 20:28:42 +08:00
|
|
|
# The sandboxing client must have access to all
|
|
|
|
"-Wl,-E", # symbols used in the sandboxed library, so these
|
|
|
|
] + exported_funcs, # must be both referenced, and exported
|
2023-11-17 02:00:29 +08:00
|
|
|
malloc = malloc,
|
2019-03-19 00:21:48 +08:00
|
|
|
deps = [
|
|
|
|
":" + name + ".lib",
|
2022-03-28 20:28:42 +08:00
|
|
|
"//sandboxed_api:client",
|
2019-03-19 00:21:48 +08:00
|
|
|
],
|
2022-10-13 21:00:42 +08:00
|
|
|
copts = default_copts,
|
2019-03-19 00:21:48 +08:00
|
|
|
**common
|
|
|
|
)
|
|
|
|
|
|
|
|
native.cc_library(
|
|
|
|
name = name + ".lib",
|
2019-11-21 17:36:09 +08:00
|
|
|
deps = [lib],
|
2019-03-19 00:21:48 +08:00
|
|
|
alwayslink = 1, # All functions are linked into depending binaries
|
2022-10-13 21:00:42 +08:00
|
|
|
copts = default_copts,
|
2019-03-19 00:21:48 +08:00
|
|
|
**common
|
|
|
|
)
|
|
|
|
|
|
|
|
embed_name = ""
|
|
|
|
embed_dir = ""
|
|
|
|
if embed:
|
|
|
|
embed_name = name
|
|
|
|
|
|
|
|
sapi_cc_embed_data(
|
|
|
|
name = name + "_embed",
|
2021-04-16 16:37:39 +08:00
|
|
|
srcs = [name + ".bin"],
|
2019-03-19 00:21:48 +08:00
|
|
|
namespace = namespace,
|
|
|
|
**common
|
|
|
|
)
|
|
|
|
embed_dir = get_embed_dir()
|
|
|
|
|
|
|
|
sapi_interface(
|
|
|
|
name = name + ".interface",
|
|
|
|
lib_name = lib_name,
|
|
|
|
lib = lib,
|
|
|
|
functions = functions,
|
|
|
|
input_files = input_files,
|
|
|
|
out = generated_header,
|
|
|
|
embed_name = embed_name,
|
|
|
|
embed_dir = embed_dir,
|
|
|
|
namespace = namespace,
|
2022-03-29 20:56:49 +08:00
|
|
|
api_version = api_version,
|
2022-04-01 16:34:18 +08:00
|
|
|
generator_version = generator_version,
|
2022-03-28 20:28:42 +08:00
|
|
|
limit_scan_depth = limit_scan_depth,
|
2019-03-19 00:21:48 +08:00
|
|
|
**common
|
|
|
|
)
|