Swift Package resource colors coming back as nil in CI test cases. But return fine during local test cases
Asked Answered
A

1

2

Strangely, this only happens when running on CI (GitHub Actions).

here is my asset catalog:

enter image description here

Here is my extension to UIColor to access these colors:

// MARK: - UIKit

public extension UIColor {
    // MARK: Design System Colors

    static let primaryColor = UIColor(named: "PrimaryColor", in: .module, compatibleWith: nil)!
    static let primaryVariant = UIColor(named: "PrimaryVariant", in: .module, compatibleWith: nil)!
    static let onPrimary = UIColor(named: "OnPrimary", in: .module, compatibleWith: nil)!
    static let onPrimaryMedium = UIColor(named: "OnPrimaryMedium", in: .module, compatibleWith: nil)!
    static let onPrimaryDisabled = UIColor(named: "OnPrimaryDisabled", in: .module, compatibleWith: nil)!

    static let surface = UIColor(named: "Surface", in: .module, compatibleWith: nil)!
    static let onSurface = UIColor(named: "OnSurface", in: .module, compatibleWith: nil)!
    static let onSurfaceMedium = UIColor(named: "OnSurfaceMedium", in: .module, compatibleWith: nil)!
    static let onSurfaceDisabled = UIColor(named: "OnSurfaceDisabled", in: .module, compatibleWith: nil)!

    static let background = UIColor(named: "Background", in: .module, compatibleWith: nil)!
    static let onBackground = UIColor(named: "OnBackground", in: .module, compatibleWith: nil)!

    static let accent = UIColor(named: "Accent", in: .module, compatibleWith: nil)!
    static let divider = UIColor(named: "Divider", in: .module, compatibleWith: nil)!

    // MARK: Status Colors

    static let statusSuccess = UIColor(named: "StatusSuccess", in: .module, compatibleWith: nil)!
    static let statusWarning = UIColor(named: "StatusWarning", in: .module, compatibleWith: nil)!
    static let statusError = UIColor(named: "StatusError", in: .module, compatibleWith: nil)!

    // MARK: Map Colors

    static let pinPlace = UIColor(named: "PinPlace", in: .module, compatibleWith: nil)!
    static let pinRoute = UIColor(named: "PinRoute", in: .module, compatibleWith: nil)!
    static let polyline = UIColor(named: "Polyline", in: .module, compatibleWith: nil)!
    static let polylineRoute = UIColor(named: "PolylineRoute", in: .module, compatibleWith: nil)!
}

I have a test case that just runs through all these colors and checks if they can be loaded from the module bundle (so that its safe to force unwrap them):

@testable import MyProject
import SwiftUI
import UIKit
import XCTest

/// Test to ensure that the colors can be loaded from the module bundle without crashing.
class MyProjectColorTest: XCTestCase {
    func testLoadUIColorFromBundle() {
        for color in UIColor.myProjectUIColors {
            XCTAssertNotNil(color)
        }
    }
}

fileprivate extension UIColor {
    static var myProjectUIColors: [UIColor] = [
        UIColor.primaryColor,
        UIColor.primaryVariant, //It fails when it tries to access this color.
        UIColor.onPrimary,
        UIColor.onPrimaryMedium,
        UIColor.onPrimaryDisabled,
        UIColor.surface,
        UIColor.onSurface,
        UIColor.onSurfaceMedium,
        UIColor.onSurfaceDisabled,
        UIColor.background,
        UIColor.onBackground,
        UIColor.accent,
        UIColor.divider,
        UIColor.statusSuccess,
        UIColor.statusWarning,
        UIColor.statusError,
        UIColor.pinPlace,
        UIColor.pinRoute,
        UIColor.polyline,
        UIColor.polylineRoute,
    ]
}

Finally:

Here is my package manifest:

// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "MyProject",
    defaultLocalization: "en",
    platforms: [
        .iOS(.v13),
        .watchOS(.v6),
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "MyProject",
            targets: ["MyProject"]
        ),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "MyProject",
            dependencies: [],
            exclude: [
                "MyProjectDemo/*",
            ],
            // I've also tried omitting this and letting SPM handle it automatically.
            resources: [
                .process("*.xcassets"),
            ]
        ),
        .testTarget(
            name: "MyProjectTests",
            dependencies: ["MyProject"]
        ),
    ]
)

Also, here's my GitHub action script:

name: Build

on:
  push:
    branches: [ master ]
    paths:
       - '.github/workflows/swiftlint.yml'
       - '.swiftlint.yml'
       - '**/*.swift'
  pull_request:
    branches: [ master, develop ]
    paths:
       - '.github/workflows/swiftlint.yml'
       - '.swiftlint.yml'
       - '**/*.swift'

jobs:
  Build:
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/[email protected]
      - name: List available Xcode versions
        run: ls /Applications | grep Xcode
      - name: Select Xcode
        run: sudo xcode-select -switch /Applications/Xcode_12.2.app
      - name: Show Build Destinations
        run: xcodebuild -showdestinations -scheme MyProject
      - name: Build
        run: xcodebuild test -scheme MyProject -destination 'platform=iOS Simulator,OS=14.2,name=iPhone 12'
  Lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/[email protected]
      - name: Lint
        uses: norio-nomura/[email protected]
Amity answered 11/12, 2020 at 4:25 Comment(0)
L
0

there is a workaround for this. Founded Here -> https://developer.apple.com/forums/thread/664295

import Foundation

class CurrentBundleFinder {}

extension Foundation.Bundle {
        
    static var myModule: Bundle = {
        /* The name of your local package, prepended by "LocalPackages_" */
        let bundleName = "UI_UI"
        let candidates = [
            /* Bundle should be present here when the package is linked into an App. */
            Bundle.main.resourceURL,
            /* Bundle should be present here when the package is linked into a framework. */
            Bundle(for: CurrentBundleFinder.self).resourceURL,
            /* For command-line tools. */
            Bundle.main.bundleURL,
            /* Bundle should be present here when running previews from a different package (this is the path to "…/Debug-iphonesimulator/"). */
            Bundle(for: CurrentBundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent(),
        ]
        for candidate in candidates {
            let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
            if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
                return bundle
            }
        }
        fatalError("unable to find bundle named \(bundleName)")
    }()
}
Lunar answered 25/3, 2021 at 15:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.