How to handle cross-platform shared libraries

1,158 views
Skip to first unread message

Andreas Hall

unread,
May 5, 2019, 12:05:40 PM5/5/19
to bazel-...@googlegroups.com
I have finally managed to get some kind of cross-platform shared libraries working in Bazel. I would like to share this, and also get a discussion started on how to handle it in a more uniform way.

I started out with meteorcloudy's example on how to create a Windows DLL, and added a linux .so archive in parallel. Now some problems occured:

  1. I would like bazel build //... to work, so I needed a way to ignore the non-working windows items for linux, and vice versa.
  2. The following section creates an cc_import library from the created shared library, however, it is NOT sertain that the cc_binary only produces one output (all depending dll's will also be transitive dependencies, shown in outputs)
native.cc_import(
    name = import_target_name,
    interface_library = ":" + import_lib_name,
    shared_library = ":" + dll_name,
)

Andreas Hall

unread,
May 5, 2019, 12:22:07 PM5/5/19
to bazel-...@googlegroups.com
3. I use a mingw toolchain for windows, and mingw does not necessarily require an interface library to import a dll. The requirement that all shared_libraries in a cc_import must have an interface_library on windows (activated by targets_windows feature), instead becomes an obstacle.

To mend problem 1., i used the "manual" tag, and use a cc_library in the end that depends on the linux or windows shared library chain.

To fix problem 2, i had to create a rule that can select one output from many.

And to fix problem 3. I use mingw:s dlltool.exe to create an interface library for me.

However, the function becomes quite complicated:

# A function that selects one output from many
load(":select_files.bzl", "select_files")
# A function that creates a .def file from a list of inputs.
load(":win_def_file.bzl", "win_def_file")
# A function that creates a MinGW interface library.
load("interface_library.bzl", "interface_library")

# This is a simple cc_shared_library rule for building a DLL Windows
# and building a .so for unix, that can be depended on by other cc rules.
#
# Example usage:
#   cc_shared_library(
#       name = "hellolib",
#       srcs = [
#           "hello-library.cpp",
#       ],
#       hdrs = ["hello-library.h"],
#       # Define COMPILING_DLL to export symbols during compiling the DLL.
#       copts = ["/DCOMPILING_DLL"],
#   )
#
def cc_shared_library(
        name,
        srcs = [],
        hdrs = [],
        exports = [],
        visibility = None,
        **kwargs):
    so_name = name + ".so"
    dll_name = name + ".dll"
    exports_name = name + "_exports"
    import_lib_name_msvc = name + "_import_lib_msvc"
    import_lib_name_mingw = name + "_import_lib_mingw"
    import_dll_target_name = name + "_dll_import"
    import_so_target_name = name + "_so_import"
    select_dll_name = name + "_dll_select"

    ### Windows ###
    # Create a def file for exports.
    win_def_file(
        name = exports_name,
        exports = exports,
        tags = ["manual"],
    )

    # Build the shared library
    native.cc_binary(
        name = dll_name,
        srcs = srcs + hdrs,
        linkshared = 1,
        win_def_file = ":" + exports_name,
        tags = ["manual"],
        **kwargs
    )
    
    # Get the import library for the dll (always built for MSVC)
    native.filegroup(
        name = import_lib_name_msvc,
        srcs = [":" + dll_name],
        output_group = "interface_library",
        tags = ["manual"],
    )

    # Create the interface library for mingw
    # (Of course I could build this in my toolchain instead).
    interface_library(
        name = import_lib_name_mingw,
        def_file = ":" + exports_name,
        tags = ["manual"],
    )
    
    # Select the DLL (might be transitive DLL:s added)
    # If I try to select the DLL name, I still get the rule.
    select_files(
        name = select_dll_name,
        srcs = [":" + dll_name],
        files = [dll_name],
        tags = ["manual"],
    )

    # Because we cannot directly depend on cc_binary from other cc rules in deps attribute,
    # we use cc_import as a bridge to depend on the dll (windows).
    native.cc_import(
        name = import_dll_target_name,
        interface_library = select({
            ":mingw": ":" + import_lib_name_mingw,
            ":msvc": ":" + import_lib_name_msvc,
        }),
        shared_library = ":" + select_dll_name,
        tags = ["manual"],
    )

    ### Linux ###
    # Linux case, Build the shared library.
    native.cc_binary(
        name = so_name,
        srcs = srcs + hdrs,
        linkshared = 1,
        tags = ["manual"],
        **kwargs
    )

    # Linux case, just import the shared_library.
    native.cc_import(
        name = import_so_target_name,
        shared_library = ":" + so_name,
        tags = ["manual"],
    )

    ### Combined ###
    # Create a new cc_library to also include the headers needed for the shared library
    native.cc_library(
        name = name,
        hdrs = hdrs,
        visibility = visibility,
        deps = select({
            ":win_x86_32": [":" + import_dll_target_name],
            ":win_x86_64": [":" + import_dll_target_name],
            "//conditions:default": [":" + import_so_target_name],
        }),
    )

That's all my thoughts :) I think this could be made simpler.. or I have missed some nice features, which can simplify the example.

Greg Estren

unread,
May 6, 2019, 5:26:29 PM5/6/19
to Andreas Hall, bazel-discuss
Hi Andreas,

I'm not sure about problem 3 offhand (that is a lot of code). But for problem 1 check out https://github.com/bazelbuild/bazel/issues/3780. There aren't ideal options today (I could imagine making each target an alias that, when built on the right platform refers to the actual rule via a select() and when for the wrong platform refers to a trivial rule that outputs something innocent). Whenever #3780 gets prioritized that should provide something cleaner.

--
You received this message because you are subscribed to the Google Groups "bazel-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bazel-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/bazel-discuss/CAC1zJ3CEq0e%2Bzb-PXtPT9wy9%3D%3D6E2r2%2BEV45HAvMfTq7hO70JQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Andreas Hall

unread,
May 7, 2019, 5:55:06 PM5/7/19
to Greg Estren, bazel-discuss
That sounds about right, but I am not sure I want to tag every item in tree, with possibly multiple platforms, and then to be forced to retag them when a new platform requirement occurs. (However, I’m not hard to convince)

I tried to use the environment and environment_groups, connected to the restricted_to field. I thought it would do something similar, but it never removed any items.

The manual tag, I thought was a quite elegant way to keep the rule-paths in the tree, but be able to ignore e.g. the rule that lacks the interface_library on windows.

A way to control the behavior of the tags, might be a solution as well.

/andreas
Reply all
Reply to author
Forward
0 new messages