diff --git a/CHANGELOG.md b/CHANGELOG.md index 156fbd5d55..de76c99325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ##### Bug Fixes - Fix redundant public accessibility analysis for protocol members declared in extensions that are referenced cross-module where the protocol itself is not. +- Remove checks causing errors when scanning multi-platform projects. ## 2.16.0 (2023-09-27) diff --git a/Sources/Frontend/Scan.swift b/Sources/Frontend/Scan.swift index 4563c63857..2c4059b2c1 100644 --- a/Sources/Frontend/Scan.swift +++ b/Sources/Frontend/Scan.swift @@ -12,9 +12,13 @@ final class Scan { } func perform(project: Project) throws -> [ScanResult] { - if !configuration.indexStorePath.isEmpty, !configuration.skipBuild { - logger.warn("The '--index-store-path' option implies '--skip-build', specify it to silence this warning") - configuration.skipBuild = true + if !configuration.indexStorePath.isEmpty { + logger.warn("When using the '--index-store-path' option please ensure that Xcode is not running. False-positives can occur if Xcode writes to the index store while Periphery is running.") + + if !configuration.skipBuild { + logger.warn("The '--index-store-path' option implies '--skip-build', specify it to silence this warning.") + configuration.skipBuild = true + } } if configuration.verbose { diff --git a/Sources/PeripheryKit/Indexer/IndexTarget.swift b/Sources/PeripheryKit/Indexer/IndexTarget.swift index f1d1262365..bbee5404e1 100644 --- a/Sources/PeripheryKit/Indexer/IndexTarget.swift +++ b/Sources/PeripheryKit/Indexer/IndexTarget.swift @@ -2,10 +2,8 @@ import Foundation public struct IndexTarget: Hashable { public let name: String - public var triple: String? - public init(name: String, triple: String? = nil) { + public init(name: String) { self.name = name - self.triple = triple } } diff --git a/Sources/PeripheryKit/Indexer/JobPool.swift b/Sources/PeripheryKit/Indexer/JobPool.swift index 8091abf7a1..acc213779b 100644 --- a/Sources/PeripheryKit/Indexer/JobPool.swift +++ b/Sources/PeripheryKit/Indexer/JobPool.swift @@ -23,7 +23,7 @@ struct JobPool { } } - func map(_ block: @escaping (T) throws -> R) throws -> [R] { + func flatMap(_ block: @escaping (T) throws -> [R]) throws -> [R] { var error: Error? var results: [R] = [] let lock = UnfairLock() @@ -36,7 +36,7 @@ struct JobPool { let result = try block(job) lock.perform { - results.append(result) + results.append(contentsOf: result) } } catch let e { error = e @@ -49,32 +49,4 @@ struct JobPool { return results } - - func compactMap(_ block: @escaping (T) throws -> R?) throws -> [R] { - var error: Error? - var results: [R] = [] - let lock = UnfairLock() - - DispatchQueue.concurrentPerform(iterations: jobs.count) { idx in - guard error == nil else { return } - - do { - let job = jobs[idx] - if let result = try block(job) { - lock.perform { - results.append(result) - } - } - } catch let e { - error = e - } - } - - if let error = error { - throw error - } - - return results - } - } diff --git a/Sources/PeripheryKit/Indexer/SwiftIndexer.swift b/Sources/PeripheryKit/Indexer/SwiftIndexer.swift index 883b138097..c91809ba25 100644 --- a/Sources/PeripheryKit/Indexer/SwiftIndexer.swift +++ b/Sources/PeripheryKit/Indexer/SwiftIndexer.swift @@ -32,47 +32,28 @@ public final class SwiftIndexer: Indexer { let (includedFiles, excludedFiles) = filterIndexExcluded(from: allSourceFiles) excludedFiles.forEach { self.logger.debug("Excluding \($0.string)") } - let stores = try JobPool(jobs: indexStorePaths) - .map { [logger] indexStorePath in + let unitsByFile = try JobPool(jobs: indexStorePaths) + .flatMap { [logger, currentFilePath] indexStorePath in logger.debug("Reading \(indexStorePath)") let indexStore = try IndexStore.open(store: URL(fileURLWithPath: indexStorePath.string), lib: .open()) - return (indexStore, indexStore.units(includeSystem: false)) - } - - let units = stores.reduce(into: [(IndexStore, IndexStoreUnit)]()) { result, tuple in - let (indexStore, units) = tuple - units.forEach { result.append((indexStore, $0)) } - } - - let unitsByFile = try JobPool(jobs: units) - .compactMap { [logger, sourceFiles, currentFilePath] (indexStore, unit) -> (FilePath, IndexStore, IndexStoreUnit)? in - guard let filePath = try indexStore.mainFilePath(for: unit) else { return nil } + let units = indexStore.units(includeSystem: false) - let file = FilePath.makeAbsolute(filePath, relativeTo: currentFilePath) + return try units.compactMap { unit -> (FilePath, IndexStore, IndexStoreUnit)? in + guard let filePath = try indexStore.mainFilePath(for: unit) else { return nil } - if includedFiles.contains(file) { - // Ignore units built for other architectures/platforms. - let validTargetTriples = sourceFiles[file]?.compactMapSet { $0.triple } ?? [] + let file = FilePath.makeAbsolute(filePath, relativeTo: currentFilePath) - if validTargetTriples.isEmpty { + if includedFiles.contains(file) { return (file, indexStore, unit) - } else { - if let unitTargetTriple = try indexStore.target(for: unit) { - if validTargetTriples.contains(unitTargetTriple) { - return (file, indexStore, unit) - } - } else { - logger.warn("No unit target triple for: \(file)") - } } - } - return nil + return nil + } } - .reduce(into: [FilePath: [(IndexStore, IndexStoreUnit)]](), { result, tuple in - let (file, indexStore, unit) = tuple - result[file, default: []].append((indexStore, unit)) - }) + .reduce(into: [FilePath: [(IndexStore, IndexStoreUnit)]](), { result, tuple in + let (file, indexStore, unit) = tuple + result[file, default: []].append((indexStore, unit)) + }) let indexedFiles = Set(unitsByFile.keys) let unindexedFiles = allSourceFiles.subtracting(excludedFiles).subtracting(indexedFiles) @@ -83,21 +64,9 @@ public final class SwiftIndexer: Indexer { throw PeripheryError.unindexedTargetsError(targets: targets, indexStorePaths: indexStorePaths) } - let jobs = try unitsByFile.map { (file, units) -> Job in - let modules = try units.reduce(into: Set()) { (set, tuple) in - let (indexStore, unit) = tuple - if let name = try indexStore.moduleName(for: unit) { - let (didInsert, _) = set.insert(name) - if !didInsert { - let targets = try units.compactMapSet { try indexStore.target(for: $0.1) } - throw PeripheryError.conflictingIndexUnitsError(file: file, module: name, unitTargets: targets) - } - } - } - let sourceFile = SourceFile(path: file, modules: modules) - + let jobs = unitsByFile.map { (file, units) -> Job in return Job( - file: sourceFile, + file: file, units: units, graph: graph, logger: logger, @@ -113,7 +82,7 @@ public final class SwiftIndexer: Indexer { try job.phaseOne() } - phaseOneLogger.debug("\(job.file.path) (\(elapsed)s)") + phaseOneLogger.debug("\(job.file) (\(elapsed)s)") } logger.endInterval(phaseOneInterval) @@ -126,7 +95,7 @@ public final class SwiftIndexer: Indexer { try job.phaseTwo() } - phaseTwoLogger.debug("\(job.file.path) (\(elapsed)s)") + phaseTwoLogger.debug("\(job.file) (\(elapsed)s)") } logger.endInterval(phaseTwoInterval) @@ -135,15 +104,16 @@ public final class SwiftIndexer: Indexer { // MARK: - Private private class Job { - let file: SourceFile + let file: FilePath private let units: [(IndexStore, IndexStoreUnit)] private let graph: SourceGraph private let logger: ContextualLogger private let configuration: Configuration + private var sourceFile: SourceFile? required init( - file: SourceFile, + file: FilePath, units: [(IndexStore, IndexStoreUnit)], graph: SourceGraph, logger: ContextualLogger, @@ -203,7 +173,7 @@ public final class SwiftIndexer: Indexer { try indexStore.forEachOccurrences(for: record, language: .swift) { occurrence in guard let usr = occurrence.symbol.usr, - let location = transformLocation(occurrence.location) + let location = try transformLocation(occurrence.location) else { return true } if !occurrence.roles.intersection([.definition, .declaration]).isEmpty { @@ -262,13 +232,14 @@ public final class SwiftIndexer: Indexer { /// Phase two associates latent references, and performs other actions that depend on the completed source graph. func phaseTwo() throws { - let multiplexingSyntaxVisitor = try MultiplexingSyntaxVisitor(file: file) + let sourceFile = try getSourceFile() + let multiplexingSyntaxVisitor = try MultiplexingSyntaxVisitor(file: sourceFile) let declarationSyntaxVisitor = multiplexingSyntaxVisitor.add(DeclarationSyntaxVisitor.self) let importSyntaxVisitor = multiplexingSyntaxVisitor.add(ImportSyntaxVisitor.self) multiplexingSyntaxVisitor.visit() - file.importStatements = importSyntaxVisitor.importStatements + sourceFile.importStatements = importSyntaxVisitor.importStatements associateLatentReferences() associateDanglingReferences() @@ -277,12 +248,29 @@ public final class SwiftIndexer: Indexer { applyCommentCommands(using: multiplexingSyntaxVisitor) } + // MARK: - Private + private var declarations: [Declaration] = [] private var childDeclsByParentUsr: [String: Set] = [:] private var referencesByUsr: [String: Set] = [:] private var danglingReferences: [Reference] = [] private var varParameterUsrs: Set = [] + private func getSourceFile() throws -> SourceFile { + if let sourceFile { return sourceFile } + + let modules = try units.reduce(into: Set()) { (set, tuple) in + let (indexStore, unit) = tuple + if let name = try indexStore.moduleName(for: unit) { + set.insert(name) + } + } + + let sourceFile = SourceFile(path: file, modules: modules) + self.sourceFile = sourceFile + return sourceFile + } + private func establishDeclarationHierarchy() { graph.withLock { for (parent, decls) in childDeclsByParentUsr { @@ -446,7 +434,7 @@ public final class SwiftIndexer: Indexer { let analyzer = UnusedParameterAnalyzer() let paramsByFunction = analyzer.analyze( - file: file, + file: syntaxVisitor.sourceFile, syntax: syntaxVisitor.syntax, locationConverter: syntaxVisitor.locationConverter, parseProtocols: true) @@ -661,8 +649,8 @@ public final class SwiftIndexer: Indexer { return refs } - private func transformLocation(_ input: IndexStoreOccurrence.Location) -> SourceLocation? { - return SourceLocation(file: file, line: Int(input.line), column: Int(input.column)) + private func transformLocation(_ input: IndexStoreOccurrence.Location) throws -> SourceLocation? { + return SourceLocation(file: try getSourceFile(), line: Int(input.line), column: Int(input.column)) } private func transformDeclarationKind(_ kind: IndexStoreSymbol.Kind, _ subKind: IndexStoreSymbol.SubKind) -> Declaration.Kind? { diff --git a/Sources/PeripheryKit/Syntax/MultiplexingSyntaxVisitor.swift b/Sources/PeripheryKit/Syntax/MultiplexingSyntaxVisitor.swift index d4d928d31e..75c97ac517 100644 --- a/Sources/PeripheryKit/Syntax/MultiplexingSyntaxVisitor.swift +++ b/Sources/PeripheryKit/Syntax/MultiplexingSyntaxVisitor.swift @@ -86,6 +86,7 @@ extension PeripherySyntaxVisitor { } final class MultiplexingSyntaxVisitor: SyntaxVisitor { + let sourceFile: SourceFile let syntax: SourceFileSyntax let locationConverter: SourceLocationConverter let sourceLocationBuilder: SourceLocationBuilder @@ -93,6 +94,7 @@ final class MultiplexingSyntaxVisitor: SyntaxVisitor { private var visitors: [PeripherySyntaxVisitor] = [] required init(file: SourceFile) throws { + self.sourceFile = file let source = try String(contentsOf: file.path.url) self.syntax = Parser.parse(source: source) self.locationConverter = SourceLocationConverter(fileName: file.path.string, tree: syntax) diff --git a/Sources/PeripheryKit/Syntax/UnusedParameterAnalyzer.swift b/Sources/PeripheryKit/Syntax/UnusedParameterAnalyzer.swift index 1b2a2e3229..e661724fd1 100644 --- a/Sources/PeripheryKit/Syntax/UnusedParameterAnalyzer.swift +++ b/Sources/PeripheryKit/Syntax/UnusedParameterAnalyzer.swift @@ -1,5 +1,6 @@ import Foundation import SwiftSyntax +import SystemPackage final class UnusedParameterAnalyzer { private enum UsageType { diff --git a/Sources/Shared/Extensions/Sequence+Extension.swift b/Sources/Shared/Extensions/Sequence+Extension.swift index 8c849e8e29..5715f4b54e 100644 --- a/Sources/Shared/Extensions/Sequence+Extension.swift +++ b/Sources/Shared/Extensions/Sequence+Extension.swift @@ -30,11 +30,4 @@ public extension Sequence { } } } - - func mapDict(_ transform: (Element) throws -> (Key, Value)) rethrows -> Dictionary { - try reduce(into: .init()) { result, element in - let pair = try transform(element) - result[pair.0] = pair.1 - } - } } diff --git a/Sources/Shared/Logger.swift b/Sources/Shared/Logger.swift index 77053ad488..290d76186b 100644 --- a/Sources/Shared/Logger.swift +++ b/Sources/Shared/Logger.swift @@ -146,10 +146,6 @@ public struct ContextualLogger { logger.debug("[\(context)] \(text)") } - public func warn(_ text: String) { - logger.warn("[\(context)] \(text)") - } - public func beginInterval(_ name: StaticString) -> SignpostInterval { logger.beginInterval(name) } diff --git a/Sources/Shared/PeripheryError.swift b/Sources/Shared/PeripheryError.swift index b67bb1da08..d42156be4b 100644 --- a/Sources/Shared/PeripheryError.swift +++ b/Sources/Shared/PeripheryError.swift @@ -20,8 +20,6 @@ public enum PeripheryError: Error, LocalizedError, CustomStringConvertible { case unindexedTargetsError(targets: Set, indexStorePaths: [FilePath]) case jsonDeserializationError(error: Error, json: String) case indexStoreNotFound(derivedDataPath: String) - case conflictingIndexUnitsError(file: FilePath, module: String, unitTargets: Set) - case invalidTargetTriple(target: String, arch: String, vendor: String, osVersion: String) public var errorDescription: String? { switch self { @@ -67,15 +65,6 @@ public enum PeripheryError: Error, LocalizedError, CustomStringConvertible { return "JSON deserialization failed: \(describe(error))\nJSON:\n\(json)" case let .indexStoreNotFound(derivedDataPath): return "Failed to find index datastore at path: \(derivedDataPath)" - case let .conflictingIndexUnitsError(file, module, targets): - var parts = ["Found conflicting index store units for '\(file)' in module '\(module)'."] - if targets.count > 1 { - parts.append("The units have conflicting build targets: \(targets.sorted().joined(separator: ", ")).") - } - parts.append("If you passed the '--index-store-path' option, ensure that Xcode is not open with a project that may write to this index store while Periphery is running.") - return parts.joined(separator: " ") - case let .invalidTargetTriple(target, arch, vendor, osVersion): - return "Failed to construct triple for target '\(target)': \(arch), \(vendor), \(osVersion)" } } diff --git a/Sources/XcodeSupport/XcodeProjectDriver.swift b/Sources/XcodeSupport/XcodeProjectDriver.swift index 5c9a6fdc7d..7ffe51ea2a 100644 --- a/Sources/XcodeSupport/XcodeProjectDriver.swift +++ b/Sources/XcodeSupport/XcodeProjectDriver.swift @@ -113,19 +113,6 @@ public final class XcodeProjectDriver { extension XcodeProjectDriver: ProjectDriver { public func build() throws { - // Copy target triples to the targets. The triple is used by the indexer to ignore index store units built for - // other architectures/platforms. - let targetTriples = try xcodebuild.buildSettings(targets: targets) - .mapDict { action in - (action.target, try action.makeTargetTriple()) - } - - for target in targets { - if let triple = targetTriples[target.name] { - target.triple = triple - } - } - guard !configuration.skipBuild else { return } if configuration.cleanBuild { @@ -164,7 +151,7 @@ extension XcodeProjectDriver: ProjectDriver { for target in targets { target.files(kind: .swift).forEach { - let indexTarget = IndexTarget(name: target.name, triple: target.triple) + let indexTarget = IndexTarget(name: target.name) sourceFiles[$0, default: []].insert(indexTarget) } } diff --git a/Sources/XcodeSupport/XcodeSchemeAction.swift b/Sources/XcodeSupport/XcodeSchemeAction.swift deleted file mode 100644 index 893b2e931e..0000000000 --- a/Sources/XcodeSupport/XcodeSchemeAction.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation -import Shared - -struct XcodeBuildAction: Decodable { - let target: String - let buildSettings: [String: String] - - func makeTargetTriple() throws -> String { - let arch = buildSettings["CURRENT_ARCH"] - let vendor = buildSettings["LLVM_TARGET_TRIPLE_VENDOR"] - let osVersion = buildSettings["LLVM_TARGET_TRIPLE_OS_VERSION"] - - if let arch, let vendor, let osVersion { - return "\(arch)-\(vendor)-\(osVersion)" - } else { - throw PeripheryError.invalidTargetTriple( - target: target, - arch: "ARCH = \(String(describing: arch))", - vendor: "LLVM_TARGET_TRIPLE_VENDOR = \(String(describing: vendor))", - osVersion: "LLVM_TARGET_TRIPLE_OS_VERSION = \(String(describing: osVersion))" - ) - } - } -} diff --git a/Sources/XcodeSupport/XcodeTarget.swift b/Sources/XcodeSupport/XcodeTarget.swift index 31c701149c..f40049050a 100644 --- a/Sources/XcodeSupport/XcodeTarget.swift +++ b/Sources/XcodeSupport/XcodeTarget.swift @@ -6,7 +6,6 @@ import Shared final class XcodeTarget { let project: XcodeProject - var triple: String? private let target: PBXTarget private var files: [ProjectFileKind: Set] = [:] diff --git a/Sources/XcodeSupport/Xcodebuild.swift b/Sources/XcodeSupport/Xcodebuild.swift index d542b2ae90..b7e4a2f1e9 100644 --- a/Sources/XcodeSupport/Xcodebuild.swift +++ b/Sources/XcodeSupport/Xcodebuild.swift @@ -89,29 +89,6 @@ public final class Xcodebuild { return Set(schemes) } - func buildSettings(targets: Set) throws -> [XcodeBuildAction] { - try targets - .reduce(into: [XcodeProject: Set]()) { result, target in - result[target.project, default: []].insert(target.name) - } - .reduce(into: [XcodeBuildAction]()) { result, pair in - let (project, targets) = pair - let args = [ - "-project", project.path.lexicallyNormalized().string, - "-showBuildSettings", - "-json" - ] + targets.flatMap { ["-target", $0] } - - let output = try shell.exec(["xcodebuild"] + args, stderr: false) - - guard let data = output.data(using: .utf8) else { return } - - let decoder = JSONDecoder() - let actions = try decoder.decode([XcodeBuildAction].self, from: data) - result.append(contentsOf: actions) - } - } - // MARK: - Private private func deserialize(_ jsonString: String) throws -> [String: Any]? { diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/AppIntent.swift b/Tests/XcodeTests/UIKitProject/MyWidget/AppIntent.swift new file mode 100644 index 0000000000..0ad5cc1611 --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/AppIntent.swift @@ -0,0 +1,18 @@ +// +// AppIntent.swift +// MyWidget +// +// Created by Ian Leitch on 30.10.23. +// + +import WidgetKit +import AppIntents + +struct ConfigurationAppIntent: WidgetConfigurationIntent { + static var title: LocalizedStringResource = "Configuration" + static var description = IntentDescription("This is an example widget.") + + // An example configurable parameter. + @Parameter(title: "Favorite Emoji", default: "😃") + var favoriteEmoji: String +} diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..eb87897008 --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..49c81cd8c4 --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/Contents.json b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000000..eb87897008 --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/Info.plist b/Tests/XcodeTests/UIKitProject/MyWidget/Info.plist new file mode 100644 index 0000000000..0f118fb75e --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/Tests/XcodeTests/UIKitProject/MyWidget/MyWidget.swift b/Tests/XcodeTests/UIKitProject/MyWidget/MyWidget.swift new file mode 100644 index 0000000000..67c5456fcc --- /dev/null +++ b/Tests/XcodeTests/UIKitProject/MyWidget/MyWidget.swift @@ -0,0 +1,92 @@ +// +// MyWidget.swift +// MyWidget +// +// Created by Ian Leitch on 30.10.23. +// + +import WidgetKit +import SwiftUI + +struct Provider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date(), configuration: ConfigurationAppIntent()) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { + SimpleEntry(date: Date(), configuration: configuration) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + var entries: [SimpleEntry] = [] + + // Generate a timeline consisting of five entries an hour apart, starting from the current date. + let currentDate = Date() + for hourOffset in 0 ..< 5 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + let entry = SimpleEntry(date: entryDate, configuration: configuration) + entries.append(entry) + } + + return Timeline(entries: entries, policy: .atEnd) + } + + func recommendations() -> [AppIntentRecommendation] { + // Create an array with all the preconfigured widgets to show. + [AppIntentRecommendation(intent: ConfigurationAppIntent(), description: "Example Widget")] + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let configuration: ConfigurationAppIntent +} + +struct MyWidgetEntryView : View { + var entry: Provider.Entry + + var body: some View { + VStack { + HStack { + Text("Time:") + Text(entry.date, style: .time) + } + + Text("Favorite Emoji:") + Text(entry.configuration.favoriteEmoji) + } + } +} + +@main +struct MyWidget: Widget { + let kind: String = "MyWidget" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in + MyWidgetEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } + } +} + +extension ConfigurationAppIntent { + fileprivate static var smiley: ConfigurationAppIntent { + let intent = ConfigurationAppIntent() + intent.favoriteEmoji = "😀" + return intent + } + + fileprivate static var starEyes: ConfigurationAppIntent { + let intent = ConfigurationAppIntent() + intent.favoriteEmoji = "🤩" + return intent + } +} + +#Preview(as: .accessoryRectangular) { + MyWidget() +} timeline: { + SimpleEntry(date: .now, configuration: .smiley) + SimpleEntry(date: .now, configuration: .starEyes) +} diff --git a/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/project.pbxproj b/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/project.pbxproj index 174d398c5e..3b161d36f1 100644 --- a/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/project.pbxproj +++ b/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/project.pbxproj @@ -32,6 +32,12 @@ 3CE3F7CC2685DEFB0047231C /* OldModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3CE3F7CA2685DEFB0047231C /* OldModel.xcdatamodeld */; }; 3CE3F7CE2685DF0F0047231C /* ModelMapping.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 3CE3F7CD2685DF0F0047231C /* ModelMapping.xcmappingmodel */; }; 3CE3F7D02685E07C0047231C /* CustomEntityMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE3F7CF2685E07C0047231C /* CustomEntityMigrationPolicy.swift */; }; + 3CFFB5A92AEF8FDE002EFB86 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CFFB5A82AEF8FDE002EFB86 /* WidgetKit.framework */; }; + 3CFFB5AB2AEF8FDE002EFB86 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CFFB5AA2AEF8FDE002EFB86 /* SwiftUI.framework */; }; + 3CFFB5AE2AEF8FDE002EFB86 /* MyWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFFB5AD2AEF8FDE002EFB86 /* MyWidget.swift */; }; + 3CFFB5B02AEF8FDE002EFB86 /* AppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFFB5AF2AEF8FDE002EFB86 /* AppIntent.swift */; }; + 3CFFB5B22AEF8FDF002EFB86 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3CFFB5B12AEF8FDF002EFB86 /* Assets.xcassets */; }; + 3CFFB5B72AEF9038002EFB86 /* MultiTargetStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CD46107256C02FE00856DAC /* MultiTargetStruct.swift */; }; 73AF86CD2968A93900BED352 /* LocalPackageTarget in Frameworks */ = {isa = PBXBuildFile; productRef = 73AF86CC2968A93900BED352 /* LocalPackageTarget */; }; /* End PBXBuildFile section */ @@ -114,6 +120,13 @@ 3CE3F7CB2685DEFB0047231C /* OldModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = OldModel.xcdatamodel; sourceTree = ""; }; 3CE3F7CD2685DF0F0047231C /* ModelMapping.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = ModelMapping.xcmappingmodel; sourceTree = ""; }; 3CE3F7CF2685E07C0047231C /* CustomEntityMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEntityMigrationPolicy.swift; sourceTree = ""; }; + 3CFFB5A72AEF8FDE002EFB86 /* MyWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MyWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 3CFFB5A82AEF8FDE002EFB86 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 3CFFB5AA2AEF8FDE002EFB86 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 3CFFB5AD2AEF8FDE002EFB86 /* MyWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyWidget.swift; sourceTree = ""; }; + 3CFFB5AF2AEF8FDE002EFB86 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = ""; }; + 3CFFB5B12AEF8FDF002EFB86 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3CFFB5B32AEF8FDF002EFB86 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73AF86C3296881F900BED352 /* LocalPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = LocalPackage; path = LocalPackages/LocalPackage; sourceTree = ""; }; /* End PBXFileReference section */ @@ -148,6 +161,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3CFFB5A42AEF8FDE002EFB86 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3CFFB5AB2AEF8FDE002EFB86 /* SwiftUI.framework in Frameworks */, + 3CFFB5A92AEF8FDE002EFB86 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -194,6 +216,7 @@ 3C1FECFE2556D5AB0001BD58 /* UIKitProjectTests */, 3C1FED182556D91C0001BD58 /* Target With Spaces */, 3C1A70B2256BBAB300E07E4A /* NotificationServiceExtension */, + 3CFFB5AC2AEF8FDE002EFB86 /* MyWidget */, 3C84965B255405AD00900DA9 /* Products */, 73F8B878296880AC004B1A5E /* Frameworks */, ); @@ -206,6 +229,7 @@ 3C1FECFD2556D5AB0001BD58 /* UIKitProjectTests.xctest */, 3C1FED172556D91C0001BD58 /* Target_With_Spaces.framework */, 3C1A70B1256BBAB300E07E4A /* NotificationServiceExtension.appex */, + 3CFFB5A72AEF8FDE002EFB86 /* MyWidgetExtension.appex */, ); name = Products; sourceTree = ""; @@ -252,6 +276,17 @@ path = MultiTarget; sourceTree = ""; }; + 3CFFB5AC2AEF8FDE002EFB86 /* MyWidget */ = { + isa = PBXGroup; + children = ( + 3CFFB5AD2AEF8FDE002EFB86 /* MyWidget.swift */, + 3CFFB5AF2AEF8FDE002EFB86 /* AppIntent.swift */, + 3CFFB5B12AEF8FDF002EFB86 /* Assets.xcassets */, + 3CFFB5B32AEF8FDF002EFB86 /* Info.plist */, + ); + path = MyWidget; + sourceTree = ""; + }; 73AF86C2296881F900BED352 /* LocalPackages */ = { isa = PBXGroup; children = ( @@ -263,6 +298,8 @@ 73F8B878296880AC004B1A5E /* Frameworks */ = { isa = PBXGroup; children = ( + 3CFFB5A82AEF8FDE002EFB86 /* WidgetKit.framework */, + 3CFFB5AA2AEF8FDE002EFB86 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -358,13 +395,30 @@ productReference = 3C84965A255405AD00900DA9 /* UIKitProject.app */; productType = "com.apple.product-type.application"; }; + 3CFFB5A62AEF8FDE002EFB86 /* MyWidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3CFFB5B62AEF8FDF002EFB86 /* Build configuration list for PBXNativeTarget "MyWidgetExtension" */; + buildPhases = ( + 3CFFB5A32AEF8FDE002EFB86 /* Sources */, + 3CFFB5A42AEF8FDE002EFB86 /* Frameworks */, + 3CFFB5A52AEF8FDE002EFB86 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MyWidgetExtension; + productName = MyWidgetExtension; + productReference = 3CFFB5A72AEF8FDE002EFB86 /* MyWidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 3C849652255405AD00900DA9 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1220; + LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1420; TargetAttributes = { 3C1A70B0256BBAB300E07E4A = { @@ -381,6 +435,9 @@ 3C849659255405AD00900DA9 = { CreatedOnToolsVersion = 12.2; }; + 3CFFB5A62AEF8FDE002EFB86 = { + CreatedOnToolsVersion = 15.1; + }; }; }; buildConfigurationList = 3C849655255405AD00900DA9 /* Build configuration list for PBXProject "UIKitProject" */; @@ -400,6 +457,7 @@ 3C1FECFC2556D5AB0001BD58 /* UIKitProjectTests */, 3C1FED162556D91C0001BD58 /* Target With Spaces */, 3C1A70B0256BBAB300E07E4A /* NotificationServiceExtension */, + 3CFFB5A62AEF8FDE002EFB86 /* MyWidgetExtension */, ); }; /* End PBXProject section */ @@ -439,6 +497,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3CFFB5A52AEF8FDE002EFB86 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3CFFB5B22AEF8FDF002EFB86 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -486,6 +552,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 3CFFB5A32AEF8FDE002EFB86 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3CFFB5B72AEF9038002EFB86 /* MultiTargetStruct.swift in Sources */, + 3CFFB5AE2AEF8FDE002EFB86 /* MyWidget.swift in Sources */, + 3CFFB5B02AEF8FDE002EFB86 /* AppIntent.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -826,6 +902,75 @@ }; name = Release; }; + 3CFFB5B42AEF8FDF002EFB86 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MyWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MyWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.github.ileitch.MyWidget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.2; + }; + name = Debug; + }; + 3CFFB5B52AEF8FDF002EFB86 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MyWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MyWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.github.ileitch.MyWidget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.2; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -874,6 +1019,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 3CFFB5B62AEF8FDF002EFB86 /* Build configuration list for PBXNativeTarget "MyWidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3CFFB5B42AEF8FDF002EFB86 /* Debug */, + 3CFFB5B52AEF8FDF002EFB86 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ diff --git a/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/xcshareddata/xcschemes/UIKitProject.xcscheme b/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/xcshareddata/xcschemes/UIKitProject.xcscheme index ffaa2d00f7..75a76b10d1 100644 --- a/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/xcshareddata/xcschemes/UIKitProject.xcscheme +++ b/Tests/XcodeTests/UIKitProject/UIKitProject.xcodeproj/xcshareddata/xcschemes/UIKitProject.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:UIKitProject.xcodeproj"> + + + +