|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +# Copyright 2020 Google |
| 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 | +"""Standalone script to build desktops apps with Firebase. |
| 17 | +
|
| 18 | +Standalone self-sufficient (no external dependencies) python script that can |
| 19 | +ease building desktop apps with Firebase, by either using the C++ source |
| 20 | +(firebase-cpp-sdk github repo) or the prebuilt release Firebase libraries. |
| 21 | +
|
| 22 | +Note that this script works with only Python3 (3.6+). |
| 23 | +Also note, that since this script runs a cmake configure step, it is advised to |
| 24 | +run it with a fresh clean build directory. |
| 25 | +
|
| 26 | +Known side effects: |
| 27 | +If building against Firebase cpp source, this script might checkout a specific |
| 28 | +branch on github containing vcpkg. This will not be required once vcpkg is in |
| 29 | +the main branch. |
| 30 | +
|
| 31 | +Example usage: |
| 32 | +Let's say we want to build the quickstart cpp example for Firebase database. |
| 33 | +As specified above, there are 2 options - build against the Firebase source or |
| 34 | +prebuilt Firebase libraries. |
| 35 | +
|
| 36 | +# Build against the Firebase cpp sdk source |
| 37 | +python3 scripts/build_desktop_app_with_firebase.py |
| 38 | + --app_dir ~/quickstart-cpp/database/testapp |
| 39 | + --sdk_dir . |
| 40 | + --build_dir build_source |
| 41 | +
|
| 42 | +(or) |
| 43 | +
|
| 44 | +# Build against the prebuilt released Firebase libraries |
| 45 | +python3 scripts/build_desktop_app_with_firebase.py |
| 46 | + --app_dir ~/quickstart-cpp/database/testapp |
| 47 | + --sdk_dir ~/prebuilt/firebase_cpp_sdk_6.15.1/ |
| 48 | + --build_dir build_prebuilt |
| 49 | +
|
| 50 | +# If the script ran successfully, it will print the path to the build directory. |
| 51 | +# The output looks like the following, |
| 52 | +Build successful! |
| 53 | +Please find your executables in build directory: |
| 54 | +/Users/<user>/quickstart-cpp/database/testapp/build_source |
| 55 | +
|
| 56 | +# Running the built example |
| 57 | +$ ./Users/<user>/quickstart-cpp/database/testapp/build_source/desktop_testapp |
| 58 | +""" |
| 59 | +import argparse |
| 60 | +import os |
| 61 | +import platform |
| 62 | +import subprocess |
| 63 | +import sys |
| 64 | + |
| 65 | + |
| 66 | +def is_path_valid_for_cmake(path): |
| 67 | + """Check if specified path is setup for cmake.""" |
| 68 | + return os.path.exists(os.path.join(path, 'CMakeLists.txt')) |
| 69 | + |
| 70 | + |
| 71 | +def is_sdk_path_source(sdk_dir): |
| 72 | + """Validate if Firebase sdk dir is Firebase cpp source dir.""" |
| 73 | + # Not the most reliable way to detect if the sdk path is source or prebuilt |
| 74 | + # but should work for our purpose. |
| 75 | + return os.path.exists(os.path.join(sdk_dir, 'build_tools')) |
| 76 | + |
| 77 | + |
| 78 | +def validate_prebuilt_args(arch, config): |
| 79 | + """Validate cmd line args for build with prebuilt libraries.""" |
| 80 | + # Some options are not available when using prebuilt libraries. |
| 81 | + if platform.system() == 'Darwin': |
| 82 | + if arch == 'x86' or config == 'Debug': |
| 83 | + raise ValueError('Prebuilt mac Firebase libraries are built for x64 and ' |
| 84 | + 'Release mode only. ' |
| 85 | + 'Please fix the command line arguments and try again') |
| 86 | + |
| 87 | + if platform.system() == 'Linux': |
| 88 | + if config == 'Debug': |
| 89 | + raise ValueError('Prebuilt linux Firebase libraries are built with ' |
| 90 | + 'Release mode only. Please fix the --config command ' |
| 91 | + 'line argument and try again.') |
| 92 | + |
| 93 | + |
| 94 | +def get_vcpkg_triplet(arch, msvc_runtime_library='static'): |
| 95 | + """Get vcpkg target triplet (platform definition). |
| 96 | +
|
| 97 | + Args: |
| 98 | + arch (str): Architecture (eg: 'x86', 'x64'). |
| 99 | + msvc_runtime_library (str): Runtime library for MSVC. |
| 100 | + (eg: 'static', 'dynamic'). |
| 101 | +
|
| 102 | + Raises: |
| 103 | + ValueError: If current OS is not win, mac or linux. |
| 104 | +
|
| 105 | + Returns: |
| 106 | + (str): Triplet name. |
| 107 | + Eg: "x64-windows-static". |
| 108 | + """ |
| 109 | + triplet_name = [arch] |
| 110 | + if platform.system() == 'Windows': |
| 111 | + triplet_name.append('windows') |
| 112 | + triplet_name.append('static') |
| 113 | + if msvc_runtime_library == 'dynamic': |
| 114 | + triplet_name.append('md') |
| 115 | + elif platform.system() == 'Darwin': |
| 116 | + triplet_name.append('osx') |
| 117 | + elif platform.system() == 'Linux': |
| 118 | + triplet_name.append('linux') |
| 119 | + else: |
| 120 | + raise ValueError('Unsupported platform. This function works only on ' |
| 121 | + 'Windows, Linux or Mac platforms.') |
| 122 | + |
| 123 | + triplet_name = '-'.join(triplet_name) |
| 124 | + print('Using vcpkg triplet: {0}'.format(triplet_name)) |
| 125 | + return triplet_name |
| 126 | + |
| 127 | + |
| 128 | +def build_source_vcpkg_dependencies(sdk_source_dir, arch, msvc_runtime_library): |
| 129 | + """Build C++ dependencies for Firebase source SDK using vcpkg. |
| 130 | +
|
| 131 | + Args: |
| 132 | + sdk_source_dir (str): Path to Firebase C++ source directory. |
| 133 | + arch (str): Platform Architecture (eg: 'x64'). |
| 134 | + msvc_runtime_library (str): Runtime library for MSVC. |
| 135 | + (eg: 'static', 'dynamic'). |
| 136 | + """ |
| 137 | + # TODO(b/174141707): Remove this once dev branch of firebase-cpp-sdk repo has |
| 138 | + # been merged onto main branch. This is required because vcpkg lives only in |
| 139 | + # dev branch currently. |
| 140 | + subprocess.run(['git', 'checkout', 'dev'], |
| 141 | + cwd=sdk_source_dir, check=True) |
| 142 | + subprocess.run(['git', 'pull'], cwd=sdk_source_dir, check=True) |
| 143 | + |
| 144 | + # sys.executable should point to the python bin running this script. We use |
| 145 | + # the same executable to execute these subprocess python scripts. |
| 146 | + subprocess.run([sys.executable, 'scripts/gha/install_prereqs_desktop.py'], |
| 147 | + cwd=sdk_source_dir, check=True) |
| 148 | + subprocess.run([sys.executable, 'scripts/gha/build_desktop.py', |
| 149 | + '--arch', arch, |
| 150 | + '--msvc_runtime_library', msvc_runtime_library, |
| 151 | + '--vcpkg_step_only'], cwd=sdk_source_dir, check=True) |
| 152 | + |
| 153 | + |
| 154 | +def build_app_with_source(app_dir, sdk_source_dir, build_dir, arch, |
| 155 | + msvc_runtime_library='static', config=None, |
| 156 | + target_format=None): |
| 157 | + """Build desktop app directly against the Firebase C++ SDK source. |
| 158 | +
|
| 159 | + Since this involves a cmake configure, it is advised to run this on a clean |
| 160 | + build directory. |
| 161 | +
|
| 162 | + Args: |
| 163 | + app_dir (str): Path to directory containing application's CMakeLists.txt. |
| 164 | + sdk_source_dir (str): Path to Firebase C++ SDK source directory. |
| 165 | + (root of firebase-cpp-sdk github repo). |
| 166 | + build_dir (str): Output build directory. |
| 167 | + arch (str): Platform Architecture (example: 'x64'). |
| 168 | + msvc_runtime_library (str): Runtime library for MSVC. |
| 169 | + (eg: 'static', 'dynamic'). |
| 170 | + config (str): Release/Debug config. |
| 171 | + If it's not specified, cmake's default is used (most likely Debug). |
| 172 | + target_format (str): If specified, build for this target format. |
| 173 | + ('frameworks' or 'libraries'). |
| 174 | + """ |
| 175 | + # Cmake configure. |
| 176 | + cmd = ['cmake', '-S', '.', '-B', build_dir] |
| 177 | + cmd.append('-DFIREBASE_CPP_SDK_DIR={0}'.format(sdk_source_dir)) |
| 178 | + |
| 179 | + # If generator is not specified, default for platform is used by cmake, else |
| 180 | + # use the specified value. |
| 181 | + if config: |
| 182 | + cmd.append('-DCMAKE_BUILD_TYPE={0}'.format(config)) |
| 183 | + # workaround, absl doesn't build without tests enabled. |
| 184 | + cmd.append('-DBUILD_TESTING=off') |
| 185 | + |
| 186 | + if platform.system() == 'Linux' and arch == 'x86': |
| 187 | + # Use a separate cmake toolchain for cross compiling linux x86 builds. |
| 188 | + vcpkg_toolchain_file_path = os.path.join(sdk_source_dir, 'external', |
| 189 | + 'vcpkg', 'scripts', |
| 190 | + 'buildsystems', 'linux_32.cmake') |
| 191 | + else: |
| 192 | + vcpkg_toolchain_file_path = os.path.join(sdk_source_dir, 'external', |
| 193 | + 'vcpkg', 'scripts', |
| 194 | + 'buildsystems', 'vcpkg.cmake') |
| 195 | + |
| 196 | + cmd.append('-DCMAKE_TOOLCHAIN_FILE={0}'.format(vcpkg_toolchain_file_path)) |
| 197 | + |
| 198 | + vcpkg_triplet = get_vcpkg_triplet(arch, msvc_runtime_library) |
| 199 | + cmd.append('-DVCPKG_TARGET_TRIPLET={0}'.format(vcpkg_triplet)) |
| 200 | + |
| 201 | + if platform.system() == 'Windows': |
| 202 | + # If building for x86, we should supply -A Win32 to cmake configure. |
| 203 | + # Since the default architecture for cmake varies from machine to machine, |
| 204 | + # it is a good practice to specify it all the time (even for x64). |
| 205 | + cmd.append('-A') |
| 206 | + windows_cmake_arch_flag_value = 'Win32' if arch == 'x86' else 'x64' |
| 207 | + cmd.append(windows_cmake_arch_flag_value) |
| 208 | + |
| 209 | + # Use our special cmake option to specify /MD (dynamic) vs /MT (static). |
| 210 | + if msvc_runtime_library == 'static': |
| 211 | + cmd.append('-DMSVC_RUNTIME_LIBRARY_STATIC=ON') |
| 212 | + |
| 213 | + if target_format: |
| 214 | + cmd.append('-DFIREBASE_XCODE_TARGET_FORMAT={0}'.format(target_format)) |
| 215 | + print('Running {0}'.format(' '.join(cmd))) |
| 216 | + subprocess.run(cmd, cwd=app_dir, check=True) |
| 217 | + |
| 218 | + # CMake build. |
| 219 | + num_cpus = str(os.cpu_count()) |
| 220 | + cmd = ['cmake', '--build', build_dir, '-j', num_cpus, '--config', config] |
| 221 | + print('Running {0}'.format(' '.join(cmd))) |
| 222 | + subprocess.run(cmd, cwd=app_dir, check=True) |
| 223 | + |
| 224 | + |
| 225 | +def build_app_with_prebuilt(app_dir, sdk_prebuilt_dir, build_dir, arch, |
| 226 | + msvc_runtime_library='static', config=None): |
| 227 | + """Build desktop app directly against the prebuilt Firebase C++ libraries. |
| 228 | +
|
| 229 | + Since this involves a cmake configure, it is advised to run this on a clean |
| 230 | + build directory. |
| 231 | +
|
| 232 | + Args: |
| 233 | + app_dir (str): Path to directory containing application's CMakeLists.txt. |
| 234 | + sdk_prebuilt_dir (str): Path to prebuilt Firebase C++ libraries. |
| 235 | + build_dir (str): Output build directory. |
| 236 | + arch (str): Platform Architecture (eg: 'x64'). |
| 237 | + msvc_runtime_library (str): Runtime library for MSVC. |
| 238 | + (eg: 'static', 'dynamic'). |
| 239 | + config (str): Release/Debug config (eg: 'Release', 'Debug') |
| 240 | + If it's not specified, cmake's default is used (most likely Debug). |
| 241 | + """ |
| 242 | + |
| 243 | + cmd = ['cmake', '-S', '.', '-B', build_dir] |
| 244 | + cmd.append('-DFIREBASE_CPP_SDK_DIR={0}'.format(sdk_prebuilt_dir)) |
| 245 | + |
| 246 | + if platform.system() == 'Windows': |
| 247 | + if arch == 'x64': |
| 248 | + cmd.append('-DCMAKE_CL_64=ON') |
| 249 | + if msvc_runtime_library == 'dynamic': |
| 250 | + cmd.append('-DMSVC_RUNTIME_MODE=MD') |
| 251 | + else: |
| 252 | + cmd.append('-DMSVC_RUNTIME_MODE=MT') |
| 253 | + |
| 254 | + if config: |
| 255 | + cmd.append('-DCMAKE_BUILD_TYPE={0}'.format(config)) |
| 256 | + |
| 257 | + print('Running {0}'.format(' '.join(cmd))) |
| 258 | + subprocess.run(cmd, cwd=app_dir, check=True) |
| 259 | + |
| 260 | + # CMake build. |
| 261 | + num_cpus = str(os.cpu_count()) |
| 262 | + cmd = ['cmake', '--build', build_dir, '-j', num_cpus, '--config', config] |
| 263 | + print('Running {0}'.format(' '.join(cmd))) |
| 264 | + subprocess.run(cmd, cwd=app_dir, check=True) |
| 265 | + |
| 266 | + |
| 267 | +def main(): |
| 268 | + args = parse_cmdline_args() |
| 269 | + |
| 270 | + if not is_path_valid_for_cmake(args.sdk_dir): |
| 271 | + print('SDK path provided is not valid. ' |
| 272 | + 'Could not find a CMakeLists.txt at the root level.\n' |
| 273 | + 'Please check the argument to "--sdk_dir".') |
| 274 | + sys.exit(1) |
| 275 | + |
| 276 | + if not is_path_valid_for_cmake(args.app_dir): |
| 277 | + print('App path provided is not valid. ' |
| 278 | + 'Could not find a CMakeLists.txt at the root level.\n' |
| 279 | + 'Please check the argument to "--app_dir"') |
| 280 | + sys.exit(1) |
| 281 | + |
| 282 | + if is_sdk_path_source(args.sdk_dir): |
| 283 | + print('SDK path provided is a Firebase C++ source directory. Building...') |
| 284 | + build_source_vcpkg_dependencies(args.sdk_dir, args.arch, |
| 285 | + args.msvc_runtime_library) |
| 286 | + build_app_with_source(args.app_dir, args.sdk_dir, args.build_dir, args.arch, |
| 287 | + args.msvc_runtime_library, args.config, |
| 288 | + args.target_format) |
| 289 | + else: |
| 290 | + validate_prebuilt_args(args.arch, args.config) |
| 291 | + print('SDK path provided is Firebase C++ prebuilt libraries. Building...') |
| 292 | + build_app_with_prebuilt(args.app_dir, args.sdk_dir, args.build_dir, |
| 293 | + args.arch, args.msvc_runtime_library, args.config) |
| 294 | + |
| 295 | + print('Build successful!\n' |
| 296 | + 'Please find your executables in the build directory: {0}'.format( |
| 297 | + os.path.join(args.app_dir, args.build_dir))) |
| 298 | + |
| 299 | + |
| 300 | +def parse_cmdline_args(): |
| 301 | + """Parse command line arguments.""" |
| 302 | + parser = argparse.ArgumentParser(description='Install Prerequisites for ' |
| 303 | + 'building cpp sdk.') |
| 304 | + parser.add_argument('--sdk_dir', help='Path to Firebase SDK - source or ' |
| 305 | + 'prebuilt libraries.', |
| 306 | + type=os.path.abspath) |
| 307 | + parser.add_argument('--app_dir', help="Path to application to build " |
| 308 | + "(directory containing application's CMakeLists.txt", |
| 309 | + type=os.path.abspath) |
| 310 | + parser.add_argument('-a', '--arch', default='x64', |
| 311 | + help='Platform architecture (x64, x86)') |
| 312 | + parser.add_argument('--msvc_runtime_library', default='static', |
| 313 | + help='Runtime library for MSVC ' |
| 314 | + '(static(/MT) or dynamic(/MD)') |
| 315 | + parser.add_argument('--build_dir', default='build', |
| 316 | + help='Output build directory') |
| 317 | + parser.add_argument('--config', default='Release', |
| 318 | + help='Release/Debug config') |
| 319 | + parser.add_argument('--target_format', default=None, |
| 320 | + help='(Mac only) whether to output frameworks (default)' |
| 321 | + 'or libraries.') |
| 322 | + args = parser.parse_args() |
| 323 | + return args |
| 324 | + |
| 325 | +if __name__ == '__main__': |
| 326 | + main() |
0 commit comments