Skip to content

Latest commit

 

History

History
620 lines (451 loc) · 26.3 KB

cc.md

File metadata and controls

620 lines (451 loc) · 26.3 KB

C/C++ Rules

There are 3 phrases in the C/C++ building: preprocessing, compiling, linking. with different flags.

The common CC attributes are:

  • warning: str = ['yes', 'no'], Whether suppress all warnings

    Example: warning='no'. The default values is 'yes', so can be omitted.

  • defs:str = [], User define macros

    Example: defs=['_MT'], can also has value, eg. 'A=1'.

  • incs: list(str) = [] header search paths

    Example: incs=['poppy/myinc'].

    Usually used for thirdparty library, use full include path in our code is more recommended.

  • optimize optimize flags

    Example: optimize=['-O3']

    There is a separated optimize attribute from the extra_cppflags, because it should be ignored in the debug mode, otherwise it will hinder debugging. but for some kind of libraries, which are mature enough and performance related, we rarely debug trace into them, such as hash, compress, crypto, you can specify always_optimize = True.

  • extra_cppflags:list(str) = [], Extra C/C++ compile flags.

    Example: extra_cppflags = ['-Wno-format-literal']. many useful flags, such as -g-fPIC are builtin. so it should be rarely used.

  • extra_linkflags:list(str) = [], Extra link flags

    Example: extra_linkflags = ['-fopenmp']. many useful flags such -g are already built in, so it should be rarely used.

  • linkflags: list = None, Overrides the global linkflags in the configuration.

    For example: linkflags = ['-fopenmp'].

    Commonly used flags such as -g are already built-in, and generally do not need to be specified. Because global options will be overridden, unless you really understand the link related options of gcc and ld, do not use this parameter lightly.

cc_library

Build a C/C++ library

cc_library can be used to build static and dynamic library. But only the static library will built default, unless it is depended by a dynamic_linked cc_binary, or run blade build with --generate-dynamic.

Example:

cc_library(
    name='lowercase',
    srcs=['lower/plowercase.cpp'],
    hdrs=['lower/plowercase.h'],
    deps=['#pthread'],
    link_all_symbols=False
)

Attributes:

  • hdrs: list

    Declares the public interface header files of the library.

    For normal CC libraries, hdrs should exist, otherwise the library may not be used. Therefore, this attribute is required, otherwise a diagnostic problem will be reported. If it surely does not exist, you should set it to empty([]) explicitly.

    The severity of the problem can be controlled by cc_library_config.hdrs_missing_severity. For problems that existed before hdrs support, you can use cc_library_config.hdrs_missing_suppress to suppress them.

    Rules for generating header files during construction, such as pb.h generated by proto_library or outs of the gen_rule target, if these header files are included, these header files will also be automatically included. Including the header file in the dependency management can avoid compilation or linking problems caused by the library that includes the header file but does not add the dependency, especially for dynamically generated header files.

    A header file can belong to multiple cc_library. cc_library will not automatically export hdrs of other cc_library that it depends on in deps. Only public header files should be declared in hdrs. For private header files, even if they are included in public header files, they do not need to be included. Private header files should be declared in its srcs.

    All CC libraries should be described by cc_library, especially for headers-only libraries. Because most library inevitably depends on other libraries, if dependency of the ordinary library is shortage, the "symbol not found" error will be reported during linking, it is easier to add missing dependencies based on error information, but for headers-only libraries, even indirect dependencies, errors are reported at the linking time of the final user, making it difficult to locate and fix the problem.

    Therefore, for header-only libraries, they also needs to be described with cc_library, its public header files need to be declared in its hdrs, and its direct dependencies need to be listed in its deps.

    If the granularity of the library is too large, some unnecessary dependencies will be included due to the enforcement of the hdrs checking. You should proper splitting the library to reduce unnecessary coupling.

    For example, gtest_prod.h of gtest is often used to declare testing support in product code, but it only contains some declarations and does not depend on the implementation part of the gtest library. It is suitable to declare it in a separate gtest_prod library instead of putting it into gtest library, Otherwise, the gtest library may be linked into the product code.

  • link_all_symbols: bool = False

    If you depends on the global initialization to register something, but didn't access these global object directly. it works for code in srcs of executable. but if the global is in a library, it will be discarded at the link time because linker find it is unsed.

    This attribute tells the linker don't discard any unused symbols in this library even if it seems to be unused.

    You'd better put these self-registering code into a separated small library. otherwise, the whole library will be linked into the executable unconditionally, increase the size of the executable.

    Youi also need to know, the link_all_symbols is the attribute of the library, not the user of it. Click here to learn more details if you have interesting.

  • binary_link_only: bool = False

    This library can only be a depenedency of the executable targets (Such as cc_binary or cc_test), but not normal cc_librarys. This attribute is useful for some exclusive libraries such as malloc library.

    For example, tcmalloc and jemalloc are both malloc library which contains same symbols (such as malloc, free), if a cc_library depends on tcmalloc, the dependent cc_binary may not depends on jemalloc any more, otherwise linker will report multiple malloc definition. This problem can be solved by setting this attribute on both 'tcmalloc' and 'jemalloc'.

    A 'binary_link_only' library can depends on other 'binary_link_only' libraries.

    cc_library(
        name = 'tcmaloc',
        binary_link_only = True,
        ...
    )
    
    cc_library(
        name = 'jemaloc',
        binary_link_only = True,
        ...
    )
  • always_optimize: bool = False

    True: Always optimize. False: Don't optimize in debug mode。 The default value is False。It only apply to cc_library.

  • prebuilt: bool = False

    Use prebuilt in cc_library is deprecated. you should use prebuilt_cc_library.

  • export_incs: list = []

    Similar to incs, but it is transitive for all targets depends on it, even if indirect depends on it.

    NOTE:

    There is no dependency code in the generated cc_library, both static and dynamic. But for the dynamic library, it contains the paths of dependencies.

    These libraries can just be used locally (such as run tests),not fit for the production environment. If you want to build a shared library can be use in the environment, you should use cc_plugin, which will include the code from it dependencies.

