Skip to content

Commit

Permalink
Fix crash when accessing bundle resources in Previews (#6008)
Browse files Browse the repository at this point in the history
* Fix crash when accessing bundle resources in previews

* Lint

* Remove previous implementation that didn't work

* Run lint

* Remove warnings

* Better fix

* Add fixture

* Lint

* Rename fixture to app_with_previews

* Add acceptance test for the fixture

* Improve comment

---------

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>
  • Loading branch information
anlaital-oura and danieleformichelli committed Mar 1, 2024
1 parent 0e88426 commit 89896af
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 39 deletions.
3 changes: 3 additions & 0 deletions Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum TuistAcceptanceFixtures {
case appWithBuildRules
case appWithFrameworkAndTests
case appWithPlugins
case appWithPreviews
case appWithSpmDependencies
case appWithTestPlan
case commandLineToolBasic
Expand Down Expand Up @@ -69,6 +70,8 @@ public enum TuistAcceptanceFixtures {
return "app_with_framework_and_tests"
case .appWithPlugins:
return "app_with_plugins"
case .appWithPreviews:
return "app_with_previews"
case .appWithSpmDependencies:
return "app_with_spm_dependencies"
case .appWithTestPlan:
Expand Down
24 changes: 11 additions & 13 deletions Sources/TuistAutomation/XcodeBuild/XcodeBuildController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import TuistCore
import TuistSupport

public final class XcodeBuildController: XcodeBuildControlling {

// MARK: - Attributes

/// Matches lines of the forms:
Expand All @@ -19,7 +18,7 @@ public final class XcodeBuildController: XcodeBuildControlling {

private let formatter: Formatting
private let environment: Environmenting

public convenience init() {
self.init(formatter: Formatter(), environment: Environment.shared)
}
Expand Down Expand Up @@ -71,9 +70,9 @@ public final class XcodeBuildController: XcodeBuildControlling {
case nil:
break
}

// Derived data path
if let derivedDataPath = derivedDataPath {
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

Expand Down Expand Up @@ -129,12 +128,12 @@ public final class XcodeBuildController: XcodeBuildControlling {
}

// Derived data path
if let derivedDataPath = derivedDataPath {
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

// Result bundle path
if let resultBundlePath = resultBundlePath {
if let resultBundlePath {
command.append(contentsOf: ["-resultBundlePath", resultBundlePath.pathString])
}

Expand Down Expand Up @@ -186,10 +185,10 @@ public final class XcodeBuildController: XcodeBuildControlling {
command.append(contentsOf: ["-archivePath", archivePath.pathString])

// Derived data path
if let derivedDataPath = derivedDataPath {
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

// Arguments
command.append(contentsOf: arguments.flatMap(\.arguments))

Expand All @@ -204,7 +203,7 @@ public final class XcodeBuildController: XcodeBuildControlling {
command.append(contentsOf: arguments.flatMap(\.xcodebuildArguments))
command.append(contentsOf: ["-output", output.pathString])
command.append("-allow-internal-distribution")

return try run(command: command)
}

Expand All @@ -228,10 +227,10 @@ public final class XcodeBuildController: XcodeBuildControlling {
command.append(contentsOf: ["-scheme", scheme])

// Derived data path
if let derivedDataPath = derivedDataPath {
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

// Target
command.append(contentsOf: target.xcodebuildArguments)

Expand All @@ -251,7 +250,7 @@ public final class XcodeBuildController: XcodeBuildControlling {
var currentTarget: String?

let flushTarget = { () in
if let currentTarget = currentTarget {
if let currentTarget {
let buildSettings = XcodeBuildSettings(
currentSettings,
target: currentTarget,
Expand Down Expand Up @@ -291,7 +290,6 @@ public final class XcodeBuildController: XcodeBuildControlling {
}

fileprivate func run(command: [String]) throws -> AsyncThrowingStream<SystemEvent<XcodeBuildOutput>, Error> {

logger.debug("Running xcodebuild command: \(command.joined(separator: " "))")
return System.shared.publisher(command)
.compactMap { [weak self] event -> SystemEvent<XcodeBuildOutput>? in
Expand Down
11 changes: 10 additions & 1 deletion Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,21 @@ public class ResourcesProjectMapper: ProjectMapping {
static let module: Bundle = {
let bundleName = "\(bundleName)"
let candidates = [
var candidates = [
Bundle.main.resourceURL,
Bundle(for: BundleFinder.self).resourceURL,
Bundle.main.bundleURL,
]
#if DEBUG
// This is a fix to make Previews work with bundled resources.
// Logic here is taken from SPM's generated `resource_bundle_accessors.swift` file,
// which is located under the derived data directory after building the project.
if let override = ProcessInfo.processInfo.environment["PACKAGE_RESOURCE_BUNDLE_PATH"] {
candidates.append(URL(fileURLWithPath: override))
}
#endif
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
// https://github.com/rhodgkins/SwiftHTTPStatusCodes
//
// HTTPStatusCodes+Extensions.swift
// HTTPStatusCodes
//
// Created by Richard Hodgkins on 07/06/2016.
// Copyright © 2016 Rich H. All rights reserved.
//

import Foundation

extension HTTPStatusCode {
Expand Down Expand Up @@ -122,7 +113,8 @@ extension HTTPStatusCode {
///
/// Used in the resumable requests proposal to resume aborted PUT or POST requests.
///
/// - seealso: [Original proposal](https://web.archive.org/web/20151013212135/http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal)
/// - seealso: [Original
/// proposal](https://web.archive.org/web/20151013212135/http://code.google.com/p/gears/wiki/ResumableHttpRequestsProposal)
@available(*, unavailable, renamed: "earlyHints", message: "Replaced by RFC standard code with different meaning")
public static let checkpoint = __Unavailable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
//
import Foundation

/// HTTP status codes as per the [IANA HTTP status code registry](http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml).
/// HTTP status codes as per the [IANA HTTP status code
/// registry](http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml).
///
/// Last updated: Fri, 21 Sep 2018 16:10:10 GMT
///
Expand Down Expand Up @@ -276,7 +277,8 @@ import Foundation
///
/// **Category**: Internet Information Services
///
/// - seealso: [Error message when you try to log on to Exchange 2007 by using Outlook Web Access: "440 Login Timeout"](http://support.microsoft.com/kb/941201/en-us)
/// - seealso: [Error message when you try to log on to Exchange 2007 by using Outlook Web Access: "440 Login
/// Timeout"](http://support.microsoft.com/kb/941201/en-us)
@objc(HTTPStatusCodeIISLoginTimeout)
case iisLoginTimeout = 440

Expand All @@ -299,7 +301,8 @@ import Foundation

/// Blocked by Windows Parental Controls: 450
///
/// A Microsoft extension. This error is given when Windows Parental Controls are turned on and are blocking access to the given webpage.
/// A Microsoft extension. This error is given when Windows Parental Controls are turned on and are blocking access to the
/// given webpage.
case blockedByWindowsParentalControls = 450

/// Unavailable For Legal Reasons: 451
Expand All @@ -323,16 +326,19 @@ import Foundation

/// nginx HTTP To HTTPS: 497
///
/// An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests.
/// An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for
/// HTTPS requests.
///
/// **Category**: nginx
case nginxHTTPToHTTPS = 497

/// Token Expired: 498
///
/// Returned by [ArcGIS for Server](https://en.wikipedia.org/wiki/ArcGIS_Server). A code of 498 indicates an expired or otherwise invalid token.
/// Returned by [ArcGIS for Server](https://en.wikipedia.org/wiki/ArcGIS_Server). A code of 498 indicates an expired or
/// otherwise invalid token.
///
/// - seealso: [Using token-based authentication](http://help.arcgis.com/en/arcgisserver/10.0/apis/soap/index.htm#Using_token_authentication.htm)
/// - seealso: [Using token-based
/// authentication](http://help.arcgis.com/en/arcgisserver/10.0/apis/soap/index.htm#Using_token_authentication.htm)
case tokenExpired = 498

/// nginx Client Closed Request: 499
Expand Down Expand Up @@ -390,7 +396,8 @@ import Foundation

/// Bandwidth Limit Exceeded: 509
///
/// The server has exceeded the bandwidth specified by the server administrator; this is often used by shared hosting providers to limit the bandwidth of customers.
/// The server has exceeded the bandwidth specified by the server administrator; this is often used by shared hosting
/// providers to limit the bandwidth of customers.
///
/// - seealso: <https://documentation.cpanel.net/display/CKB/HTTP+Error+Codes+and+Quick+Fixes#HTTPErrorCodesandQuickFixes-509BandwidthLimitExceeded>
case bandwidthLimitExceeded = 509
Expand All @@ -407,11 +414,13 @@ import Foundation

/// Site is frozen: 530
///
/// Used by the [Pantheon](https://en.wikipedia.org/wiki/Pantheon_(software)) web platform to indicate a site that has been frozen due to inactivity.
/// Used by the [Pantheon](https://en.wikipedia.org/wiki/Pantheon_(software)) web platform to indicate a site that has been
/// frozen due to inactivity.
case siteIsFrozen = 530

/// Network Connect Timeout Error: 599
///
/// This status code is not specified in any RFCs, but is used by some HTTP proxies to signal a network connect timeout behind the proxy to a client in front of the proxy.
/// This status code is not specified in any RFCs, but is used by some HTTP proxies to signal a network connect timeout behind
/// the proxy to a client in front of the proxy.
case networkConnectTimeoutError = 599
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ final class BuildAcceptanceTestWithTemplates: TuistAcceptanceTestCase {
}
}

final class BuildAcceptanceTestAppWithPreviews: TuistAcceptanceTestCase {
func test_with_previews() async throws {
try setUpFixture(.appWithPreviews)
try await run(InstallCommand.self)
try await run(GenerateCommand.self)
try await run(BuildCommand.self)
}
}

final class BuildAcceptanceTestAppWithFrameworkAndTests: TuistAcceptanceTestCase {
func test_with_framework_and_tests() async throws {
try setUpFixture(.appWithFrameworkAndTests)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import ResourcesFramework
import SwiftUI

struct TestView: View {
var body: some View {
VStack {
Button("Click to read file from bundle") {
text = readFileFromBundle()
}
Text(text)
}
}

@State var text = "-"
}

#Preview {
TestView()
}
17 changes: 17 additions & 0 deletions fixtures/app_with_previews/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ProjectDescription

let project = Project(
name: "PreviewsFramework",
targets: [
.target(
name: "PreviewsFramework",
destinations: .iOS,
product: .framework,
bundleId: "io.tuist.previewsframework",
sources: "PreviewsFramework/Sources/**",
dependencies: [
.external(name: "ResourcesFramework"),
]
),
]
)
25 changes: 25 additions & 0 deletions fixtures/app_with_previews/ResourcesFramework/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "ResourcesFramework",
platforms: [.iOS(.v15), .macOS(.v14)],
products: [
.library(
name: "ResourcesFramework",
type: .static,
targets: [
"ResourcesFramework",
]
),
],
targets: [
.target(
name: "ResourcesFramework",
resources: [
.process("Resources"),
]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation

public func readFileFromBundle() -> String {
let path = Bundle.module.url(forResource: "file", withExtension: "txt")!
return try! String(contentsOf: path)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
10 changes: 10 additions & 0 deletions fixtures/app_with_previews/Tuist/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "project_with_previews_crash",
dependencies: [
.package(path: "../ResourcesFramework"),
]
)
4 changes: 2 additions & 2 deletions fixtures/tuist_plugin/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ let package = Package(
.target(
name: "CreateFile",
dependencies: [
.product(name: "ProjectAutomation", package: "ProjectAutomation")
.product(name: "ProjectAutomation", package: "ProjectAutomation"),
]
),
.testTarget(name: "CreateFileTests"),
.target(
name: "InspectGraph",
dependencies: [
.product(name: "ProjectAutomation", package: "ProjectAutomation")
.product(name: "ProjectAutomation", package: "ProjectAutomation"),
]
),
]
Expand Down
2 changes: 1 addition & 1 deletion fixtures/tuist_plugin/Sources/CreateFile/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ try "File created with a plugin".write(
atomically: true,
encoding: .utf8
)
print("File created with a plugin!")
print("File created with a plugin!")
3 changes: 2 additions & 1 deletion fixtures/tuist_plugin/Sources/InspectGraph/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import ProjectAutomation

let graph: Graph
if CommandLine.arguments.contains("--path"),
let path = CommandLine.arguments.last {
let path = CommandLine.arguments.last
{
graph = try Tuist.graph(at: path)
} else {
graph = try Tuist.graph()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let templateTwo = Template(
description: "Custom template",
attributes: [
nameAttributeTwo,
platformAttributeTwo
platformAttributeTwo,
],
items: [
.string(path: "\(nameAttributeTwo)/custom.swift", contents: testContentsTwo),
Expand Down
2 changes: 1 addition & 1 deletion fixtures/tuist_plugin/Tests/CreateFileTests/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ final class CreateFileTests: XCTestCase {
func test_example() {
XCTAssertEqual("CreateFile", "CreateFile")
}
}
}

0 comments on commit 89896af

Please sign in to comment.