diff --git a/meta-ohos-staging/classes/gn.bbclass b/meta-ohos-staging/classes/gn.bbclass new file mode 100644 index 0000000000000000000000000000000000000000..f533f062ddc0c2171838c0383c6d89e7e6f1ad1f --- /dev/null +++ b/meta-ohos-staging/classes/gn.bbclass @@ -0,0 +1,307 @@ +# SPDX-FileCopyrightText: Huawei Inc. +# +# SPDX-License-Identifier: MIT + +# This class allows building GN-based projects. +# GN toolchain definition file for Yocto toolchain is generated using +# write_toolchain_file function originating from the meta-browser layer. This +# Yocto toolchain definition relies on the toolchain template of a given +# project. GN_TOOLCHAIN_TMPL_FILE variable defines the location of toolchain +# template in a GN label syntax. Generated Yocto toolchain has to be set as +# default toolchain by patching project's BUILDCONFIG.gn file. Also special +# yocto_flags config containing all Yocto toolchain flags is generated. This +# config have to be added to default configs of all targets using set_defaults +# function. For more information please refer do_check_yocto_toolchain_is_used +# task below. Lastly, recipe has to provide do_install task. + +DEPENDS += "gn-native ninja-native" + +# Location of the GN toolchain template file, used in write_toolchain_file(). It +# can be overridden in the recipe as needed. The name of the GN toolchain +# template does not matter and so by default gcc_toolchain template is used even +# for clang compiler +GN_TOOLCHAIN_TMPL_FILE ??= "//build/toolchain/gcc_toolchain.gni" + +# Project's GN toolchains labels that should be used by the generated Yocto +# toolchain definition - can be overridden in the recipe +GN_TARGET_TOOLCHAIN_TMPL_LABEL ??= "gcc_toolchain" +GN_HOST_TOOLCHAIN_TMPL_LABEL ??= "gcc_toolchain" + +# General GN options, like --dotfile +GN_OPTIONS ??= "" + +# GN_ARGS can be added in the recipe +GN_ARGS ?= ' \ + target_cpu="${@gn_target_arch_name(d)}" \ +' + +B = "${WORKDIR}/out" + +gn_do_configure() { + cd ${S} + gn gen ${GN_OPTIONS} --args='${GN_ARGS}' -C ${B} +} + +gn_do_compile() { + ninja -C ${B} +} + +gn_do_install() { + bbfatal " \ + Missing do_install task definition! \ + GN projects don't usually follow any particular convention with regards \ + to build artifacts, therefore do_install task has to be defined in the \ + project's recipe. \ + " +} + +# GN fails with unclear and confusing error logs when build directory is the +# same as source directory. To avoid that build directory is set to +# "S{WORKDIR}/out". Nevertheless let's make sure that B != S as devtool default +# behaviour is to override B variable to be the same as S. +# +# NOTE: devtool adds bbappend file which makes recipe to inherit externalsrc +# class with EXTERNALSRC set to workspace/sources/<recipe-name> and by default +# EXTERNALSRC_BUILD set to the same value as EXTERNALSRC. To change this +# behavior --no-same-dir option has to be passed to devtool add command. +python do_check_B_is_not_S() { + bpath = os.path.abspath(d.expand("${B}")) + spath = os.path.abspath(d.expand("${S}")) + if os.path.abspath(d.expand("${S}")) == os.path.abspath(d.expand("${B}")): + bb.fatal(''' +GN requires build and sources directories to be different. By default build +directory is set to ${WORKDIR}/out. If you're using devtool remember to use +--no-same-dir option, e.g.: +devtool add --no-same-dir <your-gn-project-name> <your-gn-project-git-url> + ''') +} + +addtask check_B_is_not_S after do_patch before do_configure + +python do_write_gn_toolchain_file () { + root_gn_dir = d.expand("${S}") + # Do not modify the below part: for simplicity hardcoded GN references: + # //build/toolchain/yocto:yocto_{target,native,flags} are used in a couple + # of places including this class implementation and patchwork for projects + # using this class + toolchain_dir = os.path.join(root_gn_dir, "build", "toolchain", "yocto") + bb.utils.mkdirhier(toolchain_dir) + toolchain_file = os.path.join(toolchain_dir, "BUILD.gn") + write_toolchain_file(d, toolchain_file) +} + +addtask write_gn_toolchain_file after do_patch before do_configure + +do_check_yocto_toolchain_is_used() { + cd "${S}" + DEFAULT_TARGET_TOOLCHAIN=$(gn desc ${B} "//build/toolchain/yocto:yocto_flags" | \ + grep "toolchain: //build/toolchain/yocto:yocto_target") + LIST_OF_GN_TARGETS_USING_YOCTO_FLAGS=$(gn refs -q ${B} \ + "//build/toolchain/yocto:yocto_flags") + if test -z "$DEFAULT_TARGET_TOOLCHAIN" || \ + test -z "$LIST_OF_GN_TARGETS_USING_YOCTO_FLAGS" + then + bbfatal "\ +No reference to yocto_target or yocto_flags! \ +You have to patch project's default toolchain and targets defaults! Those are \ +found in the BUILDCONFIG.gn file. Toolchain is set by the set_default_toolchain \ +function call and targets defaults by a series of calls to set_defaults \ +function one per target type, like executable, shared_library, etc. Typical \ +approach is to change default toolchain to (sample patch): \n\ +\n\ +--- a/build/config/BUILDCONFIG.gn\n\ ++++ b/build/config/BUILDCONFIG.gn\n\ +- set_default_toolchain(_default_toolchain)\n\ ++ set_default_toolchain(\"//build/toolchain/yocto:yocto_target\")\n\ +\n\ +and append flags to all defaults' config (sample patch): \n\ +\n\ +--- a/build/config/BUILDCONFIG.gn\n\ ++++ b/build/config/BUILDCONFIG.gn\n\ +set_defaults(\"shared_library\") {\n\ +- configs = default_configs\n\ ++ configs =\n\ ++ default_configs +\n\ ++ [ \"//build/toolchain/yocto:yocto_flags\" ]\n\ +}\n\ +\n\ + " + fi +} + +addtask do_check_yocto_toolchain_is_used after do_configure before do_compile + +EXPORT_FUNCTIONS do_configure do_compile do_install + +def is_clang(cc: str) -> bool: + """ Returns True when the argument (cc) string contains the word `clang`; + False otherwise""" + cc_basename: str = cc.split()[0] + return "clang" in cc_basename + +def gn_bool(arg: bool) -> str: + """ Returns GN-compliant lowercase boolean string from argument (arg) """ + return str(arg).lower() + +def gn_list(space_separated_str: str) -> str: + """ Returns GN-formatted list string: + ["item1", "item2"] + string extracted from a space-separated string argument + (space_separated_str) """ + return str(space_separated_str.split()).replace("'", "\"") + +# Following functions originally come from the meta-browser layer: +# https://github.com/OSSystems/meta-browser + +# GN host architecture helpers. +# +# BUILD_ARCH's value corresponds to what uname returns as the machine name. +# The mapping in gn_host_arch_name() tries to match several possible values +# returned by the Linux kernel in uname(2) into the corresponding values GN +# understands. +def gn_host_arch_name(d): + """Returns a GN architecture name corresponding to the build host's machine + architecture.""" + import re + arch_translations = { + r'aarch64.*': 'arm64', + r'arm.*': 'arm', + r'i[3456]86$': 'x86', + r'x86_64$': 'x64', + r'riscv32$': 'riscv32', + r'riscv64$': 'riscv64', + } + build_arch = d.getVar("BUILD_ARCH") + for arch_regexp, gn_arch_name in arch_translations.items(): + if re.match(arch_regexp, build_arch): + return gn_arch_name + bb.fatal('Unsuported BUILD_ARCH value: "%s"' % build_arch) + +# GN target architecture helpers. +# +# Determining the target architecture is more difficult, as there are many +# different values we can use on the Yocto side (e.g. TUNE_ARCH, TARGET_ARCH, +# MACHINEOVERRIDES etc). What we do is define the mapping with regular, +# non-Python variables with overrides that are generic enough (i.e. "x86" +# instead of "i586") and then use gn_target_arch_name() to return the right +# value with some validation. +GN_TARGET_ARCH_NAME_aarch64 = "arm64" +GN_TARGET_ARCH_NAME_arm = "arm" +GN_TARGET_ARCH_NAME_x86 = "x86" +GN_TARGET_ARCH_NAME_x86-64 = "x64" +GN_TARGET_ARCH_NAME_riscv32 = "riscv32" +GN_TARGET_ARCH_NAME_riscv64 = "riscv64" + +def gn_target_arch_name(d): + """Returns a GN architecture name corresponding to the target machine's + architecture.""" + name = d.getVar("GN_TARGET_ARCH_NAME") + if name is None: + bb.fatal('Unsupported target architecture. A valid override for the ' + 'GN_TARGET_ARCH_NAME variable could not be found.') + return name + +def gn_toolchain_file_header(d): + """Reurns GN toolchain file header as a multi-line string""" + + toolchain_tmpl_file = d.expand('${GN_TOOLCHAIN_TMPL_FILE}') + return str( + '# This file has been generated automatically.\n' + '\n' + f'import("{toolchain_tmpl_file}")\n' + '\n' + ) + +def gn_toolchain_flags(d): + """Returns GN config containing Yocto flags as a multi-line string""" + + yocto_target_cflags = gn_list(d.expand('${TARGET_CPPFLAGS} ${TARGET_CFLAGS}')) + yocto_target_cflags_cc = gn_list(d.expand('${TARGET_CXXFLAGS}')) + yocto_target_ldflags = gn_list(d.expand('${TARGET_LDFLAGS}')) + yocto_native_cflags = gn_list(d.expand('${BUILD_CPPFLAGS} ${BUILD_CFLAGS}')) + yocto_native_cflags_cc = gn_list(d.expand('${BUILD_CXXFLAGS}')) + yocto_native_ldflags = gn_list(d.expand('${BUILD_LDFLAGS}')) + + return str( + 'config("yocto_flags") {\n' + ' if (current_toolchain == "//build/toolchain/yocto:yocto_target") {\n' + f' cflags = {yocto_target_cflags}\n' + f' cflags_cc = {yocto_target_cflags_cc}\n' + f' ldflags = {yocto_target_ldflags}\n' + ' } else if (current_toolchain == "//build/toolchain/yocto:yocto_native") {\n' + f' cflags = {yocto_native_cflags}\n' + f' cflags_cc = {yocto_native_cflags_cc}\n' + f' ldflags = {yocto_native_ldflags}\n' + ' }\n' + '}\n' + '\n' + ) + +def gn_toolchain(toolchain_args): + """Returns GN toolchain definition based on provided (toolchain_args) + dictionary variable as a multi-line string""" + + # Using template to avoid having to escape curly braces in str.format + from string import Template + return Template( + '${toolchain_tmpl_name}("${toolchain_name}") {\n' + ' cc = "${cc}"\n' + ' cxx = "${cxx}"\n' + ' ar = "${ar}"\n' + ' ld = cxx # GN expects a compiler, not a linker.\n' + ' nm = "${nm}"\n' + ' readelf = "${readelf}"\n' + ' toolchain_args = {\n' + ' current_cpu = "${current_cpu}"\n' + ' current_os = "linux"\n' + ' is_clang = ${is_clang}\n' + ' }\n' + ' # TODO: In some projects not all of the above variables are used\n' + ' # Marking all of those as "not_needed" to avoid errors\n' + ' # This should be fixed in some smart way if feasible\n' + ' not_needed("*")\n' + '}\n' + ).substitute(toolchain_args) + +def gn_toolchain_native(d): + """Returns GN toolchain definition based on Yocto native toolchain""" + + native_toolchain_args = { + 'toolchain_tmpl_name': d.expand('${GN_HOST_TOOLCHAIN_TMPL_LABEL}'), + 'toolchain_name': 'yocto_native', + 'current_cpu': gn_host_arch_name(d), + 'cc': d.expand('${BUILD_CC}'), + 'cxx': d.expand('${BUILD_CXX}'), + 'ar': d.expand('${BUILD_AR}'), + 'nm': d.expand('${BUILD_NM}'), + 'readelf': d.expand('${BUILD_PREFIX}readelf'), + 'is_clang': gn_bool(is_clang(d.expand('${BUILD_CC}'))) + } + + return gn_toolchain(native_toolchain_args) + +def gn_toolchain_target(d): + """Returns GN toolchain definition based on Yocto target toolchain""" + + target_toolchain_args = { + 'toolchain_tmpl_name': d.expand('${GN_TARGET_TOOLCHAIN_TMPL_LABEL}'), + 'toolchain_name': 'yocto_target', + 'current_cpu': gn_target_arch_name(d), + 'cc': d.expand('${CC}'), + 'cxx': d.expand('${CXX}'), + 'ar': d.expand('${AR}'), + 'nm': d.expand('${NM}'), + 'readelf': d.expand('${TARGET_PREFIX}readelf'), + 'is_clang': gn_bool(is_clang(d.expand('${CC}'))) + } + + return gn_toolchain(target_toolchain_args) + +def write_toolchain_file(d, file_path): + """Creates a complete GN toolchain file in |file_path|.""" + + with open(file_path, 'w') as toolchain_file: + toolchain_file.write(gn_toolchain_file_header(d)) + toolchain_file.write(gn_toolchain_flags(d)) + toolchain_file.write(gn_toolchain_native(d)) + toolchain_file.write(gn_toolchain_target(d))