Compare commits

...

15 Commits

Author SHA1 Message Date
Hilton Campbell
3d82269812 Merge branch 'master' into local-swift-packages-at-project-root 2024-02-26 10:44:24 -08:00
Yonas Kolb
2a367acb0f update changelog 2024-02-15 21:43:10 +11:00
Yonas Kolb
54fa9b2bfa Update to 2.39.1 2024-02-15 21:38:00 +11:00
Yonas Kolb
7b5f9fb672
pin xcodeproj version (#1449) 2024-02-15 21:37:53 +11:00
freddi(Yuki Aki)
5bcbf3959d
Fix crash bundle (#1448)
* create availableModule to avoid crash on Bundle loading

* remove unnecessary line
2024-02-15 21:31:42 +11:00
Erik Schwiebert
fd48b7eb07
Add more C++ extensions to FileType.swift (#1446) 2024-02-15 10:27:48 +11:00
Yonas Kolb
d1110b1a72 Update to 2.39.0 2024-02-14 21:48:13 +11:00
Yonas Kolb
8b340b19d4 update Rainbow 2024-02-14 21:47:40 +11:00
Jaap Manenschijn
54139090a3
Make sure to create parent group structure for local packages (#1417)
* Make sure to create parent group structure for local packages

* Remove redundant localPackageGroup variable

---------

Co-authored-by: Jaap Manenschijn <jaap.maneschijn@rabobank.nl>
2024-02-14 20:18:16 +11:00
Paweł Madej
19109ac8c1
Update Rainbow version (#1424) 2024-02-14 20:12:12 +11:00
nicolasbosi95
2881fcc8fb
Support for Strings Catalogs (Xcode 15) (#1421)
* Support for xcode 15 string catalogs

* Add sample string catalog to Test Fixture and basic test to check that asset catalogs are added in the resources build phase

* Restore unintended changes

* Update Pull Request number for 'Support for Strings Catalogs' in changelog

* Update fixture yml generator

* Detect knownRegions based on locales in string catalogs
2024-02-14 20:07:04 +11:00
freddi(Yuki Aki)
2c1500761d
Support Artifact Bundle (#1388)
* support artifact bundle

* update CHANGELOG to exact PR

* build ArtifactBundle only on macOS

* update to copy SettingPresets into bundle

* fix CHANGELOG.md

* load Bundle.module

* update ArtifactBundleGen

* update ArtifactBundleGen to linux issue

* fix unnecessary code

* add lisence to bundle
2024-02-13 20:48:23 +11:00
Tatsuki Otsuka
6bbf2c6543
Add watchOS as a supported destination (#1438)
* Add a supported destination: watchOS

* Change priority

* Add test cases

* Refactor: reword test case descriptions
2024-02-13 19:12:50 +11:00
Björn Dahlgren
28383d1d36
Fix missing LD_RUNPATH_SEARCH_PATHS setting for visionOS platform (#1444) 2024-02-13 19:08:44 +11:00
Wolfgang Lutz
d935e41846
Update SpecValidationError.swift (#1439)
fix a typo
2024-02-13 19:08:01 +11:00
24 changed files with 383 additions and 51 deletions

1
.gitignore vendored
View File

@ -7,4 +7,5 @@ xcuserdata
*.xcuserstate
XcodeGen.xcodeproj
xcodegen.zip
xcodegen.artifactbundle.zip
.vscode/launch.json

View File

@ -6,6 +6,36 @@
- Added support for local Swift packages at the project root #1413 @hiltonc
## 2.39.1
### Added
- Proper defaults for `.cp` and `.cxx` files #1447 @eschwieb
### Fixed
- Fixed bundle access crash #1448 @freddi-kit
- Pinned XcodeProj version to fix breaking changes when XcodeGen is used as a dependency #1449 @yonaskolb
## 2.39.0
### Added
- Support Artifact Bundle #1388 @freddi-kit
- Added support for `.xcstrings` String Catalogs #1421 @nicolasbosi95
- Added default `LD_RUNPATH_SEARCH_PATHS` for visionOS #1444 @Dahlgren
- Added `watchOS` as a supported cross platform destination #1438 @tatsuky
### Fixed
- Fixed custom local package groups not being created #1416 @JaapManenschijn
- Fixed spec validation error type #1439 @Lutzifer
- Create parent group for local package groups if it does not exist already #1417 @JaapManenschijn
### Internal
- Updated Rainbow version #1424 @nysander
## 2.38.0
### Added

View File

@ -1,6 +1,6 @@
TOOL_NAME = XcodeGen
export EXECUTABLE_NAME = xcodegen
VERSION = 2.38.0
VERSION = 2.39.1
PREFIX = /usr/local
INSTALL_PATH = $(PREFIX)/bin/$(EXECUTABLE_NAME)
@ -48,3 +48,8 @@ brew:
archive: build
./scripts/archive.sh "$(EXECUTABLE_PATH)"
swift package plugin --allow-writing-to-package-directory generate-artifact-bundle \
--package-version $(VERSION) \
--executable-name $(EXECUTABLE_NAME) \
--build-config release \
--include-resource-path LICENSE

View File

@ -9,6 +9,15 @@
"version" : "4.6.1"
}
},
{
"identity" : "artifactbundlegen",
"kind" : "remoteSourceControl",
"location" : "https://github.com/freddi-kit/ArtifactBundleGen",
"state" : {
"revision" : "707e4ccc4b1c7e48e881cd5ea91e493a95df24bf",
"version" : "0.0.6"
}
},
{
"identity" : "graphviz",
"kind" : "remoteSourceControl",
@ -41,8 +50,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/onevcat/Rainbow.git",
"state" : {
"revision" : "626c3d4b6b55354b4af3aa309f998fae9b31a3d9",
"version" : "3.2.0"
"revision" : "e0dada9cd44e3fa7ec3b867e49a8ddbf543e3df3",
"version" : "4.0.1"
}
},
{

View File

@ -15,13 +15,13 @@ let package = Package(
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"),
.package(url: "https://github.com/yonaskolb/JSONUtilities.git", from: "4.2.0"),
.package(url: "https://github.com/kylef/Spectre.git", from: "0.9.2"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "3.0.0"),
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.13.0"),
.package(url: "https://github.com/onevcat/Rainbow.git", from: "4.0.0"),
.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: [
.executableTarget(name: "XcodeGen", dependencies: [
"XcodeGenCLI",
@ -42,6 +42,8 @@ let package = Package(
"PathKit",
"XcodeGenCore",
"GraphViz",
], resources: [
.copy("SettingPresets")
]),
.target(name: "ProjectSpec", dependencies: [
"JSONUtilities",

View File

@ -113,7 +113,7 @@ swift run xcodegen
Add the following to your Package.swift file's dependencies:
```swift
.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.38.0"),
.package(url: "https://github.com/yonaskolb/XcodeGen.git", from: "2.39.1"),
```
And then import wherever needed: `import XcodeGenKit`

View File

@ -7,5 +7,5 @@
1. Run `make release`
1. Push commit and tag to github
1. Create release from tag on GitHub using the version number and relevant changelog contents
1. Run `make archive` and upload `xcodegen.zip` to the github release
1. Run `make brew` which will open a PR on homebrew core
1. Run `make archive` and upload `xcodegen.zip` and `xcodegen.artifactbundle.zip` to the github release
1. Run `make brew` which will open a PR on homebrew core

View File

@ -1,2 +1,3 @@
LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"]
SDKROOT: xros
TARGETED_DEVICE_FAMILY: 7

View File

@ -0,0 +1,2 @@
SUPPORTED_PLATFORMS: watchos watchsimulator
TARGETED_DEVICE_FAMILY: '4'

View File

@ -72,6 +72,7 @@ extension FileType {
"bundle": FileType(buildPhase: .resources),
"xcassets": FileType(buildPhase: .resources),
"storekit": FileType(buildPhase: .resources),
"xcstrings": FileType(buildPhase: .resources),
// sources
"swift": FileType(buildPhase: .sources),
@ -79,6 +80,8 @@ extension FileType {
"m": FileType(buildPhase: .sources),
"mm": FileType(buildPhase: .sources),
"cpp": FileType(buildPhase: .sources),
"cp": FileType(buildPhase: .sources),
"cxx": FileType(buildPhase: .sources),
"c": FileType(buildPhase: .sources),
"cc": FileType(buildPhase: .sources),
"S": FileType(buildPhase: .sources),

View File

@ -105,7 +105,7 @@ public struct SpecValidationError: Error, CustomStringConvertible {
case let .duplicateDependencies(target, dependencyReference):
return "Target \(target.quoted) has the dependency \(dependencyReference.quoted) multiple times"
case let .invalidPluginPackageReference(plugin, package):
return "Plugin \(plugin) has invalide package reference \(package)"
return "Plugin \(plugin) has invalid package reference \(package)"
}
}
}

View File

@ -5,6 +5,7 @@ public enum SupportedDestination: String, CaseIterable {
case tvOS
case macOS
case macCatalyst
case watchOS
case visionOS
}
@ -20,6 +21,8 @@ extension SupportedDestination {
return "macos"
case .macCatalyst:
return "maccatalyst"
case .watchOS:
return "watchos"
case .visionOS:
return "xros"
}
@ -34,12 +37,14 @@ extension SupportedDestination {
return 0
case .tvOS:
return 1
case .visionOS:
case .watchOS:
return 2
case .macOS:
case .visionOS:
return 3
case .macCatalyst:
case .macOS:
return 4
case .macCatalyst:
return 5
}
}
}

View File

@ -3,6 +3,6 @@ import ProjectSpec
import XcodeGenCLI
import Version
let version = Version("2.38.0")
let version = Version("2.39.1")
let cli = XcodeGenCLI(version: version)
cli.execute()

View File

@ -0,0 +1 @@
../../SettingPresets/

View File

@ -249,6 +249,9 @@ extension SettingsPresetFile {
symlink.parent() + relativePath,
] + possibleSettingsPaths
}
if let moduleResourcePath = Bundle.availableModule?.path(forResource: "SettingPresets", ofType: nil) {
possibleSettingsPaths.append(Path(moduleResourcePath) + "\(path).yml")
}
guard let settingsPath = possibleSettingsPaths.first(where: { $0.exists }) else {
switch self {
@ -269,3 +272,48 @@ extension SettingsPresetFile {
return buildSettings
}
}
private class BundleFinder {}
/// The default SPM generated `Bundle.module` crashes on runtime if there is no .bundle file.
/// Below implementation modified from generated `Bundle.module` code which call `fatalError` if .bundle file not found.
private extension Bundle {
/// Returns the resource bundle associated with the current Swift module.
static let availableModule: Bundle? = {
let bundleName = "XcodeGen_XcodeGenKit"
let overrides: [URL]
#if DEBUG
// The 'PACKAGE_RESOURCE_BUNDLE_PATH' name is preferred since the expected value is a path. The
// check for 'PACKAGE_RESOURCE_BUNDLE_URL' will be removed when all clients have switched over.
// This removal is tracked by rdar://107766372.
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_PATH"]
?? ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_URL"] {
overrides = [URL(fileURLWithPath: override)]
} else {
overrides = []
}
#else
overrides = []
#endif
let candidates = overrides + [
// Bundle should be present here when the package is linked into an App.
Bundle.main.resourceURL,
// Bundle should be present here when the package is linked into a framework.
Bundle(for: BundleFinder.self).resourceURL,
// For command-line tools.
Bundle.main.bundleURL,
]
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
return nil
}()
}

View File

@ -18,7 +18,6 @@ class SourceGenerator {
private var fileReferencesByPath: [String: PBXFileElement] = [:]
private var groupsByPath: [Path: PBXGroup] = [:]
private var variantGroupsByPath: [Path: PBXVariantGroup] = [:]
private var localPackageGroup: PBXGroup?
private let project: Project
let pbxProj: PBXProj
@ -54,6 +53,11 @@ class SourceGenerator {
}
func createLocalPackage(path: Path, group: Path?) throws {
var parentGroup: String = project.options.localPackagesGroup ?? "Packages"
if let group {
parentGroup = group.string
}
let absolutePath = project.basePath + path.normalize()
// Get the local package's relative path from the project root
@ -68,31 +72,11 @@ class SourceGenerator {
)
)
var pbxGroup: PBXGroup?
if let location = group {
if location == "" {
pbxGroup = pbxProj.rootObject?.mainGroup
rootGroups.insert(fileReference)
} else {
let fullLocationPath = project.basePath + location
pbxGroup = getGroup(path: fullLocationPath, mergingChildren: [], createIntermediateGroups: true, hasCustomParent: false, isBaseGroup: true)
}
} else if localPackageGroup == nil {
let groupName = project.options.localPackagesGroup ?? "Packages"
if groupName == "" {
pbxGroup = pbxProj.rootObject?.mainGroup
rootGroups.insert(fileReference)
} else {
localPackageGroup = addObject(PBXGroup(sourceTree: .sourceRoot, name: groupName))
rootGroups.insert(localPackageGroup!)
}
}
if let pbxGroup = pbxGroup {
pbxGroup.children.append(fileReference)
if parentGroup == "" {
rootGroups.insert(fileReference)
} else {
localPackageGroup!.children.append(fileReference)
let parentGroups = parentGroup.components(separatedBy: "/")
createParentGroups(parentGroups, for: fileReference)
}
}
@ -464,6 +448,7 @@ class SourceGenerator {
let createIntermediateGroups = targetSource.createIntermediateGroups ?? project.options.createIntermediateGroups
let nonLocalizedChildren = children.filter { $0.extension != "lproj" }
let stringCatalogChildren = children.filter { $0.extension == "xcstrings" }
let directories = nonLocalizedChildren
.filter {
@ -529,6 +514,15 @@ class SourceGenerator {
}()
knownRegions.formUnion(localisedDirectories.map { $0.lastComponentWithoutExtension })
// XCode 15 - Detect known regions from locales present in string catalogs
let stringCatalogsLocales = stringCatalogChildren
.compactMap { StringCatalog(from: $0) }
.reduce(Set<String>(), { partialResult, stringCatalog in
partialResult.union(stringCatalog.includedLocales)
})
knownRegions.formUnion(stringCatalogsLocales)
// create variant groups of the base localisation first
var baseLocalisationVariantGroups: [PBXVariantGroup] = []

View File

@ -0,0 +1,82 @@
import Foundation
import JSONUtilities
import PathKit
struct StringCatalog {
/**
* Sample string catalog:
* {
* "sourceLanguage" : "en",
* "strings" : {
* "foo" : {
* "localizations" : {
* "en" : {
* ...
* },
* "es" : {
* ...
* },
* "it" : {
* ...
* }
* }
* }
* }
* }
*/
private struct CatalogItem {
private enum JSONKeys: String {
case localizations
}
private let key: String
let locales: Set<String>
init?(key: String, from jsonDictionary: JSONDictionary) {
guard let localizations = jsonDictionary[JSONKeys.localizations.rawValue] as? JSONDictionary else {
return nil
}
self.key = key
self.locales = Set(localizations.keys)
}
}
private enum JSONKeys: String {
case strings
}
private let strings: [CatalogItem]
init?(from path: Path) {
guard let catalogDictionary = try? JSONDictionary.from(url: path.url),
let catalog = StringCatalog(from: catalogDictionary) else {
return nil
}
self = catalog
}
private init?(from jsonDictionary: JSONDictionary) {
guard let stringsDictionary = jsonDictionary[JSONKeys.strings.rawValue] as? JSONDictionary else {
return nil
}
self.strings = stringsDictionary.compactMap { key, value -> CatalogItem? in
guard let stringDictionary = value as? JSONDictionary else {
return nil
}
return CatalogItem(key: key, from: stringDictionary)
}
}
var includedLocales: Set<String> {
strings.reduce(Set<String>(), { partialResult, catalogItem in
partialResult.union(catalogItem.locales)
})
}
}

View File

@ -61,6 +61,8 @@ extension Xcode {
return "wrapper.extensionkit-extension"
case ("swiftcrossimport", _):
return "wrapper.swiftcrossimport"
case ("xcstrings", _):
return "text.json.xcstrings"
default:
// fallback to XcodeProj defaults
return Xcode.filetype(extension: fileExtension)

View File

@ -143,7 +143,7 @@
979AE1767E2AF6B3B9D7F13D /* FooFeature */,
);
name = Packages;
sourceTree = SOURCE_ROOT;
sourceTree = "<group>";
};
CF3BD77AEAA56553289456BA /* SPMTests */ = {
isa = PBXGroup;

View File

@ -85,6 +85,7 @@
4B862F11762F6BB54E97E401 /* MyFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 576675973B56A96047CB4944 /* MyFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
4C1504A05321046B3ED7A839 /* Framework2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB055761199DF36DB0C629A6 /* Framework2.framework */; };
4CB673A7C0C11E04F8544BDB /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDB2B6A77D39CD5602F2125F /* Contacts.framework */; };
4CCBDB0492AB3542B2AB6D94 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AEDB7833B8AE2126630D6FCB /* Localizable.xcstrings */; };
4DA7140FF84DBF39961F3409 /* NetworkSystemExtension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 2049B6DD2AFE85F9DC9F3EB3 /* NetworkSystemExtension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4F6481557E2BEF8D749C37E3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 187E665975BB5611AF0F27E1 /* main.m */; };
5126CD91C2CB41C9B14B6232 /* DriverKitDriver.dext in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 83B5EC7EF81F7E4B6F426D4E /* DriverKitDriver.dext */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@ -838,6 +839,7 @@
AAA49985DFFE797EE8416887 /* inputList.xcfilelist */ = {isa = PBXFileReference; lastKnownFileType = text.xcfilelist; path = inputList.xcfilelist; sourceTree = "<group>"; };
AB055761199DF36DB0C629A6 /* Framework2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework2.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AEBCA8CFF769189C0D52031E /* App_iOS.xctestplan */ = {isa = PBXFileReference; path = App_iOS.xctestplan; sourceTree = "<group>"; };
AEDB7833B8AE2126630D6FCB /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
AEEFDE76B5FEC833403C0869 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
B17B8D9C9B391332CD176A35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LocalizedStoryboard.storyboard; sourceTree = "<group>"; };
B198242976C3395E31FE000A /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = "<group>"; };
@ -1139,6 +1141,7 @@
9DB22CB08CFAA455518700DB /* StandaloneFiles */,
BDA839814AF73F01F7710518 /* StaticLibrary_ObjC */,
CBDAC144248EE9D3838C6AAA /* StaticLibrary_Swift */,
6E0D17C5B4E6F01B89254309 /* String Catalogs */,
8CFD8AD4820FAB9265663F92 /* Tool */,
4C7F5EB7D6F3E0E9B426AB4A /* Utilities */,
3FEA12CF227D41EF50E5C2DB /* Vendor */,
@ -1257,6 +1260,14 @@
path = App_macOS_Tests;
sourceTree = "<group>";
};
6E0D17C5B4E6F01B89254309 /* String Catalogs */ = {
isa = PBXGroup;
children = (
AEDB7833B8AE2126630D6FCB /* Localizable.xcstrings */,
);
path = "String Catalogs";
sourceTree = "<group>";
};
795B8D70B674C850B57DD39D /* App_watchOS Extension */ = {
isa = PBXGroup;
children = (
@ -2594,6 +2605,7 @@
A9548E5DCFE92236494164DF /* LaunchScreen.storyboard in Resources */,
6E8F8303759824631C8D9DA3 /* Localizable.strings in Resources */,
E5DD0AD6F7AE1DD4AF98B83E /* Localizable.stringsdict in Resources */,
4CCBDB0492AB3542B2AB6D94 /* Localizable.xcstrings in Resources */,
2A7EB1A9A365A7EC5D49AFCF /* LocalizedStoryboard.storyboard in Resources */,
49A4B8937BB5520B36EA33F0 /* Main.storyboard in Resources */,
900CFAD929CAEE3861127627 /* MyBundle.bundle in Resources */,

View File

@ -0,0 +1,24 @@
{
"sourceLanguage" : "en",
"strings" : {
"sampleText" : {
"comment" : "Sample string in an asset catalog",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "This is a localized string"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Esta es una cadena de texto localizable."
}
}
}
}
},
"version" : "1.0"
}

View File

@ -163,6 +163,7 @@ targets:
resourceTags:
- tag1
- tag2
- String Catalogs/Localizable.xcstrings
settings:
INFOPLIST_FILE: App_iOS/Info.plist
PRODUCT_BUNDLE_IDENTIFIER: com.project.app

View File

@ -336,7 +336,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig.buildSettings["TVOS_DEPLOYMENT_TARGET"]).beNil()
}
$0.it("supportedPlaforms merges settings - iOS, tvOS") {
$0.it("supportedDestinations merges settings - iOS, tvOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.tvOS, .iOS])
let project = Project(name: "", targets: [target])
@ -355,7 +355,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
}
$0.it("supportedPlaforms merges settings - iOS, visionOS") {
$0.it("supportedDestinations merges settings - iOS, visionOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.visionOS, .iOS])
let project = Project(name: "", targets: [target])
@ -374,7 +374,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
}
$0.it("supportedPlaforms merges settings - iOS, tvOS, macOS") {
$0.it("supportedDestinations merges settings - iOS, tvOS, macOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .tvOS, .macOS])
let project = Project(name: "", targets: [target])
@ -393,7 +393,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
}
$0.it("supportedPlaforms merges settings - iOS, tvOS, macCatalyst") {
$0.it("supportedDestinations merges settings - iOS, tvOS, macCatalyst") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .tvOS, .macCatalyst])
let project = Project(name: "", targets: [target])
@ -412,7 +412,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
}
$0.it("supportedPlaforms merges settings - iOS, macOS") {
$0.it("supportedDestinations merges settings - iOS, macOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .macOS])
let project = Project(name: "", targets: [target])
@ -431,7 +431,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
}
$0.it("supportedPlaforms merges settings - tvOS, macOS") {
$0.it("supportedDestinations merges settings - tvOS, macOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.tvOS, .macOS])
let project = Project(name: "", targets: [target])
@ -449,7 +449,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME"] as? String) == "LaunchImage"
}
$0.it("supportedPlaforms merges settings - visionOS, macOS") {
$0.it("supportedDestinations merges settings - visionOS, macOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.visionOS, .macOS])
let project = Project(name: "", targets: [target])
@ -466,7 +466,7 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["ASSETCATALOG_COMPILER_APPICON_NAME"] as? String) == "AppIcon"
}
$0.it("supportedPlaforms merges settings - iOS, macCatalyst") {
$0.it("supportedDestinations merges settings - iOS, macCatalyst") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .macCatalyst])
let project = Project(name: "", targets: [target])
@ -484,7 +484,33 @@ class ProjectGeneratorTests: XCTestCase {
try expect(targetConfig1.buildSettings["ASSETCATALOG_COMPILER_APPICON_NAME"] as? String) == "AppIcon"
try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"] as? String) == "iPhone Developer"
}
$0.it("supportedDestinations merges settings - iOS, watchOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.iOS, .watchOS])
let project = Project(name: "", targets: [target])
let pbxProject = try project.generatePbxProj()
let targetConfig1 = try unwrap(pbxProject.nativeTargets.first?.buildConfigurationList?.buildConfigurations.first)
try expect(targetConfig1.buildSettings["SUPPORTED_PLATFORMS"] as? String) == "iphoneos iphonesimulator watchos watchsimulator"
try expect(targetConfig1.buildSettings["TARGETED_DEVICE_FAMILY"] as? String) == "1,2,4"
try expect(targetConfig1.buildSettings["SUPPORTS_MACCATALYST"] as? Bool) == false
try expect(targetConfig1.buildSettings["SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD"] as? Bool) == true
try expect(targetConfig1.buildSettings["SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD"] as? Bool) == true
}
$0.it("supportedDestinations merges settings - visionOS, watchOS") {
let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.visionOS, .watchOS])
let project = Project(name: "", targets: [target])
let pbxProject = try project.generatePbxProj()
let targetConfig1 = try unwrap(pbxProject.nativeTargets.first?.buildConfigurationList?.buildConfigurations.first)
try expect(targetConfig1.buildSettings["SUPPORTED_PLATFORMS"] as? String) == "watchos watchsimulator xros xrsimulator"
try expect(targetConfig1.buildSettings["TARGETED_DEVICE_FAMILY"] as? String) == "4,7"
try expect(targetConfig1.buildSettings["SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD"] as? Bool) == false
}
$0.it("generates dependencies") {
let pbxProject = try project.generatePbxProj()
@ -1584,7 +1610,8 @@ class ProjectGeneratorTests: XCTestCase {
]
)
let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: "Packages/Feature")])
let customLocalPackageGroup = "Packages/Feature"
let project = Project(name: "test", targets: [app], packages: ["XcodeGen": .local(path: "../XcodeGen", group: customLocalPackageGroup)])
let pbxProject = try project.generatePbxProj(specValidate: false)
let nativeTarget = try unwrap(pbxProject.nativeTargets.first(where: { $0.name == app.name }))
@ -1593,6 +1620,17 @@ class ProjectGeneratorTests: XCTestCase {
let frameworkPhases = nativeTarget.buildPhases.compactMap { $0 as? PBXFrameworksBuildPhase }
let packagesGroup = try unwrap(pbxProject.groups.first(where: { $0.name == "Packages" }))
let featureGroup = try unwrap(pbxProject.groups.first(where: { $0.name == "Feature" }))
guard featureGroup.parent?.uuid == packagesGroup.uuid else {
return XCTFail("Packages group should be parent of Feature group")
}
guard localPackageFile.parent?.uuid == featureGroup.uuid else {
return XCTFail("Packages group should be parent of Feature group")
}
guard let frameworkPhase = frameworkPhases.first else {
return XCTFail("frameworkPhases should have more than one")
}

View File

@ -1,7 +1,7 @@
import PathKit
import ProjectSpec
import Spectre
import XcodeGenKit
@testable import XcodeGenKit
import XcodeProj
import XCTest
import Yams
@ -41,6 +41,13 @@ class SourceGeneratorTests: XCTestCase {
try file.write("")
}
}
func createFile(at relativePath: Path, content: String) throws -> Path {
let filePath = directoryPath + relativePath
try filePath.parent().mkpath()
try filePath.write(content)
return filePath
}
func removeDirectories() {
try? directoryPath.delete()
@ -561,6 +568,7 @@ class SourceGeneratorTests: XCTestCase {
- file.h
- GoogleService-Info.plist
- file.xcconfig
- Localizable.xcstrings
B:
- file.swift
- file.xcassets
@ -617,6 +625,7 @@ class SourceGeneratorTests: XCTestCase {
try pbxProj.expectFile(paths: ["A", "file.h"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["A", "GoogleService-Info.plist"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["A", "file.xcconfig"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["A", "Localizable.xcstrings"], buildPhase: .resources)
try pbxProj.expectFile(paths: ["B", "file.swift"], buildPhase: BuildPhaseSpec.none)
try pbxProj.expectFile(paths: ["B", "file.xcassets"], buildPhase: BuildPhaseSpec.none)
@ -1238,6 +1247,69 @@ class SourceGeneratorTests: XCTestCase {
try expect(pbxProj.rootObject!.attributes["knownAssetTags"] as? [String]) == ["tag1", "tag2", "tag3"]
}
$0.it("Detects all locales present in a String Catalog") {
/// This is a catalog with gaps:
/// - String "foo" is translated into English (en) and Spanish (es)
/// - String "bar" is translated into English (en) and Italian (it)
///
/// It is aimed at representing real world scenarios where translators have not finished translating all strings into their respective languages.
/// The expectation in this kind of cases is that `includedLocales` returns all locales found at least once in the catalog.
/// In this example, `includedLocales` is expected to be a set only containing "en", "es" and "it".
let stringCatalogContent = """
{
"sourceLanguage" : "en",
"strings" : {
"foo" : {
"comment" : "Sample string in an asset catalog",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Foo English"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Foo Spanish"
}
}
}
},
"bar" : {
"comment" : "Another sample string in an asset catalog",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bar English"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bar Italian"
}
}
}
}
},
"version" : "1.0"
}
"""
let testStringCatalogRelativePath = Path("Localizable.xcstrings")
let testStringCatalogPath = try createFile(at: testStringCatalogRelativePath, content: stringCatalogContent)
guard let stringCatalog = StringCatalog(from: testStringCatalogPath) else {
throw failure("Failed decoding string catalog from \(testStringCatalogPath)")
}
try expect(stringCatalog.includedLocales.sorted(by: { $0 < $1 })) == ["en", "es", "it"]
}
}
}
}