Compare commits

...

4 Commits

Author SHA1 Message Date
Roman Aliyev
63f35564f0
Merge f0e1db79e0 into f8842228c5 2024-07-03 10:08:10 -07:00
Ernesto Cambuston
f8842228c5
Test action macroExpansion allows unavailable buildable reference. (#1471)
* fix buildable ref

* fix test

* add test
2024-07-03 21:41:03 +10:00
Kohki Miki
7eb5e9bd06
Remove GraphViz feature (#1485)
* Remove GraphViz to pass build on Xcode 16

* Update documentations
2024-07-02 17:06:02 +10:00
Roman Aliyev
f0e1db79e0 added examples of production-ready manifests for common tasks. 2023-09-24 17:22:56 +02:00
14 changed files with 376 additions and 207 deletions

View File

@ -2,6 +2,8 @@
## Next Version
- Removed `xcodegen dump --type graphviz` feature. @giginet
## 2.41.0
### Added

View File

@ -0,0 +1,53 @@
# (iOS) Alternate app icons
## Description
Adds alternate app icons to include in the built product.
## File structure
```diff
.
├── MyApp
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   │   ├── AppIcon.appiconset
│   │   │   ├── AppIcon.png
| | | └── Contents.json
+│   │   ├── AppIcon2.appiconset
+│   │   │   ├── AppIcon.png
+| | | └── Contents.json
+│   │   ├── AppIcon3.appiconset
+│   │   │   ├── AppIcon.png
+| | | └── Contents.json
│   │   └── Contents.json
│   ├── LaunchScreen.storyboard
│   └── RootViewController.swift
└── project.yml
```
## project.yml
```diff
name: MyApp
targets:
MyApp:
type: application
platform: iOS
deploymentTarget: 12.0
settings:
TARGETED_DEVICE_FAMILY: 1
MARKETING_VERSION: 1.0
CURRENT_PROJECT_VERSION: 1
DEVELOPMENT_TEAM: MYTEAMID
PRODUCT_BUNDLE_IDENTIFIER: com.mycompany.myapp
GENERATE_INFOPLIST_FILE: YES
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
INFOPLIST_KEY_UILaunchStoryboardName: LaunchScreen.storyboard
INFOPLIST_KEY_UISupportedInterfaceOrientations: UIInterfaceOrientationPortrait
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS: YES
+ ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES: AppIcon2 AppIcon3
sources:
- MyApp
```

View File

@ -0,0 +1,72 @@
# (iOS) Environments
## Description
Best way to setup Development, Testing and Production environments.
## File structure
```diff
.
├── MyApp
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   │   ├── AppIcon.appiconset
│   │   │   ├── AppIcon.png
| | | └── Contents.json
│   │   └── Contents.json
│   ├── LaunchScreen.storyboard
│   └── RootViewController.swift
└── project.yml
```
## project.yml
```diff
name: MyApp
+configs:
+ Dev Debug: debug
+ Test Debug: debug
+ Prod Debug: debug
+ Dev Release: release
+ Test Release: release
+ Prod Release: release
+settings:
+ configs:
+ Dev Debug:
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG DEV
+ Test Debug:
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG TEST
+ Prod Debug:
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG PROD
+ Dev Release:
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEV
+ Test Release:
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS: TEST
+ Prod Release:
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS: PROD
targets:
MyApp:
type: application
platform: iOS
deploymentTarget: 12.0
settings:
TARGETED_DEVICE_FAMILY: 1
MARKETING_VERSION: 1.0
CURRENT_PROJECT_VERSION: 1
DEVELOPMENT_TEAM: MYTEAMID
PRODUCT_BUNDLE_IDENTIFIER: com.mycompany.myapp
GENERATE_INFOPLIST_FILE: YES
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
INFOPLIST_KEY_UILaunchStoryboardName: LaunchScreen.storyboard
INFOPLIST_KEY_UISupportedInterfaceOrientations: UIInterfaceOrientationPortrait
sources:
- MyApp
+ scheme:
+ configVariants:
+ - Dev
+ - Test
+ - Prod
```

View File

@ -0,0 +1,90 @@
# (iOS) Playground
## Description
An alternative to XCode Playground.
Example of Source.swift:
```swift
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
var greeting = "Hello, playground"
print(greeting)
return true
}
}
```
Another example with UI:
```swift
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let window = UIWindow()
self.window = window
let rootViewController = RootViewController(nibName: nil, bundle: nil)
window.rootViewController = rootViewController
window.makeKeyAndVisible()
return true
}
}
class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
```
## File structure
```diff
.
├── Source.swift
└── project.yml
```
## project.yml
```diff
name: MyApp
targets:
MyApp:
type: application
platform: iOS
deploymentTarget: 12.0
settings:
TARGETED_DEVICE_FAMILY: 1
MARKETING_VERSION: 1.0
CURRENT_PROJECT_VERSION: 1
DEVELOPMENT_TEAM: MYTEAMID
PRODUCT_BUNDLE_IDENTIFIER: com.mycompany.myapp
GENERATE_INFOPLIST_FILE: YES
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
- INFOPLIST_KEY_UILaunchStoryboardName: LaunchScreen.storyboard
+ INFOPLIST_KEY_UILaunchScreen_Generation: YES
INFOPLIST_KEY_UISupportedInterfaceOrientations: UIInterfaceOrientationPortrait
sources:
- Source.swift
```

View File

@ -0,0 +1,58 @@
# (iOS) UI Testing Bundle
## Description
Adds a user interface testing bundle that uses the XCTest framework.
## File structure
```diff
.
├── MyApp
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   │   ├── AppIcon.appiconset
│   │   │   ├── AppIcon.png
| | | └── Contents.json
│   │   └── Contents.json
│   ├── LaunchScreen.storyboard
│   └── RootViewController.swift
+├── MyAppUITests
+│   └── SomeUITests.swift
└── project.yml
```
## project.yml
```diff
name: MyApp
targets:
MyApp:
type: application
platform: iOS
deploymentTarget: 12.0
settings:
TARGETED_DEVICE_FAMILY: 1
MARKETING_VERSION: 1.0
CURRENT_PROJECT_VERSION: 1
DEVELOPMENT_TEAM: MYTEAMID
PRODUCT_BUNDLE_IDENTIFIER: com.mycompany.myapp
GENERATE_INFOPLIST_FILE: YES
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
INFOPLIST_KEY_UILaunchStoryboardName: LaunchScreen.storyboard
INFOPLIST_KEY_UISupportedInterfaceOrientations: UIInterfaceOrientationPortrait
sources:
- MyApp
+ MyAppUITests:
+ type: bundle.ui-testing
+ platform: iOS
+ settings:
+ DEVELOPMENT_TEAM: MYTEAMID
+ PRODUCT_BUNDLE_IDENTIFIER: com.company.myappuitests
+ GENERATE_INFOPLIST_FILE: YES
+ sources:
+ - MyAppUITests
+ dependencies:
+ - target: MyApp
```

View File

@ -0,0 +1,58 @@
# (iOS) Unit Testing Bundle
## Description
Adds a unit test bundle that uses the XCTest framework.
## File structure
```diff
.
├── MyApp
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   │   ├── AppIcon.appiconset
│   │   │   ├── AppIcon.png
| | | └── Contents.json
│   │   └── Contents.json
│   ├── LaunchScreen.storyboard
│   └── RootViewController.swift
+├── MyAppTests
+│   └── SomeTests.swift
└── project.yml
```
## project.yml
```diff
name: MyApp
targets:
MyApp:
type: application
platform: iOS
deploymentTarget: 12.0
settings:
TARGETED_DEVICE_FAMILY: 1
MARKETING_VERSION: 1.0
CURRENT_PROJECT_VERSION: 1
DEVELOPMENT_TEAM: MYTEAMID
PRODUCT_BUNDLE_IDENTIFIER: com.mycompany.myapp
GENERATE_INFOPLIST_FILE: YES
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents: YES
INFOPLIST_KEY_UILaunchStoryboardName: LaunchScreen.storyboard
INFOPLIST_KEY_UISupportedInterfaceOrientations: UIInterfaceOrientationPortrait
sources:
- MyApp
+ MyAppTests:
+ type: bundle.unit-test
+ platform: iOS
+ settings:
+ DEVELOPMENT_TEAM: MYTEAMID
+ PRODUCT_BUNDLE_IDENTIFIER: com.company.myapptests
+ GENERATE_INFOPLIST_FILE: YES
+ sources:
+ - MyAppTests
+ dependencies:
+ - target: MyApp
```

View File

@ -18,15 +18,6 @@
"version" : "0.0.6"
}
},
{
"identity" : "graphviz",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftDocOrg/GraphViz.git",
"state" : {
"revision" : "70bebcf4597b9ce33e19816d6bbd4ba9b7bdf038",
"version" : "0.2.0"
}
},
{
"identity" : "jsonutilities",
"kind" : "remoteSourceControl",

View File

@ -19,7 +19,6 @@ let package = Package(
.package(url: "https://github.com/tuist/XcodeProj.git", exact: "8.13.0"),
.package(url: "https://github.com/jakeheis/SwiftCLI.git", from: "6.0.3"),
.package(url: "https://github.com/mxcl/Version", from: "2.0.0"),
.package(url: "https://github.com/SwiftDocOrg/GraphViz.git", exact: "0.2.0"),
.package(url: "https://github.com/freddi-kit/ArtifactBundleGen", exact: "0.0.6")
],
targets: [
@ -41,7 +40,6 @@ let package = Package(
"XcodeProj",
"PathKit",
"XcodeGenCore",
"GraphViz",
], resources: [
.copy("SettingPresets")
]),

View File

@ -35,7 +35,6 @@ The project spec is a YAML or JSON file that defines your targets, configuration
- ✅ Distribute your spec amongst multiple files for easy **sharing** and overriding
- ✅ Easily create **multi-platform** frameworks
- ✅ Integrate **Carthage** frameworks without any work
- ✅ Export **Dependency Diagrams** to view in [Graphviz](https://www.graphviz.org)
Given an example project spec:
@ -138,27 +137,6 @@ Options:
There are other commands as well such as `xcodegen dump` which lets one output the resolved spec in many different formats, or write it to a file. Use `xcodegen help` to see more detailed usage information.
## Dependency Diagrams
<details>
<summary>Click to expand!</summary>
#### How to export dependency diagrams:
To stdout:
```
xcodegen dump --type graphviz
```
To a file:
```
xcodegen dump --type graphviz --file Graph.viz
```
During implementation, `graphviz` formatting was validated using [GraphvizOnline](https://dreampuf.github.io/GraphvizOnline/), [WebGraphviz](http://www.webgraphviz.com), and [Graphviz on MacOS](https://graphviz.org).
</details>
## Editing
```shell
git clone https://github.com/yonaskolb/XcodeGen.git

View File

@ -41,8 +41,6 @@ class DumpCommand: ProjectCommand {
output = try Yams.dump(object: project.toJSONDictionary())
case .summary:
output = project.debugDescription
case .graphviz:
output = GraphVizGenerator().generateModuleGraphViz(targets: project.targets)
}
if let file = file {
@ -61,7 +59,6 @@ private enum DumpType: String, ConvertibleFromString, CaseIterable {
case parsedJSON = "parsed-json"
case parsedYaml = "parsed-yaml"
case summary
case graphviz
static var defaultValue: DumpType { .yaml }
}

View File

@ -1,66 +0,0 @@
import DOT
import Foundation
import GraphViz
import ProjectSpec
extension Dependency {
var graphVizName: String {
switch self.type {
case .bundle, .package, .sdk, .framework, .carthage:
return "[\(self.type)]\\n\(reference)"
case .target:
return reference
}
}
}
extension Dependency.DependencyType: CustomStringConvertible {
public var description: String {
switch self {
case .bundle: return "bundle"
case .package: return "package"
case .framework: return "framework"
case .carthage: return "carthage"
case .sdk: return "sdk"
case .target: return "target"
}
}
}
extension Node {
init(target: Target) {
self.init(target.name)
self.shape = .box
}
init(dependency: Dependency) {
self.init(dependency.reference)
self.shape = .box
self.label = dependency.graphVizName
}
}
public class GraphVizGenerator {
public init() {}
public func generateModuleGraphViz(targets: [Target]) -> String {
return DOTEncoder().encode(generateGraph(targets: targets))
}
func generateGraph(targets: [Target]) -> Graph {
var graph = Graph(directed: true)
targets.forEach { target in
target.dependencies.forEach { dependency in
let from = Node(target: target)
graph.append(from)
let to = Node(dependency: dependency)
graph.append(to)
var edge = Edge(from: from, to: to)
edge.style = .dashed
graph.append(edge)
}
}
return graph
}
}

View File

@ -286,10 +286,12 @@ public class SchemeGenerator {
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 testAction = XCScheme.TestAction(
buildConfiguration: scheme.test?.config ?? defaultDebugConfig.name,
macroExpansion: buildableReference,
macroExpansion: testMacroExpansionBuildableRef,
testables: testables,
testPlans: testPlans.isEmpty ? nil : testPlans,
preActions: scheme.test?.preActions.map(getExecutionAction) ?? [],

View File

@ -1,104 +0,0 @@
import ProjectSpec
import Spectre
@testable import XcodeGenKit
import XCTest
private let app = Target(
name: "MyApp",
type: .application,
platform: .iOS,
settings: Settings(buildSettings: ["SETTING_1": "VALUE"]),
dependencies: [
Dependency(type: .target, reference: "MyInternalFramework"),
Dependency(type: .bundle, reference: "Resources"),
Dependency(type: .carthage(findFrameworks: true, linkType: .static), reference: "MyStaticFramework"),
Dependency(type: .carthage(findFrameworks: true, linkType: .dynamic), reference: "MyDynamicFramework"),
Dependency(type: .framework, reference: "MyExternalFramework"),
Dependency(type: .package(products: ["MyPackage"]), reference: "MyPackage"),
Dependency(type: .sdk(root: "MySDK"), reference: "MySDK"),
]
)
private let framework = Target(
name: "MyFramework",
type: .framework,
platform: .iOS,
settings: Settings(buildSettings: ["SETTING_2": "VALUE"])
)
private let uiTest = Target(
name: "MyAppUITests",
type: .uiTestBundle,
platform: .iOS,
settings: Settings(buildSettings: ["SETTING_3": "VALUE"]),
dependencies: [Dependency(type: .target, reference: "MyApp")]
)
private let targets = [app, framework, uiTest]
class GraphVizGeneratorTests: XCTestCase {
func testGraphOutput() throws {
describe {
let graph = GraphVizGenerator().generateGraph(targets: targets)
$0.it("generates the expected number of nodes") {
try expect(graph.nodes.count) == 16
}
$0.it("generates box nodes") {
try expect(graph.nodes.filter { $0.shape == .box }.count) == 16
}
$0.it("generates the expected carthage nodes") {
try expect(graph.nodes.filter { $0.label?.contains("[carthage]") ?? false }.count) == 2
}
$0.it("generates the expected sdk nodes") {
try expect(graph.nodes.filter { $0.label?.contains("[sdk]") ?? false }.count) == 1
}
$0.it("generates the expected Framework nodes") {
try expect(graph.nodes.filter { $0.label?.contains("[framework]") ?? false }.count) == 1
}
$0.it("generates the expected package nodes") {
try expect(graph.nodes.filter { $0.label?.contains("[package]") ?? false }.count) == 1
}
$0.it("generates the expected bundle nodes") {
try expect(graph.nodes.filter { $0.label?.contains("[bundle]") ?? false }.count) == 1
}
$0.it("generates the expected edges") {
try expect(graph.edges.count) == 8
}
$0.it("generates dashed edges") {
try expect(graph.edges.filter { $0.style == .dashed }.count) == 8
}
$0.it("generates the expected output") {
let output = GraphVizGenerator().generateModuleGraphViz(targets: targets)
try expect(output) == """
digraph {
MyApp [shape=box]
MyInternalFramework [label=MyInternalFramework shape=box]
MyApp [shape=box]
Resources [label="[bundle]\\nResources" shape=box]
MyApp [shape=box]
MyStaticFramework [label="[carthage]\\nMyStaticFramework" shape=box]
MyApp [shape=box]
MyDynamicFramework [label="[carthage]\\nMyDynamicFramework" shape=box]
MyApp [shape=box]
MyExternalFramework [label="[framework]\\nMyExternalFramework" shape=box]
MyApp [shape=box]
MyPackage [label="[package]\\nMyPackage" shape=box]
MyApp [shape=box]
MySDK [label="[sdk]\\nMySDK" shape=box]
MyAppUITests [shape=box]
MyApp [label=MyApp shape=box]
MyApp -> MyInternalFramework [style=dashed]
MyApp -> Resources [style=dashed]
MyApp -> MyStaticFramework [style=dashed]
MyApp -> MyDynamicFramework [style=dashed]
MyApp -> MyExternalFramework [style=dashed]
MyApp -> MyPackage [style=dashed]
MyApp -> MySDK [style=dashed]
MyAppUITests -> MyApp [style=dashed]
}
"""
}
}
}
}

View File

@ -479,6 +479,46 @@ class SchemeGeneratorTests: XCTestCase {
try expect(xcscheme.launchAction?.macroExpansion?.buildableName) == "MyApp.app"
}
$0.it("generates scheme with macroExpansion from tests when the main target is not part of the scheme") {
let app = Target(
name: "MyApp",
type: .application,
platform: .iOS,
dependencies: []
)
let mockApp = Target(
name: "MockApp",
type: .application,
platform: .iOS,
dependencies: []
)
let testBundle = Target(
name: "TestBundle",
type: .unitTestBundle,
platform: .iOS
)
let appTarget = Scheme.BuildTarget(target: .local(app.name), buildTypes: [.running])
let mockAppTarget = Scheme.BuildTarget(target: .local(mockApp.name), buildTypes: [.testing])
let testBundleTarget = Scheme.BuildTarget(target: .local(testBundle.name), buildTypes: [.testing])
let scheme = Scheme(
name: "TestScheme",
build: Scheme.Build(targets: [appTarget, mockAppTarget, testBundleTarget]),
run: Scheme.Run(config: "Debug", macroExpansion: "MyApp")
)
let project = Project(
name: "test",
targets: [app, mockApp, testBundle],
schemes: [scheme]
)
let xcodeProject = try project.generateXcodeProject()
let xcscheme = try unwrap(xcodeProject.sharedData?.schemes.first)
try expect(xcscheme.testAction?.macroExpansion?.buildableName) == "MockApp.app"
}
$0.it("generates scheme with test target of local swift package") {
let targetScheme = TargetScheme(
testTargets: [Scheme.Test.TestTarget(targetReference: TestableTargetReference(name: "XcodeGenKitTests", location: .package("XcodeGen")))])