"""Generate a dependency-free Xcode project. Run with Python 3 from any directory.""" import hashlib import json import pathlib import plistlib ROOT = pathlib.Path(__file__).resolve().parents[1] objects = {} def uid(name): return hashlib.sha256(name.encode()).hexdigest()[:24].upper() def add(key_name, isa, **fields): key = uid(key_name) objects[key] = dict(isa=isa, **fields) return key def configurations(name, settings): configs = [] for mode in ("Debug", "Release"): values = dict(settings) values.update(SWIFT_OPTIMIZATION_LEVEL="-Onone" if mode == "Debug" else "-O") if mode == "Debug": values.update(ENABLE_TESTABILITY="YES", SWIFT_ACTIVE_COMPILATION_CONDITIONS="DEBUG") configs.append(add(name + mode, "XCBuildConfiguration", name=mode, buildSettings=values)) return add(name + "configs", "XCConfigurationList", buildConfigurations=configs, defaultConfigurationIsVisible=0, defaultConfigurationName="Release") def source_group(folder): refs, builds = [], [] for path in sorted((ROOT / folder).glob("*.swift")): ref = add(path.relative_to(ROOT).as_posix(), "PBXFileReference", lastKnownFileType="sourcecode.swift", path=path.name, sourceTree="") refs.append(ref) builds.append(add("build/" + path.relative_to(ROOT).as_posix(), "PBXBuildFile", fileRef=ref)) return add(folder, "PBXGroup", path=folder, sourceTree="", children=refs), builds app_group, app_sources = source_group("MusicBridge") test_group, test_sources = source_group("MusicBridgeTests") fixture = add("fixture", "PBXFileReference", lastKnownFileType="text.json", path="state-v1.json", sourceTree="") objects[test_group]["children"].append(fixture) fixture_build = add("fixture-build", "PBXBuildFile", fileRef=fixture) products = [] targets = [] for name, sources, is_test in [("MusicBridge", app_sources, False), ("MusicBridgeTests", test_sources, True)]: product = add(name + "product", "PBXFileReference", explicitFileType="wrapper.cfbundle" if is_test else "wrapper.application", includeInIndex=0, path=name + (".xctest" if is_test else ".app"), sourceTree="BUILT_PRODUCTS_DIR") products.append(product) phases = [add(name + "sources", "PBXSourcesBuildPhase", buildActionMask=2147483647, files=sources, runOnlyForDeploymentPostprocessing=0), add(name + "frameworks", "PBXFrameworksBuildPhase", buildActionMask=2147483647, files=[], runOnlyForDeploymentPostprocessing=0), add(name + "resources", "PBXResourcesBuildPhase", buildActionMask=2147483647, files=[fixture_build] if is_test else [], runOnlyForDeploymentPostprocessing=0)] settings = dict(PRODUCT_NAME="$(TARGET_NAME)", PRODUCT_BUNDLE_IDENTIFIER="ru.yukinoki.musicbridge" + (".tests" if is_test else ""), SWIFT_VERSION="5.0", IPHONEOS_DEPLOYMENT_TARGET="17.0", SDKROOT="iphoneos", SUPPORTED_PLATFORMS="iphoneos iphonesimulator", TARGETED_DEVICE_FAMILY="1", CODE_SIGN_STYLE="Automatic", GENERATE_INFOPLIST_FILE="YES" if is_test else "NO") deps = [] if is_test: settings.update(TEST_HOST="$(BUILT_PRODUCTS_DIR)/MusicBridge.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MusicBridge", BUNDLE_LOADER="$(TEST_HOST)") proxy = add("test-proxy", "PBXContainerItemProxy", containerPortal=uid("project"), proxyType=1, remoteGlobalIDString=uid("MusicBridgetarget"), remoteInfo="MusicBridge") deps = [add("test-dependency", "PBXTargetDependency", target=uid("MusicBridgetarget"), targetProxy=proxy)] else: settings.update(INFOPLIST_FILE="MusicBridge/Info.plist", CURRENT_PROJECT_VERSION="1", MARKETING_VERSION="0.1.0") targets.append(add(name + "target", "PBXNativeTarget", name=name, productName=name, productReference=product, productType="com.apple.product-type.bundle.unit-test" if is_test else "com.apple.product-type.application", buildConfigurationList=configurations(name, settings), buildPhases=phases, buildRules=[], dependencies=deps)) product_group = add("products", "PBXGroup", name="Products", sourceTree="", children=products) main_group = add("main", "PBXGroup", sourceTree="", children=[app_group, test_group, product_group]) project = add("project", "PBXProject", attributes=dict(LastUpgradeCheck="1600", BuildIndependentTargetsInParallel="YES"), buildConfigurationList=configurations("project", dict(CLANG_ENABLE_MODULES="YES", CLANG_ENABLE_OBJC_ARC="YES")), compatibilityVersion="Xcode 14.0", developmentRegion="ru", knownRegions=["ru", "en", "Base"], mainGroup=main_group, productRefGroup=product_group, projectDirPath="", projectRoot="", targets=targets) def encode(value, indent=0): if isinstance(value, dict): return "{\n" + "".join("\t" * (indent + 1) + json.dumps(k) + " = " + encode(v, indent + 1) + ";\n" for k, v in value.items()) + "\t" * indent + "}" if isinstance(value, list): return "(" + ", ".join(encode(v, indent) for v in value) + ("," if value else "") + ")" return json.dumps(value, ensure_ascii=True) project_dir = ROOT / "MusicBridge.xcodeproj" project_dir.mkdir(exist_ok=True) (project_dir / "project.pbxproj").write_bytes(("// !$*UTF8*$!\n" + encode(dict(archiveVersion=1, classes={}, objectVersion=56, objects=objects, rootObject=project)) + "\n").encode("utf-8")) scheme_dir = project_dir / "xcshareddata" / "xcschemes" scheme_dir.mkdir(parents=True, exist_ok=True) def reference(target, product): return f'' app_ref = reference("MusicBridge", "MusicBridge.app") test_ref = reference("MusicBridgeTests", "MusicBridgeTests.xctest") (scheme_dir / "MusicBridge.xcscheme").write_bytes(f''' {app_ref} {test_ref} {app_ref} {app_ref} '''.encode("utf-8")) info = dict(CFBundleDevelopmentRegion="ru", CFBundleDisplayName="MusicBridge", CFBundleExecutable="$(EXECUTABLE_NAME)", CFBundleIdentifier="$(PRODUCT_BUNDLE_IDENTIFIER)", CFBundleInfoDictionaryVersion="6.0", CFBundleName="$(PRODUCT_NAME)", CFBundlePackageType="APPL", CFBundleShortVersionString="$(MARKETING_VERSION)", CFBundleVersion="$(CURRENT_PROJECT_VERSION)", LSRequiresIPhoneOS=True, UILaunchScreen={}, UISupportedInterfaceOrientations=["UIInterfaceOrientationPortrait"], NSLocalNetworkUsageDescription="MusicBridge подключается к вашему ПК, чтобы показывать музыку и управлять воспроизведением.", NSAppTransportSecurity=dict(NSAllowsLocalNetworking=True)) (ROOT / "MusicBridge" / "Info.plist").write_bytes(plistlib.dumps(info, sort_keys=False)) print("Generated Xcode project, shared scheme and Info.plist.")