diff --git a/Sources/ProjectDescription/PrivacyManifest.swift b/Sources/ProjectDescription/PrivacyManifest.swift new file mode 100644 index 00000000000..b59504aa664 --- /dev/null +++ b/Sources/ProjectDescription/PrivacyManifest.swift @@ -0,0 +1,48 @@ +import Foundation + +/// Describe the data your app or third-party SDK collects and the reasons required APIs it uses. +public struct PrivacyManifest: Codable, Equatable { + /// A Boolean that indicates whether your app or third-party SDK uses data for tracking as defined under the App + /// Tracking Transparency framework. For more information, see [User Privacy and Data + /// Use](https://developer.apple.com/app-store/user-privacy-and-data-use/). + public var tracking: Bool + + /// An array of strings that lists the internet domains your app or third-party SDK connects to that + /// engage in tracking. If the user has not granted tracking permission through the App Tracking Transparency framework, + /// network requests to these domains fail and your app receives an error. If you set `tracking` to true then you need to + /// provide at least one internet domain in NSPrivacyTrackingDomains; otherwise, you can provide zero or more domains. + public var trackingDomains: [String] + + /// An array of dictionaries that describes the data types your app or third-party SDK collects. For + /// information on the keys and values to use in the dictionaries, see [Describing data use in privacy manifests](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests). + public var collectedDataTypes: [[String: Plist.Value]] + + /// An array of dictionaries that describe the API types your app or third-party SDK accesses that have + /// been designated as APIs that require reasons to access. For information on the keys and values to use in the dictionaries, + /// see [Describing use of required reason API](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api). + public var accessedApiTypes: [[String: Plist.Value]] + + /// Returns a PrivacyManifest. + /// - Parameter tracking: A Boolean that indicates whether your app or third-party SDK uses data for tracking. + /// - Parameter trackingDomains: An array of strings that lists the internet domains your app or third-party SDK connects to + /// that engage in tracking. + /// - Parameter collectedDataTypes: An array of dictionaries that describes the data types your app or third-party SDK + /// collects. + /// - Parameter accessedApiTypes: An array of dictionaries that describe the API types your app or third-party SDK accesses + /// that have + /// been designated as APIs that require reasons to access. + /// - Returns: PrivacyManifest. + public static func privacyManifest( + tracking: Bool, + trackingDomains: [String], + collectedDataTypes: [[String: Plist.Value]], + accessedApiTypes: [[String: Plist.Value]] + ) -> Self { + PrivacyManifest( + tracking: tracking, + trackingDomains: trackingDomains, + collectedDataTypes: collectedDataTypes, + accessedApiTypes: accessedApiTypes + ) + } +} diff --git a/Sources/ProjectDescription/ResourceFileElements.swift b/Sources/ProjectDescription/ResourceFileElements.swift index d5bb90918b2..8f009eb40bc 100644 --- a/Sources/ProjectDescription/ResourceFileElements.swift +++ b/Sources/ProjectDescription/ResourceFileElements.swift @@ -5,8 +5,11 @@ public struct ResourceFileElements: Codable, Equatable { /// List of resource file elements public var resources: [ResourceFileElement] - public static func resources(_ resources: [ResourceFileElement]) -> Self { - self.init(resources: resources) + /// Define your apps privacy manifest + public var privacyManifest: PrivacyManifest? + + public static func resources(_ resources: [ResourceFileElement], privacyManifest: PrivacyManifest? = nil) -> Self { + self.init(resources: resources, privacyManifest: privacyManifest) } } diff --git a/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift b/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift index a254e441691..433c6b6baba 100644 --- a/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift +++ b/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift @@ -37,6 +37,7 @@ public enum TuistAcceptanceFixtures { case iosAppWithLocalSwiftPackage case iosAppWithMultiConfigs case iosAppWithPluginsAndTemplates + case iosAppWithPrivacyManifest case iosAppWithRemoteBinarySwiftPackage case iosAppWithRemoteSwiftPackage case iosAppWithStaticFrameworks @@ -140,6 +141,8 @@ public enum TuistAcceptanceFixtures { return "ios_app_with_multi_configs" case .iosAppWithPluginsAndTemplates: return "ios_app_with_plugins_and_templates" + case .iosAppWithPrivacyManifest: + return "ios_app_with_privacy_manifest" case .iosAppWithRemoteBinarySwiftPackage: return "ios_app_with_remote_binary_swift_package" case .iosAppWithRemoteSwiftPackage: diff --git a/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift b/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift index 8e6a2d1bace..9601a0631d3 100644 --- a/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift +++ b/Sources/TuistGenerator/Generator/BuildPhaseGenerator.swift @@ -349,7 +349,7 @@ final class BuildPhaseGenerator: BuildPhaseGenerating { pbxBuildFiles.append(contentsOf: try generateResourcesBuildFile( target: target, - files: target.resources, + files: target.resources.resources, fileElements: fileElements )) diff --git a/Sources/TuistGenerator/Generator/ProjectDescriptorGenerator.swift b/Sources/TuistGenerator/Generator/ProjectDescriptorGenerator.swift index 51dbe495664..84b3c1b9627 100644 --- a/Sources/TuistGenerator/Generator/ProjectDescriptorGenerator.swift +++ b/Sources/TuistGenerator/Generator/ProjectDescriptorGenerator.swift @@ -296,7 +296,7 @@ final class ProjectDescriptorGenerator: ProjectDescriptorGenerating { var attributes: [String: Any] = [:] /// ODR tags - let tags = project.targets.map { $0.resources.map(\.tags).flatMap { $0 } }.flatMap { $0 } + let tags = project.targets.map { $0.resources.resources.map(\.tags).flatMap { $0 } }.flatMap { $0 } let uniqueTags = Set(tags).sorted() if !uniqueTags.isEmpty { diff --git a/Sources/TuistGenerator/Generator/ProjectFileElements.swift b/Sources/TuistGenerator/Generator/ProjectFileElements.swift index 37e403d41a0..d55b281f36d 100644 --- a/Sources/TuistGenerator/Generator/ProjectFileElements.swift +++ b/Sources/TuistGenerator/Generator/ProjectFileElements.swift @@ -173,7 +173,7 @@ class ProjectFileElements { // Elements var elements = Set() elements.formUnion(files.map { GroupFileElement(path: $0, group: target.filesGroup) }) - elements.formUnion(target.resources.map { + elements.formUnion(target.resources.resources.map { GroupFileElement( path: $0.path, group: target.filesGroup, diff --git a/Sources/TuistGenerator/Linter/TargetLinter.swift b/Sources/TuistGenerator/Linter/TargetLinter.swift index 9ab06356203..df232d96337 100644 --- a/Sources/TuistGenerator/Linter/TargetLinter.swift +++ b/Sources/TuistGenerator/Linter/TargetLinter.swift @@ -117,7 +117,7 @@ class TargetLinter: TargetLinting { private func lintCopiedFiles(target: Target) -> [LintingIssue] { var issues: [LintingIssue] = [] - let files = target.resources.map(\.path) + let files = target.resources.resources.map(\.path) let entitlements = files.filter { $0.pathString.contains(".entitlements") } if let targetInfoPlistPath = target.infoPlist?.path, files.contains(targetInfoPlistPath) { @@ -193,7 +193,7 @@ class TargetLinter: TargetLinting { return [] } - if target.resources.isEmpty == false { + if target.resources.resources.isEmpty == false { return [ LintingIssue( reason: "Target \(target.name) cannot contain resources. \(target.product) targets do not support resources", diff --git a/Sources/TuistGenerator/Mappers/GeneratePrivacyManifestProjectMapper.swift b/Sources/TuistGenerator/Mappers/GeneratePrivacyManifestProjectMapper.swift new file mode 100644 index 00000000000..5c2832e9e6a --- /dev/null +++ b/Sources/TuistGenerator/Mappers/GeneratePrivacyManifestProjectMapper.swift @@ -0,0 +1,62 @@ +import Foundation +import TSCBasic +import TuistCore +import TuistGraph +import TuistSupport +import XcodeProj + +/// A project mapper that generates derived privacyManifest files for targets that define it as a dictonary. +public final class GeneratePrivacyManifestProjectMapper: ProjectMapping { + public init() {} + + // MARK: - ProjectMapping + + public func map(project: Project) throws -> (Project, [SideEffectDescriptor]) { + logger.debug("Transforming project \(project.name): Synthesizing privacy manifest files'") + + let results = try project.targets + .reduce(into: (targets: [Target](), sideEffects: [SideEffectDescriptor]())) { results, target in + let (updatedTarget, sideEffects) = try map(target: target, project: project) + results.targets.append(updatedTarget) + results.sideEffects.append(contentsOf: sideEffects) + } + + return (project.with(targets: results.targets), results.sideEffects) + } + + // MARK: - Private + + private func map(target: Target, project: Project) throws -> (Target, [SideEffectDescriptor]) { + guard let privacyManifest = target.resources.privacyManifest else { + return (target, []) + } + + let dictionary: [String: Any] = [ + "NSPrivacyTracking": privacyManifest.tracking, + "NSPrivacyTrackingDomains": privacyManifest.trackingDomains, + "NSPrivacyCollectedDataTypes": privacyManifest.collectedDataTypes.map { $0.mapValues { $0.value } }, + "NSPrivacyAccessedAPITypes": privacyManifest.accessedApiTypes.map { $0.mapValues { $0.value } }, + ] + + let data = try PropertyListSerialization.data( + fromPropertyList: dictionary, + format: .xml, + options: 0 + ) + + let privacyManifestPath = project.path + .appending(component: Constants.DerivedDirectory.name) + .appending(component: Constants.DerivedDirectory.privacyManifest) + .appending(component: target.name) + .appending(component: "PrivacyInfo.xcprivacy") + let sideEffect = SideEffectDescriptor.file(FileDescriptor(path: privacyManifestPath, contents: data)) + + var resources = target.resources + resources.resources.append(.init(path: privacyManifestPath)) + + var newTarget = target + newTarget.resources = resources + + return (newTarget, [sideEffect]) + } +} diff --git a/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift b/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift index 42bbb4a3a3f..4292edfc0c6 100644 --- a/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift +++ b/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift @@ -31,7 +31,7 @@ public class ResourcesProjectMapper: ProjectMapping { // swiftlint:disable:this // swiftlint:disable:next function_body_length public func mapTarget(_ target: Target, project: Project) throws -> ([Target], [SideEffectDescriptor]) { - if target.resources.isEmpty, target.coreDataModels.isEmpty { return ([target], []) } + if target.resources.resources.isEmpty, target.coreDataModels.isEmpty { return ([target], []) } var additionalTargets: [Target] = [] var sideEffects: [SideEffectDescriptor] = [] @@ -59,7 +59,7 @@ public class ResourcesProjectMapper: ProjectMapping { // swiftlint:disable:this coreDataModels: target.coreDataModels, filesGroup: target.filesGroup ) - modifiedTarget.resources = [] + modifiedTarget.resources.resources = [] modifiedTarget.copyFiles = [] modifiedTarget.dependencies.append(.target(name: bundleName, condition: .when(target.dependencyPlatformFilters))) additionalTargets.append(resourcesTarget) @@ -81,7 +81,7 @@ public class ResourcesProjectMapper: ProjectMapping { // swiftlint:disable:this if project.isExternal, target.supportsSources, target.sources.contains(where: { $0.path.extension == "m" || $0.path.extension == "mm" }), - !target.resources.filter({ $0.path.extension != "xcprivacy" }).isEmpty + !target.resources.resources.filter({ $0.path.extension != "xcprivacy" }).isEmpty { let (headerFilePath, headerData) = synthesizedObjcHeaderFile(bundleName: bundleName, target: target, project: project) diff --git a/Sources/TuistGenerator/Mappers/SynthesizedResourceInterfaceProjectMapper.swift b/Sources/TuistGenerator/Mappers/SynthesizedResourceInterfaceProjectMapper.swift index 33328231464..86cc53cdc7b 100644 --- a/Sources/TuistGenerator/Mappers/SynthesizedResourceInterfaceProjectMapper.swift +++ b/Sources/TuistGenerator/Mappers/SynthesizedResourceInterfaceProjectMapper.swift @@ -70,7 +70,7 @@ public final class SynthesizedResourceInterfaceProjectMapper: ProjectMapping { / /// Map and generate resource interfaces for a given `Target` and `Project` private func mapTarget(_ target: Target, project: Project) throws -> (Target, [SideEffectDescriptor]) { - guard !target.resources.isEmpty, target.supportsSources else { return (target, []) } + guard !target.resources.resources.isEmpty, target.supportsSources else { return (target, []) } var target = target @@ -152,7 +152,7 @@ public final class SynthesizedResourceInterfaceProjectMapper: ProjectMapping { / target: Target, developmentRegion: String? ) -> [AbsolutePath] { - let resourcesPaths = target.resources + let resourcesPaths = target.resources.resources .map(\.path) var paths = resourcesPaths diff --git a/Sources/TuistGraph/Models/PrivacyManifest.swift b/Sources/TuistGraph/Models/PrivacyManifest.swift new file mode 100644 index 00000000000..be1e2fc39ed --- /dev/null +++ b/Sources/TuistGraph/Models/PrivacyManifest.swift @@ -0,0 +1,23 @@ +import Foundation + +public struct PrivacyManifest: Codable, Equatable { + public var tracking: Bool + + public var trackingDomains: [String] + + public var collectedDataTypes: [[String: Plist.Value]] + + public var accessedApiTypes: [[String: Plist.Value]] + + public init( + tracking: Bool, + trackingDomains: [String], + collectedDataTypes: [[String: Plist.Value]], + accessedApiTypes: [[String: Plist.Value]] + ) { + self.tracking = tracking + self.trackingDomains = trackingDomains + self.collectedDataTypes = collectedDataTypes + self.accessedApiTypes = accessedApiTypes + } +} diff --git a/Sources/TuistGraph/Models/ResourceFileElements.swift b/Sources/TuistGraph/Models/ResourceFileElements.swift new file mode 100644 index 00000000000..ec17e6fdfe1 --- /dev/null +++ b/Sources/TuistGraph/Models/ResourceFileElements.swift @@ -0,0 +1,15 @@ +import Foundation + +public struct ResourceFileElements: Codable, Equatable { + public var resources: [ResourceFileElement] + + public var privacyManifest: PrivacyManifest? + + public init( + _ resources: [ResourceFileElement], + privacyManifest: PrivacyManifest? = nil + ) { + self.resources = resources + self.privacyManifest = privacyManifest + } +} diff --git a/Sources/TuistGraph/Models/Target.swift b/Sources/TuistGraph/Models/Target.swift index 8589b391c84..3f13d5b6de7 100644 --- a/Sources/TuistGraph/Models/Target.swift +++ b/Sources/TuistGraph/Models/Target.swift @@ -31,7 +31,7 @@ public struct Target: Equatable, Hashable, Comparable, Codable { public var settings: Settings? public var dependencies: [TargetDependency] public var sources: [SourceFile] - public var resources: [ResourceFileElement] + public var resources: ResourceFileElements public var copyFiles: [CopyFilesAction] public var headers: Headers? public var coreDataModels: [CoreDataModel] @@ -60,7 +60,7 @@ public struct Target: Equatable, Hashable, Comparable, Codable { entitlements: Entitlements? = nil, settings: Settings? = nil, sources: [SourceFile] = [], - resources: [ResourceFileElement] = [], + resources: ResourceFileElements = .init([]), copyFiles: [CopyFilesAction] = [], headers: Headers? = nil, coreDataModels: [CoreDataModel] = [], diff --git a/Sources/TuistGraphTesting/Models/Target+TestData.swift b/Sources/TuistGraphTesting/Models/Target+TestData.swift index 675de306dfc..999953413cf 100644 --- a/Sources/TuistGraphTesting/Models/Target+TestData.swift +++ b/Sources/TuistGraphTesting/Models/Target+TestData.swift @@ -16,7 +16,7 @@ extension Target { entitlements: Entitlements? = nil, settings: Settings? = Settings.test(), sources: [SourceFile] = [], - resources: [ResourceFileElement] = [], + resources: ResourceFileElements = .init([]), copyFiles: [CopyFilesAction] = [], coreDataModels: [CoreDataModel] = [], headers: Headers? = nil, @@ -74,7 +74,7 @@ extension Target { entitlements: Entitlements? = nil, settings: Settings? = Settings.test(), sources: [SourceFile] = [], - resources: [ResourceFileElement] = [], + resources: ResourceFileElements = .init([]), copyFiles: [CopyFilesAction] = [], coreDataModels: [CoreDataModel] = [], headers: Headers? = nil, @@ -131,7 +131,7 @@ extension Target { entitlements: Entitlements? = nil, settings: Settings? = nil, sources: [SourceFile] = [], - resources: [ResourceFileElement] = [], + resources: ResourceFileElements = .init([]), copyFiles: [CopyFilesAction] = [], coreDataModels: [CoreDataModel] = [], headers: Headers? = nil, diff --git a/Sources/TuistKit/Mappers/Factories/ProjectMapperFactory.swift b/Sources/TuistKit/Mappers/Factories/ProjectMapperFactory.swift index 90432d1dbc6..e794d095b24 100644 --- a/Sources/TuistKit/Mappers/Factories/ProjectMapperFactory.swift +++ b/Sources/TuistKit/Mappers/Factories/ProjectMapperFactory.swift @@ -74,6 +74,9 @@ public final class ProjectMapperFactory: ProjectMapperFactorying { // Entitlements mappers.append(GenerateEntitlementsProjectMapper()) + // Privacy Manifest + mappers.append(GeneratePrivacyManifestProjectMapper()) + // Template macros mappers.append(IDETemplateMacrosMapper()) diff --git a/Sources/TuistKit/Services/ProjectAutomation+ManifestMapper.swift b/Sources/TuistKit/Services/ProjectAutomation+ManifestMapper.swift index 75a1c6c1541..8215a0d48ed 100644 --- a/Sources/TuistKit/Services/ProjectAutomation+ManifestMapper.swift +++ b/Sources/TuistKit/Services/ProjectAutomation+ManifestMapper.swift @@ -69,7 +69,7 @@ extension ProjectAutomation.Target { product: target.product.rawValue, bundleId: target.bundleId, sources: target.sources.map(\.path.pathString), - resources: target.resources.map(\.path.pathString), + resources: target.resources.resources.map(\.path.pathString), settings: ProjectAutomation.Settings.from(target.settings), dependencies: dependencies ) diff --git a/Sources/TuistLoader/Models+ManifestMappers/Target+ManifestMapper.swift b/Sources/TuistLoader/Models+ManifestMappers/Target+ManifestMapper.swift index eb2db60ec73..a71558783a2 100644 --- a/Sources/TuistLoader/Models+ManifestMappers/Target+ManifestMapper.swift +++ b/Sources/TuistLoader/Models+ManifestMappers/Target+ManifestMapper.swift @@ -134,7 +134,7 @@ extension TuistGraph.Target { generatorPaths: GeneratorPaths // swiftlint:disable:next large_tuple ) throws -> ( - resources: [TuistGraph.ResourceFileElement], + resources: TuistGraph.ResourceFileElements, playgrounds: [AbsolutePath], coreDataModels: [AbsolutePath], invalidResourceGlobs: [InvalidGlob] @@ -143,8 +143,17 @@ extension TuistGraph.Target { TuistGraph.Target.isResource(path: path) } + let privacyManifest: TuistGraph.PrivacyManifest? = manifest.resources?.privacyManifest.map { + return TuistGraph.PrivacyManifest( + tracking: $0.tracking, + trackingDomains: $0.trackingDomains, + collectedDataTypes: $0.collectedDataTypes.map { $0.mapValues { TuistGraph.Plist.Value.from(manifest: $0) }}, + accessedApiTypes: $0.accessedApiTypes.map { $0.mapValues { TuistGraph.Plist.Value.from(manifest: $0) }} + ) + } + var invalidResourceGlobs: [InvalidGlob] = [] - var filteredResources: [TuistGraph.ResourceFileElement] = [] + var filteredResources: TuistGraph.ResourceFileElements = .init([], privacyManifest: privacyManifest) var playgrounds: Set = [] var coreDataModels: Set = [] @@ -163,14 +172,14 @@ extension TuistGraph.Target { for fileElement in allResources { switch fileElement { - case .folderReference: filteredResources.append(fileElement) + case .folderReference: filteredResources.resources.append(fileElement) case let .file(path, _, _): if path.extension == "playground" { playgrounds.insert(path) } else if path.extension == "xcdatamodeld" { coreDataModels.insert(path) } else { - filteredResources.append(fileElement) + filteredResources.resources.append(fileElement) } } } diff --git a/Sources/TuistLoader/SwiftPackageManager/PackageInfoMapper.swift b/Sources/TuistLoader/SwiftPackageManager/PackageInfoMapper.swift index 4a185c01111..4657e75b069 100644 --- a/Sources/TuistLoader/SwiftPackageManager/PackageInfoMapper.swift +++ b/Sources/TuistLoader/SwiftPackageManager/PackageInfoMapper.swift @@ -500,7 +500,7 @@ public final class PackageInfoMapper: PackageInfoMapping { var headers: ProjectDescription.Headers? var sources: SourceFilesList? - var resources: ResourceFileElements? + var resources: ProjectDescription.ResourceFileElements? if target.type.supportsPublicHeaderPath { headers = try Headers.from(moduleMap: moduleMap) @@ -762,7 +762,7 @@ extension SourceFilesList { } } -extension ResourceFileElements { +extension ProjectDescription.ResourceFileElements { fileprivate static func from( sources: [String]?, resources: [PackageInfo.Target.Resource], diff --git a/Sources/TuistSupport/Constants.swift b/Sources/TuistSupport/Constants.swift index 1e2c9e64d08..2d6f18e9a99 100644 --- a/Sources/TuistSupport/Constants.swift +++ b/Sources/TuistSupport/Constants.swift @@ -41,6 +41,7 @@ public enum Constants { public static let name = "Derived" public static let infoPlists = "InfoPlists" public static let entitlements = "Entitlements" + public static let privacyManifest = "PrivacyManifests" public static let moduleMaps = "ModuleMaps" public static let sources = "Sources" public static let signingKeychain = "signing.keychain" diff --git a/Tests/TuistGeneratorAcceptanceTests/GenerateAcceptanceTests.swift b/Tests/TuistGeneratorAcceptanceTests/GenerateAcceptanceTests.swift index f93fee7dce5..addf257a7ed 100644 --- a/Tests/TuistGeneratorAcceptanceTests/GenerateAcceptanceTests.swift +++ b/Tests/TuistGeneratorAcceptanceTests/GenerateAcceptanceTests.swift @@ -129,6 +129,26 @@ final class GenerateAcceptanceTestiOSAppWithFrameworkAndResources: TuistAcceptan "App.app", destination: "Debug-iphonesimulator" ) + try XCTAssertDirectoryContentEqual( + fixturePath.appending(components: "App", "Derived", "PrivacyManifests", "App"), + [ + "PrivacyInfo.xcprivacy", + ] + ) + } +} + +final class GenerateAcceptanceTestiOSAppWithPrivacyManifest: TuistAcceptanceTestCase { + func test_ios_app_with_privacy_manifest() async throws { + try setUpFixture(.iosAppWithPrivacyManifest) + try await run(GenerateCommand.self) + try await run(BuildCommand.self) + try XCTAssertDirectoryContentEqual( + fixturePath.appending(components: "Derived", "PrivacyManifests", "MyApp"), + [ + "PrivacyInfo.xcprivacy", + ] + ) } } diff --git a/Tests/TuistGeneratorTests/Generator/BuildPhaseGeneratorTests.swift b/Tests/TuistGeneratorTests/Generator/BuildPhaseGeneratorTests.swift index 288b9874231..716ccc18515 100644 --- a/Tests/TuistGeneratorTests/Generator/BuildPhaseGeneratorTests.swift +++ b/Tests/TuistGeneratorTests/Generator/BuildPhaseGeneratorTests.swift @@ -475,7 +475,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { // When try subject.generateResourcesBuildPhase( path: "/path", - target: .test(resources: resources), + target: .test(resources: .init(resources)), graphTraverser: graphTraverser, pbxTarget: nativeTarget, fileElements: fileElements, @@ -524,7 +524,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { // When try subject.generateResourcesBuildPhase( path: "/path", - target: .test(resources: files.map { .file(path: $0) }), + target: .test(resources: .init(files.map { .file(path: $0) })), graphTraverser: GraphTraverser(graph: .test(path: path)), pbxTarget: nativeTarget, fileElements: fileElements, @@ -572,7 +572,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { // When try subject.generateResourcesBuildPhase( path: "/path", - target: .test(resources: resourceFiles.map { .file(path: $0) }), + target: .test(resources: .init(resourceFiles.map { .file(path: $0) })), graphTraverser: GraphTraverser(graph: .test(path: path)), pbxTarget: nativeTarget, fileElements: fileElements, @@ -591,7 +591,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { versions: [try AbsolutePath(validating: "/Model.xcdatamodeld/1.xcdatamodel")], currentVersion: "1" ) - let target = Target.test(resources: [], coreDataModels: [coreDataModel]) + let target = Target.test(resources: .init([]), coreDataModels: [coreDataModel]) let fileElements = ProjectFileElements() let pbxproj = PBXProj() @@ -627,7 +627,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { // Given let temporaryPath = try temporaryPath() let path = try AbsolutePath(validating: "/image.png") - let target = Target.test(resources: [.file(path: path)]) + let target = Target.test(resources: .init([.file(path: path)])) let fileElements = ProjectFileElements() let pbxproj = PBXProj() let fileElement = PBXFileReference() @@ -664,7 +664,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { .file(path: "/file.type", tags: ["fileTag"]), .folderReference(path: "/folder", tags: ["folderTag"]), ] - let target = Target.test(resources: resources) + let target = Target.test(resources: .init(resources)) let fileElements = ProjectFileElements() let pbxproj = PBXProj() @@ -714,7 +714,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { .file(path: "/visionOS.type", inclusionCondition: .when([.visionos])), .file(path: "/watchOS.type", inclusionCondition: .when([.watchos])), ] - let target = Target.test(resources: resources) + let target = Target.test(resources: .init(resources)) let fileElements = ProjectFileElements() let pbxproj = PBXProj() @@ -1234,7 +1234,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { let pbxProject = createPbxProject(pbxproj: pbxproj) let target = Target.test( sources: [], - resources: [], + resources: .init([]), scripts: [ TargetScript( name: "post", @@ -1309,7 +1309,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { let pbxProject = createPbxProject(pbxproj: pbxproj) let target = Target.test( sources: [], - resources: [], + resources: .init([]), scripts: [ TargetScript( name: "post", @@ -1375,7 +1375,7 @@ final class BuildPhaseGeneratorTests: TuistUnitTestCase { let pbxProject = createPbxProject(pbxproj: pbxproj) let target = Target.test( sources: [], - resources: [], + resources: .init([]), scripts: [ TargetScript( name: "post", diff --git a/Tests/TuistGeneratorTests/Generator/ProjectDescriptorGeneratorTests.swift b/Tests/TuistGeneratorTests/Generator/ProjectDescriptorGeneratorTests.swift index c901b1f6be6..d5409c81e72 100644 --- a/Tests/TuistGeneratorTests/Generator/ProjectDescriptorGeneratorTests.swift +++ b/Tests/TuistGeneratorTests/Generator/ProjectDescriptorGeneratorTests.swift @@ -195,9 +195,11 @@ final class ProjectDescriptorGeneratorTests: TuistUnitTestCase { let project = Project.test( path: path, targets: [ - .test(resources: try resources.map { - .file(path: path.appending(try RelativePath(validating: $0))) - }), + .test(resources: .init( + try resources.map { + .file(path: path.appending(try RelativePath(validating: $0))) + } + )), ] ) @@ -283,7 +285,7 @@ final class ProjectDescriptorGeneratorTests: TuistUnitTestCase { ] let project = Project.test( path: path, - targets: [.test(resources: resources)] + targets: [.test(resources: .init(resources))] ) // When diff --git a/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift b/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift index 955bc249c65..7d4a4c2f8d8 100644 --- a/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift +++ b/Tests/TuistGeneratorTests/Generator/ProjectFileElementsTests.swift @@ -393,10 +393,12 @@ final class ProjectFileElementsTests: TuistUnitTestCase { entitlements: .file(path: try AbsolutePath(validating: "/project/app.entitlements")), settings: settings, sources: [SourceFile(path: try AbsolutePath(validating: "/project/file.swift"))], - resources: [ - .file(path: try AbsolutePath(validating: "/project/image.png")), - .folderReference(path: try AbsolutePath(validating: "/project/reference")), - ], + resources: .init( + [ + .file(path: try AbsolutePath(validating: "/project/image.png")), + .folderReference(path: try AbsolutePath(validating: "/project/reference")), + ] + ), copyFiles: [ CopyFilesAction( name: "Copy Templates", diff --git a/Tests/TuistGeneratorTests/Generator/TargetGeneratorTests.swift b/Tests/TuistGeneratorTests/Generator/TargetGeneratorTests.swift index 5caf0938c7e..c0e30ff371b 100644 --- a/Tests/TuistGeneratorTests/Generator/TargetGeneratorTests.swift +++ b/Tests/TuistGeneratorTests/Generator/TargetGeneratorTests.swift @@ -175,7 +175,7 @@ final class TargetGeneratorTests: XCTestCase { let graphTraverser = GraphTraverser(graph: graph) let target = Target.test( sources: [], - resources: [], + resources: .init([]), scripts: [ TargetScript( name: "post", diff --git a/Tests/TuistGeneratorTests/Linter/TargetLinterTests.swift b/Tests/TuistGeneratorTests/Linter/TargetLinterTests.swift index 293234159d6..e1d0f2df25b 100644 --- a/Tests/TuistGeneratorTests/Linter/TargetLinterTests.swift +++ b/Tests/TuistGeneratorTests/Linter/TargetLinterTests.swift @@ -108,10 +108,12 @@ final class TargetLinterTests: TuistUnitTestCase { let target = Target.test( infoPlist: .file(path: infoPlistPath), - resources: [ - .file(path: infoPlistPath), - .file(path: googeServiceInfoPlistPath), - ] + resources: .init( + [ + .file(path: infoPlistPath), + .file(path: googeServiceInfoPlistPath), + ] + ) ) let got = subject.lint(target: target) @@ -134,7 +136,7 @@ final class TargetLinterTests: TuistUnitTestCase { func test_lint_when_a_entitlements_file_is_being_copied() { let path = try! AbsolutePath(validating: "/App.entitlements") - let target = Target.test(resources: [.file(path: path)]) + let target = Target.test(resources: .init([.file(path: path)])) let got = subject.lint(target: target) @@ -178,8 +180,8 @@ final class TargetLinterTests: TuistUnitTestCase { let path = temporaryPath.appending(component: "Image.png") let element = ResourceFileElement.file(path: path) - let staticLibrary = Target.test(product: .staticLibrary, resources: [element]) - let dynamicLibrary = Target.test(product: .dynamicLibrary, resources: [element]) + let staticLibrary = Target.test(product: .staticLibrary, resources: .init([element])) + let dynamicLibrary = Target.test(product: .dynamicLibrary, resources: .init([element])) let staticResult = subject.lint(target: staticLibrary) XCTContainsLintingIssue( @@ -208,7 +210,7 @@ final class TargetLinterTests: TuistUnitTestCase { sources: [ SourceFile(path: "/path/to/some/source.swift"), ], - resources: [] + resources: .init([]) ) // When @@ -230,7 +232,7 @@ final class TargetLinterTests: TuistUnitTestCase { destinations: .macOS, product: .bundle, sources: [], - resources: [] + resources: .init([]) ) // When @@ -245,9 +247,11 @@ final class TargetLinterTests: TuistUnitTestCase { let bundle = Target.empty( destinations: .iOS, product: .bundle, - resources: [ - .file(path: "/path/to/some/asset.png"), - ] + resources: .init( + [ + .file(path: "/path/to/some/asset.png"), + ] + ) ) // When diff --git a/Tests/TuistGeneratorTests/ProjectMappers/ResourcesProjectMapperTests.swift b/Tests/TuistGeneratorTests/ProjectMappers/ResourcesProjectMapperTests.swift index acc9b776655..87d87b2ca83 100644 --- a/Tests/TuistGeneratorTests/ProjectMappers/ResourcesProjectMapperTests.swift +++ b/Tests/TuistGeneratorTests/ProjectMappers/ResourcesProjectMapperTests.swift @@ -33,7 +33,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { func test_map_when_a_target_that_has_resources_and_doesnt_supports_them() throws { // Given let resources: [ResourceFileElement] = [.file(path: "/image.png")] - let target = Target.test(product: .staticLibrary, sources: ["/Absolute/File.swift"], resources: resources) + let target = Target.test(product: .staticLibrary, sources: ["/Absolute/File.swift"], resources: .init(resources)) project = Project.test(targets: [target]) // Got @@ -60,7 +60,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { let gotTarget = try XCTUnwrap(gotProject.targets.first) XCTAssertEqual(gotTarget.name, target.name) XCTAssertEqual(gotTarget.product, target.product) - XCTAssertEqual(gotTarget.resources.count, 0) + XCTAssertEqual(gotTarget.resources.resources.count, 0) XCTAssertEqual(gotTarget.sources.count, 2) XCTAssertEqual(gotTarget.sources.last?.path, expectedPath) XCTAssertNotNil(gotTarget.sources.last?.contentHash) @@ -77,7 +77,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { XCTAssertEqual(resourcesTarget.bundleId, "\(target.bundleId).resources") XCTAssertEqual(resourcesTarget.deploymentTargets, target.deploymentTargets) XCTAssertEqual(resourcesTarget.filesGroup, target.filesGroup) - XCTAssertEqual(resourcesTarget.resources, resources) + XCTAssertEqual(resourcesTarget.resources.resources, resources) XCTAssertEqual(resourcesTarget.settings?.base, [ "CODE_SIGNING_ALLOWED": "NO", ]) @@ -86,7 +86,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { func testMap_whenDisableBundleAccessorsIsTrue_doesNotGenerateAccessors() throws { // Given let resources: [ResourceFileElement] = [.file(path: "/image.png")] - let target = Target.test(product: .staticLibrary, resources: resources) + let target = Target.test(product: .staticLibrary, resources: .init(resources)) project = Project.test( options: .test( disableBundleAccessors: true @@ -105,7 +105,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { func testMap_whenNoSwiftSources() throws { // Given let resources: [ResourceFileElement] = [.file(path: "/image.png")] - let target = Target.test(product: .staticLibrary, sources: ["/Absolute/File.m"], resources: resources) + let target = Target.test(product: .staticLibrary, sources: ["/Absolute/File.m"], resources: .init(resources)) project = Project.test( targets: [target] ) @@ -120,7 +120,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { let gotTarget = try XCTUnwrap(gotProject.targets.first) XCTAssertEqual(gotTarget.name, target.name) XCTAssertEqual(gotTarget.product, target.product) - XCTAssertEqual(gotTarget.resources.count, 0) + XCTAssertEqual(gotTarget.resources.resources.count, 0) XCTAssertEqual(gotTarget.sources.count, 1) XCTAssertEqual(gotTarget.dependencies.count, 1) XCTAssertEqual( @@ -135,7 +135,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { XCTAssertEqual(resourcesTarget.bundleId, "\(target.bundleId).resources") XCTAssertEqual(resourcesTarget.deploymentTargets, target.deploymentTargets) XCTAssertEqual(resourcesTarget.filesGroup, target.filesGroup) - XCTAssertEqual(resourcesTarget.resources, resources) + XCTAssertEqual(resourcesTarget.resources.resources, resources) } func test_map_when_a_target_that_has_core_data_models_and_doesnt_supports_them() throws { @@ -170,7 +170,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { let gotTarget = try XCTUnwrap(gotProject.targets.first) XCTAssertEqual(gotTarget.name, target.name) XCTAssertEqual(gotTarget.product, target.product) - XCTAssertEqual(gotTarget.resources.count, 0) + XCTAssertEqual(gotTarget.resources.resources.count, 0) XCTAssertEqual(gotTarget.sources.count, 2) XCTAssertEqual(gotTarget.sources.last?.path, expectedPath) XCTAssertNotNil(gotTarget.sources.last?.contentHash) @@ -193,7 +193,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { func test_map_when_a_target_that_has_resources_and_supports_them() throws { // Given let resources: [ResourceFileElement] = [.file(path: "/image.png")] - let target = Target.test(product: .framework, sources: ["/Absolute/File.swift"], resources: resources) + let target = Target.test(product: .framework, sources: ["/Absolute/File.swift"], resources: .init(resources)) project = Project.test(targets: [target]) // Got @@ -220,7 +220,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { let gotTarget = try XCTUnwrap(gotProject.targets.first) XCTAssertEqual(gotTarget.name, target.name) XCTAssertEqual(gotTarget.product, target.product) - XCTAssertEqual(gotTarget.resources, resources) + XCTAssertEqual(gotTarget.resources.resources, resources) XCTAssertEqual(gotTarget.sources.count, 2) XCTAssertEqual(gotTarget.sources.last?.path, expectedPath) XCTAssertNotNil(gotTarget.sources.last?.contentHash) @@ -268,7 +268,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { func test_map_when_a_target_has_no_resources() throws { // Given let resources: [ResourceFileElement] = [] - let target = Target.test(product: .framework, resources: resources) + let target = Target.test(product: .framework, resources: .init(resources)) project = Project.test(targets: [target]) // Got @@ -285,7 +285,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { .file(path: "/Some/ResourceA"), .file(path: "/Some/ResourceB"), ] - let target = Target.test(product: .bundle, resources: resources) + let target = Target.test(product: .bundle, resources: .init(resources)) project = Project.test(targets: [target]) // Got @@ -303,7 +303,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { name: "test-tuist", product: .staticLibrary, sources: ["/Absolute/File.swift"], - resources: resources + resources: .init(resources) ) project = Project.test(targets: [target]) @@ -331,7 +331,7 @@ final class ResourcesProjectMapperTests: TuistUnitTestCase { // Given let sources: [SourceFile] = ["/ViewController.m"] let resources: [ResourceFileElement] = [.file(path: "/AbsolutePath/Project/Resources/image.png")] - let target = Target.test(product: .staticLibrary, sources: sources, resources: resources) + let target = Target.test(product: .staticLibrary, sources: sources, resources: .init(resources)) project = Project.test(path: try AbsolutePath(validating: "/AbsolutePath/Project"), targets: [target], isExternal: true) // Got diff --git a/Tests/TuistGeneratorTests/ProjectMappers/SynthesizedResourceInterfaceProjectMapperTests.swift b/Tests/TuistGeneratorTests/ProjectMappers/SynthesizedResourceInterfaceProjectMapperTests.swift index ee5fbd3e050..f817ce001cb 100644 --- a/Tests/TuistGeneratorTests/ProjectMappers/SynthesizedResourceInterfaceProjectMapperTests.swift +++ b/Tests/TuistGeneratorTests/ProjectMappers/SynthesizedResourceInterfaceProjectMapperTests.swift @@ -82,19 +82,21 @@ final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase { let targetA = Target.test( name: "TargetA", - resources: [ - .folderReference(path: aAssets), - .file(path: frenchStrings), - .file(path: frenchStringsDict), - .file(path: englishStrings), - .file(path: englishStringsDict), - .file(path: emptyPlist), - .file(path: environmentPlist), - .file(path: ttfFont), - .file(path: otfFont), - .file(path: ttcFont), - .file(path: lottieFile), - ] + resources: .init( + [ + .folderReference(path: aAssets), + .file(path: frenchStrings), + .file(path: frenchStringsDict), + .file(path: englishStrings), + .file(path: englishStringsDict), + .file(path: emptyPlist), + .file(path: environmentPlist), + .file(path: ttfFont), + .file(path: otfFont), + .file(path: ttcFont), + .file(path: lottieFile), + ] + ) ) let resourceSynthesizers: [ResourceSynthesizer] = [ @@ -331,19 +333,21 @@ final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase { let targetA = Target.test( name: "TargetA", - resources: [ - .folderReference(path: aAssets), - .file(path: frenchStrings), - .file(path: frenchStringsDict), - .file(path: englishStrings), - .file(path: englishStringsDict), - .file(path: emptyPlist), - .file(path: environmentPlist), - .file(path: ttfFont), - .file(path: otfFont), - .file(path: ttcFont), - .file(path: lottieFile), - ] + resources: .init( + [ + .folderReference(path: aAssets), + .file(path: frenchStrings), + .file(path: frenchStringsDict), + .file(path: englishStrings), + .file(path: englishStringsDict), + .file(path: emptyPlist), + .file(path: environmentPlist), + .file(path: ttfFont), + .file(path: otfFont), + .file(path: ttcFont), + .file(path: lottieFile), + ] + ) ) let resourceSynthesizers: [ResourceSynthesizer] = [ @@ -418,9 +422,11 @@ final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase { targets: [ .test( name: "TargetA", - resources: [ - .file(path: ttfFont), - ] + resources: .init( + [ + .file(path: ttfFont), + ] + ) ), ], resourceSynthesizers: makeResourceSynthesizers() @@ -455,9 +461,11 @@ final class SynthesizedResourceInterfaceProjectMapperTests: TuistUnitTestCase { targets: [ .test( name: "TargetA", - resources: [ - .file(path: ttfFont), - ] + resources: .init( + [ + .file(path: ttfFont), + ] + ) ), ], resourceSynthesizers: makeResourceSynthesizers() diff --git a/Tests/TuistKitTests/Mappers/Factories/ProjectMapperFactoryTests.swift b/Tests/TuistKitTests/Mappers/Factories/ProjectMapperFactoryTests.swift index 6096cbc2141..df78dfd0fae 100644 --- a/Tests/TuistKitTests/Mappers/Factories/ProjectMapperFactoryTests.swift +++ b/Tests/TuistKitTests/Mappers/Factories/ProjectMapperFactoryTests.swift @@ -59,6 +59,18 @@ final class ProjectMapperFactoryTests: TuistUnitTestCase { XCTAssertContainsElementOfType(got, GenerateInfoPlistProjectMapper.self, after: DeleteDerivedDirectoryProjectMapper.self) } + func test_default_contains_the_generate_privacy_manifest_mapper() { + // When + let got = subject.default() + + // Then + XCTAssertContainsElementOfType( + got, + GeneratePrivacyManifestProjectMapper.self, + after: DeleteDerivedDirectoryProjectMapper.self + ) + } + func test_default_contains_the_ide_template_macros_mapper() { // When let got = subject.default() diff --git a/Tests/TuistLoaderTests/SwiftPackageManager/PackageInfoMapperTests.swift b/Tests/TuistLoaderTests/SwiftPackageManager/PackageInfoMapperTests.swift index 3810c730e5c..cfe5e603806 100644 --- a/Tests/TuistLoaderTests/SwiftPackageManager/PackageInfoMapperTests.swift +++ b/Tests/TuistLoaderTests/SwiftPackageManager/PackageInfoMapperTests.swift @@ -3199,7 +3199,7 @@ final class PackageInfoMapperTests: TuistUnitTestCase { } } -private func defaultSpmResources(_ target: String, customPath: String? = nil) -> ResourceFileElements { +private func defaultSpmResources(_ target: String, customPath: String? = nil) -> ProjectDescription.ResourceFileElements { let fullPath: String if let customPath { fullPath = customPath diff --git a/fixtures/ios_app_with_framework_and_resources/App/Project.swift b/fixtures/ios_app_with_framework_and_resources/App/Project.swift index bf42c204395..d3a02079100 100644 --- a/fixtures/ios_app_with_framework_and_resources/App/Project.swift +++ b/fixtures/ios_app_with_framework_and_resources/App/Project.swift @@ -10,20 +10,36 @@ let project = Project( bundleId: "io.tuist.App", infoPlist: "Config/App-Info.plist", sources: "Sources/**", - resources: [ - "Resources/**/*.png", - "Resources/*.xcassets", - "Resources/**/*.txt", - "Resources/**/*.strings", - "Resources/**/*.stringsdict", - "Resources/**/*.plist", - "Resources/**/*.otf", - "Resources/resource_without_extension", - .glob(pattern: "ODRResources/*.png", tags: ["tag1"]), - .glob(pattern: "ODRResources/odr_text.txt", tags: ["tag2"]), - .folderReference(path: "Examples"), - .folderReference(path: "ODRExamples", tags: ["tag1", "tag2"]), - ], + resources: .resources( + [ + "Resources/**/*.png", + "Resources/*.xcassets", + "Resources/**/*.txt", + "Resources/**/*.strings", + "Resources/**/*.stringsdict", + "Resources/**/*.plist", + "Resources/**/*.otf", + "Resources/resource_without_extension", + .glob(pattern: "ODRResources/*.png", tags: ["tag1"]), + .glob(pattern: "ODRResources/odr_text.txt", tags: ["tag2"]), + .folderReference(path: "Examples"), + .folderReference(path: "ODRExamples", tags: ["tag1", "tag2"]), + ], + privacyManifest: .privacyManifest( + tracking: false, + trackingDomains: [], + collectedDataTypes: [ + ], + accessedApiTypes: [ + [ + "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults", + "NSPrivacyAccessedAPITypeReasons": [ + "CA92.1", + ], + ], + ] + ) + ), dependencies: [ .project(target: "Framework1", path: "../Framework1"), .project(target: "StaticFramework", path: "../StaticFramework"), diff --git a/fixtures/ios_app_with_privacy_manifest/.gitignore b/fixtures/ios_app_with_privacy_manifest/.gitignore new file mode 100644 index 00000000000..24b244f9c45 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/.gitignore @@ -0,0 +1,70 @@ +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno + +### Projects ### +*.xcodeproj +*.xcworkspace + +### Tuist derived files ### +graph.dot +Derived/ + +### Tuist managed dependencies ### +Tuist/.build \ No newline at end of file diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..eb878970081 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..9221b9bb1a3 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/Contents.json b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/LaunchScreen.storyboard b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/LaunchScreen.storyboard new file mode 100644 index 00000000000..865e9329f37 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Sources/ContentView.swift b/fixtures/ios_app_with_privacy_manifest/MyApp/Sources/ContentView.swift new file mode 100644 index 00000000000..48f62ebea83 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Sources/ContentView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +public struct ContentView: View { + public init() {} + + public var body: some View { + Text("Hello, World!") + .padding() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Sources/MyAppApp.swift b/fixtures/ios_app_with_privacy_manifest/MyApp/Sources/MyAppApp.swift new file mode 100644 index 00000000000..2db0d884171 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Sources/MyAppApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct MyAppApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/MyApp/Tests/MyAppTests.swift b/fixtures/ios_app_with_privacy_manifest/MyApp/Tests/MyAppTests.swift new file mode 100644 index 00000000000..c36005b54f2 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/MyApp/Tests/MyAppTests.swift @@ -0,0 +1,8 @@ +import Foundation +import XCTest + +final class MyAppTests: XCTestCase { + func test_twoPlusTwo_isFour() { + XCTAssertEqual(2 + 2, 4) + } +} diff --git a/fixtures/ios_app_with_privacy_manifest/Project.swift b/fixtures/ios_app_with_privacy_manifest/Project.swift new file mode 100644 index 00000000000..f9d0e8d5127 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/Project.swift @@ -0,0 +1,55 @@ +import ProjectDescription + +let project = Project( + name: "MyApp", + targets: [ + .target( + name: "MyApp", + destinations: .iOS, + product: .app, + bundleId: "io.tuist.MyApp", + infoPlist: .extendingDefault( + with: [ + "UILaunchStoryboardName": "LaunchScreen.storyboard", + ] + ), + sources: ["MyApp/Sources/**"], + resources: .resources( + ["MyApp/Resources/**"], + privacyManifest: .privacyManifest( + tracking: false, + trackingDomains: [], + collectedDataTypes: [ + [ + "NSPrivacyCollectedDataType": "NSPrivacyCollectedDataTypeName", + "NSPrivacyCollectedDataTypeLinked": false, + "NSPrivacyCollectedDataTypeTracking": false, + "NSPrivacyCollectedDataTypePurposes": [ + "NSPrivacyCollectedDataTypePurposeAppFunctionality", + ], + ], + ], + accessedApiTypes: [ + [ + "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults", + "NSPrivacyAccessedAPITypeReasons": [ + "CA92.1", + ], + ], + ] + ) + ), + dependencies: [] + ), + .target( + name: "MyAppTests", + destinations: .iOS, + product: .unitTests, + bundleId: "io.tuist.MyAppTests", + infoPlist: .default, + sources: ["MyApp/Tests/**"], + resources: [], + dependencies: [.target(name: "MyApp")] + ), + ] +) diff --git a/fixtures/ios_app_with_privacy_manifest/README.md b/fixtures/ios_app_with_privacy_manifest/README.md new file mode 100644 index 00000000000..45d6d874f94 --- /dev/null +++ b/fixtures/ios_app_with_privacy_manifest/README.md @@ -0,0 +1,3 @@ +# Application with Privacy Manifest + +This example contains an example that uses a [Privacy Manifest](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files), which describe the data the app collects and the reasons required APIs it uses. \ No newline at end of file