Fix missing dependencies errors caused by hdrs

In large-scale C++ projects, dependency management is very important, and header files have not been included in it for a long time. Starting with Blade 2.0, header files have also been included in dependency management. When a cc target includes a header file, it also needs to put the cc_library it belongs to in its own deps, otherwise Blade will check and report the problem.

The lack of a declaration on the library to which the header files belong will cause the following problems:

  • The dependencies between libraries cannot be transferred correctly. If the library to which an undeclared header file belongs adds new dependencies in the future, it may cause link errors.
  • For the header files generated during the build, the lack of a dependency declaration for the library to which they belong will cause these header files to have not yet been generated at compile time, resulting in compilation errors.
  • To make matters worse, if these header files already exist but have not been updated, outdated header files may be used during compilation, which will cause runtime errors that are more difficult to troubleshoot.

The severity of the problem can be controlled by the cc_config.hdr_dep_missing_severity configuration item. For problems that existed before hdrs were supported, It can be suppressed by cc_config.hdr_dep_missing_suppress.

Blade can detect two kind of missing dependencies:

  • Missing dependenvy

    The header files are included in srcs or hdrs through the #include directive, but the library to which they belong is not declared in deps, or these header files are not declared in any hdrs of cc_library at all. Problems and solution:

    • The library to which the header file belongs is not declared in the deps of this target, just follow the instruction to fix it
    • The header file is a private header file of the library to which it belongs, and direct use is prohibited
    • The header file should be the public header file of this target, it should be declared in its hdr
    • The header file should be the target's private header file, it should be declared in its src
    • The header file should be a public header file of other libraries, but there is no declaration, just declare it in the corresponding library hdr
  • Missing indirect dependency

    One of the indirect included header file(included in the header file) does not appear in the deps of this target and its transitive dependencies.

    We only do this check for the header files generated during compilation. Because for the rules for generating header files (such as proto_library or possibly gen_rule), if the dependencies are missing, it may cause these header files to be ungenerated or outdated when compiling the current target, resulting in compilation errors. Fixing this error is a little more troublesome. You need to analyze the include stack reported by the error message, starting from the source file, and searching upwards in the library to which each header file belongs, whether it depends on the library to which the included header file belongs.

    At this time, you may encounter a situation, that is, some libraries with pure header files have no implementation files, so there is no orresponding cc_library to describe it. To fix this issue, you need to write a new cc_library for it, add header files in the hdrs, add the implementation dependencies in its deps, and add it to the deps of which library depends on it.

    This can solve the root cause, but it does require some effort. The simple and rude solution is to add the reported missing library to the current target deps, which is equivalent to relying on the implementation details of some libraries, which is not recommended.

