Skip to content

Commit 6b8d956

Browse files
cynthiajoanCynthia Jiangfirebase-workflow-trigger-bot
authored
Add logic to build Android Unity SDK (#230)
Android build logic in python scripts Co-authored-by: Cynthia Jiang <[email protected]> Co-authored-by: firebase-workflow-trigger-bot <[email protected]>
1 parent 768b6e3 commit 6b8d956

File tree

2 files changed

+294
-26
lines changed

2 files changed

+294
-26
lines changed

aar_builder/merge_aar.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/python
2+
#
3+
# Copyright 2022 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
"""Merge a list of srcaar files into one
17+
18+
Single srcaar file usually looks like this
19+
├── AndroidManifest.xml
20+
├── R.txt
21+
├── classes.jar
22+
├── jni
23+
│ └── x86_64
24+
│ └── libFirebaseCppRemoteConfig.so
25+
├── proguard.txt
26+
└── res
27+
28+
And a merged one will look like this
29+
├── AndroidManifest.xml
30+
├── R.txt
31+
├── classes.jar
32+
├── jni
33+
│ ├── armeabi-v7a
34+
│ │ └── libFirebaseCppRemoteConfig.so
35+
│ └── x86_64
36+
│ └── libFirebaseCppRemoteConfig.so
37+
├── proguard.txt
38+
└── res
39+
40+
Example usage:
41+
python merge_aar.py --inputs=<srcaar1> --inputs=<srcaar2> --output=<srcaar output>
42+
"""
43+
import os
44+
import zipfile
45+
import tempfile
46+
from absl import app, flags, logging
47+
48+
FLAGS = flags.FLAGS
49+
50+
flags.DEFINE_multi_string(
51+
"inputs", None,
52+
"A list of srcaars to merge together.")
53+
flags.DEFINE_string(
54+
'output', None,
55+
"Output file location for merged srcaar file."
56+
)
57+
58+
59+
def main(argv):
60+
if len(argv) > 1:
61+
raise app.UsageError('Too many command-line arguments.')
62+
input_srcaars = FLAGS.inputs
63+
if len(input_srcaars) <= 1:
64+
raise app.UsageError(
65+
'Input srcaars needs more than 1 entry, currently only %d.'.format(len(input_srcaars)))
66+
# temp folder to exact input srcaar files in
67+
base_temp_dir = tempfile.mkdtemp()
68+
for input in input_srcaars:
69+
if os.stat(input).st_size == 0:
70+
# Ignore empty aar file
71+
logging.debug("input %s is empty.", input)
72+
continue
73+
# Extacting each input srcaar files into the same temp folder
74+
# For same files that already extracted from previous srcaar file,
75+
# the extract operation will just keep one copy.
76+
with zipfile.ZipFile(input) as zip_aar:
77+
logging.debug("Extracting %s.", input)
78+
zip_aar.extractall(base_temp_dir)
79+
80+
# Create the output AAR.
81+
output_aar_file = FLAGS.output
82+
if os.path.exists(output_aar_file) and os.path.isfile(output_aar_file):
83+
# remove the existing srcaar with the output name.
84+
os.remove(output_aar_file)
85+
86+
# Write the temp folder as the output srcaar file.
87+
with zipfile.ZipFile(output_aar_file, "w", allowZip64=True) as zip_file:
88+
for current_root, folders, filenames in os.walk(base_temp_dir):
89+
for folder in folders:
90+
fullpath = os.path.join(current_root, folder)
91+
zip_file.write(fullpath, os.path.relpath(fullpath, base_temp_dir))
92+
for filename in filenames:
93+
fullpath = os.path.join(current_root, filename)
94+
zip_file.write(fullpath, os.path.relpath(fullpath, base_temp_dir))
95+
logging.debug("Archived directory %s to %s", base_temp_dir, output_aar_file)
96+
97+
98+
if __name__ == '__main__':
99+
flags.mark_flag_as_required("inputs")
100+
flags.mark_flag_as_required("output")
101+
app.run(main)

scripts/build_scripts/build_zips.py

Lines changed: 193 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
Example usage:
2020
python build_zips.py --platform=macos --targets=auth --targets=firestore
2121
"""
22+
import glob
2223
import os
23-
import re
24-
import subprocess
2524
import shutil
25+
import subprocess
26+
import zipfile
27+
import tempfile
2628

27-
from absl import app
28-
from absl import flags
29-
from absl import logging
29+
from absl import app, flags, logging
3030

3131
SUPPORT_PLATFORMS = ("linux", "macos", "windows", "ios", "android")
3232
SUPPORT_TARGETS = [
@@ -55,6 +55,9 @@
5555

5656
ANDROID_SUPPORT_ARCHITECTURE = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
5757

58+
g_mobile_target_architectures = []
59+
g_cpp_sdk_realpath = ""
60+
5861
FLAGS = flags.FLAGS
5962
flags.DEFINE_string(
6063
'platform', None,
@@ -103,17 +106,22 @@ def get_build_path(platform, clean_build=False):
103106
return platform_path
104107

105108

106-
def get_cpp_folder_args():
109+
def get_cpp_folder_args(source_path):
107110
"""Get the cmake args to pass in local Firebase C++ SDK folder.
108111
If not found, will download from Firebase C++ git repo.
109-
112+
113+
Args:
114+
source_path: root source folder cd back.
115+
110116
Returns:
111117
cmake args with the folder path of local Firebase C++ SDK.
112118
Empty string if not found.
113119
"""
120+
global g_cpp_sdk_realpath
114121
cpp_folder = os.path.join(os.getcwd(), "..", "firebase-cpp-sdk")
115122
if os.path.exists(cpp_folder):
116-
return "-DFIREBASE_CPP_SDK_DIR=" + os.path.realpath(cpp_folder)
123+
g_cpp_sdk_realpath = os.path.realpath(cpp_folder)
124+
return "-DFIREBASE_CPP_SDK_DIR=" + g_cpp_sdk_realpath
117125
else:
118126
return ""
119127

@@ -184,6 +192,7 @@ def get_ios_args(source_path):
184192
else:
185193
devices = SUPPORT_DEVICE
186194

195+
global g_mobile_target_architectures
187196
# check architecture input
188197
if (len(devices) > 1):
189198
archs_to_check = IOS_SUPPORT_ARCHITECTURE
@@ -195,13 +204,14 @@ def get_ios_args(source_path):
195204
raise app.UsageError(
196205
'Wrong architecture "{}" for device type {}, please pick from {}'.format(
197206
arch, ",".join(devices), ",".join(archs_to_check)))
198-
archs = FLAGS.architecture
207+
g_mobile_target_architectures = FLAGS.architecture
199208
else:
200-
archs = archs_to_check
209+
g_mobile_target_architectures = archs_to_check
201210

202-
if len(archs) != len(IOS_SUPPORT_ARCHITECTURE):
211+
if len(g_mobile_target_architectures) != len(IOS_SUPPORT_ARCHITECTURE):
203212
# Need to override only if the archs are not default
204-
result_args.append("-DCMAKE_OSX_ARCHITECTURES=" + ";".join(archs))
213+
result_args.append("-DCMAKE_OSX_ARCHITECTURES=" +
214+
";".join(g_mobile_target_architectures))
205215

206216
if len(devices) != len(SUPPORT_DEVICE):
207217
# Need to override if only passed in device or simulator
@@ -214,6 +224,149 @@ def get_ios_args(source_path):
214224
return result_args
215225

216226

227+
def get_android_args():
228+
"""Get the cmake args for android platform specific.
229+
230+
Returns:
231+
camke args for android platform.
232+
"""
233+
result_args = []
234+
# get Android NDK path
235+
system_android_ndk_home = os.getenv('ANDROID_NDK_HOME')
236+
if system_android_ndk_home:
237+
toolchain_path = os.path.join(
238+
system_android_ndk_home, "build", "cmake", "android.toolchain.cmake")
239+
result_args.append("-DCMAKE_TOOLCHAIN_FILE=" + toolchain_path)
240+
logging.info("Use ANDROID_NDK_HOME(%s) cmake toolchain(%s)",
241+
system_android_ndk_home, toolchain_path)
242+
else:
243+
system_android_home = os.getenv('ANDROID_HOME')
244+
if system_android_home:
245+
toolchain_files = glob.glob(os.path.join(system_android_home,
246+
"**", "build", "cmake", "android.toolchain.cmake"), recursive=True)
247+
if toolchain_files:
248+
result_args.append("-DCMAKE_TOOLCHAIN_FILE=" + toolchain_files[0])
249+
logging.info("Use ANDROID_HOME(%s) cmake toolchain (%s)",
250+
system_android_home, toolchain_files[0])
251+
else:
252+
raise app.UsageError(
253+
'Neither ANDROID_NDK_HOME nor ANDROID_HOME is set.')
254+
255+
# get architecture setup
256+
global g_mobile_target_architectures
257+
if FLAGS.architecture:
258+
for arch in FLAGS.architecture:
259+
if arch not in ANDROID_SUPPORT_ARCHITECTURE:
260+
raise app.UsageError(
261+
'Wrong architecture "{}", please pick from {}'.format(
262+
arch, ",".join(ANDROID_SUPPORT_ARCHITECTURE)))
263+
g_mobile_target_architectures = FLAGS.architecture
264+
else:
265+
g_mobile_target_architectures = ANDROID_SUPPORT_ARCHITECTURE
266+
267+
if len(g_mobile_target_architectures) == 1:
268+
result_args.append("-DANDROID_ABI="+g_mobile_target_architectures[0])
269+
270+
result_args.append("-DFIREBASE_ANDROID_BUILD=true")
271+
# android default to build release.
272+
result_args.append("-DCMAKE_BUILD_TYPE=release")
273+
return result_args
274+
275+
276+
def make_android_multi_arch_build(cmake_args, merge_script):
277+
"""Make android build for different architectures, and then combine them together
278+
Args:
279+
cmake_args: cmake arguments used to build each architecture.
280+
merge_script: script path to merge the srcaar files.
281+
"""
282+
global g_mobile_target_architectures
283+
# build multiple archictures
284+
current_folder = os.getcwd()
285+
for arch in g_mobile_target_architectures:
286+
if not os.path.exists(arch):
287+
os.makedirs(arch)
288+
os.chdir(arch)
289+
cmake_args.append("-DANDROID_ABI="+arch)
290+
subprocess.call(cmake_args)
291+
subprocess.call("make")
292+
293+
cmake_pack_args = [
294+
"cpack",
295+
".",
296+
]
297+
subprocess.call(cmake_pack_args)
298+
os.chdir(current_folder)
299+
300+
# merge them
301+
zip_base_name = ""
302+
srcarr_list = []
303+
base_temp_dir = tempfile.mkdtemp()
304+
for arch in g_mobile_target_architectures:
305+
# find *Android.zip in subfolder architecture
306+
arch_zip_path = glob.glob(os.path.join(arch, "*Android.zip"))
307+
if not arch_zip_path:
308+
logging.error("No *Android.zip generated for architecture %s", arch)
309+
return
310+
if not zip_base_name:
311+
# first architecture, so extract to the final temp folder. The following
312+
# srcaar files will merge to the ones in this folder.
313+
zip_base_name = arch_zip_path[0]
314+
with zipfile.ZipFile(zip_base_name) as zip_file:
315+
zip_file.extractall(base_temp_dir)
316+
srcarr_list.extend(glob.glob(os.path.join(
317+
base_temp_dir, "**", "*.srcaar"), recursive=True))
318+
else:
319+
temporary_dir = tempfile.mkdtemp()
320+
# from the second *Android.zip, we only need to extract *.srcaar files to operate the merge.
321+
with zipfile.ZipFile(arch_zip_path[0]) as zip_file:
322+
for file in zip_file.namelist():
323+
if file.endswith('.srcaar'):
324+
zip_file.extract(file, temporary_dir)
325+
logging.debug("Unpacked file %s from zip file %s to %s",
326+
file, arch_zip_path, temporary_dir)
327+
328+
for srcaar_file in srcarr_list:
329+
srcaar_name = os.path.basename(srcaar_file)
330+
matching_files = glob.glob(os.path.join(
331+
temporary_dir, "**", "*"+srcaar_name), recursive=True)
332+
if matching_files:
333+
merge_args = [
334+
"python",
335+
merge_script,
336+
"--inputs=" + srcaar_file,
337+
"--inputs=" + matching_files[0],
338+
"--output=" + srcaar_file,
339+
]
340+
subprocess.call(merge_args)
341+
logging.debug("merging %s to %s", matching_files[0], srcaar_file)
342+
343+
# achive the temp folder to the final firebase_unity-<version>-Android.zip
344+
final_zip_path = os.path.join(current_folder, os.path.basename(zip_base_name))
345+
with zipfile.ZipFile(final_zip_path, "w", allowZip64=True) as zip_file:
346+
for current_root, _, filenames in os.walk(base_temp_dir):
347+
for filename in filenames:
348+
fullpath = os.path.join(current_root, filename)
349+
zip_file.write(fullpath, os.path.relpath(fullpath, base_temp_dir))
350+
logging.info("Generated Android multi-arch (%s) zip %s",
351+
",".join(g_mobile_target_architectures), final_zip_path)
352+
353+
354+
def is_android_build():
355+
"""
356+
Returns:
357+
If the build platform is android
358+
"""
359+
return FLAGS.platform == "android"
360+
361+
362+
def is_ios_build():
363+
"""
364+
Returns:
365+
If the build platform is ios
366+
"""
367+
return FLAGS.platform == "ios"
368+
369+
217370
def main(argv):
218371
if len(argv) > 1:
219372
raise app.UsageError('Too many command-line arguments.')
@@ -222,15 +375,20 @@ def main(argv):
222375
raise app.UsageError('Wrong platform "{}", please pick from {}'.format(
223376
platform, ",".join(SUPPORT_PLATFORMS)))
224377

225-
cmake_cpp_folder_args = get_cpp_folder_args()
226-
build_path = get_build_path(platform, FLAGS.clean_build)
227-
228378
source_path = os.getcwd()
379+
cmake_cpp_folder_args = get_cpp_folder_args(source_path)
380+
build_path = get_build_path(platform, FLAGS.clean_build)
381+
if is_android_build() and g_cpp_sdk_realpath:
382+
# For android build, if we find local cpp folder,
383+
# We trigger the cpp android build first.
384+
os.chdir(g_cpp_sdk_realpath)
385+
subprocess.call("./gradlew")
386+
os.chdir(source_path)
229387

230388
os.chdir(build_path)
231389
cmake_setup_args = [
232390
"cmake",
233-
"..",
391+
source_path,
234392
"-DFIREBASE_INCLUDE_UNITY=ON",
235393
"-DFIREBASE_UNITY_BUILD_TESTS=ON",
236394
"-DFIREBASE_CPP_BUILD_STUB_TESTS=ON",
@@ -249,19 +407,28 @@ def main(argv):
249407
if FLAGS.cmake_extras:
250408
cmake_setup_args.extend(FLAGS.cmake_extras)
251409

252-
if platform == "ios":
410+
if is_ios_build():
253411
cmake_setup_args.extend(get_ios_args(source_path))
412+
elif is_android_build():
413+
cmake_setup_args.extend(get_android_args())
254414

415+
global g_mobile_target_architectures
255416
logging.info("cmake_setup_args is: " + " ".join(cmake_setup_args))
256-
257-
subprocess.call(cmake_setup_args)
258-
subprocess.call("make")
259-
260-
cmake_pack_args = [
261-
"cpack",
262-
".",
263-
]
264-
subprocess.call(cmake_pack_args)
417+
if is_android_build() and len(g_mobile_target_architectures) > 1:
418+
logging.info("Build android with multiple architectures %s",
419+
",".join(g_mobile_target_architectures))
420+
# android multi architecture build is a bit different
421+
make_android_multi_arch_build(cmake_setup_args, os.path.join(
422+
source_path, "aar_builder", "merge_aar.py"))
423+
else:
424+
subprocess.call(cmake_setup_args)
425+
subprocess.call("make")
426+
427+
cmake_pack_args = [
428+
"cpack",
429+
".",
430+
]
431+
subprocess.call(cmake_pack_args)
265432

266433
os.chdir(source_path)
267434

0 commit comments

Comments
 (0)