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

509 lines
20 KiB
Swift

import Yams
import XCTest
import Spectre
import PathKit
import XcodeProj
import ProjectSpec
import XcodeGenKit
import TestSupport
extension Project {
func generateXcodeProject(validate: Bool = true, file: String = #file, line: Int = #line) throws -> XcodeProj {
try doThrowing(file: file, line: line) {
if validate {
try self.validate()
}
let generator = ProjectGenerator(project: self)
return try generator.generateXcodeProject(userName: "someUser")
}
}
func generatePbxProj(specValidate: Bool = true, projectValidate: Bool = true, file: String = #file, line: Int = #line) throws -> PBXProj {
try doThrowing(file: file, line: line) {
let xcodeProject = try generateXcodeProject(validate: specValidate).pbxproj
if projectValidate {
try xcodeProject.validate()
}
return xcodeProject
}
}
}
extension PBXProj {
// validates that a PBXProj is correct
// TODO: Use xclint?
func validate() throws {
let mainGroup = try getMainGroup()
func validateGroup(_ group: PBXGroup) throws {
// check for duplicte children
let dictionary = Dictionary(grouping: group.children) { $0.hashValue }
let mostChildren = dictionary.sorted { $0.value.count > $1.value.count }
if let first = mostChildren.first, first.value.count > 1 {
throw failure("Group \"\(group.nameOrPath)\" has duplicated children:\n - \(group.children.map { $0.nameOrPath }.joined(separator: "\n - "))")
}
for child in group.children {
if let group = child as? PBXGroup {
try validateGroup(group)
}
}
}
try validateGroup(mainGroup)
}
func getMainGroup(function: String = #function, file: String = #file, line: Int = #line) throws -> PBXGroup {
guard let mainGroup = projects.first?.mainGroup else {
throw failure("Couldn't find main group", file: file, line: line)
}
return mainGroup
}
}
class PBXProjGeneratorTests: XCTestCase {
func testGroupOrdering() {
describe {
let directoryPath = Path("TestDirectory")
func createDirectories(_ directories: String) throws {
let yaml = try Yams.load(yaml: directories)!
func getFiles(_ file: Any, path: Path) -> [Path] {
if let array = file as? [Any] {
return array.flatMap { getFiles($0, path: path) }
} else if let string = file as? String {
return [path + string]
} else if let dictionary = file as? [String: Any] {
var array: [Path] = []
for (key, value) in dictionary {
array += getFiles(value, path: path + key)
}
return array
} else {
return []
}
}
let files = getFiles(yaml, path: directoryPath).filter { $0.extension != nil }
for file in files {
try file.parent().mkpath()
try file.write("")
}
}
func removeDirectories() {
try? directoryPath.delete()
}
$0.before {
removeDirectories()
}
$0.after {
removeDirectories()
}
$0.it("setups group ordering with groupSortPosition = .top") {
var options = SpecOptions()
options.groupSortPosition = .top
options.groupOrdering = [
GroupOrdering(
order: [
"Sources",
"Resources",
"Tests",
"Support files",
"Configurations",
]
),
GroupOrdering(
pattern: "^.*Screen$",
order: [
"View",
"Presenter",
"Interactor",
"Entities",
"Assembly",
]
),
]
let directories = """
Configurations:
- file.swift
Resources:
- file.swift
Sources:
- MainScreen:
- mainScreen1.swift
- mainScreen2.swift
- Assembly:
- file.swift
- Entities:
- file.swift
- Interactor:
- file.swift
- Presenter:
- file.swift
- View:
- file.swift
Support files:
- file.swift
Tests:
- file.swift
UITests:
- file.swift
"""
try createDirectories(directories)
let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Configurations", "Resources", "Sources", "Support files", "Tests", "UITests"])
let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: options)
let projGenerator = PBXProjGenerator(project: project)
let pbxProj = try project.generatePbxProj()
let group = try pbxProj.getMainGroup()
projGenerator.setupGroupOrdering(group: group)
let mainGroups = group.children.map { $0.nameOrPath }
try expect(mainGroups) == ["Sources", "Resources", "Tests", "Support files", "Configurations", "UITests", "Products"]
let screenGroups = group.children
.first { $0.nameOrPath == "Sources" }
.flatMap { $0 as? PBXGroup }?
.children
.first { $0.nameOrPath == "MainScreen" }
.flatMap { $0 as? PBXGroup }?
.children
.map { $0.nameOrPath }
try expect(screenGroups) == ["View", "Presenter", "Interactor", "Entities", "Assembly", "mainScreen1.swift", "mainScreen2.swift"]
}
$0.it("setups group ordering with groupSortPosition = .bottom") {
var options = SpecOptions()
options.groupSortPosition = .bottom
options.groupOrdering = [
GroupOrdering(
order: [
"Sources",
"Resources",
"Tests",
"Support files",
"Configurations",
]
),
GroupOrdering(
pattern: "^.*Screen$",
order: [
"View",
"Presenter",
"Interactor",
"Entities",
"Assembly",
]
),
]
let directories = """
Configurations:
- file.swift
Resources:
- file.swift
Sources:
- MainScreen:
- mainScreen1.swift
- mainScreen2.swift
- Assembly:
- file.swift
- Entities:
- file.swift
- Interactor:
- file.swift
- Presenter:
- file.swift
- View:
- file.swift
Support files:
- file.swift
Tests:
- file.swift
UITests:
- file.swift
"""
try createDirectories(directories)
let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Configurations", "Resources", "Sources", "Support files", "Tests", "UITests"])
let project = Project(basePath: directoryPath, name: "Test", targets: [target], options: options)
let projGenerator = PBXProjGenerator(project: project)
let pbxProj = try project.generatePbxProj()
let group = try pbxProj.getMainGroup()
projGenerator.setupGroupOrdering(group: group)
let mainGroups = group.children.map { $0.nameOrPath }
try expect(mainGroups) == ["Sources", "Resources", "Tests", "Support files", "Configurations", "UITests", "Products"]
let screenGroups = group.children
.first { $0.nameOrPath == "Sources" }
.flatMap { $0 as? PBXGroup }?
.children
.first { $0.nameOrPath == "MainScreen" }
.flatMap { $0 as? PBXGroup }?
.children
.map { $0.nameOrPath }
try expect(screenGroups) == ["mainScreen1.swift", "mainScreen2.swift", "View", "Presenter", "Interactor", "Entities", "Assembly"]
}
$0.it("sorts SPM packages") {
var options = SpecOptions()
options.groupSortPosition = .top
options.groupOrdering = [
GroupOrdering(
order: [
"Sources",
"Resources",
"Tests",
"Packages",
"Support files",
"Configurations",
]
),
GroupOrdering(
pattern: "Packages",
order: [
"FeatureA",
"FeatureB",
"Common",
]
),
]
let directories = """
Configurations:
- file.swift
Resources:
- file.swift
Sources:
- MainScreen:
- mainScreen1.swift
- mainScreen2.swift
- Assembly:
- file.swift
- Entities:
- file.swift
- Interactor:
- file.swift
- Presenter:
- file.swift
- View:
- file.swift
Support files:
- file.swift
Packages:
- Common:
- Package.swift
- FeatureA:
- Package.swift
- FeatureB:
- Package.swift
Tests:
- file.swift
UITests:
- file.swift
"""
try createDirectories(directories)
let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Configurations", "Resources", "Sources", "Support files", "Tests", "UITests"])
let project = Project(
basePath: directoryPath,
name: "Test",
targets: [target],
packages: [
"Common": .local(path: "Packages/Common", group: nil, excludeFromProject: false),
"FeatureA": .local(path: "Packages/FeatureA", group: nil, excludeFromProject: false),
"FeatureB": .local(path: "Packages/FeatureB", group: nil, excludeFromProject: false),
],
options: options
)
let projGenerator = PBXProjGenerator(project: project)
let pbxProj = try project.generatePbxProj()
let group = try pbxProj.getMainGroup()
projGenerator.setupGroupOrdering(group: group)
let mainGroups = group.children.map { $0.nameOrPath }
try expect(mainGroups) == ["Sources", "Resources", "Tests", "Packages", "Support files", "Configurations", "UITests", "Products"]
let packages = group.children
.first { $0.nameOrPath == "Packages" }
.flatMap { $0 as? PBXGroup }?
.children
.map(\.nameOrPath)
try expect(packages) == ["FeatureA", "FeatureB", "Common"]
}
}
}
func testDefaultLastUpgradeCheckWhenUserDidSpecifyInvalidValue() throws {
let lastUpgradeKey = "LastUpgradeCheck"
let attributes: [String: Any] = [lastUpgradeKey: 1234]
let project = Project(name: "Test", attributes: attributes)
let projGenerator = PBXProjGenerator(project: project)
let pbxProj = try projGenerator.generate()
for pbxProject in pbxProj.projects {
XCTAssertEqual(pbxProject.attributes[lastUpgradeKey] as? String, project.xcodeVersion)
}
}
func testOverrideLastUpgradeCheckWhenUserDidSpecifyValue() throws {
let lastUpgradeKey = "LastUpgradeCheck"
let lastUpgradeValue = "1234"
let attributes: [String: Any] = [lastUpgradeKey: lastUpgradeValue]
let project = Project(name: "Test", attributes: attributes)
let projGenerator = PBXProjGenerator(project: project)
let pbxProj = try projGenerator.generate()
for pbxProject in pbxProj.projects {
XCTAssertEqual(pbxProject.attributes[lastUpgradeKey] as? String, lastUpgradeValue)
}
}
func testDefaultLastUpgradeCheckWhenUserDidNotSpecifyValue() throws {
let lastUpgradeKey = "LastUpgradeCheck"
let project = Project(name: "Test")
let projGenerator = PBXProjGenerator(project: project)
let pbxProj = try projGenerator.generate()
for pbxProject in pbxProj.projects {
XCTAssertEqual(pbxProject.attributes[lastUpgradeKey] as? String, project.xcodeVersion)
}
}
func testPlatformDependencies() {
describe {
let directoryPath = Path("TestDirectory")
func createDirectories(_ directories: String) throws {
let yaml = try Yams.load(yaml: directories)!
func getFiles(_ file: Any, path: Path) -> [Path] {
if let array = file as? [Any] {
return array.flatMap { getFiles($0, path: path) }
} else if let string = file as? String {
return [path + string]
} else if let dictionary = file as? [String: Any] {
var array: [Path] = []
for (key, value) in dictionary {
array += getFiles(value, path: path + key)
}
return array
} else {
return []
}
}
let files = getFiles(yaml, path: directoryPath).filter { $0.extension != nil }
for file in files {
try file.parent().mkpath()
try file.write("")
}
}
func removeDirectories() {
try? directoryPath.delete()
}
$0.before {
removeDirectories()
}
$0.after {
removeDirectories()
}
$0.it("setups target with different dependencies") {
let directories = """
Sources:
- MainScreen:
- Entities:
- file.swift
"""
try createDirectories(directories)
let target1 = Target(name: "TestAll", type: .application, platform: .iOS, sources: ["Sources"])
let target2 = Target(name: "TestiOS", type: .application, platform: .iOS, sources: ["Sources"])
let target3 = Target(name: "TestmacOS", type: .application, platform: .iOS, sources: ["Sources"])
let dependency1 = Dependency(type: .target, reference: "TestAll", platformFilter: .all)
let dependency2 = Dependency(type: .target, reference: "TestiOS", platformFilter: .iOS)
let dependency3 = Dependency(type: .target, reference: "TestmacOS", platformFilter: .macOS)
let dependency4 = Dependency(type: .package(products: ["Swinject"]), reference: "Swinject", platformFilter: .iOS)
let target = Target(name: "Test", type: .application, platform: .iOS, sources: ["Sources"], dependencies: [dependency1, dependency2, dependency3, dependency4])
let swinjectPackage = SwiftPackage.remote(url: "https://github.com/Swinject/Swinject", versionRequirement: .exact("2.8.0"))
let project = Project(basePath: directoryPath, name: "Test", targets: [target, target1, target2, target3], packages: ["Swinject": swinjectPackage])
let pbxProj = try project.generatePbxProj()
let targets = pbxProj.projects.first?.targets
let testTarget = pbxProj.projects.first?.targets.first(where: { $0.name == "Test" })
let testTargetDependencies = testTarget?.dependencies
try expect(targets?.count) == 4
try expect(testTargetDependencies?.count) == 3
try expect(testTargetDependencies?[0].platformFilter).beNil()
try expect(testTargetDependencies?[1].platformFilter) == "ios"
try expect(testTargetDependencies?[2].platformFilter) == "maccatalyst"
try expect(testTarget?.frameworksBuildPhase()?.files?.count) == 1
try expect(testTarget?.frameworksBuildPhase()?.files?[0].platformFilter) == "ios"
}
$0.it("places resources before sources buildPhase") {
let directories = """
Sources:
- MainScreen:
- Entities:
- file.swift
- image.jpg
"""
try createDirectories(directories)
let target1 = Target(
name: "TestAll",
type: .application,
platform: .iOS,
sources: ["Sources"],
putResourcesBeforeSourcesBuildPhase: true
)
let target2 = Target(
name: "TestiOS",
type: .application,
platform: .iOS,
sources: ["Sources"],
putResourcesBeforeSourcesBuildPhase: false
)
let project = Project(basePath: directoryPath, name: "Test", targets: [target1, target2])
let pbxProj = try project.generatePbxProj()
let targets = pbxProj.projects.first?.targets
try expect(targets?.count) == 2
try expect(targets?.first?.buildPhases.first).to.beOfType(PBXResourcesBuildPhase.self)
try expect(targets?.first?.buildPhases.last).to.beOfType(PBXSourcesBuildPhase.self)
try expect(targets?.last?.buildPhases.first).to.beOfType(PBXSourcesBuildPhase.self)
try expect(targets?.last?.buildPhases.last).to.beOfType(PBXResourcesBuildPhase.self)
}
}
}
}