| #!/usr/bin/env python3 |
| # |
| # Copyright (C) 2014-2020 Apple Inc. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions |
| # are met: |
| # |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # |
| # THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import errno |
| import os |
| import pathlib |
| import re |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
| |
| from xml.etree import ElementTree as ET |
| |
| # We always want the real system version |
| os.environ['SYSTEM_VERSION_COMPAT'] = '0' |
| |
| MISSING_HEADERS = [ |
| "usr/include/libxslt", |
| "usr/include/mach/mach.h", |
| "usr/include/mach/mach_error.h", |
| "usr/include/mach/mach_types.defs", |
| "usr/include/mach/machine/machine_types.defs", |
| "usr/include/mach/std_types.defs", |
| "usr/include/mach/task.h", |
| "usr/include/objc/Protocol.h", |
| "usr/include/objc/objc-class.h", |
| "usr/include/objc/objc-runtime.h", |
| "usr/include/readline/history.h", |
| "usr/include/readline/readline.h", |
| ] |
| |
| MISSING_FRAMEWORKS = [ |
| ("AVKit.framework", True), |
| ("AudioToolbox.framework", True), |
| ("AudioUnit.framework", False), |
| ("CFNetwork.framework", True), |
| ("CoreImage.framework", False), |
| ("IOKit.framework", True), |
| ("IOSurface.framework", True), |
| ("LocalAuthentication.framework", False), |
| ("MediaAccessibility.framework", True), |
| ("MediaToolbox.framework", False), |
| ("Metal.framework", True), |
| ("OpenGLES.framework", True), |
| ("QuartzCore.framework", True), |
| ("UIKit.framework", True), |
| ("VideoToolbox.framework", False), |
| ] |
| |
| xcode_version = subprocess.run(['/usr/bin/xcodebuild', '-version'], capture_output=True, encoding='ascii').stdout.splitlines()[0].split(' ')[1] |
| if tuple(int(n) for n in xcode_version.split('.')) >= (13, 3): |
| mac_xcspec_location = f'Platforms/MacOSX.platform/Developer/Library/Xcode/PrivatePlugIns/IDEOSXSupportCore.xcplugindata/Contents/Resources' |
| elif int(xcode_version.split('.')[0]) >= 12: |
| mac_xcspec_location = f'Platforms/MacOSX.platform/Developer/Library/Xcode/PrivatePlugIns/IDEOSXSupportCore.ideplugin/Contents/Resources' |
| else: |
| mac_xcspec_location = 'Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications' |
| |
| ideplugin = 'XCBSpecifications' if tuple(int(n) for n in xcode_version.split('.')) >= (13, 3) else 'IDEiOSSupportCore' |
| |
| XCSPEC_INFO = [dict( |
| id='com.apple.product-type.tool', |
| dest=f'../PlugIns/{ideplugin}.ideplugin/Contents/Resources/Embedded-Shared.xcspec', |
| content=''' |
| // Tool (normal Unix command-line executable) |
| { Type = ProductType; |
| Identifier = com.apple.product-type.tool; |
| Class = PBXToolProductType; |
| Name = "Command-line Tool"; |
| Description = "Standalone command-line tool"; |
| IconNamePrefix = "TargetExecutable"; |
| DefaultTargetName = "Command-line Tool"; |
| DefaultBuildProperties = { |
| FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)"; |
| MACH_O_TYPE = "mh_execute"; |
| EXECUTABLE_PREFIX = ""; |
| EXECUTABLE_SUFFIX = ""; |
| REZ_EXECUTABLE = YES; |
| INSTALL_PATH = "/usr/local/bin"; |
| FRAMEWORK_FLAG_PREFIX = "-framework"; |
| LIBRARY_FLAG_PREFIX = "-l"; |
| LIBRARY_FLAG_NOSPACE = YES; |
| GCC_DYNAMIC_NO_PIC = NO; |
| GCC_SYMBOLS_PRIVATE_EXTERN = YES; |
| GCC_INLINES_ARE_PRIVATE_EXTERN = YES; |
| STRIP_STYLE = "all"; |
| CODE_SIGNING_ALLOWED = YES; |
| }; |
| PackageTypes = ( |
| com.apple.package-type.mach-o-executable // default |
| ); |
| WantsSigningEditing = YES; |
| WantsBundleIdentifierEditing = YES; |
| } |
| ''', |
| ), dict( |
| id='com.apple.package-type.mach-o-executable', |
| dest=f'../PlugIns/{ideplugin}.ideplugin/Contents/Resources/Embedded-Shared.xcspec', |
| content=''' |
| { Type = PackageType; |
| Identifier = com.apple.package-type.mach-o-executable; |
| Name = "Mach-O Executable"; |
| Description = "Mach-O executable"; |
| DefaultBuildSettings = { |
| EXECUTABLE_PREFIX = ""; |
| EXECUTABLE_SUFFIX = ""; |
| EXECUTABLE_NAME = "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)"; |
| EXECUTABLE_PATH = "$(EXECUTABLE_NAME)"; |
| }; |
| ProductReference = { |
| FileType = compiled.mach-o.executable; |
| Name = "$(EXECUTABLE_NAME)"; |
| IsLaunchable = YES; |
| }; |
| } |
| ''', |
| )] |
| |
| AVAILABILITY_FILE = pathlib.Path("usr/local/include/AvailabilityProhibitedInternal.h") |
| AVAILABILTY_TEXT = """\ |
| // Handle __IOS_PROHIBITED and friends. |
| #undef __OS_AVAILABILITY |
| #define __OS_AVAILABILITY(...) |
| |
| // Take care of {A,S}PI_AVAILABLE{,_BEGIN,_END} |
| #undef __API_AVAILABLE_GET_MACRO |
| #define __API_AVAILABLE_GET_MACRO(...) __NULL_AVAILABILITY |
| |
| #undef SWIFT_AVAILABILITY |
| #define SWIFT_AVAILABILITY __NULL_AVAILABILITY |
| |
| // Take care of {A,S}PI_DEPRECATED{,WITH_REPLACEMENT}{,_BEGIN,_END} |
| #undef __API_DEPRECATED_MSG_GET_MACRO |
| #define __API_DEPRECATED_MSG_GET_MACRO(...) __NULL_AVAILABILITY |
| |
| // Take care of API_UNAVAILABLE{,_BEGIN,_END} |
| #undef __API_UNAVAILABLE_GET_MACRO |
| #define __API_UNAVAILABLE_GET_MACRO(...) __NULL_AVAILABILITY |
| |
| #define __NULL_AVAILABILITY(...) |
| """ |
| |
| SDKS_TO_UPDATE = [ |
| "iphoneos", |
| "iphonesimulator", |
| "appletvos", |
| "appletvsimulator", |
| "watchos", |
| "watchsimulator", |
| ] |
| |
| PLIST_BUDDY_PATH = pathlib.Path("/usr/libexec/PlistBuddy") |
| |
| |
| def xcode_developer_dir(): |
| result = subprocess.run( |
| ["xcode-select", "-p"], |
| capture_output=True, encoding="utf-8", check=True, |
| ) |
| return pathlib.Path(result.stdout.strip()) |
| |
| |
| def sdk_directory(sdk): |
| result = subprocess.run( |
| ["xcrun", "--sdk", sdk, "--show-sdk-path"], |
| capture_output=True, encoding="utf-8", check=True, |
| ) |
| return pathlib.Path(result.stdout.strip()) |
| |
| |
| def get_and_check_sdk_directories(source_sdk, dest_sdk): |
| source_sdk_path = sdk_directory(source_sdk) |
| dest_sdk_path = sdk_directory(dest_sdk) |
| |
| if not source_sdk_path: |
| raise RuntimeError(f"Could not find SDK: {source_sdk}") |
| |
| if not dest_sdk_path: |
| raise RuntimeError(f"Could not find SDK: {dest_sdk}") |
| |
| print(source_sdk_path) |
| print(dest_sdk_path) |
| return source_sdk_path, dest_sdk_path |
| |
| |
| def do_copy(source_path, dest_path): |
| |
| def ensure_parent_exists(path): |
| os.makedirs(path.parent, exist_ok=True) |
| |
| def copy_file(source_path, dest_path): |
| print(f"Copying file") |
| print(f"From: {source_path}") |
| print(f"To: {dest_path}") |
| if dest_path.exists(): |
| dest_path.unlink() |
| ensure_parent_exists(dest_path) |
| shutil.copy2(source_path, dest_path) |
| |
| def copy_directory(source_path, dest_path): |
| print(f"Copying directory") |
| print(f"From: {source_path}") |
| print(f"To: {dest_path}") |
| if dest_path.exists(): |
| shutil.rmtree(dest_path) |
| ensure_parent_exists(dest_path) |
| shutil.copytree(source_path, dest_path) |
| |
| if source_path.is_file(): |
| copy_file(source_path, dest_path) |
| return |
| |
| if source_path.is_dir(): |
| copy_directory(source_path, dest_path) |
| return |
| |
| raise RuntimeError(f"{source_path} does not exist") |
| |
| |
| # .tbd files contain information about how to link a product to a |
| # library/framework. This information includes the architecture that the |
| # library is built for. If we're copying from an SDK that supports one set of |
| # architectures to an SDK that supports another set of architectures, we need |
| # to adjust that information in the .tbd file. |
| # |
| # Note that we don't need to be too particular about this. We only need to |
| # link; we don't need to run. This allows us to simply specify the full set of |
| # architectures for which WebKit is built, regardless of whether the associated |
| # library/framework was actually built for all those architectures. |
| |
| def patch_tbd_architecture(framework_path): |
| for tbd_path in framework_path.glob("*.tbd"): |
| with open(tbd_path, "r") as f: |
| lines = f.readlines() |
| |
| modified_lines = [] |
| for line in lines: |
| if re.match(".*(archs|targets): +\[.*\]", line) is not None: |
| line = re.sub("\[.*\]", "[ i386, x86_64, arm64, arm64e, arm64_32, armv7k ]", line) |
| modified_lines.append(line) |
| |
| with open(tbd_path, "w") as f: |
| f.writelines(modified_lines) |
| |
| |
| # Xcode is driven by .xcspec files. These describe many aspects of the system, |
| # including what to build and how to build it. These .xcspec files can be |
| # global or they can be platform- or SDK-specific. In the case of building |
| # products for the embedded systems, there is some information in some macOS |
| # .xcspec files that need to be transferred to the embedded platforms. This |
| # function finds that information, extracts it from the macOS files, and copies |
| # it to the embedded files. |
| |
| def update_xcspec_files(): |
| for spec_info in XCSPEC_INFO: |
| dest_spec_path = xcode_developer_dir() / spec_info['dest'] |
| if not dest_spec_path.exists(): |
| raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), dest_spec_path) |
| |
| result = subprocess.run( |
| [PLIST_BUDDY_PATH, '-x', '-c', 'Print', dest_spec_path], |
| capture_output=True, encoding='utf-8', check=True, |
| ) |
| if result.returncode != 0: |
| raise OSError(f'Failed to convert {dest_spec_path} to XML') |
| |
| found = False |
| for topLevel in ET.fromstring(result.stdout.strip()): |
| for element in topLevel: |
| for key in element: |
| if key.tag == 'string' and key.text == spec_info['id']: |
| found = True |
| break |
| if found: |
| break |
| if found: |
| break |
| if found: |
| print(f'{spec_info["id"]} alread in {dest_spec_path}') |
| continue |
| |
| with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as temp: |
| temp.write(spec_info['content']) |
| temp.flush() |
| |
| print(f'Inserting: {spec_info["id"]}') |
| print(f'To: {dest_spec_path}') |
| subprocess.run([PLIST_BUDDY_PATH, '-c', 'add 0 dict', dest_spec_path], capture_output=True, check=True) |
| subprocess.run([PLIST_BUDDY_PATH, '-c', f'merge {temp.name} 0', dest_spec_path], capture_output=True, check=True) |
| |
| |
| def copy_missing_headers(source_sdk, dest_sdk): |
| if source_sdk == dest_sdk: |
| return |
| source_sdk_path, dest_sdk_path = get_and_check_sdk_directories(source_sdk, dest_sdk) |
| for missing_header in MISSING_HEADERS: |
| do_copy(source_sdk_path / missing_header, dest_sdk_path / missing_header) |
| |
| |
| def copy_missing_frameworks(source_sdk, dest_sdk): |
| if source_sdk == dest_sdk: |
| return |
| source_sdk_path, dest_sdk_path = get_and_check_sdk_directories(source_sdk, dest_sdk) |
| source_frameworks_path = source_sdk_path / "System" / "Library" / "Frameworks" |
| dest_frameworks_path = dest_sdk_path / "System" / "Library" / "Frameworks" |
| for missing_framework, force in MISSING_FRAMEWORKS: |
| source_framework_path = source_frameworks_path / missing_framework |
| dest_framework_path = dest_frameworks_path / missing_framework |
| if force or not dest_framework_path.exists(): |
| do_copy(source_framework_path, dest_framework_path) |
| patch_tbd_architecture(dest_framework_path) |
| |
| |
| # Some functions that WebKit needs to call are marked as "unavailable" on the |
| # embedded platforms. Create a stub header that will nullify the effect of the |
| # annotations on those functions. Note that there's no guarantee that the |
| # resulting product can run -- we just want to build. |
| |
| def create_availability_header(sdk): |
| sdk_path = sdk_directory(sdk) |
| availability_header_path = sdk_path / AVAILABILITY_FILE |
| print(f"Creating/updating {availability_header_path}") |
| os.makedirs(availability_header_path.parent, exist_ok=True) |
| with open(availability_header_path, "w") as f: |
| f.write(AVAILABILTY_TEXT) |
| |
| |
| def main(): |
| if not os.geteuid() == 0 and not os.access(xcode_developer_dir(), os.R_OK | os.W_OK | os.X_OK, effective_ids=True): |
| raise RuntimeError(f"{__file__} must be run as root") |
| |
| update_xcspec_files() |
| |
| for sdk in SDKS_TO_UPDATE: |
| copy_missing_headers("macosx", sdk) |
| copy_missing_frameworks("iphoneos", sdk) |
| create_availability_header(sdk) |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| main() |