blob: d65d32f02ad94ac1c2a959f24e732bf88a728381 [file] [log] [blame]
# 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
)
)