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 12, 2024
1 parent 5271cb3 commit fc91295
Show file tree
Hide file tree
Showing 46 changed files with 781 additions and 12 deletions.
14 changes: 12 additions & 2 deletions Sources/ProjectDescription/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ 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 initial install tags associated with on demand resources
public var odrInitialInstallTags: [String]

/// The target's prefetched tag order associated with on demand resources
public var odrPrefetchOrder: [String]

public static func target(
name: String,
destinations: Destinations,
Expand All @@ -92,7 +98,9 @@ public struct Target: Codable, Equatable {
additionalFiles: [FileElement] = [],
buildRules: [BuildRule] = [],
mergedBinaryType: MergedBinaryType = .disabled,
mergeable: Bool = false
mergeable: Bool = false,
odrInitialInstallTags: [String] = [],
odrPrefetchOrder: [String] = []
) -> Self {
self.init(
name: name,
Expand All @@ -116,7 +124,9 @@ public struct Target: Codable, Equatable {
additionalFiles: additionalFiles,
buildRules: buildRules,
mergedBinaryType: mergedBinaryType,
mergeable: mergeable
mergeable: mergeable,
odrInitialInstallTags: odrInitialInstallTags,
odrPrefetchOrder: odrPrefetchOrder
)
}
}
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 !target.odrInitialInstallTags.isEmpty {
settings["ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS"] = .string(
target.odrInitialInstallTags.sorted().map {
$0.replacingOccurrences(of: " ", with: "\\ ")
}.joined(separator: " ")
)
}

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

return settings
}

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

private let assetFormats: Set = [
"arresourcegroup",
"cubetextureset",
"dataset",
"imageset",
"imagestack",
"spriteatlas",
"textureset",
]

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] {
// On Demand Resources tags
var tags = project.targets.map { $0.resources.resources.map(\.tags).flatMap { $0 } }.flatMap { $0 }

let initialInstallTags = project.targets.map { $0.odrInitialInstallTags.map { $0 } }.flatMap { $0 }
let prefetchOrderTags = project.targets.map { $0.odrPrefetchOrder.map { $0 } }.flatMap { $0 }
tags.append(contentsOf: initialInstallTags)
tags.append(contentsOf: prefetchOrderTags)

var assetsTags: [String] = []
for target in project.targets {
target.resources.resources.forEach {
guard let children = try? $0.path.path.recursiveChildren() else { return }
children.forEach {
guard let pathExtension = $0.extension, assetFormats.contains(pathExtension) else { return }
guard let contents = try? $0.children() else { return }
contents.forEach {
guard $0.lastComponent == "Contents.json" else { return }
if let data = try? Data(contentsOf: $0.url),
let attributes = try? JSONDecoder().decode(ContentJson.self, from: data)
{
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
11 changes: 11 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,16 @@ class TargetLinter: TargetLinting {
}
return []
}

private func lintOnDemandResourcesTags(target: Target) -> [LintingIssue] {
let intersection = Set(target.odrInitialInstallTags).intersection(Set(target.odrPrefetchOrder))
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 ingored by Xcode",
severity: .warning
)
}
}
}

extension TargetDependency {
Expand Down
8 changes: 7 additions & 1 deletion Sources/TuistGraph/Models/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
public var prune: Bool
public let mergedBinaryType: MergedBinaryType
public let mergeable: Bool
public let odrInitialInstallTags: [String]
public let odrPrefetchOrder: [String]

// MARK: - Init

Expand Down Expand Up @@ -75,7 +77,9 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
buildRules: [BuildRule] = [],
prune: Bool = false,
mergedBinaryType: MergedBinaryType = .disabled,
mergeable: Bool = false
mergeable: Bool = false,
odrInitialInstallTags: [String] = [],
odrPrefetchOrder: [String] = []
) {
self.name = name
self.product = product
Expand Down Expand Up @@ -103,6 +107,8 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
self.prune = prune
self.mergedBinaryType = mergedBinaryType
self.mergeable = mergeable
self.odrInitialInstallTags = odrInitialInstallTags
self.odrPrefetchOrder = odrPrefetchOrder
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ extension TuistGraph.Target {
additionalFiles: additionalFiles,
buildRules: buildRules,
mergedBinaryType: mergedBinaryType,
mergeable: manifest.mergeable
mergeable: manifest.mergeable,
odrInitialInstallTags: manifest.odrInitialInstallTags,
odrPrefetchOrder: manifest.odrPrefetchOrder
)
}

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
28 changes: 28 additions & 0 deletions Tests/TuistGeneratorAcceptanceTests/GenerateAcceptanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,34 @@ 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",
"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
17 changes: 17 additions & 0 deletions Tests/TuistGeneratorTests/Linter/TargetLinterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -454,4 +454,21 @@ final class TargetLinterTests: TuistUnitTestCase {
severity: .warning
))
}

func test_lint_when_target_has_invalid_on_demand_resources_tags() throws {
// Given
let target = Target.empty(
odrInitialInstallTags: ["tag1", "tag2"],
odrPrefetchOrder: ["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 ingored by Xcode",
severity: .warning
))
}
}
70 changes: 70 additions & 0 deletions fixtures/ios_app_with_on_demand_resources/.gitignore
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit fc91295

Please sign in to comment.