mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Introduce swift-xcodegen
This is a tool specifically designed to generate Xcode projects for the Swift repo (as well as a couple of adjacent repos such as LLVM and Clang). It aims to provide a much more user-friendly experience than the CMake Xcode generation (`build-script --xcode`).
This commit is contained in:
439
utils/swift-xcodegen/Sources/Xcodeproj/XcodeProjectModel.swift
Normal file
439
utils/swift-xcodegen/Sources/Xcodeproj/XcodeProjectModel.swift
Normal file
@@ -0,0 +1,439 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift open source project
|
||||
//
|
||||
// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See http://swift.org/LICENSE.txt for license information
|
||||
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/*
|
||||
A very simple rendition of the Xcode project model. There is only sufficient
|
||||
functionality to allow creation of Xcode projects in a somewhat readable way,
|
||||
and serialization to .xcodeproj plists. There is no consistency checking to
|
||||
ensure, for example, that build settings have valid values, dependency cycles
|
||||
are not created, etc.
|
||||
|
||||
Everything here is geared toward supporting project generation. The intended
|
||||
usage model is for custom logic to build up a project using Xcode terminology
|
||||
(e.g. "group", "reference", "target", "build phase"), but there is almost no
|
||||
provision for modifying the model after it has been built up. The intent is
|
||||
to create it as desired from the start.
|
||||
|
||||
Rather than try to represent everything that Xcode's project model supports,
|
||||
the approach is to start small and to add functionality as needed.
|
||||
|
||||
Note that this API represents only the project model — there is no notion of
|
||||
workspaces, schemes, etc (although schemes are represented individually in a
|
||||
separate API). The notion of build settings is also somewhat different from
|
||||
what it is in Xcode: instead of an open-ended mapping of build configuration
|
||||
names to dictionaries of build settings, here there is a single set of common
|
||||
build settings plus two overlay sets for debug and release. The generated
|
||||
project has just the two Debug and Release configurations, created by merging
|
||||
the common set into the release and debug sets. This allows a more natural
|
||||
configuration of the settings, since most values are the same between Debug
|
||||
and Release. Also, the build settings themselves are represented as structs
|
||||
of named fields, instead of dictionaries with arbitrary name strings as keys.
|
||||
|
||||
It is expected that some of these simplifications will need to be lifted over
|
||||
time, based on need. That should be done carefully, however, to avoid ending
|
||||
up with an overly complicated model.
|
||||
|
||||
Some things that are incomplete in even this first model:
|
||||
- copy files build phases are incomplete
|
||||
- shell script build phases are incomplete
|
||||
- file types in file references are specified using strings; should be enums
|
||||
so that the client doesn't have to hardcode the mapping to Xcode file type
|
||||
identifiers
|
||||
- debug and release settings override common settings; they should be merged
|
||||
in a way that respects `$(inhertied)` when the same setting is defined in
|
||||
common and in debug or release
|
||||
- there is no good way to control the ordering of the `Products` group in the
|
||||
main group; it needs to be added last in order to appear after the other
|
||||
references
|
||||
*/
|
||||
|
||||
public struct Xcode {
|
||||
|
||||
/// An Xcode project, consisting of a tree of groups and file references,
|
||||
/// a list of targets, and some additional information. Note that schemes
|
||||
/// are outside of the project data model.
|
||||
public class Project {
|
||||
public let mainGroup: Group
|
||||
public var buildSettings: BuildSettingsTable
|
||||
public var productGroup: Group?
|
||||
public var projectDir: String
|
||||
public var targets: [Target]
|
||||
public init() {
|
||||
self.mainGroup = Group(path: "")
|
||||
self.buildSettings = BuildSettingsTable()
|
||||
self.productGroup = nil
|
||||
self.projectDir = ""
|
||||
self.targets = []
|
||||
}
|
||||
|
||||
/// Creates and adds a new target (which does not initially have any
|
||||
/// build phases).
|
||||
public func addTarget(objectID: String? = nil, productType: Target.ProductType? = nil, name: String) -> Target {
|
||||
let target = Target(objectID: objectID ?? "TARGET_\(name)", productType: productType, name: name)
|
||||
targets.append(target)
|
||||
return target
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract base class for all items in the group hierarchy.
|
||||
public class Reference {
|
||||
/// Relative path of the reference. It is usually a literal, but may
|
||||
/// in fact contain build settings.
|
||||
public var path: String
|
||||
/// Determines the base path for the reference's relative path.
|
||||
public var pathBase: RefPathBase
|
||||
/// Name of the reference, if different from the last path component
|
||||
/// (if not set, Xcode will use the last path component as the name).
|
||||
public var name: String?
|
||||
|
||||
/// Determines the base path for a reference's relative path (this is
|
||||
/// what for some reason is called a "source tree" in Xcode).
|
||||
public enum RefPathBase: String {
|
||||
/// An absolute path
|
||||
case absolute = "<absolute>"
|
||||
/// Indicates that the path is relative to the source root (i.e.
|
||||
/// the "project directory").
|
||||
case projectDir = "SOURCE_ROOT"
|
||||
/// Indicates that the path is relative to the path of the parent
|
||||
/// group.
|
||||
case groupDir = "<group>"
|
||||
/// Indicates that the path is relative to the effective build
|
||||
/// directory (which varies depending on active scheme, active run
|
||||
/// destination, or even an overridden build setting.
|
||||
case buildDir = "BUILT_PRODUCTS_DIR"
|
||||
}
|
||||
|
||||
init(path: String, pathBase: RefPathBase = .groupDir, name: String? = nil) {
|
||||
self.path = path
|
||||
self.pathBase = pathBase
|
||||
self.name = name
|
||||
}
|
||||
|
||||
/// Whether this is either a group or directory reference (blue folder).
|
||||
public var isDirectoryLike: Bool {
|
||||
if self is Xcode.Group {
|
||||
return true
|
||||
}
|
||||
if let ref = self as? Xcode.FileReference {
|
||||
return ref.isDirectory
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a file system entity (a file, folder, etc).
|
||||
public final class FileReference: Reference {
|
||||
public var objectID: String?
|
||||
public var fileType: String?
|
||||
public var isDirectory: Bool
|
||||
|
||||
init(path: String, isDirectory: Bool, pathBase: RefPathBase = .groupDir, name: String? = nil, fileType: String? = nil, objectID: String? = nil) {
|
||||
self.isDirectory = isDirectory
|
||||
super.init(path: path, pathBase: pathBase, name: name)
|
||||
self.objectID = objectID
|
||||
self.fileType = fileType
|
||||
}
|
||||
}
|
||||
|
||||
/// A group that can contain References (FileReferences and other Groups).
|
||||
/// The resolved path of a group is used as the base path for any child
|
||||
/// references whose source tree type is GroupRelative.
|
||||
public final class Group: Reference {
|
||||
public var subitems = [Reference]()
|
||||
|
||||
/// Creates and appends a new Group to the list of subitems.
|
||||
/// The new group is returned so that it can be configured.
|
||||
@discardableResult
|
||||
public func addGroup(
|
||||
path: String,
|
||||
pathBase: RefPathBase = .groupDir,
|
||||
name: String? = nil
|
||||
) -> Group {
|
||||
let group = Group(path: path, pathBase: pathBase, name: name)
|
||||
subitems.append(group)
|
||||
return group
|
||||
}
|
||||
|
||||
/// Creates and appends a new FileReference to the list of subitems.
|
||||
@discardableResult
|
||||
public func addFileReference(
|
||||
path: String,
|
||||
isDirectory: Bool,
|
||||
pathBase: RefPathBase = .groupDir,
|
||||
name: String? = nil,
|
||||
fileType: String? = nil,
|
||||
objectID: String? = nil
|
||||
) -> FileReference {
|
||||
let fref = FileReference(path: path, isDirectory: isDirectory, pathBase: pathBase, name: name, fileType: fileType, objectID: objectID)
|
||||
subitems.append(fref)
|
||||
return fref
|
||||
}
|
||||
}
|
||||
|
||||
/// An Xcode target, representing a single entity to build.
|
||||
public final class Target {
|
||||
public var objectID: String?
|
||||
public var name: String
|
||||
public var productName: String
|
||||
public var productType: ProductType?
|
||||
public var buildSettings: BuildSettingsTable
|
||||
public var buildPhases: [BuildPhase]
|
||||
public var productReference: FileReference?
|
||||
public var dependencies: [TargetDependency]
|
||||
public enum ProductType: String {
|
||||
case application = "com.apple.product-type.application"
|
||||
case staticArchive = "com.apple.product-type.library.static"
|
||||
case dynamicLibrary = "com.apple.product-type.library.dynamic"
|
||||
case framework = "com.apple.product-type.framework"
|
||||
case executable = "com.apple.product-type.tool"
|
||||
case unitTest = "com.apple.product-type.bundle.unit-test"
|
||||
}
|
||||
init(objectID: String?, productType: ProductType?, name: String) {
|
||||
self.objectID = objectID
|
||||
self.name = name
|
||||
self.productType = productType
|
||||
self.productName = name
|
||||
self.buildSettings = BuildSettingsTable()
|
||||
self.buildPhases = []
|
||||
self.dependencies = []
|
||||
}
|
||||
|
||||
// FIXME: There's a lot repetition in these methods; using generics to
|
||||
// try to avoid that raised other issues in terms of requirements on
|
||||
// the Reference class, though.
|
||||
|
||||
/// Adds a "headers" build phase, i.e. one that copies headers into a
|
||||
/// directory of the product, after suitable processing.
|
||||
@discardableResult
|
||||
public func addHeadersBuildPhase() -> HeadersBuildPhase {
|
||||
let phase = HeadersBuildPhase()
|
||||
buildPhases.append(phase)
|
||||
return phase
|
||||
}
|
||||
|
||||
/// Adds a "sources" build phase, i.e. one that compiles sources and
|
||||
/// provides them to be linked into the executable code of the product.
|
||||
@discardableResult
|
||||
public func addSourcesBuildPhase() -> SourcesBuildPhase {
|
||||
let phase = SourcesBuildPhase()
|
||||
buildPhases.append(phase)
|
||||
return phase
|
||||
}
|
||||
|
||||
/// Adds a "frameworks" build phase, i.e. one that links compiled code
|
||||
/// and libraries into the executable of the product.
|
||||
@discardableResult
|
||||
public func addFrameworksBuildPhase() -> FrameworksBuildPhase {
|
||||
let phase = FrameworksBuildPhase()
|
||||
buildPhases.append(phase)
|
||||
return phase
|
||||
}
|
||||
|
||||
/// Adds a "copy files" build phase, i.e. one that copies files to an
|
||||
/// arbitrary location relative to the product.
|
||||
@discardableResult
|
||||
public func addCopyFilesBuildPhase(dstDir: String) -> CopyFilesBuildPhase {
|
||||
let phase = CopyFilesBuildPhase(dstDir: dstDir)
|
||||
buildPhases.append(phase)
|
||||
return phase
|
||||
}
|
||||
|
||||
/// Adds a "shell script" build phase, i.e. one that runs a custom
|
||||
/// shell script as part of the build.
|
||||
@discardableResult
|
||||
public func addShellScriptBuildPhase(
|
||||
script: String, inputs: [String], outputs: [String], alwaysRun: Bool
|
||||
) -> ShellScriptBuildPhase {
|
||||
let phase = ShellScriptBuildPhase(
|
||||
script: script, inputs: inputs, outputs: outputs, alwaysRun: alwaysRun
|
||||
)
|
||||
buildPhases.append(phase)
|
||||
return phase
|
||||
}
|
||||
|
||||
/// Adds a dependency on another target.
|
||||
/// FIXME: We do not check for cycles. Should we? This is an extremely
|
||||
/// minimal API so it's not clear that we should.
|
||||
public func addDependency(on target: Target) {
|
||||
dependencies.append(TargetDependency(target: target))
|
||||
}
|
||||
|
||||
/// A simple wrapper to prevent ownership cycles in the `dependencies`
|
||||
/// property.
|
||||
public struct TargetDependency {
|
||||
public unowned var target: Target
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract base class for all build phases in a target.
|
||||
public class BuildPhase {
|
||||
public var files: [BuildFile] = []
|
||||
|
||||
/// Adds a new build file that refers to `fileRef`.
|
||||
@discardableResult
|
||||
public func addBuildFile(fileRef: FileReference) -> BuildFile {
|
||||
let buildFile = BuildFile(fileRef: fileRef)
|
||||
files.append(buildFile)
|
||||
return buildFile
|
||||
}
|
||||
}
|
||||
|
||||
/// A "headers" build phase, i.e. one that copies headers into a directory
|
||||
/// of the product, after suitable processing.
|
||||
public final class HeadersBuildPhase: BuildPhase {
|
||||
// Nothing extra yet.
|
||||
}
|
||||
|
||||
/// A "sources" build phase, i.e. one that compiles sources and provides
|
||||
/// them to be linked into the executable code of the product.
|
||||
public final class SourcesBuildPhase: BuildPhase {
|
||||
// Nothing extra yet.
|
||||
}
|
||||
|
||||
/// A "frameworks" build phase, i.e. one that links compiled code and
|
||||
/// libraries into the executable of the product.
|
||||
public final class FrameworksBuildPhase: BuildPhase {
|
||||
// Nothing extra yet.
|
||||
}
|
||||
|
||||
/// A "copy files" build phase, i.e. one that copies files to an arbitrary
|
||||
/// location relative to the product.
|
||||
public final class CopyFilesBuildPhase: BuildPhase {
|
||||
public var dstDir: String
|
||||
init(dstDir: String) {
|
||||
self.dstDir = dstDir
|
||||
}
|
||||
}
|
||||
|
||||
/// A "shell script" build phase, i.e. one that runs a custom shell script.
|
||||
public final class ShellScriptBuildPhase: BuildPhase {
|
||||
public var script: String
|
||||
public var inputs: [String]
|
||||
public var outputs: [String]
|
||||
public var alwaysRun: Bool
|
||||
init(script: String, inputs: [String], outputs: [String], alwaysRun: Bool) {
|
||||
self.script = script
|
||||
self.inputs = inputs
|
||||
self.outputs = outputs
|
||||
self.alwaysRun = alwaysRun
|
||||
}
|
||||
}
|
||||
|
||||
/// A build file, representing the membership of a file reference in a
|
||||
/// build phase of a target.
|
||||
public final class BuildFile {
|
||||
public var fileRef: FileReference?
|
||||
init(fileRef: FileReference) {
|
||||
self.fileRef = fileRef
|
||||
}
|
||||
|
||||
public var settings = Settings()
|
||||
|
||||
/// A set of file settings.
|
||||
public struct Settings: Encodable {
|
||||
public var ATTRIBUTES: [String]?
|
||||
public var COMPILER_FLAGS: String?
|
||||
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A table of build settings, which for the sake of simplicity consists
|
||||
/// (in this simplified model) of a set of common settings, and a set of
|
||||
/// overlay settings for Debug and Release builds. There can also be a
|
||||
/// file reference to an .xcconfig file on which to base the settings.
|
||||
public final class BuildSettingsTable {
|
||||
/// Common build settings are in both generated configurations (Debug
|
||||
/// and Release).
|
||||
public var common = BuildSettings()
|
||||
|
||||
/// Debug build settings are overlaid over the common settings in the
|
||||
/// generated Debug configuration.
|
||||
public var debug = BuildSettings()
|
||||
|
||||
/// Release build settings are overlaid over the common settings in the
|
||||
/// generated Release configuration.
|
||||
public var release = BuildSettings()
|
||||
|
||||
/// An optional file reference to an .xcconfig file.
|
||||
public var xcconfigFileRef: FileReference?
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
/// A set of build settings, which is represented as a struct of optional
|
||||
/// build settings. This is not optimally efficient, but it is great for
|
||||
/// code completion and type-checking.
|
||||
public struct BuildSettings: Encodable {
|
||||
// Note: although some of these build settings sound like booleans,
|
||||
// they are all either strings or arrays of strings, because even
|
||||
// a boolean may be a macro reference expression.
|
||||
public var BUILT_PRODUCTS_DIR: String?
|
||||
public var CLANG_CXX_LANGUAGE_STANDARD: String?
|
||||
public var CLANG_ENABLE_MODULES: String?
|
||||
public var CLANG_ENABLE_OBJC_ARC: String?
|
||||
public var COMBINE_HIDPI_IMAGES: String?
|
||||
public var COPY_PHASE_STRIP: String?
|
||||
public var CURRENT_PROJECT_VERSION: String?
|
||||
public var DEBUG_INFORMATION_FORMAT: String?
|
||||
public var DEFINES_MODULE: String?
|
||||
public var DYLIB_INSTALL_NAME_BASE: String?
|
||||
public var EMBEDDED_CONTENT_CONTAINS_SWIFT: String?
|
||||
public var ENABLE_NS_ASSERTIONS: String?
|
||||
public var ENABLE_TESTABILITY: String?
|
||||
public var FRAMEWORK_SEARCH_PATHS: [String]?
|
||||
public var GCC_C_LANGUAGE_STANDARD: String?
|
||||
public var GCC_OPTIMIZATION_LEVEL: String?
|
||||
public var GCC_PREPROCESSOR_DEFINITIONS: [String]?
|
||||
public var GCC_GENERATE_DEBUGGING_SYMBOLS: String?
|
||||
public var GCC_WARN_64_TO_32_BIT_CONVERSION: String?
|
||||
public var HEADER_SEARCH_PATHS: [String]?
|
||||
public var INFOPLIST_FILE: String?
|
||||
public var LD_RUNPATH_SEARCH_PATHS: [String]?
|
||||
public var LIBRARY_SEARCH_PATHS: [String]?
|
||||
public var MACOSX_DEPLOYMENT_TARGET: String?
|
||||
public var IPHONEOS_DEPLOYMENT_TARGET: String?
|
||||
public var TVOS_DEPLOYMENT_TARGET: String?
|
||||
public var WATCHOS_DEPLOYMENT_TARGET: String?
|
||||
public var DRIVERKIT_DEPLOYMENT_TARGET: String?
|
||||
public var MODULEMAP_FILE: String?
|
||||
public var ONLY_ACTIVE_ARCH: String?
|
||||
public var OTHER_CFLAGS: [String]?
|
||||
public var OTHER_CPLUSPLUSFLAGS: [String]?
|
||||
public var OTHER_LDFLAGS: [String]?
|
||||
public var OTHER_SWIFT_FLAGS: [String]?
|
||||
public var PRODUCT_BUNDLE_IDENTIFIER: String?
|
||||
public var PRODUCT_MODULE_NAME: String?
|
||||
public var PRODUCT_NAME: String?
|
||||
public var PROJECT_DIR: String?
|
||||
public var PROJECT_NAME: String?
|
||||
public var SDKROOT: String?
|
||||
public var SKIP_INSTALL: String?
|
||||
public var SUPPORTED_PLATFORMS: [String]?
|
||||
public var SUPPORTS_MACCATALYST: String?
|
||||
public var SWIFT_ACTIVE_COMPILATION_CONDITIONS: [String]?
|
||||
public var SWIFT_COMPILATION_MODE: String?
|
||||
public var SWIFT_ENABLE_EXPLICIT_MODULES: String?
|
||||
public var SWIFT_FORCE_STATIC_LINK_STDLIB: String?
|
||||
public var SWIFT_FORCE_DYNAMIC_LINK_STDLIB: String?
|
||||
public var SWIFT_INCLUDE_PATHS: [String]?
|
||||
public var SWIFT_MODULE_ALIASES: [String: String]?
|
||||
public var SWIFT_OPTIMIZATION_LEVEL: String?
|
||||
public var SWIFT_VERSION: String?
|
||||
public var TARGET_NAME: String?
|
||||
public var TARGET_BUILD_DIR: String?
|
||||
public var USE_HEADERMAP: String?
|
||||
public var LD: String?
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user