| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from contextlib import contextmanager |
| import re |
| |
| DEPS = [ |
| 'depot_tools/gsutil', |
| 'flutter/osx_sdk', |
| 'flutter/zip', |
| 'recipe_engine/context', |
| 'recipe_engine/file', |
| 'recipe_engine/path', |
| 'recipe_engine/raw_io', |
| 'recipe_engine/runtime', |
| 'recipe_engine/step', |
| ] |
| |
| BUCKET_NAME = 'flutter_infra_release' |
| |
| # These regex are used to detect lib paths that need to be patched. |
| LIBUSBMUXD_PATTERN = r'^\t?(.*(libusbmuxd.*\.dylib)).*' |
| LIBPLIST_PATTERN = r'^\t?(.*(libplist.*\.dylib)).*' |
| LIBSSL_PATTERN = r'^\t?(.*(libssl.*\.dylib)).*' |
| LIBCRYPTO_PATTERN = r'^\t?(.*(libcrypto.*\.dylib)).*' |
| LIBIMOBILEDEVICE_PATTERN = r'^\t?(.*(libimobiledevice.*\.dylib)).*' |
| LIBIMOBILEDEVICEGLUE_PATTERN = r'^\t?(.*(libimobiledevice-glue.*\.dylib)).*' |
| |
| DIRNAME_PATTERN_DICT = { |
| 'libusbmuxd': LIBUSBMUXD_PATTERN, 'libplist': LIBPLIST_PATTERN, |
| 'libssl': LIBSSL_PATTERN, 'libcrypto': LIBCRYPTO_PATTERN, |
| 'libimobiledevice': LIBIMOBILEDEVICE_PATTERN, |
| 'libimobiledeviceglue': LIBIMOBILEDEVICEGLUE_PATTERN |
| } |
| |
| # Map between package and its artifacts that need path patch. |
| BIANRY_ARTIFACT_MAP = { |
| 'libusbmuxd': ['iproxy'], 'openssl': ['libcrypto.3.dylib'], |
| 'libimobiledevice': ['idevicescreenshot', 'idevicesyslog'] |
| } |
| |
| |
| def ParseOtoolPath(input_string): |
| '''Parse paths that need to be patched to relative paths via otool.''' |
| input_lines = input_string.split('\n') |
| output = {} |
| for input_line in input_lines: |
| for dirname, pattern in DIRNAME_PATTERN_DICT.items(): |
| # re.match searches from beginning of string |
| match = re.match(pattern, input_line) |
| if match: |
| old_path = match.group(1) |
| new_path = '@loader_path/../%s' % match.group(2) |
| output[old_path] = new_path |
| return output |
| |
| |
| def PatchLoadPath(api, ouput_path, package_name): |
| '''Update dynamically linked paths in a binary''' |
| if package_name not in BIANRY_ARTIFACT_MAP: |
| return |
| artifacts = BIANRY_ARTIFACT_MAP[package_name] |
| for artifact in artifacts: |
| artifact_path = ouput_path.join(artifact) |
| otool_step_data = api.step( |
| 'Get linked paths from %s before patch' % artifact, |
| ['otool', '-L', artifact_path], |
| stdout=api.raw_io.output_text() |
| ) |
| old_paths_to_new_paths = ParseOtoolPath(otool_step_data.stdout.rstrip()) |
| for old_path in old_paths_to_new_paths: |
| new_path = old_paths_to_new_paths[old_path] |
| api.step( |
| 'Patch %s with install_name_tool' % artifact_path, |
| ['install_name_tool', '-change', old_path, new_path, artifact_path], |
| ) |
| api.step( |
| 'Get linked paths from %s after patch' % artifact_path, |
| ['otool', '-L', artifact_path] |
| ) |
| |
| |
| def GetCloudPath(api, package_name, commit_sha): |
| """Location of cloud bucket for unsigned binaries""" |
| version_namespace = 'led' if api.runtime.is_experimental else commit_sha |
| return 'ios-usb-dependencies/unsigned/%s/%s/%s.zip' % ( |
| package_name, version_namespace, package_name |
| ) |
| |
| |
| def UpdateEnv( |
| api, env, env_prefixes, package_name, package_install_dir, update_path, |
| update_library_path, update_pkg_config_path |
| ): |
| """Updates environment variables based on requirments. |
| |
| Args: |
| |
| env(dict): current environment variables. |
| env_prefixes(dict): current environment prefixes variables. |
| package_name(str): the name of the package to be built. |
| package_install_dir(path): path to the package install dir. |
| upload(bool): a flag indicating whether there are artifacts to upload. |
| update_path(bool): a flag indicating whether there are PATH updates. |
| update_library_path(bool): a flag indicating whether there are LIBRARY_PATH updates. |
| update_pkg_config_path(bool): a flag indicating whether there are PKG_CONFIG_PATH updates. |
| """ |
| if package_name == 'libimobiledeviceglue': |
| env['CPATH'] = '%s/include' % package_install_dir |
| if update_path: |
| paths = env_prefixes.get('PATH', []) |
| paths.append('%s/bin' % package_install_dir) |
| env_prefixes['PATH'] = paths |
| if update_library_path: |
| library_path = env_prefixes.get('LIBRARY_PATH', []) |
| library_path.append('%s/lib' % package_install_dir) |
| env_prefixes['LIBRARY_PATH'] = library_path |
| if update_pkg_config_path: |
| pkg_config_path = env_prefixes.get('PKG_CONFIG_PATH', []) |
| pkg_config_path.append('%s/lib/pkgconfig' % package_install_dir) |
| env_prefixes['PKG_CONFIG_PATH'] = pkg_config_path |
| |
| |
| def GetDylibFilenames(api, dir, package_name): |
| """Returns a list of file names that matches dylib file regex in given directory. |
| |
| A package_name maps to a regex pattern based on DIRNAME_PATTERN_DICT(dict). All |
| file names in the current dir that matches this regex pattern are returned. |
| |
| Args: |
| |
| dir(Path): The directory in which to search for dylib files. |
| package_name(str): Name of the ios usb dependency passed in. |
| """ |
| directory_paths = api.file.listdir( |
| "checking dylib file inside: %s" % dir, |
| dir, |
| test_data=[ |
| dir.join("libimobiledevice-1.0.6.dylib"), |
| dir.join("libplist-2.0.3.dylib") |
| ] |
| ) |
| directory_string_paths = [('%s' % path) for path in directory_paths] |
| matched_dylib_names = [] |
| |
| pattern = DIRNAME_PATTERN_DICT[package_name] |
| for dylib_path in directory_string_paths: |
| # re.match searches from beginning of string |
| match = re.match(pattern, dylib_path) |
| if match: |
| matched_dylib_names.append(dylib_path.split("/")[-1]) |
| return matched_dylib_names |
| |
| |
| def EmbedCodesignConfiguration(api, package_out_dir, package_name): |
| """Embed metadata file for Mac code signing into ios usb dependency artifacts. |
| |
| Two files are embedded into the generated artifact. |
| entitlements.txt: The list of binaries that need to be code signed with entitlements. |
| without_entitlements.txt: Filenames of binaries in artifacts that should be code signed, but not with entitlements. |
| |
| Args: |
| |
| package_out_dir(Path): The directory in which ios usb dependency artifact is generated. |
| package_name(str): Name of the ios usb dependency passed in. |
| """ |
| entitlement_file_contents = [] |
| without_entitlement_file_contents = [] |
| if package_name == "ios-deploy": |
| entitlement_file_contents = ["ios-deploy"] |
| elif package_name == "libimobiledevice": |
| entitlement_file_contents = [ |
| "idevicescreenshot", |
| "idevicesyslog", |
| ] |
| without_entitlement_file_contents = GetDylibFilenames( |
| api, package_out_dir, package_name |
| ) |
| elif package_name == "libplist": |
| without_entitlement_file_contents = GetDylibFilenames( |
| api, package_out_dir, package_name |
| ) |
| elif package_name == "libusbmuxd": |
| entitlement_file_contents = [ |
| "iproxy", |
| ] |
| without_entitlement_file_contents = GetDylibFilenames( |
| api, package_out_dir, package_name |
| ) |
| elif package_name == "openssl": |
| without_entitlement_file_contents = GetDylibFilenames( |
| api, package_out_dir, "libssl" |
| ) + GetDylibFilenames(api, package_out_dir, "libcrypto") |
| elif package_name == "libimobiledeviceglue": |
| without_entitlement_file_contents = GetDylibFilenames( |
| api, package_out_dir, package_name |
| ) |
| |
| api.file.write_text( |
| "writing entitlements codesign list for %s" % package_name, |
| package_out_dir.join("entitlements.txt"), |
| '\n'.join(entitlement_file_contents) + '\n' |
| ) |
| api.file.write_text( |
| "writing the list of files to be codesigned without entitlements for %s" % |
| package_name, package_out_dir.join("without_entitlements.txt"), |
| '\n'.join(without_entitlement_file_contents) + '\n' |
| ) |
| |
| |
| def UploadPackage( |
| api, package_name, work_dir, package_out_dir, upload, commit_sha |
| ): |
| """Upload package artifacts to GCS. |
| |
| Args: |
| |
| package_name(str): the name of the package to be built. |
| work_dir(path): path to work dir. |
| package_out_dir(bool): path to the artifacts location. |
| upload(bool): a flag indicating whether there are artifacts to upload. |
| commit_sha(str): commit sha of current build. |
| """ |
| if not upload: |
| return |
| EmbedCodesignConfiguration(api, package_out_dir, package_name) |
| package_zip_file = work_dir.join('%s.zip' % package_name) |
| api.zip.directory( |
| 'zipping %s dir' % package_name, package_out_dir, package_zip_file |
| ) |
| api.gsutil.upload( |
| package_zip_file, |
| BUCKET_NAME, |
| GetCloudPath(api, package_name, commit_sha), |
| link_name='%s.zip' % package_name, |
| name='upload of %s.zip' % package_name, |
| ) |
| |
| |
| def BuildPackage( |
| api, |
| env, |
| env_prefixes, |
| package_name, |
| upload=False, |
| update_path=False, |
| update_library_path=False, |
| update_pkg_config_path=False |
| ): |
| """Builds packages and and upload artifacts to GCS. |
| |
| Args: |
| |
| env(dict): current environment variables. |
| env_prefixes(dict): current environment prefixes variables. |
| package_name(str): the name of the package to be built. |
| upload(bool): a flag indicating whether there are artifacts to upload. |
| update_path(bool): a flag indicating whether there are PATH updates. |
| update_library_path(bool): a flag indicating whether there are LIBRARY_PATH updates. |
| update_pkg_config_path(bool): a flag indicating whether there are PKG_CONFIG_PATH updates. |
| """ |
| work_dir = api.path['start_dir'] |
| src_dir = work_dir.join('src') |
| package_src_dir = work_dir.join('src').join(package_name) |
| package_install_dir = work_dir.join('src').join('%s_install' % package_name) |
| package_out_dir = src_dir.join('%s_output' % package_name) |
| api.file.ensure_directory('mkdir %s' % package_src_dir, package_src_dir) |
| api.file.ensure_directory( |
| 'mkdir %s' % package_install_dir, package_install_dir |
| ) |
| api.file.ensure_directory('mkdir %s' % package_out_dir, package_out_dir) |
| |
| build_script = api.resource('%s.sh' % package_name) |
| api.step('make %s executable' % build_script, ['chmod', '777', build_script]) |
| with api.context(env=env, env_prefixes=env_prefixes): |
| api.step( |
| 'install %s' % package_name, |
| [build_script, package_src_dir, package_install_dir, package_out_dir] |
| ) |
| commit_sha_file = package_src_dir.join('commit_sha.txt') |
| commit_sha = None |
| if api.path.exists(commit_sha_file): |
| commit_sha = api.file.read_text( |
| 'read commit_sha.txt for %s' % package_name, commit_sha_file |
| ).strip() |
| UpdateEnv( |
| api, env, env_prefixes, package_name, package_install_dir, update_path, |
| update_library_path, update_pkg_config_path |
| ) |
| PatchLoadPath(api, package_out_dir, package_name) |
| UploadPackage( |
| api, package_name, work_dir, package_out_dir, upload, commit_sha |
| ) |
| |
| |
| def RunSteps(api): |
| with api.osx_sdk('ios', devicelab=True): |
| env_prefixes = {'PATH': [], 'LIBRARY_PATH': []} |
| env = {} |
| BuildPackage(api, env, env_prefixes, 'ios-deploy', upload=True) |
| BuildPackage( |
| api, env, env_prefixes, 'libplist', upload=True, update_path=True |
| ) |
| BuildPackage(api, env, env_prefixes, 'bison', update_path=True) |
| BuildPackage(api, env, env_prefixes, 'libtasn1', update_path=True) |
| BuildPackage(api, env, env_prefixes, 'libusb', update_library_path=True) |
| BuildPackage( |
| api, |
| env, |
| env_prefixes, |
| 'libimobiledeviceglue', |
| upload=True, |
| update_library_path=True, |
| update_pkg_config_path=True |
| ) |
| BuildPackage( |
| api, |
| env, |
| env_prefixes, |
| 'libusbmuxd', |
| upload=True, |
| update_path=True, |
| update_pkg_config_path=True |
| ) |
| BuildPackage( |
| api, |
| env, |
| env_prefixes, |
| 'openssl', |
| upload=True, |
| update_path=True, |
| update_library_path=True, |
| update_pkg_config_path=True |
| ) |
| BuildPackage(api, env, env_prefixes, 'libimobiledevice', upload=True) |
| |
| |
| def GenTests(api): |
| yield api.test( |
| 'basic', |
| api.path.exists( |
| api.path['start_dir'].join('src').join('ios-deploy' |
| ).join('commit_sha.txt'), |
| ), |
| api.step_data( |
| 'Get linked paths from iproxy before patch', |
| stdout=api.raw_io.output_text( |
| '\t/opt/s/w/ir/x/w/src/libusbmuxd_install/lib/libusbmuxd-2.0.6.dylib (compatibility version 7.0.0, current version 7.0.0)' |
| ), |
| retcode=0 |
| ) |
| ) |