Skip to content

Commit

Permalink
Support Multiple Header Paths (#459)
Browse files Browse the repository at this point in the history
Resolves #354

### Short description 📝

Add support to specify array of paths / glob patterns for headers such as the case below:

```
Headers(public: ["Sources/A/**/*.h", "Sources/B/**/*.h"],
        private: ["Sources/C/**/*.h", "Sources/D/**/*.h"],
        project: "Sources/E/**/*.h")
```

### Solution 📦

Similar to the way multiple source file paths/glob patterns were implemented  (#266). We can use `FileList` and `ExpressibleByStringLiteral` to  expose public, private and project headers as either a String or Array of Strings. The original `FileList` used for sources was not reused as it contained `compilerFlags` which is specific to source files. A new `FileList` containing only paths/glob patterns was created for use with headers. This way `FileList` can be potentially reused elsewhere.

### Implementation 👩‍💻👨‍💻

- [X] Remove `FileList` type alias from `SourceFilesList` and references to it 
- [X] Create new `FileList` without `compilerFlags`
- [X] Modify `Headers` to use the new `FileList` for public, private and project headers
- [X] Add tests to `GeneratorModelLoaderTests` to verify newly supported cases work
- [X] Create `ios_app_with_headers` fixture to support new array of strings case for headers
- [X] Write test that verifies `ios_app_with_headers` fixture targets build

### Test Plan
- Run `tuist generate` inside the `ios_app_with_headers` fixture
- Verify that the Framework1 target has the correct header files specified in the manifest
  • Loading branch information
adamkhazi committed Jul 25, 2019
1 parent c148243 commit f84fe72
Show file tree
Hide file tree
Showing 33 changed files with 487 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
- Support for executing Tuist by running `swift project ...` https://github.com/tuist/tuist/pull/447 by @pepibumur.
- New manifest model, `TuistConfig`, to easily configure Tuist's functionalities https://github.com/tuist/tuist/pull/446 by @pepibumur.
- Adding ability to re-generate individual projects https://github.com/tuist/tuist/pull/457 by @kwridan
- Support multiple header paths https://github.com/tuist/tuist/pull/459 by @adamkhazi

### Fixed

Expand Down
25 changes: 25 additions & 0 deletions Sources/ProjectDescription/FileList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

public final class FileList: Codable {
/// List glob patterns.
public let globs: [String]

/// Initializes the files list with the glob patterns.
///
/// - Parameter globs: Glob patterns.
public init(globs: [String]) {
self.globs = globs
}
}

extension FileList: ExpressibleByStringLiteral {
public convenience init(stringLiteral value: String) {
self.init(globs: [value])
}
}

extension FileList: ExpressibleByArrayLiteral {
public convenience init(arrayLiteral elements: String...) {
self.init(globs: elements)
}
}
14 changes: 7 additions & 7 deletions Sources/ProjectDescription/Headers.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import Foundation

/// Headers
public class Headers: Codable {
public final class Headers: Codable {
/// Relative path to public headers.
public let `public`: String?
public let `public`: FileList?

/// Relative path to private headers.
public let `private`: String?
public let `private`: FileList?

/// Relative path to project headers.
public let project: String?
public let project: FileList?

public init(public: String? = nil,
private: String? = nil,
project: String? = nil) {
public init(public: FileList? = nil,
private: FileList? = nil,
project: FileList? = nil) {
self.public = `public`
self.private = `private`
self.project = project
Expand Down
6 changes: 2 additions & 4 deletions Sources/ProjectDescription/SourceFilesList.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// MARK: - FileList

public typealias FileList = SourceFilesList

/// A model to refer to source files that supports passing compiler flags.
public final class SourceFileGlob: ExpressibleByStringLiteral, Codable {
/// Relative glob pattern.
Expand Down Expand Up @@ -59,13 +57,13 @@ public final class SourceFilesList: Codable {
}

/// Support file as single string
extension FileList: ExpressibleByStringLiteral {
extension SourceFilesList: ExpressibleByStringLiteral {
public convenience init(stringLiteral value: String) {
self.init(globs: [value])
}
}

extension FileList: ExpressibleByArrayLiteral {
extension SourceFilesList: ExpressibleByArrayLiteral {
public convenience init(arrayLiteral elements: SourceFileGlob...) {
self.init(globs: elements)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/ProjectDescription/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class Target: Codable {
public let dependencies: [TargetDependency]

/// Relative paths to the sources directory.
public let sources: FileList?
public let sources: SourceFilesList?

/// Relative paths to the resources directory.
public let resources: [FileElement]?
Expand Down Expand Up @@ -91,7 +91,7 @@ public class Target: Codable {
productName: String? = nil,
bundleId: String,
infoPlist: InfoPlist,
sources: FileList? = nil,
sources: SourceFilesList? = nil,
resources: [FileElement]? = nil,
headers: Headers? = nil,
entitlements: String? = nil,
Expand Down
15 changes: 12 additions & 3 deletions Sources/TuistKit/Generator/GeneratorModelLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,18 @@ extension TuistGenerator.CoreDataModel {

extension TuistGenerator.Headers {
static func from(manifest: ProjectDescription.Headers, path: AbsolutePath, fileHandler: FileHandling) -> TuistGenerator.Headers {
let `public` = manifest.public.map { headerFiles(path: path, glob: $0, fileHandler: fileHandler) } ?? []
let `private` = manifest.private.map { headerFiles(path: path, glob: $0, fileHandler: fileHandler) } ?? []
let project = manifest.project.map { headerFiles(path: path, glob: $0, fileHandler: fileHandler) } ?? []
let `public` = manifest.public?.globs.flatMap {
headerFiles(path: path, glob: $0, fileHandler: fileHandler)
} ?? []

let `private` = manifest.private?.globs.flatMap {
headerFiles(path: path, glob: $0, fileHandler: fileHandler)
} ?? []

let project = manifest.project?.globs.flatMap {
headerFiles(path: path, glob: $0, fileHandler: fileHandler)
} ?? []

return Headers(public: `public`, private: `private`, project: project)
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/ProjectDescriptionTests/HeadersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ final class HeadersTests: XCTestCase {
func test_toJSON() {
let subject = Headers(public: "public", private: "private", project: "project")

XCTAssertCodableEqualToJson(subject, "{\"private\": \"private\", \"project\": \"project\", \"public\": \"public\"}")
XCTAssertCodableEqualToJson(subject, "{\"public\":{\"globs\":[\"public\"]},\"private\":{\"globs\":[\"private\"]},\"project\":{\"globs\":[\"project\"]}}")
}
}
20 changes: 10 additions & 10 deletions Tests/ProjectDescriptionTests/TargetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ final class TargetTests: XCTestCase {
let expected = """
{
"headers": {
"public": "public\\/*",
"private": "private\\/*",
"project": "project\\/*"
"public": { "globs": ["public\\/*"] },
"private": { "globs": ["private\\/*"] },
"project": { "globs": ["project\\/*"] }
},
"bundle_id": "bundle_id",
"core_data_models": [
Expand Down Expand Up @@ -133,11 +133,11 @@ final class TargetTests: XCTestCase {
productName: "product_name",
bundleId: "bundle_id",
infoPlist: "info.plist",
sources: FileList(globs: ["sources/*"]),
sources: SourceFilesList(globs: ["sources/*"]),
resources: ["resources/*"],
headers: Headers(public: "public/*",
private: "private/*",
project: "project/*"),
headers: Headers(public: ["public/*"],
private: ["private/*"],
project: ["project/*"]),
entitlements: "entitlement",
actions: [
TargetAction.post(path: "path", arguments: ["arg"], name: "name"),
Expand All @@ -159,9 +159,9 @@ final class TargetTests: XCTestCase {
let expected = """
{
"headers": {
"public": "public\\/*",
"private": "private\\/*",
"project": "project\\/*"
"public": { "globs": ["public\\/*"] },
"private": { "globs": ["private\\/*"] },
"project": { "globs": ["project\\/*"] }
},
"bundle_id": "bundle_id",
"core_data_models": [
Expand Down
72 changes: 72 additions & 0 deletions Tests/TuistKitTests/Generator/GeneratorModelLoaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,78 @@ class GeneratorModelLoaderTest: XCTestCase {
].map { fileHandler.currentPath.appending(RelativePath($0)) })
}

func test_headersArray() throws {
// Given
try fileHandler.createFiles([
"Sources/public/A/A1.h",
"Sources/public/A/A1.m",
"Sources/public/B/B1.h",
"Sources/public/B/B1.m",

"Sources/private/C/C1.h",
"Sources/private/C/C1.m",
"Sources/private/D/D1.h",
"Sources/private/D/D1.m",

"Sources/project/E/E1.h",
"Sources/project/E/E1.m",
"Sources/project/F/F1.h",
"Sources/project/F/F1.m",
])

let manifest = HeadersManifest(public: ["Sources/public/A/*.h", "Sources/public/B/*.h"],
private: ["Sources/private/C/*.h", "Sources/private/D/*.h"],
project: ["Sources/project/E/*.h", "Sources/project/F/*.h"])

// When
let model = TuistGenerator.Headers.from(manifest: manifest, path: path, fileHandler: fileHandler)

// Then
XCTAssertEqual(model.public, [
"Sources/public/A/A1.h",
"Sources/public/B/B1.h",
].map { fileHandler.currentPath.appending(RelativePath($0)) })

XCTAssertEqual(model.private, [
"Sources/private/C/C1.h",
"Sources/private/D/D1.h",
].map { fileHandler.currentPath.appending(RelativePath($0)) })

XCTAssertEqual(model.project, [
"Sources/project/E/E1.h",
"Sources/project/F/F1.h",
].map { fileHandler.currentPath.appending(RelativePath($0)) })
}

func test_headersStringAndArrayMix() throws {
// Given
try fileHandler.createFiles([
"Sources/public/A/A1.h",
"Sources/public/A/A1.m",

"Sources/project/C/C1.h",
"Sources/project/C/C1.m",
"Sources/project/D/D1.h",
"Sources/project/D/D1.m",
])

let manifest = HeadersManifest(public: "Sources/public/A/*.h",
project: ["Sources/project/C/*.h", "Sources/project/D/*.h"])

// When
let model = TuistGenerator.Headers.from(manifest: manifest, path: path, fileHandler: fileHandler)

// Then
XCTAssertEqual(model.public, [
"Sources/public/A/A1.h",
].map { fileHandler.currentPath.appending(RelativePath($0)) })

XCTAssertEqual(model.project, [
"Sources/project/C/C1.h",
"Sources/project/D/D1.h",
].map { fileHandler.currentPath.appending(RelativePath($0)) })
}

func test_coreDataModel() throws {
// Given
try fileHandler.touch(path.appending(component: "model.xcdatamodeld"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extension Target {
productName: String? = nil,
bundleId: String = "com.some.bundle.id",
infoPlist: InfoPlist = .file(path: "Info.plist"),
sources: FileList = "Sources/**",
sources: SourceFilesList = "Sources/**",
resources: [FileElement] = "Resources/**",
headers: Headers? = nil,
entitlements: String? = "app.entitlements",
Expand Down
3 changes: 3 additions & 0 deletions docs/usage/1-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ let project = Project(name: "MyApp",
infoPlist: "Info.plist",
sources: ["Sources/**"],
resources: ["Resources/**"],
headers: Headers(public: ["Sources/public/A/**", "Sources/public/B/**"],
private: "Sources/private/**",
project: ["Sources/project/A/**", "Sources/project/B/**"]),
dependencies: [
/* Target dependencies can be defined here */
/* .framework(path: "framework") */
Expand Down
27 changes: 23 additions & 4 deletions docs/usage/project.swift.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ let project = Project(name: "MyProject",
resources: [
"Resources/**",
.folderReference(path: "Stubs")
],
],
headers: Headers(public: ["Sources/public/A/**", "Sources/public/B/**"],
private: "Sources/private/**",
project: ["Sources/project/A/**", "Sources/project/B/**"]),
dependencies: [
.project(target: "Framework1", path: "../Framework1"),
.project(target: "Framework2", path: "../Framework2")
Expand Down Expand Up @@ -304,27 +307,43 @@ It represents the target headers:
{
name: 'Public',
description: 'Relative glob pattern that points to the public headers',
type: 'String',
type: 'FileList',
optional: true,
default: 'nil',
},
{
name: 'Private',
description: 'Relative glob pattern that points to the private headers',
type: 'String',
type: 'FileList',
optional: true,
default: 'nil',
},
{
name: 'Project',
description: 'Relative glob pattern that points to the project headers',
type: 'String',
type: 'FileList',
optional: true,
default: 'nil',
},
]}
/>

## File List

It represents a list of glob patterns that refer to files:

<PropertiesTable
props={[
{
name: 'Globs',
description: 'Glob pattern to the files.',
type: '[String]',
optional: false,
default: '',
}
]}
/>

## Core Data Model

The `CoreDataModel` type represents a Core Data model:
Expand Down
13 changes: 13 additions & 0 deletions features/generate.feature
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ Feature: Generate a new project using Tuist
Then I should be able to build the scheme Framework1
Then the product 'Framework1.framework' with destination 'Debug-iphoneos' contains the Info.plist key 'Test'

Scenario: The project is an iOS application with headers (ios_app_with_headers)
Given that tuist is available
And I have a working directory
Then I copy the fixture ios_app_with_headers into the working directory
Then tuist generates the project
Then I should be able to build the scheme App
Then I should be able to test the scheme AppTests
Then I should be able to build the scheme Framework1-iOS
Then I should be able to build the scheme Framework1-macOS
Then I should be able to test the scheme Framework1Tests
Then I should be able to build the scheme MainApp_Manifest
Then I should be able to build the scheme Framework1_Manifest

Scenario: The project is a directory without valid manifest file (invalid_workspace_manifest_name)
Given that tuist is available
And I have a working directory
Expand Down
43 changes: 43 additions & 0 deletions fixtures/ios_app_with_headers/App/Config/App-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright Tuist©. All rights reserved.</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

0 comments on commit f84fe72

Please sign in to comment.