Since Blade fully managed the dependency of header files, for any header files that are not declared in the hdrs or src of any library, an error will be reported after the build. If these header files should belong to the current target, According to whether it is public or private, add it to hdrs or srcs respectively. If it belongs to other libraries, it should be added to hdrs of other libraries, and you cannot include undeclared or private header files of other libraries.

For undeclared header files that already existed in the code base before the upgrade, you can use the cc_config.allowed_undeclared_hdrs configuration item to mask the check.

prebuilt_cc_library

For libraries without source code, library should be put under the lib{32,64} sub dir accordingly. The attributes deps, export_incs, link_all_symbols is still avialiable, but other attributes, include compile and link related options, are not not present in prebuilt_cc_library.

Attributes:

Example:

prebuilt_cc_library(
    name = 'mysql',
    deps = [':mystring', '#pthread']
)

foreign_cc_library

NOTE: This feature is still experimental.

There are already a large number of existing libraries in the world. They are built with different build systems. If you want to add Blade build support to them, you need to invest a lot of time cost and maintenance. foreign_cc_library is used to describe C/C++ libraries not built directly by Blade but generated by other build systems, such as make or cmake. The main difference between foreign_cc_library and prebuilt_cc_library is that the library it describes is dynamically generated by Blade during the build by calling other build systems, The library described by prebuilt_cc_library is placed in the source tree before the build. So generally foreign_cc_library always needs to be used with gen_rule.

Considering that a large number of C/C++ libraries are built with GNU Autotools, the default parameters of foreign_cc_library are adapted to the autotools installation directory layout. In order to find the library and header files correctly, foreign_cc_library assumes that the package will be installed in a certain directory after the build (that is, the path specified by the --prefix parameter of configure), and the header file is in the include subdirectory, The library file is installed in the lib subdirectory.

Attributes:

  • name: str, The name of the library
  • install_dir: str, The installation directory after the package is built
  • lib_dir: list, The subdirectory name of the library in the installation directory
  • has_dynamic: bool = False, Whether this library has a dynamic linked edition.

Example1, zlib

zlib can be the simplest example of autotools package. Assuming that zlib-1.2.11.tar.gz is in the thirparty/zlib directory, its BUILD file is thirdparty/zlib/BUILD:

# Assume that after executing this rule, the built package will be installed under `build64_release/thirdparty/openssl`,
# then the header file is under `include/openssl`, and the library file is under `lib`.
# We have developed general build rules for autotools and cmake, but they are still in the experimental state.
# Here we still use `gen_rule` to examplify.
gen_rule(
    name = 'zlib_build',
    srcs = ['zlib-1.2.11.tar.gz'],
    outs = ['lib/libz.a', 'include/zlib.h', 'include/zconf.h'],
    cmd = '...',  # tar xf,configure, make, make install...
)

# Describe the installed library of zlib

foreign_cc_library(
    name = 'z',  # The name of the library is `libz.a`,under the `lib` subdirectory
    install_dir = '', # The installation directory is `build64_release/thirdparty/libz`
    # lib_dir= 'lib', # The dafault os OK and can be omitted.
    deps = [':zlib_build'],
)

Use the above library:

cc_binary(
    name = 'use_zlib',
    srcs = ['use_zlib.cc'],
    deps = ['//thirdparty/openssl:ssl'],
)

use_openssl.cc:

#include "thirdparty/zlib/include/zlib.h"
// or
#include "zlib.h"
// because `thirdparty/zlib/include` has been exported in the `foreign_cc_library` defaultly

示例 2,openssl

Strictly speaking, openssl is not built with autotools, but it is generally compatible with autotools. Its autotools-like configure file is Config, and the directory layout after installation is compatible. However, its header file paths have package name prefix, which is not directly under include but under the include/openssl subdirectory.

Suppose openssl-1.1.0.tar.gz is in the thirparty/openssl directory, and its BUILD file is thirdparty/openssl/BUILD:

