Skip to content

Commit

Permalink
Add On Demand Resources Support
Browse files Browse the repository at this point in the history
  • Loading branch information
kapitoshka438 committed Apr 20, 2024
1 parent 2573944 commit f2767e3
Show file tree
Hide file tree
Showing 50 changed files with 837 additions and 12 deletions.
12 changes: 12 additions & 0 deletions Sources/ProjectDescription/OnDemandResourcesTags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// On-demand resources tags associated with Initial Install and Prefetched Order categories
public struct OnDemandResourcesTags: Codable, Equatable {
/// Initial install tags associated with on demand resources
public let initialInstall: [String]?
/// Prefetched tag order associated with on demand resources
public let prefetchOrder: [String]?

public init(initialInstall: [String]?, prefetchOrder: [String]?) {
self.initialInstall = initialInstall
self.prefetchOrder = prefetchOrder
}
}
9 changes: 7 additions & 2 deletions Sources/ProjectDescription/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public struct Target: Codable, Equatable {
/// Specifies whether if the target can be merged as part of another binary or not
public var mergeable: Bool

/// The target's tags associated with on demand resources
public var onDemandResourcesTags: OnDemandResourcesTags?

public static func target(
name: String,
destinations: Destinations,
Expand All @@ -92,7 +95,8 @@ public struct Target: Codable, Equatable {
additionalFiles: [FileElement] = [],
buildRules: [BuildRule] = [],
mergedBinaryType: MergedBinaryType = .disabled,
mergeable: Bool = false
mergeable: Bool = false,
onDemandResourcesTags: OnDemandResourcesTags? = nil
) -> Self {
self.init(
name: name,
Expand All @@ -116,7 +120,8 @@ public struct Target: Codable, Equatable {
additionalFiles: additionalFiles,
buildRules: buildRules,
mergedBinaryType: mergedBinaryType,
mergeable: mergeable
mergeable: mergeable,
onDemandResourcesTags: onDemandResourcesTags
)
}
}
3 changes: 3 additions & 0 deletions Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public enum TuistAcceptanceFixtures {
case iosAppWithLocalBinarySwiftPackage
case iosAppWithLocalSwiftPackage
case iosAppWithMultiConfigs
case iosAppWithOnDemandResources
case iosAppWithPluginsAndTemplates
case iosAppWithPrivacyManifest
case iosAppWithRemoteBinarySwiftPackage
Expand Down Expand Up @@ -139,6 +140,8 @@ public enum TuistAcceptanceFixtures {
return "ios_app_with_local_swift_package"
case .iosAppWithMultiConfigs:
return "ios_app_with_multi_configs"
case .iosAppWithOnDemandResources:
return "ios_app_with_on_demand_resources"
case .iosAppWithPluginsAndTemplates:
return "ios_app_with_plugins_and_templates"
case .iosAppWithPrivacyManifest:
Expand Down
16 changes: 16 additions & 0 deletions Sources/TuistGenerator/Generator/ConfigGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,22 @@ final class ConfigGenerator: ConfigGenerating {
}
}

if let initialInstallTags = target.onDemandResourcesTags?.initialInstall, !initialInstallTags.isEmpty {
settings["ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS"] = .string(
initialInstallTags.sorted().map {
$0.replacingOccurrences(of: " ", with: "\\ ")
}.joined(separator: " ")
)
}

if let prefetchOrder = target.onDemandResourcesTags?.prefetchOrder, !prefetchOrder.isEmpty {
settings["ON_DEMAND_RESOURCES_PREFETCH_ORDER"] = .string(
prefetchOrder.map {
$0.replacingOccurrences(of: " ", with: "\\ ")
}.joined(separator: " ")
)
}

return settings
}

Expand Down
61 changes: 61 additions & 0 deletions Sources/TuistGenerator/Generator/KnownAssetTagsGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Foundation
import PathKit
import TuistGraph

private struct ContentJson: Decodable {
struct ContentProperties: Decodable {
enum CodingKeys: String, CodingKey {
case onDemandResourceTags = "on-demand-resource-tags"
}

let onDemandResourceTags: [String]
}

let properties: ContentProperties
}

protocol KnownAssetTagsGenerating: AnyObject {
func generate(project: Project) throws -> [String]
}

final class KnownAssetTagsGenerator: KnownAssetTagsGenerating {
func generate(project: Project) throws -> [String] {
var tags = project.targets.map { $0.resources.resources.map(\.tags).flatMap { $0 } }.flatMap { $0 }

let initialInstallTags = project.targets.compactMap {
$0.onDemandResourcesTags?.initialInstall?.compactMap { $0 }
}.flatMap { $0 }

let prefetchOrderTags = project.targets.compactMap {
$0.onDemandResourcesTags?.prefetchOrder?.compactMap { $0 }
}.flatMap { $0 }

tags.append(contentsOf: initialInstallTags)
tags.append(contentsOf: prefetchOrderTags)

var assetContentsPaths: Set<Path> = []
let decoder = JSONDecoder()
for target in project.targets {
for resource in target.resources.resources {
guard let children = try? resource.path.path.recursiveChildren() else { continue }
let contents = children.filter { $0.lastComponent == "Contents.json" }
for content in contents {
assetContentsPaths.insert(content)
}
}
}

var assetsTags: [String] = []
for path in assetContentsPaths {
guard let data = try? Data(contentsOf: path.url) else { continue }
guard let attributes = try? decoder.decode(ContentJson.self, from: data) else { continue }
assetsTags.append(contentsOf: attributes.properties.onDemandResourceTags)
}

tags.append(contentsOf: assetsTags)

let uniqueTags = Set(tags).sorted()

return uniqueTags
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,9 @@ final class ProjectDescriptorGenerator: ProjectDescriptorGenerating {
private func generateAttributes(project: Project) -> [String: Any] {
var attributes: [String: Any] = [:]

/// ODR tags
let tags = project.targets.map { $0.resources.resources.map(\.tags).flatMap { $0 } }.flatMap { $0 }
let uniqueTags = Set(tags).sorted()

if !uniqueTags.isEmpty {
attributes["KnownAssetTags"] = uniqueTags
// On Demand Resources tags
if let knownAssetTags = try? KnownAssetTagsGenerator().generate(project: project), !knownAssetTags.isEmpty {
attributes["KnownAssetTags"] = knownAssetTags
}

// BuildIndependentTargetsInParallel
Expand Down
15 changes: 15 additions & 0 deletions Sources/TuistGenerator/Linter/TargetLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class TargetLinter: TargetLinting {
issues.append(contentsOf: validateCoreDataModelsExist(target: target))
issues.append(contentsOf: validateCoreDataModelVersionsExist(target: target))
issues.append(contentsOf: lintMergeableLibrariesOnlyAppliesToDynamicTargets(target: target))
issues.append(contentsOf: lintOnDemandResourcesTags(target: target))
for script in target.scripts {
issues.append(contentsOf: targetScriptLinter.lint(script))
}
Expand Down Expand Up @@ -292,6 +293,20 @@ class TargetLinter: TargetLinting {
}
return []
}

private func lintOnDemandResourcesTags(target: Target) -> [LintingIssue] {
guard let odrTags = target.onDemandResourcesTags else { return [] }
guard let initialInstall = odrTags.initialInstall, let prefetchOrder = odrTags.prefetchOrder else {
return []
}
let intersection = Set(initialInstall).intersection(Set(prefetchOrder))
return intersection.map { tag in
LintingIssue(
reason: "Prefetched Order Tag \"\(tag)\" is already assigned to Initial Install Tags category for the target \(target.name) and will be ignored by Xcode",
severity: .warning
)
}
}
}

extension TargetDependency {
Expand Down
9 changes: 9 additions & 0 deletions Sources/TuistGraph/Models/OnDemandResourcesTags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public struct OnDemandResourcesTags: Codable, Equatable {
public let initialInstall: [String]?
public let prefetchOrder: [String]?

public init(initialInstall: [String]?, prefetchOrder: [String]?) {
self.initialInstall = initialInstall
self.prefetchOrder = prefetchOrder
}
}
5 changes: 4 additions & 1 deletion Sources/TuistGraph/Models/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
public var prune: Bool
public let mergedBinaryType: MergedBinaryType
public let mergeable: Bool
public let onDemandResourcesTags: OnDemandResourcesTags?

// MARK: - Init

Expand Down Expand Up @@ -75,7 +76,8 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
buildRules: [BuildRule] = [],
prune: Bool = false,
mergedBinaryType: MergedBinaryType = .disabled,
mergeable: Bool = false
mergeable: Bool = false,
onDemandResourcesTags: OnDemandResourcesTags? = nil
) {
self.name = name
self.product = product
Expand Down Expand Up @@ -103,6 +105,7 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
self.prune = prune
self.mergedBinaryType = mergedBinaryType
self.mergeable = mergeable
self.onDemandResourcesTags = onDemandResourcesTags
}

/// Target can be included in the link phase of other targets
Expand Down
6 changes: 4 additions & 2 deletions Sources/TuistGraphTesting/Models/Target+TestData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ extension Target {
environmentVariables: [String: EnvironmentVariable] = [:],
filesGroup: ProjectGroup = .group(name: "Project"),
dependencies: [TargetDependency] = [],
rawScriptBuildPhases: [RawScriptBuildPhase] = []
rawScriptBuildPhases: [RawScriptBuildPhase] = [],
onDemandResourcesTags: OnDemandResourcesTags? = nil
) -> Target {
Target(
name: name,
Expand All @@ -160,7 +161,8 @@ extension Target {
environmentVariables: environmentVariables,
filesGroup: filesGroup,
dependencies: dependencies,
rawScriptBuildPhases: rawScriptBuildPhases
rawScriptBuildPhases: rawScriptBuildPhases,
onDemandResourcesTags: onDemandResourcesTags
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ extension TuistGraph.Target {
TuistGraph.BuildRule.from(manifest: $0)
}

let onDemandResourcesTags = manifest.onDemandResourcesTags.map {
TuistGraph.OnDemandResourcesTags(initialInstall: $0.initialInstall, prefetchOrder: $0.prefetchOrder)
}

return TuistGraph.Target(
name: name,
destinations: destinations,
Expand All @@ -123,7 +127,8 @@ extension TuistGraph.Target {
additionalFiles: additionalFiles,
buildRules: buildRules,
mergedBinaryType: mergedBinaryType,
mergeable: manifest.mergeable
mergeable: manifest.mergeable,
onDemandResourcesTags: onDemandResourcesTags
)
}

Expand Down
8 changes: 8 additions & 0 deletions Tests/TuistAcceptanceTests/EditAcceptanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ final class EditAcceptanceTestSPMPackage: TuistAcceptanceTestCase {
}
}

final class EditAcceptanceTestAppWithOnDemandResources: TuistAcceptanceTestCase {
func test_app_with_on_demand_resources() async throws {
try setUpFixture(.iosAppWithOnDemandResources)
try await run(EditCommand.self)
try build(scheme: "Manifests")
}
}

extension TuistAcceptanceTestCase {
fileprivate func build(scheme: String) throws {
try System.shared.runAndPrint(
Expand Down
30 changes: 30 additions & 0 deletions Tests/TuistGeneratorAcceptanceTests/GenerateAcceptanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,36 @@ final class GenerateAcceptanceTestiOSAppWithFrameworkAndResources: TuistAcceptan
}
}

final class GenerateAcceptanceTestiOSAppWithOnDemandResources: TuistAcceptanceTestCase {
func test_ios_app_with_on_demand_resources() async throws {
try setUpFixture(.iosAppWithOnDemandResources)
try await run(GenerateCommand.self)
try await run(BuildCommand.self)
let pbxprojPath = xcodeprojPath.appending(component: "project.pbxproj")
let data = try Data(contentsOf: pbxprojPath.url)
let pbxProj = try PBXProj(data: data)
let attributes = try XCTUnwrap(pbxProj.projects.first?.attributes)
let knownAssetTags = try XCTUnwrap(attributes["KnownAssetTags"] as? [String])
let givenTags = [
"ar-resource-group",
"cube-texture",
"data",
"data file",
"datafile",
"datafolder",
"image",
"image-stack",
"json",
"nestedimage",
"newfolder",
"sprite",
"tag with space",
"texture",
]
XCTAssertEqual(knownAssetTags, givenTags)
}
}

final class GenerateAcceptanceTestiOSAppWithPrivacyManifest: TuistAcceptanceTestCase {
func test_ios_app_with_privacy_manifest() async throws {
try setUpFixture(.iosAppWithPrivacyManifest)
Expand Down
19 changes: 19 additions & 0 deletions Tests/TuistGeneratorTests/Linter/TargetLinterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -454,4 +454,23 @@ final class TargetLinterTests: TuistUnitTestCase {
severity: .warning
))
}

func test_lint_when_target_has_invalid_on_demand_resources_tags() throws {
// Given
let target = Target.empty(
onDemandResourcesTags: .init(
initialInstall: ["tag1", "tag2"],
prefetchOrder: ["tag2", "tag3"]
)
)

// When
let got = subject.lint(target: target)

// Then
XCTContainsLintingIssue(got, .init(
reason: "Prefetched Order Tag \"tag2\" is already assigned to Initial Install Tags category for the target \(target.name) and will be ignored by Xcode",
severity: .warning
))
}
}

0 comments on commit f2767e3

Please sign in to comment.