| #!/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' |
| |
| 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'Library/Xcode/Plug-ins/{ideplugin}.ideplugin/Contents/Resources/Embedded-Shared.xcspec' if tuple(int(n) for n in xcode_version.split('.')) >= (14, 3) else 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'Library/Xcode/Plug-ins/{ideplugin}.ideplugin/Contents/Resources/Embedded-Shared.xcspec' if tuple(int(n) for n in xcode_version.split('.')) >= (14, 3) else 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; |
| }; |
| } |
| ''', |
| )] |
| |
| 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()) |
| |
| |
| # 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 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() |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| main() |