├── .buckconfig ├── .gitignore ├── LICENSE ├── README.md ├── apps ├── BUCK ├── animals-app-manifest.xml ├── debug.keystore ├── debug.keystore.properties └── mammals-app-manifest.xml ├── bucklets └── DEFS ├── images └── dep-graph.png ├── scripts └── analyze-apk.sh └── src └── com └── facebook ├── example ├── animals │ ├── AnimalsActivity.java │ ├── BUCK │ ├── Penguin.java │ ├── jni │ │ ├── BUCK │ │ └── animals.c │ └── manifest.xml ├── habitat │ ├── BUCK │ ├── Ice.java │ └── jni │ │ ├── BUCK │ │ └── habitat.c └── mammals │ ├── BUCK │ ├── SeaLion.java │ ├── activity │ ├── BUCK │ ├── MammalsActivity.java │ └── manifest.xml │ └── jni │ ├── BUCK │ └── mammals.c ├── jnimerge ├── BUCK ├── jni_lib_merge.c ├── jni_lib_merge.h └── map_code_generator.py └── soloader ├── BUCK ├── MergedSoMapping.java └── SoLoader.java /.buckconfig: -------------------------------------------------------------------------------- 1 | [java] 2 | src_roots = src 3 | [android] 4 | target = android-25 5 | build_tools_version = 26.0.2 6 | [ndk] 7 | ndk_version = 15.2.4203891 8 | gcc_version = 4.9 9 | app_platform = android-15 10 | cpu_abis = armv7, x86 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /buck-out 2 | /.buckd 3 | /.idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android native library merging 2 | 3 | ## The Problem 4 | 5 | Android developers who use lots of C++ code 6 | might be familiar with the [native library limit][the_limit] 7 | that exists in Android versions prior to 4.3. 8 | When targeting older Android versions, 9 | one must carefully manage the number of libraries in their app 10 | to avoid hitting this limit. 11 | This is especially tricky because 12 | libraries loaded by the system count against this limit, 13 | and the number of those libraries can vary between devices. 14 | 15 | [the_limit]: https://android.googlesource.com/platform/bionic/+/ba98d9237b0eabc1d8caf2600fd787b988645249%5E%21/ 16 | 17 | One solution is to 18 | manually combine multiple small libraries into one larger one. 19 | However, this usually is not a scalable solution. 20 | Combining libraries requires moving source code around 21 | and carefully managing compilation settings and dependencies. 22 | It can also make code less modular, 23 | which can be problematic 24 | if your organization is building multiple apps from a shared codebase. 25 | 26 | ## Our Solution 27 | 28 | We have developed a more scalable solution to this problem 29 | and applied it to most of our Android apps, 30 | including Facebook, 31 | Instagram, 32 | Messenger, 33 | Messenger Lite, 34 | Moments, 35 | and Pages Manager. 36 | This solution not only allows us to 37 | avoid the native library limit on older Android devices, 38 | but it does so without harming performance or increasing app size. 39 | 40 | ### Merging Objects with Per-App Configuration 41 | 42 | The first part of the solution is to 43 | combine multiple native libraries. 44 | Because merging shared objects (`.so` files) is impractical, 45 | we needed to change the way we link our libraries, 46 | which forced us to integrate this feature into our build system 47 | [Buck](https://buckbuild.com/). 48 | The [feature](https://github.com/facebook/buck/commit/7b7bc87e) 49 | allows each application to specify which libraries should be merged 50 | so they can avoid accidentally bringing in unnecessary dependencies. 51 | Buck then takes care of 52 | collecting all the objects (`.o` files) for each merged library 53 | and linking them together with the proper dependencies. 54 | 55 | This works great, as long as 56 | there are no common symbols between the libraries being merged. 57 | For example, pure C++ libraries rarely duplicate symbols. 58 | However, on Android, many of our libraries use JNI, 59 | which means they have exactly one symbol that is duplicated: `JNI_OnLoad`. 60 | This is the entry point for JNI setup in the library, 61 | and almost all of our libraries define it. 62 | 63 | Since we have issues with only this one duplicate symbol, 64 | we handle it in a special way. 65 | 66 | ### Merging JNI_OnLoad 67 | 68 | The first step in eliminating the symbol conflict is to 69 | make each library rename its `JNI_OnLoad` function. 70 | This is easy enough with the C preprocessor. 71 | Next, we need a way to find all of those renamed functions 72 | so we can call them at the appropriate time. 73 | We accomplish this with custom ELF sections. 74 | (There's a good description on [this blog][custom_elf].) 75 | In short, each JNI library to be merged defines a small registration object 76 | that includes the library name and a pointer to its `JNI_OnLoad`. 77 | Then the linker will automatically concatenate them into an array 78 | and define a pair of symbols that we can use to find them. 79 | 80 | [custom_elf]: http://mgalgs.github.io/2013/05/10/hacking-your-ELF-for-fun-and-profit.html 81 | 82 | Once all the `JNI_OnLoad` function pointers are 83 | paired with their library names and placed in an array, 84 | our glue code can find and call them 85 | when we try to load the original libraries. 86 | 87 | ### Loading the libraries 88 | 89 | We want our Java code to continue loading libraries by their original names, 90 | because different libraries might be merged in different apps. 91 | Therefore, we need to wrap all of our `System.loadLibrary` calls 92 | with a method that knows how to 93 | map the original library names to the merged names. 94 | Fortunately, our apps already use 95 | [SoLoader](https://github.com/facebook/SoLoader), 96 | so all we had to do was generate some code 97 | to let SoLoader look up the proper merged library names. 98 | When it first loads a merged native library, 99 | we call a custom `JNI_OnLoad` that 100 | registers each of the original `JNI_OnLoad` function pointers 101 | as normal JNI methods. 102 | Then, `SoLoader` is able to call those methods 103 | only when the original library is loaded. 104 | This prevents us from loading classes earlier than we should. 105 | 106 | ### Extra challenges 107 | 108 | When implementing this native library merging strategy, 109 | we ran into a few unexpected problems. 110 | 111 | Some libraries might be merged in one app but not in another. 112 | In theory, this should be fine: 113 | We define `JNI_OnLoad` as a weak symbol 114 | that is replaced by our special merge-aware `JNI_OnLoad` 115 | only if we end up merging that library. 116 | However, older versions of Android 117 | will refuse to return any weak symbol when calling `dlopen`. 118 | We got around this by changing the name to `JNI_OnLoad_Weak`, 119 | then using linker flags to define `JNI_OnLoad` as a strong alias 120 | for whichever `JNI_OnLoad_Weak` ends up being used. 121 | 122 | When using custom ELF sections, the `gold` linker always 123 | outputs the special `__start` and `__end` symbols as 124 | [global symbols][gold_global]. 125 | This is not a problem when producing a single merged library, 126 | but when there are multiple libraries, 127 | they can end up pointing to each other's custom sections, 128 | which breaks registration. 129 | Declaring the references to these symbols as hidden 130 | takes precedence over the linker-generaged global symbols, 131 | so the symbols become hidden and the problem goes away. 132 | 133 | [gold_global]: https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gold/layout.cc;hb=refs/tags/binutils-2_29_1.1#l2186 134 | 135 | ## Putting it all together 136 | 137 | We have published a repository that serves as a demonstration of these techniques. 138 | First, take a look at 139 | [`refs/heads/initial-code`](https://github.com/fbsamples/android-native-library-merging-demo/commit/initial-code). 140 | This is a small codebase showing two apps that have some shared code. 141 | 142 | ![dependency graph](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/images/dep-graph.png) 143 | 144 | [`refs/heads/add-jni`](https://github.com/fbsamples/android-native-library-merging-demo/commit/add-jni) 145 | replaces some of the Java code 146 | (really just some string constants) 147 | with JNI. 148 | Now each of the apps has a few native libraries in it. 149 | 150 | [`refs/heads/merge-libraries`](https://github.com/fbsamples/android-native-library-merging-demo/commit/merge-libraries) 151 | is where we turn everything on. 152 | Let's walk through it file by file. 153 | 154 | [`bucklets/DEFS`](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/bucklets/DEFS) 155 | defines some wrappers for our build rules. 156 | This is Buck's main mechanism for extension. 157 | It allows expanding one declared rule into multiple physical rules, 158 | or (in our case) modifying arguments to a rule. 159 | We define two wrappers. 160 | The `my_android_binary` wrapper 161 | automatically applies our project-specific configuration 162 | (glue library, code generator, and symbols to make local) 163 | whenever a merge map is present. 164 | The `my_cxx_library` wrapper adds the `allow_jni_merging` flag 165 | as a shorthand for the tweaks we need to make to `JNI_OnLoad`. 166 | Note that these tweaks are safe 167 | even if the library won't be merged in all apps. 168 | 169 | The changes to the existing code are fairly simple. 170 | We just apply `allow_jni_merging` to our C++ 171 | and change `System.loadLibrary` to `SoLoader.loadLibrary`. 172 | 173 | [`src/com/facebook/soloader`](https://github.com/fbsamples/android-native-library-merging-demo/tree/master/src/com/facebook/soloader) 174 | is a simplified version of `SoLoader` 175 | that has support only for remapping merged library names. 176 | It also includes `MergedSoMapping`, 177 | a compilation stub 178 | that can also be used in apps that don't use merging. 179 | 180 | [`map_code_generator.py`](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/src/com/facebook/jnimerge/map_code_generator.py) 181 | is the code generator that will 182 | convert the text version of the merged library map 183 | into Java code we can use to load the libraries at runtime. 184 | It produces a method, `mapLibName`, 185 | that can report which libraries were merged. 186 | For example, `mapLibName("libanimals.so")` will return `"libeverything.so"`. 187 | It produces a nested class, `Invoke_JNI_OnLoad`, 188 | with a number of native methods. 189 | Our glue code will bind each one of these to 190 | one of the original (pre-merged) `JNI_OnLoad` function pointers. 191 | Finally, it produces `invokeJniOnload`, 192 | which can invoke the proper `JNI_OnLoad` for a given library, by name. 193 | The generated code is used only by SoLoader. 194 | 195 | [`jni_lib_merge.h`](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/src/com/facebook/jnimerge/jni_lib_merge.h) 196 | is included in our C++ files automatically 197 | (because we added `allow_jni_merging`). 198 | It uses the C preprocessor to wrap `JNI_OnLoad`, 199 | create the registration object in our custom section, 200 | and make sure our library can be loaded cleanly, regardless of 201 | whether merging is actually enabled. 202 | This requires a few tricks. 203 | See the comments in that file for details. 204 | 205 | [`jni_lib_merge.c`](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/src/com/facebook/jnimerge/jni_lib_merge.c) 206 | is our glue library, 207 | which will automatically be included in every merged library. 208 | It's responsible for defining the real `JNI_OnLoad`. 209 | When the merged library is loaded, 210 | it collects all of the function pointers 211 | for the wrappers of the original `JNI_OnLoad` functions 212 | and registers them with `Invoke_JNI_OnLoad` 213 | so they can be called at the appropriate time. 214 | 215 | Finally, 216 | [`apps/BUCK`](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/src/com/facebook/jnimerge/BUCK) 217 | defines the merge map for each app. 218 | In this case, we're merging all libraries into a single `libeverything`. 219 | However, it's also possible to merge different subsets of libraries together: 220 | for example, one library for everything that's related during app startup 221 | and another for everything that's needed for camera effects. 222 | 223 | Once these changes are made, we can run 224 | `buck install //apps:animals` 225 | and see that the resulting APK has only `libeverything.so`, 226 | but all the functionality from the original libraries remains. 227 | 228 | ## Scaling 229 | 230 | Having a mechanism for merging native libraries is great, 231 | but we also need a policy for determining what to merge. 232 | This requires some understanding of the structure of the app. 233 | The 234 | [`scripts/analyze-apk.sh`](https://github.com/fbsamples/android-native-library-merging-demo/blob/master/scripts/analyze-apk.sh) 235 | script can help with that process. 236 | When run on an APK, 237 | it generates an image that shows all native libraries in the app, 238 | draws edges between them to represent their dependencies, 239 | and colors the ones with `JNI_OnLoad` 240 | so you know which ones need `allow_jni_merging`. 241 | Sometimes, a commonly used library will create too many edges to see clearly. 242 | In these cases, 243 | they can be filtered out by editing the `grep` command and rerunning. 244 | 245 | Using this graph, we can make some decisions about which libraries to merge. 246 | One good choice is to merge all libraries used during app startup. 247 | Since they have to be loaded anyway, we might as well load them all together. 248 | A cluster of libraries with similar names might be another good candidate. 249 | Frequently, it makes sense to merge a library with all of its dependencies, 250 | though that can be a mistake if the dependencies are often used on their own. 251 | A rule of thumb is that you want to combine as many libraries as possible 252 | while minimizing the amount of code that is loaded unnecessarily. 253 | 254 | One minor issue that pops up when writing the merge map 255 | is that you need to specify it as patterns of build target names. 256 | We generate a file at 257 | `buck-out/path/to/app#generate_native_lib_merge_map_generated_code/shared_object_targets.txt`, 258 | which will show the build target that generated each native library. 259 | 260 | ## Wrapping up 261 | 262 | Implementing native library merging took many steps, 263 | but the end result is that 264 | native library developers can 265 | declare their libraries and dependencies as they please 266 | without being aware of it. 267 | Each app in our codebase can declare its own `native_library_merge_map` 268 | to transparently merge libraries based on its own usage patterns. 269 | This makes it easy for us to push back the native library limit 270 | and let our linker do better inter-library optimizations. 271 | -------------------------------------------------------------------------------- /apps/BUCK: -------------------------------------------------------------------------------- 1 | include_defs("//bucklets/DEFS") 2 | 3 | my_android_binary( 4 | name = "mammals", 5 | manifest_skeleton = "mammals-app-manifest.xml", 6 | keystore = ":debug_keystore", 7 | native_library_merge_map = { 8 | "libeverything.so": [".*"], 9 | }, 10 | deps = [ 11 | "//src/com/facebook/example/mammals/activity:activity", 12 | ], 13 | ) 14 | 15 | my_android_binary( 16 | name = "animals", 17 | manifest_skeleton = "animals-app-manifest.xml", 18 | keystore = ":debug_keystore", 19 | native_library_merge_map = { 20 | "libeverything.so": [".*"], 21 | }, 22 | deps = [ 23 | "//src/com/facebook/example/animals:animals", 24 | ], 25 | ) 26 | 27 | keystore( 28 | name = "debug_keystore", 29 | properties = "debug.keystore.properties", 30 | store = "debug.keystore", 31 | ) 32 | -------------------------------------------------------------------------------- /apps/animals-app-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbsamples/android-native-library-merging-demo/aec6f361050f21916e95679aada0f4081aafe655/apps/debug.keystore -------------------------------------------------------------------------------- /apps/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.alias=android 2 | key.store.password=android 3 | key.alias.password=android 4 | -------------------------------------------------------------------------------- /apps/mammals-app-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /bucklets/DEFS: -------------------------------------------------------------------------------- 1 | def my_android_binary(**kwargs): 2 | if "native_library_merge_map" in kwargs: 3 | kwargs["native_library_merge_glue"] = "//src/com/facebook/jnimerge:glue" 4 | kwargs["native_library_merge_code_generator"] = "//src/com/facebook/jnimerge:code-generator" 5 | else: 6 | kwargs["deps"] = kwargs["deps"] + ["//src/com/facebook/soloader:mapping-stub"] 7 | android_binary(**kwargs) 8 | 9 | 10 | def my_cxx_library(**kwargs): 11 | if kwargs.pop("allow_jni_merging", None): 12 | if "soname" not in kwargs: 13 | raise Exception( 14 | "JNI merging currently requires an explicit soname, " 15 | "but %s doesn't have one." 16 | % kwargs["name"]) 17 | kwargs["deps"] = kwargs.get("deps", []) + ["//src/com/facebook/jnimerge:jnimerge"] 18 | kwargs["platform_preprocessor_flags"] = \ 19 | kwargs.get("platform_preprocessor_flags", []) + [ 20 | (r'.*\bandroid\b.*', [ 21 | '-DORIGINAL_SONAME="%s"' % kwargs["soname"].replace("$(ext)", "so"), 22 | "-include", "jni_lib_merge.h", 23 | ])] 24 | kwargs["platform_linker_flags"] = kwargs.get("platform_linker_flags", []) + [ 25 | (r'.*\bandroid\b.*', [ 26 | "-Wl,--defsym=JNI_OnLoad=JNI_OnLoad_Weak", 27 | ])] 28 | cxx_library(**kwargs) 29 | 30 | -------------------------------------------------------------------------------- /images/dep-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbsamples/android-native-library-merging-demo/aec6f361050f21916e95679aada0f4081aafe655/images/dep-graph.png -------------------------------------------------------------------------------- /scripts/analyze-apk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | ARCH=armeabi-v7a 5 | 6 | WORKDIR=`mktemp -d` 7 | trap "rm -rf $WORKDIR" EXIT HUP INT TERM 8 | 9 | APK=`realpath $1` 10 | 11 | cd $WORKDIR 12 | unzip -q $APK "lib/$ARCH/*" 13 | cd lib/$ARCH 14 | 15 | ( 16 | set +e 17 | echo 'digraph G {' 18 | for OBJ in *.so ; do 19 | objdump -p $OBJ | awk 'BEGIN{obj="'$OBJ'"};/NEEDED/{print "\"" obj "\" -> \"" $2 "\";"}' | grep -f <(ls *.so | sed 's/^/> "/') 20 | objdump -T $OBJ | grep -q JNI_OnLoad && echo \"$OBJ\"' [fillcolor = pink, style = filled];' 21 | done 22 | echo '}' 23 | ) | sed 's/"lib/"/g;s/\.so"/"/g' | grep -v -e 'regexes-to-ignore' | dot -Tpng > library-graph.png 24 | -------------------------------------------------------------------------------- /src/com/facebook/example/animals/AnimalsActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.example.animals; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import android.widget.TextView; 22 | import com.facebook.example.mammals.SeaLion; 23 | 24 | public class AnimalsActivity extends Activity { 25 | @Override 26 | public void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | 29 | String text = 30 | "Mammal: " + SeaLion.getDescription() + "\n" + 31 | "Bird: " + Penguin.getDescription(); 32 | 33 | TextView view = new TextView(this); 34 | setContentView(view); 35 | view.setText(text); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/facebook/example/animals/BUCK: -------------------------------------------------------------------------------- 1 | android_library( 2 | name = "animals", 3 | manifest = "manifest.xml", 4 | srcs = glob(["*.java"]), 5 | deps = [ 6 | "//src/com/facebook/example/animals/jni:jni", 7 | "//src/com/facebook/example/habitat:habitat", 8 | "//src/com/facebook/example/mammals:mammals", 9 | "//src/com/facebook/soloader:soloader", 10 | ], 11 | visibility = ["PUBLIC"], 12 | ) 13 | -------------------------------------------------------------------------------- /src/com/facebook/example/animals/Penguin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.example.animals; 18 | 19 | import com.facebook.example.habitat.Ice; 20 | import com.facebook.soloader.SoLoader; 21 | 22 | public class Penguin { 23 | static { 24 | SoLoader.loadLibrary("animals"); 25 | } 26 | 27 | public static String getDescription() { 28 | return getName() + " on " + Ice.getName(); 29 | } 30 | 31 | private static native String getName(); 32 | } 33 | -------------------------------------------------------------------------------- /src/com/facebook/example/animals/jni/BUCK: -------------------------------------------------------------------------------- 1 | include_defs("//bucklets/DEFS") 2 | 3 | my_cxx_library( 4 | name = "jni", 5 | soname = "libanimals.$(ext)", 6 | allow_jni_merging = True, 7 | srcs = glob(["*.c"]), 8 | visibility = [ 9 | "//src/com/facebook/example/animals:animals", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/facebook/example/animals/jni/animals.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | static jstring nativeGetName(JNIEnv* env, jclass cls) { 21 | return (*env)->NewStringUTF(env, "penguin"); 22 | } 23 | 24 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { 25 | JNIEnv* env; 26 | if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6)) { 27 | return JNI_ERR; 28 | } 29 | 30 | jclass penguinClass = (*env)->FindClass(env, "com/facebook/example/animals/Penguin"); 31 | if (penguinClass == NULL) { 32 | return JNI_ERR; 33 | } 34 | JNINativeMethod methods[] = { 35 | {"getName", "()Ljava/lang/String;", nativeGetName}, 36 | }; 37 | size_t nMethods = sizeof(methods) / sizeof(methods[0]); 38 | jint result = (*env)->RegisterNatives(env, penguinClass, methods, nMethods); 39 | if (result != 0) { 40 | return JNI_ERR; 41 | } 42 | 43 | return JNI_VERSION_1_6; 44 | } 45 | -------------------------------------------------------------------------------- /src/com/facebook/example/animals/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/com/facebook/example/habitat/BUCK: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "habitat", 3 | srcs = glob(["*.java"]), 4 | deps = [ 5 | "//src/com/facebook/example/habitat/jni:jni", 6 | "//src/com/facebook/soloader:soloader", 7 | ], 8 | visibility = ["PUBLIC"], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/facebook/example/habitat/Ice.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.example.habitat; 18 | 19 | import com.facebook.soloader.SoLoader; 20 | 21 | public class Ice { 22 | static { 23 | SoLoader.loadLibrary("habitat"); 24 | } 25 | 26 | private static int dxWorkaround = 0; 27 | 28 | public static native String getName(); 29 | } 30 | -------------------------------------------------------------------------------- /src/com/facebook/example/habitat/jni/BUCK: -------------------------------------------------------------------------------- 1 | include_defs("//bucklets/DEFS") 2 | 3 | my_cxx_library( 4 | name = "jni", 5 | soname = "libhabitat.$(ext)", 6 | allow_jni_merging = True, 7 | srcs = glob(["*.c"]), 8 | visibility = [ 9 | "//src/com/facebook/example/habitat:habitat", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/facebook/example/habitat/jni/habitat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | static jstring nativeGetName(JNIEnv* env, jclass cls) { 21 | return (*env)->NewStringUTF(env, "ice"); 22 | } 23 | 24 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { 25 | JNIEnv* env; 26 | if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6)) { 27 | return JNI_ERR; 28 | } 29 | 30 | jclass iceClass = (*env)->FindClass(env, "com/facebook/example/habitat/Ice"); 31 | if (iceClass == NULL) { 32 | return JNI_ERR; 33 | } 34 | JNINativeMethod methods[] = { 35 | {"getName", "()Ljava/lang/String;", nativeGetName}, 36 | }; 37 | size_t nMethods = sizeof(methods) / sizeof(methods[0]); 38 | jint result = (*env)->RegisterNatives(env, iceClass, methods, nMethods); 39 | if (result != 0) { 40 | return JNI_ERR; 41 | } 42 | 43 | return JNI_VERSION_1_6; 44 | } 45 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/BUCK: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "mammals", 3 | srcs = glob(["*.java"]), 4 | deps = [ 5 | "//src/com/facebook/example/mammals/jni:jni", 6 | "//src/com/facebook/soloader:soloader", 7 | ], 8 | visibility = ["PUBLIC"], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/SeaLion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.example.mammals; 18 | 19 | import com.facebook.soloader.SoLoader; 20 | 21 | public class SeaLion { 22 | static { 23 | SoLoader.loadLibrary("mammals"); 24 | } 25 | 26 | public static String getDescription() { 27 | return "just a " + getName(); 28 | } 29 | 30 | private static native String getName(); 31 | } 32 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/activity/BUCK: -------------------------------------------------------------------------------- 1 | android_library( 2 | name = "activity", 3 | manifest = "manifest.xml", 4 | srcs = glob(["*.java"]), 5 | deps = [ 6 | "//src/com/facebook/example/habitat:habitat", 7 | "//src/com/facebook/example/mammals:mammals", 8 | ], 9 | visibility = ["PUBLIC"], 10 | ) 11 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/activity/MammalsActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.example.mammals.activity; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import android.widget.TextView; 22 | import com.facebook.example.habitat.Ice; 23 | import com.facebook.example.mammals.SeaLion; 24 | 25 | public class MammalsActivity extends Activity { 26 | private int justKidding; 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | String text = 33 | "Mammal: " + SeaLion.getDescription() + "\n" + 34 | "Environment: " + Ice.getName(); 35 | 36 | TextView view = new TextView(this); 37 | setContentView(view); 38 | view.setText(text); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/activity/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/jni/BUCK: -------------------------------------------------------------------------------- 1 | include_defs("//bucklets/DEFS") 2 | 3 | my_cxx_library( 4 | name = "jni", 5 | soname = "libmammals.$(ext)", 6 | allow_jni_merging = True, 7 | srcs = glob(["*.c"]), 8 | visibility = [ 9 | "//src/com/facebook/example/mammals:mammals", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/facebook/example/mammals/jni/mammals.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | static jstring nativeGetName(JNIEnv* env, jclass cls) { 21 | return (*env)->NewStringUTF(env, "sea lion"); 22 | } 23 | 24 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { 25 | JNIEnv* env; 26 | if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6)) { 27 | return JNI_ERR; 28 | } 29 | 30 | jclass seaLionClass = (*env)->FindClass(env, "com/facebook/example/mammals/SeaLion"); 31 | if (seaLionClass == NULL) { 32 | return JNI_ERR; 33 | } 34 | JNINativeMethod methods[] = { 35 | {"getName", "()Ljava/lang/String;", nativeGetName}, 36 | }; 37 | size_t nMethods = sizeof(methods) / sizeof(methods[0]); 38 | jint result = (*env)->RegisterNatives(env, seaLionClass, methods, nMethods); 39 | if (result != 0) { 40 | return JNI_ERR; 41 | } 42 | 43 | return JNI_VERSION_1_6; 44 | } 45 | -------------------------------------------------------------------------------- /src/com/facebook/jnimerge/BUCK: -------------------------------------------------------------------------------- 1 | python_library( 2 | name = "code-gen-lib", 3 | srcs = ["map_code_generator.py"], 4 | ) 5 | 6 | python_binary( 7 | name = "code-generator", 8 | main_module = "src.com.facebook.jnimerge.map_code_generator", 9 | visibility = ["PUBLIC"], 10 | deps = [ 11 | ":code-gen-lib", 12 | ], 13 | ) 14 | 15 | cxx_library( 16 | name = "jnimerge", 17 | exported_headers = [ 18 | "jni_lib_merge.h", 19 | ], 20 | header_namespace = "", 21 | visibility = ["PUBLIC"], 22 | ) 23 | 24 | cxx_library( 25 | name = "glue", 26 | srcs = [ 27 | "jni_lib_merge.c", 28 | ], 29 | force_static = True, 30 | headers = [ 31 | "jni_lib_merge.h", 32 | ], 33 | visibility = ["PUBLIC"], 34 | ) 35 | -------------------------------------------------------------------------------- /src/com/facebook/jnimerge/jni_lib_merge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // To be linked into merged libraries. 18 | 19 | #define _GNU_SOURCE 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "jni_lib_merge.h" 29 | // We define JNI_OnLoad_Weak directly, so this macro doesn't hurt anything, 30 | // but undef it anyway just to avoid possible future confusion. 31 | #undef JNI_OnLoad 32 | #define PRINT(...) JNI_MERGE_PRINT(__VA_ARGS__) 33 | 34 | // Class with mapping that will be generated by buck. 35 | static const char* invoke_class_name = 36 | "com/facebook/soloader/MergedSoMapping$Invoke_JNI_OnLoad"; 37 | 38 | // Stub pre-merged library to ensure that our custom section gets created 39 | // when we merge a group of native libraries with no JNI_OnLoad. 40 | static struct pre_merge_jni_library pmjl_stub 41 | __attribute__ ((__section__("pre_merge_jni_libraries"))) 42 | = { 43 | .name = "jni_lib_merge-stub", 44 | .onload_func = NULL, 45 | }; 46 | 47 | // References to custom section bounds, filled in by linker. 48 | // Marking these hidden here takes precedence over the global visibility 49 | // of the generated symbols. This ensures that they are hidden in 50 | // the shared object, so they can't be accidentally referenced by 51 | // another merged library. 52 | __attribute__ ((__visibility__("hidden"))) 53 | extern struct pre_merge_jni_library __start_pre_merge_jni_libraries; 54 | __attribute__ ((__visibility__("hidden"))) 55 | extern struct pre_merge_jni_library __stop_pre_merge_jni_libraries; 56 | 57 | 58 | // Returns a malloc'ed string. 59 | static char* method_name_for_invoke(const char* soname) { 60 | char* name = strdup(soname); 61 | if (!name) { 62 | // Can't log here, since we don't know if we're depending on Android logging. 63 | assert(!"Failed to strdup soname."); 64 | abort(); 65 | } 66 | for (char* c = name; *c != '\0'; ++c) { 67 | if (!isalnum(*c) && *c != '_') { 68 | *c = '_'; 69 | } 70 | } 71 | return name; 72 | } 73 | 74 | 75 | // Replacement for weak JNI_OnLoad. 76 | jint JNI_OnLoad_Weak(JavaVM* vm, void* reserved) { 77 | (void)reserved; 78 | 79 | PRINT("Entering merged library JNI_OnLoad.\n"); 80 | 81 | // Get the JNI Env so we can register the original JNI_OnLoad methods. 82 | JNIEnv* env; 83 | if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_2) != JNI_OK) { 84 | return JNI_ERR; 85 | } 86 | 87 | // Find the class we need to register with. 88 | jclass invoke_class = (*env)->FindClass(env, invoke_class_name); 89 | if (invoke_class == NULL) { 90 | return JNI_ERR; 91 | } 92 | 93 | // Construct the argument to RegisterNatives with proper sanitized names 94 | // and function pointers. 95 | struct pre_merge_jni_library* start = &__start_pre_merge_jni_libraries; 96 | struct pre_merge_jni_library* stop = &__stop_pre_merge_jni_libraries; 97 | size_t num_merged_libraries = stop - start; 98 | PRINT("Preparing %d pre-merged libs (including stub)\n", num_merged_libraries); 99 | JNINativeMethod* natives = calloc(num_merged_libraries, sizeof(*natives)); 100 | assert(natives != NULL); 101 | if (natives == NULL) { 102 | abort(); 103 | } 104 | JNINativeMethod* cur_native = natives; 105 | struct pre_merge_jni_library* pmjl = start; 106 | for (size_t i = 0; i < num_merged_libraries; i++, pmjl++) { 107 | if (pmjl == &pmjl_stub) { 108 | continue; 109 | } 110 | char* name = method_name_for_invoke(pmjl->name); 111 | PRINT("Preparing to register %s. onload_func: %p\n", name, pmjl->onload_func); 112 | 113 | cur_native->name = name; 114 | cur_native->signature = "()I"; 115 | cur_native->fnPtr = pmjl->onload_func; 116 | cur_native++; 117 | } 118 | 119 | size_t num_actual_methods = cur_native - natives; 120 | PRINT("About to register %d actual methods.\n", num_actual_methods); 121 | jint ret = (*env)->RegisterNatives(env, invoke_class, natives, num_actual_methods); 122 | 123 | for (size_t i = 0; i < num_actual_methods; i++) { 124 | free((void*)natives[i].name); 125 | } 126 | free(natives); 127 | 128 | if (ret < 0) { 129 | // Exception already thrown. 130 | return JNI_ERR; 131 | } 132 | 133 | return JNI_VERSION_1_6; 134 | } 135 | -------------------------------------------------------------------------------- /src/com/facebook/jnimerge/jni_lib_merge.h: -------------------------------------------------------------------------------- 1 | // Copyright 2004-present Facebook. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #ifdef JNI_MERGE_PRINT_ONLOAD 9 | # ifdef __ANDROID__ 10 | # include 11 | # define JNI_MERGE_PRINT(...) \ 12 | __android_log_print(ANDROID_LOG_DEBUG, "jni_lib_merge", __VA_ARGS__) 13 | # else 14 | # include 15 | # define JNI_MERGE_PRINT(...) fprintf(stderr, __VA_ARGS__); 16 | # endif 17 | #else 18 | # define JNI_MERGE_PRINT(...) do{}while(0) 19 | #endif 20 | 21 | #ifdef __cplusplus 22 | #define JNI_MERGE_GET_JAVA_VM(env, vm) (env->GetJavaVM(&vm)) 23 | #else 24 | #define JNI_MERGE_GET_JAVA_VM(env, vm) ((*env)->GetJavaVM(env, &vm)) 25 | #endif 26 | 27 | struct pre_merge_jni_library { 28 | const char* name; 29 | int (*onload_func)(JNIEnv*, jclass); 30 | }; 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | JNIEXPORT jint JNICALL JNI_OnLoad_Weak(JavaVM* vm, void* reserved); 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | 40 | #define JNI_OnLoad \ 41 | /* Need to make JNI_OnLoad weak, which requires splitting it into */ \ 42 | /* a declaration and definition, which is a little risky. */ \ 43 | /* Hopefully they didn't use 'extern "C"'. */ \ 44 | JNI_OnLoad_Weak(JavaVM* vm, void* reserved) __attribute__ ((weak)); \ 45 | \ 46 | /* We rename the declared JNI_OnLoad to this so we can call it */ \ 47 | /* from either our weak JNI_OnLoad or our merge-friendly init function. */ \ 48 | static jint pre_merge_original_JNI_OnLoad(JavaVM* vm, void* reserved); \ 49 | \ 50 | /* Merge-friendly wrapper for the original JNI_OnLoad, called by JNI. */ \ 51 | /* Return non-zero to indicate failure. */ \ 52 | static inline jint pre_merge_jni_library_wrapper_for_JNI_OnLoad(JNIEnv* env, jclass clazz) { \ 53 | (void)clazz; \ 54 | JNI_MERGE_PRINT("In JNI_OnLoad wrapper for %s", ORIGINAL_SONAME); \ 55 | /* Note: relying on SoLoader's synchronization for thread-safety. */ \ 56 | static char already_loaded = 0; \ 57 | if (already_loaded) { return 0; } \ 58 | already_loaded = 1; \ 59 | JavaVM* vm; \ 60 | jint ret = JNI_MERGE_GET_JAVA_VM(env, vm); \ 61 | if (ret < 0) { \ 62 | /* Exception already thrown. */ \ 63 | return -1; \ 64 | } \ 65 | JNI_MERGE_PRINT("Calling original JNI_OnLoad for %s", ORIGINAL_SONAME); \ 66 | ret = pre_merge_original_JNI_OnLoad(vm, NULL); \ 67 | if (!(ret == JNI_VERSION_1_2 || ret == JNI_VERSION_1_4 || ret == JNI_VERSION_1_6)) { \ 68 | return -1; \ 69 | } \ 70 | return 0; \ 71 | } \ 72 | \ 73 | jint JNI_OnLoad_Weak(JavaVM* vm, void* reserved) { \ 74 | /* This path will be taken if we're not merged. */ \ 75 | /* Just call the original JNI_OnLoad. */ \ 76 | return pre_merge_original_JNI_OnLoad(vm, reserved); \ 77 | } \ 78 | \ 79 | /* Register our name and wrapper in the proper section. */ \ 80 | static struct pre_merge_jni_library pre_merge_jni_library_register_object \ 81 | __attribute__ ((__section__("pre_merge_jni_libraries"))) \ 82 | __attribute__ ((__used__)) \ 83 | = { \ 84 | .name = ORIGINAL_SONAME, \ 85 | .onload_func = pre_merge_jni_library_wrapper_for_JNI_OnLoad, \ 86 | }; \ 87 | \ 88 | /* Re-start the JNI_OnLoad prototype to capture the body. */ \ 89 | static jint pre_merge_original_JNI_OnLoad 90 | -------------------------------------------------------------------------------- /src/com/facebook/jnimerge/map_code_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | from __future__ import print_function 5 | from __future__ import unicode_literals 6 | import sys 7 | import collections 8 | import re 9 | 10 | 11 | def get_base(libname): 12 | m = re.search(r'lib([-\w]+).so', libname) 13 | if not m: 14 | raise Exception('Bad library name: ' + libname) 15 | return m.group(1) 16 | 17 | def sanitize(libname): 18 | return re.sub(r'\W', '_', libname) 19 | 20 | def main(argv): 21 | pre_merge = [] 22 | merged_to_constituents = collections.defaultdict(list) 23 | 24 | with open(argv[1]) as handle: 25 | for line in handle: 26 | src, dst = [ get_base(w) for w in line.strip().split() ] 27 | pre_merge.append(src) 28 | merged_to_constituents[dst].append(src) 29 | 30 | with open(argv[2], 'w') as handle: 31 | handle.write('''\ 32 | package com.facebook.soloader; 33 | 34 | class MergedSoMapping { 35 | static String mapLibName(String preMergedLibName) { 36 | switch (preMergedLibName) { 37 | ''') 38 | 39 | for merged, constituents in sorted(merged_to_constituents.items()): 40 | for constituent in constituents: 41 | handle.write(' case "%s":\n' % constituent) 42 | handle.write(' return "%s";\n' % merged) 43 | 44 | handle.write('''\ 45 | default: 46 | return null; 47 | } 48 | } 49 | 50 | static void invokeJniOnload(String preMergedLibName) { 51 | int result = 0; 52 | switch (preMergedLibName) { 53 | ''') 54 | 55 | for pm in sorted(pre_merge): 56 | handle.write(' case "%s":\n' % pm) 57 | handle.write(' result = Invoke_JNI_OnLoad.lib%s_so();\n' % sanitize(pm)) 58 | handle.write(' break;\n') 59 | 60 | handle.write('''\ 61 | default: 62 | throw new IllegalArgumentException( 63 | "Unknown library: " + preMergedLibName); 64 | } 65 | 66 | if (result != 0) { 67 | throw new UnsatisfiedLinkError("Failed to invoke native library JNI_OnLoad"); 68 | } 69 | } 70 | 71 | static class Invoke_JNI_OnLoad { 72 | ''') 73 | 74 | for pm in sorted(pre_merge): 75 | handle.write(' static native int lib%s_so();\n' % sanitize(pm)) 76 | 77 | handle.write('''\ 78 | } 79 | } 80 | ''') 81 | 82 | if __name__ == '__main__': 83 | sys.exit(main(sys.argv)) 84 | -------------------------------------------------------------------------------- /src/com/facebook/soloader/BUCK: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "soloader", 3 | srcs = [ 4 | "SoLoader.java", 5 | ], 6 | provided_deps = [ 7 | ":mapping-stub", 8 | ], 9 | visibility = ["PUBLIC"], 10 | ) 11 | 12 | java_library( 13 | name = "mapping-stub", 14 | srcs = ["MergedSoMapping.java"], 15 | deps = [ 16 | ], 17 | visibility = ["PUBLIC"], 18 | ) 19 | -------------------------------------------------------------------------------- /src/com/facebook/soloader/MergedSoMapping.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.soloader; 18 | 19 | class MergedSoMapping { 20 | static String mapLibName(String preMergedLibName) { 21 | return null; 22 | } 23 | 24 | static void invokeJniOnload(String preMergedLibName) { 25 | throw new IllegalArgumentException( 26 | "Unknown library: " + preMergedLibName); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/facebook/soloader/SoLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-present, Facebook, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.facebook.soloader; 18 | 19 | public class SoLoader { 20 | public static synchronized void loadLibrary(String libName) { 21 | String merged = MergedSoMapping.mapLibName(libName); 22 | if (merged != null) { 23 | System.loadLibrary(merged); 24 | MergedSoMapping.invokeJniOnload(libName); 25 | } else { 26 | System.loadLibrary(libName); 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------