362 lines
13 KiB
Python
Executable File
362 lines
13 KiB
Python
Executable File
#!/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 int(xcode_version.split('.')[0]) >= 12:
|
|
mac_xcspec_location = 'Platforms/MacOSX.platform/Developer/Library/Xcode/PrivatePlugIns/IDEOSXSupportCore.ideplugin/Contents/Resources'
|
|
else:
|
|
mac_xcspec_location = 'Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications'
|
|
|
|
XCSPEC_INFO = [dict(
|
|
id='com.apple.product-type.tool',
|
|
dest='../PlugIns/IDEiOSSupportCore.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='../PlugIns/IDEiOSSupportCore.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), 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()
|