# Assuming that after executing this rule, the built package will be installed under `build64_release/thirdparty/openssl`,
# then the header file is under `include/openssl` and the library file is under `lib`.
gen_rule(
    name = 'openssl_build',
    srcs = ['openssl-1.1.0.tar.gz'],
    outs = ['lib/libcrypto.a', 'lib/libss.a'],
    cmd = '...',  # tar xf,Config, make, make install...
    export_incs = 'include', # Let the compiler find the `openssl` subdirectory under `include`
)

# Describe the above 2 libraries

foreign_cc_library(
    name = 'crypto',  # The name of the library is libcrypto.a, under the `lib` subdirectory
    install_dir = '', # The installation directory is `build64_release/thirdparty/openssl`
    deps = [':openssl_build'],
)

foreign_cc_library(
    name = 'ssl',  # The name of the library is libssl.a, under the `lib` subdirectory
    install_dir = '',
    deps  = [':openssl_build', ':crypto'],
)

Use the above library:

cc_binary(
    name = 'use_openssl',
    srcs = ['use_openssl.cc'],
    deps = ['//thirdparty/openssl:ssl'],
)

use_openssl.cc:

#include "openssl/ssl.h"  // Contains package name

cc_binary

Build executable from source.

Example:

cc_binary(
    name='prstr',
    srcs=['./src/mystr_main/mystring.cpp'],
    deps=['#pthread',':lowercase',':uppercase','#dl'],
)
  • dynamic_link:bool = False

    cc_binary is static linked defaultly for easy deploying, include libstdc++. For some technology limitation, glibc is not static linked. we can link it statically, but there are still some problems.

    If you want to generate a dynamic linked executable, you can set it to True,all dependency libraries will be linked statically, except prebuilt libraries if they has no dynamic version. This may reduce disk usage, but program will startup slower. Usually it can be use for local testing, but it didn't fit for deploy.

  • export_dynamic: = True

    Usually, dynamic library will only access symbols from its dependencies. but for some special case, shared library need to access symbols defined in the host executable, so come the attribute.

    This attribute tells linker to put all symbols into its dynamic symbol table. make them visible for loaded shared libraries. for more details, see --export-dynamic in man ld(1).

cc_test

cc_binary, with gtest gtest_main be linked automatically,

Test will be ran in a test sandbox, it can't access source code directly. If you want to pass some data files into it, you must need testdata attribute.

  • testdata: list = []

    Copy test data files into the test sandbox, make them visible to the test code. This attribute supports the following forms:

    • 'file'

      Use the original filename in test code.

    • '//your_proj/path/file'

      Use "your_proj/path/file" form to access in test code.

    • ('//your_proj/path/file', "new_name")

      Use the "new_name" in test code.

Example:

cc_test(
    name = 'textfile_test',
    srcs = 'textfile_test.cpp',
    deps = ':io',
    testdata = [
        'test_dos.txt',
        '//your_proj/path/file',
        ('//your_proj/path/file', 'new_name')
    ]
)

cc_plugin

Link all denendencies into the generated so file, make it can be easily loaded in other languages.

cc_plugin(
    name='mystring',
    srcs=['./src/mystr/mystring.cpp'],
    deps=['#pthread',':lowercase',':uppercase','#dl'],
    warning='no',
    defs=['_MT'],
    optimize=['O3']
)

Attributes:

  • prefix: str = 'lib', the file name prefix of the generated dynamic library, the default is lib
  • suffix: str = '.so', the file name prefix of the generated dynamic library, the default is .so
  • allow_undefined: bool = False, whether undefined symbols are allowed when linking. Because many plug-in libraries depend on the symbol names provided by the host process at runtime, the definition of these symbols does not exist in the link phase.
  • strip: bool = False, whether to remove the debugging symbol information. If enabled, the size of the generated library can be reduced, but symbolic debugging cannot be performed.
  • linker_scripts: list(string), uses linker scripts. The linker script is a script used to control the linking process. Its role is mainly to specify how to put the sections in the input file into the output file and to control the layout of the sections in the input file in the program address space. The linker has a default built-in linking script, which can be viewed with ld --verbose. This option will replace the system's default linking script. The linker script files usually have the extension .ld or .lds. Linker scripts are usually quite complex, so if you just want to control the version or visibility of the symbols, use the version_scripts option below.
  • version_scripts: list(string), using linker "version" script. The linker version script is used to control the version and visibility of the symbol, if no version id is specified, it only to control the visibility of the symbol. Linker version script files usually have the extension exp, sym, .ver or .map.

