mirror of
https://github.com/yonaskolb/XcodeGen.git
synced 2024-09-11 12:56:03 +03:00
Compare commits
15 Commits
feb41c717f
...
3d82269812
Author | SHA1 | Date | |
---|---|---|---|
|
3d82269812 | ||
|
2a367acb0f | ||
|
54fa9b2bfa | ||
|
7b5f9fb672 | ||
|
5bcbf3959d | ||
|
fd48b7eb07 | ||
|
d1110b1a72 | ||
|
8b340b19d4 | ||
|
54139090a3 | ||
|
19109ac8c1 | ||
|
2881fcc8fb | ||
|
2c1500761d | ||
|
6bbf2c6543 | ||
|
28383d1d36 | ||
|
d935e41846 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,4 +7,5 @@ xcuserdata
|
||||
*.xcuserstate
|
||||
XcodeGen.xcodeproj
|
||||
xcodegen.zip
|
||||
xcodegen.artifactbundle.zip
|
||||
.vscode/launch.json
|
||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -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
|
||||
|
7
Makefile
7
Makefile
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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`
|
||||
|
@ -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
|
@ -1,2 +1,3 @@
|
||||
LD_RUNPATH_SEARCH_PATHS: ["$(inherited)", "@executable_path/Frameworks"]
|
||||
SDKROOT: xros
|
||||
TARGETED_DEVICE_FAMILY: 7
|
||||
|
2
SettingPresets/SupportedDestinations/watchOS.yml
Normal file
2
SettingPresets/SupportedDestinations/watchOS.yml
Normal file
@ -0,0 +1,2 @@
|
||||
SUPPORTED_PLATFORMS: watchos watchsimulator
|
||||
TARGETED_DEVICE_FAMILY: '4'
|
@ -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),
|
||||
|
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
1
Sources/XcodeGenKit/SettingPresets
Symbolic link
1
Sources/XcodeGenKit/SettingPresets
Symbolic link
@ -0,0 +1 @@
|
||||
../../SettingPresets/
|
@ -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
|
||||
}()
|
||||
}
|
||||
|
@ -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] = []
|
||||
|
82
Sources/XcodeGenKit/StringCatalogDecoding.swift
Normal file
82
Sources/XcodeGenKit/StringCatalogDecoding.swift
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -143,7 +143,7 @@
|
||||
979AE1767E2AF6B3B9D7F13D /* FooFeature */,
|
||||
);
|
||||
name = Packages;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CF3BD77AEAA56553289456BA /* SPMTests */ = {
|
||||
isa = PBXGroup;
|
||||
|
@ -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 */,
|
||||
|
@ -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"
|
||||
}
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user