Skip to content

Commit

Permalink
Ensuring Library search paths and Swift import paths are set (#308)
Browse files Browse the repository at this point in the history
- A few additional build settings are needed when linking against pre-built static libraries
- `LIBRARY_SEARCH_PATHS` needs to point to the directory containing the `.a` file
- `SWIFT_INCLUDE_PATHS` need to point to the directory containing the `.swiftmodule` file

Test Plan:

- Generate `fixtures/ios_app_with_static_libraries`
- Verify the application builds
- Verify the `LIBRARY_SEARCH_PATHS` and `SWIFT_INCLUDE_PATHS` settings are set on target `A`
  • Loading branch information
kwridan committed Mar 27, 2019
1 parent 3dea22a commit 5956b94
Show file tree
Hide file tree
Showing 21 changed files with 353 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
- Fix `instance method nearly matches optional requirements` warning in generated `AppDelegate.swift` in iOS projects https://github.com/tuist/tuist/pull/291 by @BalestraPatrick
- Fix Header & Framework search paths override project or xcconfig settings https://github.com/tuist/tuist/pull/301 by @ollieatkinson
- Unit tests bundle for an app target compile & run https://github.com/tuist/tuist/pull/300 by @ollieatkinson
- `LIBRARY_SEARCH_PATHS` and `SWIFT_INCLUDE_PATHS` are now set https://github.com/tuist/tuist/pull/308 by @kwridan

## 0.12.0

Expand Down
76 changes: 56 additions & 20 deletions Sources/TuistKit/Generator/LinkGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ final class LinkGenerator: LinkGenerating {
system: Systeming = System()) throws {
let embeddableFrameworks = try graph.embeddableFrameworks(path: path, name: target.name, system: system)
let headersSearchPaths = graph.librariesPublicHeadersFolders(path: path, name: target.name)
let librarySearchPaths = graph.librariesSearchPaths(path: path, name: target.name)
let swiftIncludePaths = graph.librariesSwiftIncludePaths(path: path, name: target.name)
let linkableModules = try graph.linkableDependencies(path: path, name: target.name)

try generateEmbedPhase(dependencies: embeddableFrameworks,
Expand All @@ -92,6 +94,14 @@ final class LinkGenerator: LinkGenerating {
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)

try setupLibrarySearchPaths(librarySearchPaths,
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)

try setupSwiftIncludePaths(swiftIncludePaths,
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)

try generateLinkingPhase(dependencies: linkableModules,
pbxTarget: pbxTarget,
pbxproj: pbxproj,
Expand Down Expand Up @@ -168,39 +178,55 @@ final class LinkGenerator: LinkGenerating {
return nil
}
.map { $0.removingLastComponent() }
.map { $0.relative(to: sourceRootPath).asString }
.map { "$(SRCROOT)/\($0)" }

if paths.isEmpty { return }
let uniquePaths = Array(Set(paths)).sorted()
try setup(setting: "FRAMEWORK_SEARCH_PATHS",
paths: uniquePaths,
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)
}

let configurationList = pbxTarget.buildConfigurationList
let buildConfigurations = configurationList?.buildConfigurations
func setupHeadersSearchPath(_ paths: [AbsolutePath],
pbxTarget: PBXTarget,
sourceRootPath: AbsolutePath) throws {
try setup(setting: "HEADER_SEARCH_PATHS",
paths: paths,
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)
}

let pathsValue = Set(paths).sorted().joined(separator: " ")
buildConfigurations?.forEach { buildConfiguration in
var frameworkSearchPaths = (buildConfiguration.buildSettings["FRAMEWORK_SEARCH_PATHS"] as? String) ?? "$(inherited)"
if frameworkSearchPaths.isEmpty {
frameworkSearchPaths = pathsValue
} else {
frameworkSearchPaths.append(" \(pathsValue)")
}
buildConfiguration.buildSettings["FRAMEWORK_SEARCH_PATHS"] = frameworkSearchPaths
}
func setupLibrarySearchPaths(_ paths: [AbsolutePath],
pbxTarget: PBXTarget,
sourceRootPath: AbsolutePath) throws {
try setup(setting: "LIBRARY_SEARCH_PATHS",
paths: paths,
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)
}

func setupHeadersSearchPath(_ headersFolders: [AbsolutePath],
func setupSwiftIncludePaths(_ paths: [AbsolutePath],
pbxTarget: PBXTarget,
sourceRootPath: AbsolutePath) throws {
let relativePaths = headersFolders
try setup(setting: "SWIFT_INCLUDE_PATHS",
paths: paths,
pbxTarget: pbxTarget,
sourceRootPath: sourceRootPath)
}

private func setup(setting: String,
paths: [AbsolutePath],
pbxTarget: PBXTarget,
sourceRootPath: AbsolutePath) throws {
let relativePaths = paths
.map { $0.relative(to: sourceRootPath).asString }
.map { "$(SRCROOT)/\($0)" }
guard let configurationList = pbxTarget.buildConfigurationList else {
throw LinkGeneratorError.missingConfigurationList(targetName: pbxTarget.name)
}

let pathsValue = relativePaths.joined(separator: " ")
configurationList.buildConfigurations.forEach {
var headers = ($0.buildSettings["HEADER_SEARCH_PATHS"] as? String) ?? "$(inherited)"
headers.append(" \(relativePaths.joined(separator: " "))")
$0.buildSettings["HEADER_SEARCH_PATHS"] = headers
$0.append(setting: setting, value: pathsValue)
}
}

Expand Down Expand Up @@ -266,3 +292,13 @@ final class LinkGenerator: LinkGenerating {
pbxTarget.buildPhases.append(buildPhase)
}
}

private extension XCBuildConfiguration {
func append(setting name: String, value: String) {
guard !value.isEmpty else {
return
}
let existing = (buildSettings[name] as? String) ?? "$(inherited)"
buildSettings[name] = [existing, value].joined(separator: " ")
}
}
20 changes: 20 additions & 0 deletions Sources/TuistKit/Graph/Graph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ protocol Graphing: AnyObject {

func linkableDependencies(path: AbsolutePath, name: String) throws -> [DependencyReference]
func librariesPublicHeadersFolders(path: AbsolutePath, name: String) -> [AbsolutePath]
func librariesSearchPaths(path: AbsolutePath, name: String) -> [AbsolutePath]
func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> [AbsolutePath]
func embeddableFrameworks(path: AbsolutePath, name: String, system: Systeming) throws -> Set<DependencyReference>
func targetDependencies(path: AbsolutePath, name: String) -> [TargetNode]
func staticDependencies(path: AbsolutePath, name: String) -> [DependencyReference]
Expand Down Expand Up @@ -174,6 +176,24 @@ class Graph: Graphing {
.map(\.publicHeaders)
}

func librariesSearchPaths(path: AbsolutePath, name: String) -> [AbsolutePath] {
guard let targetNode = findTargetNode(path: path, name: name) else {
return []
}

return targetNode.libraryDependencies
.map { $0.path.removingLastComponent() }
}

func librariesSwiftIncludePaths(path: AbsolutePath, name: String) -> [AbsolutePath] {
guard let targetNode = findTargetNode(path: path, name: name) else {
return []
}

return targetNode.libraryDependencies
.compactMap { $0.swiftModuleMap?.removingLastComponent() }
}

func embeddableFrameworks(path: AbsolutePath,
name: String,
system: Systeming) throws -> Set<DependencyReference> {
Expand Down
98 changes: 98 additions & 0 deletions Tests/TuistKitTests/Generator/LinkGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,78 @@ final class LinkGeneratorErrorTests: XCTestCase {
}
}

func test_setupLibrarySearchPaths_multiplePaths() throws {
// Given
let searchPaths = [
AbsolutePath("/path/to/libraries"),
AbsolutePath("/path/to/other/libraries"),
]
let sourceRootPath = AbsolutePath("/path")
let xcodeprojElements = createXcodeprojElements()

// When
try subject.setupLibrarySearchPaths(searchPaths,
pbxTarget: xcodeprojElements.pbxTarget,
sourceRootPath: sourceRootPath)

// Then
let config = xcodeprojElements.config
XCTAssertEqual(config.buildSettings["LIBRARY_SEARCH_PATHS"] as? String,
"$(inherited) $(SRCROOT)/to/libraries $(SRCROOT)/to/other/libraries")
}

func test_setupLibrarySearchPaths_noPaths() throws {
// Given
let searchPaths: [AbsolutePath] = []
let sourceRootPath = AbsolutePath("/path")
let xcodeprojElements = createXcodeprojElements()

// When
try subject.setupLibrarySearchPaths(searchPaths,
pbxTarget: xcodeprojElements.pbxTarget,
sourceRootPath: sourceRootPath)

// Then
let config = xcodeprojElements.config
XCTAssertNil(config.buildSettings["LIBRARY_SEARCH_PATHS"])
}

func test_setupSwiftIncludePaths_multiplePaths() throws {
// Given
let searchPaths = [
AbsolutePath("/path/to/libraries"),
AbsolutePath("/path/to/other/libraries"),
]
let sourceRootPath = AbsolutePath("/path")
let xcodeprojElements = createXcodeprojElements()

// When
try subject.setupSwiftIncludePaths(searchPaths,
pbxTarget: xcodeprojElements.pbxTarget,
sourceRootPath: sourceRootPath)

// Then
let config = xcodeprojElements.config
XCTAssertEqual(config.buildSettings["SWIFT_INCLUDE_PATHS"] as? String,
"$(inherited) $(SRCROOT)/to/libraries $(SRCROOT)/to/other/libraries")
}

func test_setupSwiftIncludePaths_noPaths() throws {
// Given
let searchPaths: [AbsolutePath] = []
let sourceRootPath = AbsolutePath("/path")
let xcodeprojElements = createXcodeprojElements()

// When
try subject.setupSwiftIncludePaths(searchPaths,
pbxTarget: xcodeprojElements.pbxTarget,
sourceRootPath: sourceRootPath)

// Then
let config = xcodeprojElements.config
XCTAssertNil(config.buildSettings["SWIFT_INCLUDE_PATHS"])
}

func test_generateLinkingPhase() throws {
var dependencies: [DependencyReference] = []
dependencies.append(DependencyReference.absolute(AbsolutePath("/test.framework")))
Expand Down Expand Up @@ -197,4 +269,30 @@ final class LinkGeneratorErrorTests: XCTestCase {
XCTAssertEqual($0 as? LinkGeneratorError, LinkGeneratorError.missingProduct(name: "waka.framework"))
}
}

// MARK: - Helpers

struct XcodeprojElements {
var pbxproj: PBXProj
var pbxTarget: PBXNativeTarget
var config: XCBuildConfiguration
}

func createXcodeprojElements() -> XcodeprojElements {
let pbxproj = PBXProj()
let pbxTarget = PBXNativeTarget(name: "Test")
pbxproj.add(object: pbxTarget)

let configurationList = XCConfigurationList(buildConfigurations: [])
pbxproj.add(object: configurationList)
pbxTarget.buildConfigurationList = configurationList

let config = XCBuildConfiguration(name: "Debug")
pbxproj.add(object: config)
configurationList.buildConfigurations.append(config)

return XcodeprojElements(pbxproj: pbxproj,
pbxTarget: pbxTarget,
config: config)
}
}
46 changes: 46 additions & 0 deletions Tests/TuistKitTests/Graph/GraphTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,50 @@ final class GraphTests: XCTestCase {
DependencyReference.absolute(frameworkPath),
])
}

func test_librariesSearchPaths() throws {
// Given
let target = Target.test(name: "Main")
let precompiledNode = LibraryNode(path: AbsolutePath("/test/test.a"),
publicHeaders: AbsolutePath("/test/public/"))
let project = Project.test(targets: [target])
let targetNode = TargetNode(project: project,
target: target,
dependencies: [precompiledNode])
let cache = GraphLoaderCache()
cache.add(targetNode: targetNode)
let graph = Graph.test(cache: cache)

// When
let got = graph.librariesSearchPaths(path: project.path,
name: target.name)

// Then
XCTAssertEqual(got, [AbsolutePath("/test")])
}

func test_librariesSwiftIncludePaths() throws {
// Given
let target = Target.test(name: "Main")
let precompiledNodeA = LibraryNode(path: AbsolutePath("/test/test.a"),
publicHeaders: AbsolutePath("/test/public/"),
swiftModuleMap: AbsolutePath("/test/modules/test.swiftmodulemap"))
let precompiledNodeB = LibraryNode(path: AbsolutePath("/test/another.a"),
publicHeaders: AbsolutePath("/test/public/"),
swiftModuleMap: nil)
let project = Project.test(targets: [target])
let targetNode = TargetNode(project: project,
target: target,
dependencies: [precompiledNodeA, precompiledNodeB])
let cache = GraphLoaderCache()
cache.add(targetNode: targetNode)
let graph = Graph.test(cache: cache)

// When
let got = graph.librariesSwiftIncludePaths(path: project.path,
name: target.name)

// Then
XCTAssertEqual(got, [AbsolutePath("/test/modules")])
}
}
10 changes: 10 additions & 0 deletions fixtures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,19 @@ Workspace:
- BTests (iOS unit tests)
```

A standalone C project is used to generate a prebuilt static library:
```
- C:
- C (static library iOS)
- CTests (iOS unit tests)
```

Dependencies:
- App -> A
- A -> B
- A -> prebuild C (libC.a)

Note: to re-create `libC.a` run `fixtures/ios_app_with_static_libraries/Modules/C/build.sh` and copy the contents of `fixtures/ios_app_with_static_libraries/Modules/C/prebuilt`

## ios_app_with_static_frameworks

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ let project = Project(name: "A",
sources: "Sources/**",
dependencies: [
.project(target: "B", path: "../B"),
.library(path: "../prebuilt/C/libC.a",
publicHeaders: "../prebuilt/C",
swiftModuleMap: "../prebuilt/C/C.swiftmodule")
]),
Target(name: "ATests",
platform: .iOS,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import B
import C

public class A {
public static let value: String = "aValue"

public static func printFromA() {
print("print from A")
B.printFromB()
C.printFromC()
}
}
26 changes: 26 additions & 0 deletions fixtures/ios_app_with_static_libraries/Modules/C/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

0 comments on commit 5956b94

Please sign in to comment.