prefix and suffix control the file name of the generated dynamic library, assuming name='file', the default generated library is libfile.so, set prefix='', then it becomes file.so.

Controlling the visibility of symbols in dynamic libraries

To control the external visibility of symbols in the linking result, you can use GCC extended attributes in the source code or command line options.

With the linker version file, it is possible to control or override the visibility settings in the source code when linking,eg:

{
global:  # Globally visible
    # Support wildcards
    Name1;
    _Name2*;
    extern "C++" {  # C++ symbols
        # Quoted strings do not match wildcards
        "a()";
        "a(int)";
        "operator<<(std::ostream&, B const&)";

        # Unquoted strings match wildcards
        B::*;
        typeinfo?for?magic::*;
        vtable?for?magic::*;"
    };
local:  # The rest are local symbols, not visible
    *;
};

To see the visibility of symbols in the library, you can use the nm command.

000000000000010c t _init
                 U puts@@GLIBC_2.2.5
00000000000000000060 t register_tm_clones
00000000000000f0 T hello
00000000000000000100 t world

The second column is the symbol type. If lowercase, the symbol is usually local; if uppercase, the symbol is global (external). U is the undefined external symbols that the library depends on, don't care.

cc_plugin is mainly used to create various extensions, such as JNI, python extension and other dynamic libraries that are dynamically loaded by calling certain functions during runtime. It will be ignored when linking even if it appears in the deps of other cc targets.

resource_library

Compile static data file to be resource, which can be accessed in the program directly.

When we deploy a program, it often requires many other files to be deployed together. it's often boring. Blade support resource_library, make it quite easy to put resource files into the executable easily. For example, put there static pages into a library:

resource_library(
    name = 'static_resource',
    srcs = [
        'static/favicon.ico',
        'static/forms.html',
        'static/forms.js',
        'static/jquery-1.4.2.min.js',
        'static/jquery.json-2.2.min.js',
        'static/methods.html',
        'static/poppy.html'
    ]
)

It will generate a library libstatic_resource.a,and a header file static_resource.h(with full include path)。

When you need tp use it, you need to include static_resource.h(with full path from workspace root) and also "common/base/static_resource.h",

Use STATIC_RESOURCE macro to simplify access to the data (defined in "common/base/static_resource.h"):

StringPiece data = STATIC_RESOURCE(poppy_static_favicon_ico);

The argument of STATIC_RESOURCE is the full path of the data file, and replace all of the non-alphanum and hyphen character to unserscore(_):

The data is readonly static storage. can be accessed in any time.

NOTE: There is a little drawback for static resource, it can;t be updated at the runtime, so consider it before using.

cu_library

build a C++ library target containing CUDA device code using nvcc.

Rules are similar to cc_library .

The attributes cuda_path and extra_cuflags are added.
cuda_path is pointed to cuda installed path in the local repository with prefix //, with that nvcc binary {cuda_path}/bin/nvcc and include search path {cuda_path}/include can be found automatically. Environment variable below will be ignored if use cuda_path.
extra_cuflags support some flags which only work for cuda.

Environment variable NVCC is pointed to the path of nvcc binary,such as NVCC=/usr/local/cuda/bin/nvcc blade build.
Environment variable CUDA_PATH is pointed to the installed path of cuda, with which {CUDA_PATH}/include and {CUDA_PATH}/samples/common/inc will be added to include search path.

Priority: cuda_path > NVCC/CUDA_PATH.

Example:

cu_library(
    name = 'template_gpu',
    srcs = ['template_gpu.cu'],
    hdrs = [],
    # cuda_path = '//thirdparty/cuda',
)

cu_binary

build a C++ executable target containing CUDA device code using nvcc.

Rules are similar to cc_binary . Use environment variables reference to cu_library.

Example:

cu_binary(
    name = 'template',
    srcs = ['template.cu'],
    deps = [':template_cpu'],
    # cuda_path = '//thirdparty/cuda',
)

cu_test

build a C++ ut target containing CUDA device code using nvcc.

Rules are similar to cc_test . Use environment variables reference to cu_library.

Example:

cu_test(
    name = 'cu_test',
    srcs = ['cu_test.cu'],
    deps = [':template_cpu'],
    # cuda_path = '//thirdparty/cuda',
)