1# Copyright 2019 The Chromium Authors. All rights reserved.
2
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import("//build/config/clang/clang.gni")
7
8# This template creates a set of shared libraries, by linking a single
9# "partitioned" shared library, then splitting it into multiple pieces.
10# The intention is to facilitate code-splitting between a base library and
11# additional feature-specific libraries that may be obtained and loaded at a
12# later time.
13#
14# The combined library is an intermediate product made by leveraging the LLVM
15# toolchain.  Code modules may be labeled via compiler flag as belonging to a
16# particular partition.  At link time, any symbols reachable by only a single
17# partition's entrypoints will be located in a partition-specific library
18# segment.  After linking, the segments are split apart using objcopy into
19# separate libraries.  The main library is then packaged with the application
20# as usual, while feature libraries may be packaged, delivered and loaded
21# separately (via an Android Dynamic Feature Module).
22#
23# When loading a feature library, the intended address of the library must be
24# supplied to the loader, so that it can be mapped to the memory location.  The
25# address offsets of the feature libraries are stored in the base library and
26# accessed through special symbols named according to the partitions.
27#
28# The template instantiates targets for the base library, as well as each
29# specified partition, based on the root target name.  Example:
30#
31#   - libmonochrome            (base library)
32#   - libmonochrome_foo        (partition library for feature 'foo')
33#   - libmonochrome_bar        (partition library for feature 'bar')
34#
35# Note that the feature library filenames are chosen based on the main
36# library's name (eg. libmonochrome_foo.so), but the soname of the feature
37# library is based on the feature name (eg. "foo").  This should generally be
38# okay, with the caveat that loading the library multiple times *might* cause
39# problems in Android.
40#
41# This template uses shared_library's default configurations.
42#
43# Variables:
44#   partitions: A list of library partition names to extract, in addition to
45#     the base library.
46
47template("partitioned_shared_library") {
48  assert(is_clang)
49  forward_variables_from(invoker, [ "testonly" ])
50
51  _combined_library_target = "${target_name}__combined"
52
53  # Strip "lib" from target names; it will be re-added to output libraries.
54  _output_name = string_replace(target_name, "lib", "")
55
56  shared_library(_combined_library_target) {
57    forward_variables_from(invoker, "*", [ "partitions" ])
58    if (!defined(ldflags)) {
59      ldflags = []
60    }
61    ldflags += [
62      "-Wl,-soname,lib${_output_name}.so",
63      "--link-only",
64    ]
65
66    # This shared library is an intermediate artifact that should not packaged
67    # into the final build. Therefore, reset metadata.
68    metadata = {
69    }
70  }
71
72  template("partition_action") {
73    action(target_name) {
74      deps = [ ":$_combined_library_target" ]
75      script = "//build/extract_partition.py"
76      sources =
77          [ "$root_out_dir/lib.unstripped/lib${_output_name}__combined.so" ]
78      outputs = [
79        invoker.unstripped_output,
80        invoker.stripped_output,
81      ]
82      data = [ invoker.unstripped_output ]
83      metadata = {
84        shared_libraries = [ invoker.stripped_output ]
85      }
86      args = [
87        "--objcopy",
88        rebase_path("$clang_base_path/bin/llvm-objcopy", root_build_dir),
89        "--unstripped-output",
90        rebase_path(invoker.unstripped_output, root_build_dir),
91        "--stripped-output",
92        rebase_path(invoker.stripped_output, root_build_dir),
93      ]
94      if (defined(invoker.partition) && invoker.partition != "") {
95        args += [
96          "--partition",
97          "${invoker.partition}",
98        ]
99      }
100      args += [ rebase_path(sources[0], root_build_dir) ]
101    }
102  }
103
104  partition_action(target_name) {
105    stripped_output = "$root_out_dir/lib${_output_name}.so"
106    unstripped_output = "$root_out_dir/lib.unstripped/lib${_output_name}.so"
107  }
108
109  # Note that as of now, non-base partition libraries are placed in a
110  # subdirectory of the root output directory.  This is because partition
111  # sonames are not sensitive to the filename of the base library, and as such,
112  # their corresponding file names may be generated multiple times by different
113  # base libraries.  To avoid collisions, each base library target has a
114  # corresponding subdir for its extra partitions.
115  #
116  # If this proves problematic to various pieces of infrastructure, a proposed
117  # alternative is allowing the linker to rename partitions.  For example,
118  # feature "foo" may be a partition.  If two different base libraries both
119  # define "foo" partitions, the linker may be made to accept an extra command
120  # to rename the partition's soname to "foo1" or "foo2".  Other build config
121  # can name the libraries foo1.so and foo2.so, allowing them to reside in the
122  # same directory.
123  foreach(_partition, invoker.partitions) {
124    partition_action("${target_name}_${_partition}") {
125      partition = "${_partition}_partition"
126      stripped_output = "$root_out_dir/lib${_output_name}_${partition}.so"
127      unstripped_output =
128          "$root_out_dir/lib.unstripped/lib${_output_name}_${partition}.so"
129    }
130  }
131}
132
133set_defaults("partitioned_shared_library") {
134  configs = default_shared_library_configs
135}
136