Files
Leeksov 4647310322 GLEGram 12.5 — Initial public release
Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
2026-04-06 09:48:12 +03:00

565 lines
26 KiB
Swift

import Foundation
import ProjectSpec
import XcodeProj
import PathKit
private func suitableConfig(for type: ConfigType, in project: Project) -> Config {
if let defaultConfig = Config.defaultConfigs.first(where: { $0.type == type }),
project.configs.contains(defaultConfig) {
return defaultConfig
}
return project.configs.first { $0.type == type }!
}
public class SchemeGenerator {
let project: Project
let pbxProj: PBXProj
var defaultDebugConfig: Config {
suitableConfig(for: .debug, in: project)
}
var defaultReleaseConfig: Config {
suitableConfig(for: .release, in: project)
}
public init(project: Project, pbxProj: PBXProj) {
self.project = project
self.pbxProj = pbxProj
}
private var projects: [ProjectReference: PBXProj] = [:]
func getPBXProj(from reference: ProjectReference) throws -> PBXProj {
if let cachedProject = projects[reference] {
return cachedProject
}
let pbxproj = try XcodeProj(path: project.basePath + Path(reference.path)).pbxproj
projects[reference] = pbxproj
return pbxproj
}
public func generateSchemes() throws -> (
shared: [XCScheme],
user: [XCScheme],
management: XCSchemeManagement?
) {
var schemes: [(Scheme, ProjectTarget?)] = []
for scheme in project.schemes {
schemes.append((scheme, nil))
}
for target in project.projectTargets {
if let targetScheme = target.scheme {
if targetScheme.configVariants.isEmpty {
let schemeName = target.name
let debugConfig = suitableConfig(for: .debug, in: project)
let releaseConfig = suitableConfig(for: .release, in: project)
let scheme = Scheme(
name: schemeName,
target: target,
targetScheme: targetScheme,
project: project,
debugConfig: debugConfig.name,
releaseConfig: releaseConfig.name
)
schemes.append((scheme, target))
} else {
for configVariant in targetScheme.configVariants {
let schemeName = "\(target.name) \(configVariant)"
let debugConfig = project.configs
.first(including: configVariant, for: .debug)!
let releaseConfig = project.configs
.first(including: configVariant, for: .release)!
let scheme = Scheme(
name: schemeName,
target: target,
targetScheme: targetScheme,
project: project,
debugConfig: debugConfig.name,
releaseConfig: releaseConfig.name
)
schemes.append((scheme, target))
}
}
}
}
var sharedSchemes: [XCScheme] = []
var userSchemes: [XCScheme] = []
var schemeManagements: [XCSchemeManagement.UserStateScheme] = []
for (scheme, projectTarget) in schemes {
let xcscheme = try generateScheme(scheme, for: projectTarget)
if scheme.management?.shared == false {
userSchemes.append(xcscheme)
} else {
sharedSchemes.append(xcscheme)
}
if let management = scheme.management {
schemeManagements.append(
XCSchemeManagement.UserStateScheme(
name: scheme.name + ".xcscheme",
shared: management.shared,
orderHint: management.orderHint,
isShown: management.isShown
)
)
}
}
return (
shared: sharedSchemes,
user: userSchemes,
management: schemeManagements.isEmpty
? nil
: XCSchemeManagement(schemeUserState: schemeManagements, suppressBuildableAutocreation: nil)
)
}
public func generateScheme(_ scheme: Scheme, for target: ProjectTarget? = nil) throws -> XCScheme {
func getBuildableReference(_ target: TargetReference) throws -> XCScheme.BuildableReference {
let pbxProj: PBXProj
let projectFilePath: String
switch target.location {
case .project(let project):
guard let projectReference = self.project.getProjectReference(project) else {
throw SchemeGenerationError.missingProject(project)
}
pbxProj = try getPBXProj(from: projectReference)
projectFilePath = projectReference.path
case .local:
pbxProj = self.pbxProj
projectFilePath = "\(self.project.name).xcodeproj"
}
guard let pbxTarget = pbxProj.targets(named: target.name).first else {
throw SchemeGenerationError.missingTarget(target, projectPath: projectFilePath)
}
let buildableName: String
switch target.location {
case .project:
buildableName = pbxTarget.productNameWithExtension() ?? pbxTarget.name
case .local:
guard let _buildableName =
project.getTarget(target.name)?.filename ??
project.getAggregateTarget(target.name)?.name else {
fatalError("Unable to determinate \"buildableName\" for build target: \(target)")
}
buildableName = _buildableName
}
return XCScheme.BuildableReference(
referencedContainer: "container:\(projectFilePath)",
blueprint: pbxTarget,
buildableName: buildableName,
blueprintName: target.name
)
}
func getBuildableTestableReference(_ target: TestableTargetReference) throws -> XCScheme.BuildableReference {
switch target.location {
case .package(let packageName):
guard let package = self.project.getPackage(packageName),
case let .local(path, _, _) = package else {
throw SchemeGenerationError.missingPackage(packageName)
}
return XCScheme.BuildableReference(
referencedContainer: "container:\(path)",
blueprintIdentifier: target.name,
buildableName: target.name,
blueprintName: target.name
)
default:
return try getBuildableReference(target.targetReference)
}
}
func getBuildEntry(_ buildTarget: Scheme.BuildTarget) throws -> XCScheme.BuildAction.Entry {
let buildableReference = try getBuildableTestableReference(buildTarget.target)
return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes)
}
let testTargets = scheme.test?.targets ?? []
let testBuildTargets = testTargets.map {
Scheme.BuildTarget(target: $0.targetReference, buildTypes: BuildType.testOnly)
}
let testBuildTargetEntries = try testBuildTargets.map(getBuildEntry)
let buildActionEntries: [XCScheme.BuildAction.Entry] = try scheme.build.targets.map(getBuildEntry)
func getExecutionAction(_ action: Scheme.ExecutionAction) -> XCScheme.ExecutionAction {
// ExecutionActions can require the use of build settings. Xcode allows the settings to come from a build or test target.
let environmentBuildable = action.settingsTarget.flatMap { settingsTarget in
(buildActionEntries + testBuildTargetEntries)
.first { settingsTarget == $0.buildableReference.blueprintName }?
.buildableReference
}
return XCScheme.ExecutionAction(
scriptText: action.script,
title: action.name,
shellToInvoke: action.shell,
environmentBuildable: environmentBuildable
)
}
let schemeTarget: ProjectTarget?
if let targetName = scheme.run?.executable {
schemeTarget = project.getTarget(targetName)
} else {
guard let firstTarget = scheme.build.targets.first else {
throw SchemeGenerationError.missingBuildTargets(scheme.name)
}
let name = scheme.build.targets.first { $0.buildTypes.contains(.running) }?.target.name ?? firstTarget.target.name
schemeTarget = target ?? project.getTarget(name)
}
let shouldExecuteOnLaunch = schemeTarget?.shouldExecuteOnLaunch == true
let buildableReference = buildActionEntries.first(where: { $0.buildableReference.blueprintName == schemeTarget?.name })?.buildableReference ?? buildActionEntries.first!.buildableReference
let runnables = makeProductRunnables(for: schemeTarget, buildableReference: buildableReference)
let buildAction = XCScheme.BuildAction(
buildActionEntries: buildActionEntries,
preActions: scheme.build.preActions.map(getExecutionAction),
postActions: scheme.build.postActions.map(getExecutionAction),
parallelizeBuild: scheme.build.parallelizeBuild,
buildImplicitDependencies: scheme.build.buildImplicitDependencies,
runPostActionsOnFailure: scheme.build.runPostActionsOnFailure
)
let testables: [XCScheme.TestableReference] = zip(testTargets, testBuildTargetEntries).map { testTarget, testBuildEntries in
var locationScenarioReference: XCScheme.LocationScenarioReference?
if var location = testTarget.location {
if location.contains(".gpx") {
var path = Path(components: [project.options.schemePathPrefix, location])
path = path.simplifyingParentDirectoryReferences()
location = path.string
}
let referenceType = location.contains(".gpx") ? "0" : "1"
locationScenarioReference = XCScheme.LocationScenarioReference(identifier: location, referenceType: referenceType)
}
return XCScheme.TestableReference(
skipped: testTarget.skipped,
parallelizable: testTarget.parallelizable,
randomExecutionOrdering: testTarget.randomExecutionOrder,
buildableReference: testBuildEntries.buildableReference,
locationScenarioReference: locationScenarioReference,
skippedTests: testTarget.skippedTests.map(XCScheme.TestItem.init),
selectedTests: testTarget.selectedTests.map(XCScheme.TestItem.init),
useTestSelectionWhitelist: !testTarget.selectedTests.isEmpty ? true : nil
)
}
let coverageBuildableTargets = try scheme.test?.coverageTargets.map {
try getBuildableTestableReference($0)
} ?? []
let testCommandLineArgs = scheme.test.map { XCScheme.CommandLineArguments($0.commandLineArguments) }
let launchCommandLineArgs = scheme.run.map { XCScheme.CommandLineArguments($0.commandLineArguments) }
let profileCommandLineArgs = scheme.profile.map { XCScheme.CommandLineArguments($0.commandLineArguments) }
let testVariables = scheme.test.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables }
let launchVariables = scheme.run.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables }
let profileVariables = scheme.profile.flatMap { $0.environmentVariables.isEmpty ? nil : $0.environmentVariables }
let defaultTestPlanIndex = scheme.test?.testPlans.firstIndex { $0.defaultPlan } ?? 0
let testPlans = scheme.test?.testPlans.enumerated().map { index, testPlan in
XCScheme.TestPlanReference(reference: "container:\(testPlan.path)", default: defaultTestPlanIndex == index)
} ?? []
let testBuildableEntries = buildActionEntries.filter({ $0.buildFor.contains(.testing) }) + testBuildTargetEntries
let testMacroExpansionBuildableRef = testBuildableEntries.map(\.buildableReference).contains(buildableReference) ? buildableReference : testBuildableEntries.first?.buildableReference
let testMacroExpansion: XCScheme.BuildableReference = buildActionEntries.first(
where: { value in
if let macroExpansion = scheme.test?.macroExpansion {
return value.buildableReference.blueprintName == macroExpansion
}
return false
}
)?.buildableReference ?? testMacroExpansionBuildableRef ?? buildableReference
let testAction = XCScheme.TestAction(
buildConfiguration: scheme.test?.config ?? defaultDebugConfig.name,
macroExpansion: testMacroExpansion,
testables: testables,
testPlans: testPlans.isEmpty ? nil : testPlans,
preActions: scheme.test?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.test?.postActions.map(getExecutionAction) ?? [],
selectedDebuggerIdentifier: (scheme.test?.debugEnabled ?? Scheme.Test.debugEnabledDefault) ? XCScheme.defaultDebugger : "",
selectedLauncherIdentifier: (scheme.test?.debugEnabled ?? Scheme.Test.debugEnabledDefault) ? XCScheme.defaultLauncher : "Xcode.IDEFoundation.Launcher.PosixSpawn",
shouldUseLaunchSchemeArgsEnv: scheme.test?.shouldUseLaunchSchemeArgsEnv ?? true,
codeCoverageEnabled: scheme.test?.gatherCoverageData ?? Scheme.Test.gatherCoverageDataDefault,
codeCoverageTargets: coverageBuildableTargets,
onlyGenerateCoverageForSpecifiedTargets: !coverageBuildableTargets.isEmpty,
disableMainThreadChecker: scheme.test?.disableMainThreadChecker ?? Scheme.Test.disableMainThreadCheckerDefault,
commandlineArguments: testCommandLineArgs,
environmentVariables: testVariables,
language: scheme.test?.language,
region: scheme.test?.region,
systemAttachmentLifetime: scheme.test?.systemAttachmentLifetime,
preferredScreenCaptureFormat: scheme.test?.preferredScreenCaptureFormat,
customLLDBInitFile: scheme.test?.customLLDBInit
)
let allowLocationSimulation = scheme.run?.simulateLocation?.allow ?? true
var locationScenarioReference: XCScheme.LocationScenarioReference?
if let simulateLocation = scheme.run?.simulateLocation, var identifier = simulateLocation.defaultLocation, let referenceType = simulateLocation.referenceType {
if referenceType == .gpx {
var path = Path(components: [project.options.schemePathPrefix, identifier])
path = path.simplifyingParentDirectoryReferences()
identifier = path.string
}
locationScenarioReference = XCScheme.LocationScenarioReference(identifier: identifier, referenceType: referenceType.rawValue)
}
var storeKitConfigurationFileReference: XCScheme.StoreKitConfigurationFileReference?
if let storeKitConfiguration = scheme.run?.storeKitConfiguration {
let storeKitConfigurationPath = Path(components: [project.options.schemePathPrefix, storeKitConfiguration]).simplifyingParentDirectoryReferences()
storeKitConfigurationFileReference = XCScheme.StoreKitConfigurationFileReference(identifier: storeKitConfigurationPath.string)
}
let macroExpansion: XCScheme.BuildableReference?
if let macroExpansionName = scheme.run?.macroExpansion,
let resolvedMacroExpansion = buildActionEntries.first(where: { $0.buildableReference.blueprintName == macroExpansionName })?.buildableReference {
macroExpansion = resolvedMacroExpansion
} else {
macroExpansion = shouldExecuteOnLaunch ? nil : buildableReference
}
let launchAction = XCScheme.LaunchAction(
runnable: shouldExecuteOnLaunch ? runnables.launch : nil,
buildConfiguration: scheme.run?.config ?? defaultDebugConfig.name,
preActions: scheme.run?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.run?.postActions.map(getExecutionAction) ?? [],
macroExpansion: macroExpansion,
selectedDebuggerIdentifier: selectedDebuggerIdentifier(for: schemeTarget, run: scheme.run),
selectedLauncherIdentifier: selectedLauncherIdentifier(for: schemeTarget, run: scheme.run),
askForAppToLaunch: scheme.run?.askForAppToLaunch,
allowLocationSimulation: allowLocationSimulation,
locationScenarioReference: locationScenarioReference,
enableGPUFrameCaptureMode: scheme.run?.enableGPUFrameCaptureMode ?? XCScheme.LaunchAction.defaultGPUFrameCaptureMode,
disableGPUValidationMode: !(scheme.run?.enableGPUValidationMode ?? Scheme.Run.enableGPUValidationModeDefault),
disableMainThreadChecker: scheme.run?.disableMainThreadChecker ?? Scheme.Run.disableMainThreadCheckerDefault,
disablePerformanceAntipatternChecker: scheme.run?.disableThreadPerformanceChecker ?? Scheme.Run.disableThreadPerformanceCheckerDefault,
stopOnEveryMainThreadCheckerIssue: scheme.run?.stopOnEveryMainThreadCheckerIssue ?? Scheme.Run.stopOnEveryMainThreadCheckerIssueDefault,
commandlineArguments: launchCommandLineArgs,
environmentVariables: launchVariables,
language: scheme.run?.language,
region: scheme.run?.region,
launchAutomaticallySubstyle: scheme.run?.launchAutomaticallySubstyle ?? launchAutomaticallySubstyle(for: schemeTarget),
storeKitConfigurationFileReference: storeKitConfigurationFileReference,
customLLDBInitFile: scheme.run?.customLLDBInit
)
let profileAction = XCScheme.ProfileAction(
buildableProductRunnable: shouldExecuteOnLaunch ? runnables.profile : nil,
buildConfiguration: scheme.profile?.config ?? defaultReleaseConfig.name,
preActions: scheme.profile?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.profile?.postActions.map(getExecutionAction) ?? [],
macroExpansion: shouldExecuteOnLaunch ? nil : buildableReference,
shouldUseLaunchSchemeArgsEnv: scheme.profile?.shouldUseLaunchSchemeArgsEnv ?? true,
askForAppToLaunch: scheme.profile?.askForAppToLaunch,
commandlineArguments: profileCommandLineArgs,
environmentVariables: profileVariables
)
let analyzeAction = XCScheme.AnalyzeAction(buildConfiguration: scheme.analyze?.config ?? defaultDebugConfig.name)
let archiveAction = XCScheme.ArchiveAction(
buildConfiguration: scheme.archive?.config ?? defaultReleaseConfig.name,
revealArchiveInOrganizer: scheme.archive?.revealArchiveInOrganizer ?? true,
customArchiveName: scheme.archive?.customArchiveName,
preActions: scheme.archive?.preActions.map(getExecutionAction) ?? [],
postActions: scheme.archive?.postActions.map(getExecutionAction) ?? []
)
let lastUpgradeVersion = project.attributes["LastUpgradeCheck"] as? String ?? project.xcodeVersion
return XCScheme(
name: scheme.name,
lastUpgradeVersion: lastUpgradeVersion,
version: project.schemeVersion,
buildAction: buildAction,
testAction: testAction,
launchAction: launchAction,
profileAction: profileAction,
analyzeAction: analyzeAction,
archiveAction: archiveAction,
wasCreatedForAppExtension: schemeTarget
.flatMap { $0.type.isExtension ? true : nil }
)
}
private func launchAutomaticallySubstyle(for target: ProjectTarget?) -> String? {
if target?.type.isExtension == true {
return "2"
} else {
return nil
}
}
private func makeProductRunnables(for target: ProjectTarget?, buildableReference: XCScheme.BuildableReference) -> (launch: XCScheme.Runnable, profile: XCScheme.BuildableProductRunnable) {
let buildable = XCScheme.BuildableProductRunnable(buildableReference: buildableReference)
if target?.type.isWatchApp == true {
let remote = XCScheme.RemoteRunnable(
buildableReference: buildableReference,
bundleIdentifier: "com.apple.Carousel",
runnableDebuggingMode: "2"
)
return (remote, buildable)
} else {
return (buildable, buildable)
}
}
private func selectedDebuggerIdentifier(for target: ProjectTarget?, run: Scheme.Run?) -> String {
if target?.type.canUseDebugLauncher != false && run?.debugEnabled ?? Scheme.Run.debugEnabledDefault {
return XCScheme.defaultDebugger
} else {
return ""
}
}
private func selectedLauncherIdentifier(for target: ProjectTarget?, run: Scheme.Run?) -> String {
if target?.type.canUseDebugLauncher != false && run?.debugEnabled ?? Scheme.Run.debugEnabledDefault {
return XCScheme.defaultLauncher
} else {
return "Xcode.IDEFoundation.Launcher.PosixSpawn"
}
}
}
enum SchemeGenerationError: Error, CustomStringConvertible {
case missingTarget(TargetReference, projectPath: String)
case missingPackage(String)
case missingProject(String)
case missingBuildTargets(String)
var description: String {
switch self {
case .missingTarget(let target, let projectPath):
return "Unable to find target named \"\(target)\" in \"\(projectPath)\""
case .missingProject(let project):
return "Unable to find project reference named \"\(project)\" in project.yml"
case .missingBuildTargets(let name):
return "Unable to find at least one build target in scheme \"\(name)\""
case .missingPackage(let package):
return "Unable to find swift package named \"\(package)\" in project.yml"
}
}
}
extension Scheme {
public init(name: String, target: ProjectTarget, targetScheme: TargetScheme, project: Project, debugConfig: String, releaseConfig: String) {
self.init(
name: name,
build: .init(
targets: Scheme.buildTargets(for: target, project: project),
buildImplicitDependencies: targetScheme.buildImplicitDependencies,
preActions: targetScheme.preActions,
postActions: targetScheme.postActions
),
run: .init(
config: debugConfig,
commandLineArguments: targetScheme.commandLineArguments,
environmentVariables: targetScheme.environmentVariables,
disableMainThreadChecker: targetScheme.disableMainThreadChecker,
stopOnEveryMainThreadCheckerIssue: targetScheme.stopOnEveryMainThreadCheckerIssue,
disableThreadPerformanceChecker: targetScheme.disableThreadPerformanceChecker,
language: targetScheme.language,
region: targetScheme.region,
storeKitConfiguration: targetScheme.storeKitConfiguration
),
test: .init(
config: debugConfig,
gatherCoverageData: targetScheme.gatherCoverageData,
coverageTargets: targetScheme.coverageTargets,
disableMainThreadChecker: targetScheme.disableMainThreadChecker,
commandLineArguments: targetScheme.commandLineArguments,
targets: targetScheme.testTargets,
environmentVariables: targetScheme.environmentVariables,
testPlans: targetScheme.testPlans,
language: targetScheme.language,
region: targetScheme.region
),
profile: .init(
config: releaseConfig,
commandLineArguments: targetScheme.commandLineArguments,
environmentVariables: targetScheme.environmentVariables
),
analyze: .init(
config: debugConfig
),
archive: .init(
config: releaseConfig
),
management: targetScheme.management
)
}
private static func buildTargets(for target: ProjectTarget, project: Project) -> [BuildTarget] {
let buildTarget = Scheme.BuildTarget(target: TestableTargetReference.local(target.name))
switch target.type {
case .watchApp, .watch2App:
let hostTarget = project.targets
.first { projectTarget in
projectTarget.dependencies.contains { $0.reference == target.name }
}
.map { BuildTarget(target: TestableTargetReference.local($0.name)) }
return hostTarget.map { [buildTarget, $0] } ?? [buildTarget]
default:
return [buildTarget]
}
}
}
extension PBXProductType {
var canUseDebugLauncher: Bool {
// Extensions don't use the lldb launcher
return !isExtension
}
var isWatchApp: Bool {
switch self {
case .watchApp, .watch2App:
return true
default:
return false
}
}
}
extension Scheme.Test {
var systemAttachmentLifetime: XCScheme.TestAction.AttachmentLifetime? {
switch (captureScreenshotsAutomatically, deleteScreenshotsWhenEachTestSucceeds) {
case (false, _):
return .keepNever
case (true, false):
return .keepAlways
case (true, true):
return nil
}
}
}