109 lines
8.3 KiB
Python
109 lines
8.3 KiB
Python
"""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="<group>")
|
|
refs.append(ref)
|
|
builds.append(add("build/" + path.relative_to(ROOT).as_posix(), "PBXBuildFile", fileRef=ref))
|
|
return add(folder, "PBXGroup", path=folder, sourceTree="<group>", 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="<group>")
|
|
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="<group>", children=products)
|
|
main_group = add("main", "PBXGroup", sourceTree="<group>", 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'<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="{uid(target + "target")}" BuildableName="{product}" BlueprintName="{target}" ReferencedContainer="container:MusicBridge.xcodeproj"/>'
|
|
app_ref = reference("MusicBridge", "MusicBridge.app")
|
|
test_ref = reference("MusicBridgeTests", "MusicBridgeTests.xctest")
|
|
(scheme_dir / "MusicBridge.xcscheme").write_bytes(f'''<?xml version="1.0" encoding="UTF-8"?>
|
|
<Scheme LastUpgradeVersion="1600" version="1.3">
|
|
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES"><BuildActionEntries>
|
|
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">{app_ref}</BuildActionEntry>
|
|
</BuildActionEntries></BuildAction>
|
|
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv="YES"><Testables><TestableReference skipped="NO">{test_ref}</TestableReference></Testables></TestAction>
|
|
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.IDEFoundation.Launcher.LLDB" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES"><BuildableProductRunnable runnableDebuggingMode="0">{app_ref}</BuildableProductRunnable></LaunchAction>
|
|
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES"><BuildableProductRunnable runnableDebuggingMode="0">{app_ref}</BuildableProductRunnable></ProfileAction>
|
|
<AnalyzeAction buildConfiguration="Debug"/><ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/>
|
|
</Scheme>'''.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.")
|