Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate copy-frameworks #2731

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
be91674
Add automatic copying of the linked Carthage frameworks
dimazen Mar 12, 2019
9f7997d
Add filtering out of the Static frameworks during linked frameworks l…
dimazen Mar 13, 2019
d63fd53
Remove relying on the @rpath during linked frameworks inferring
dimazen Mar 14, 2019
94dba4c
Add automatic option for copy-frameworks command
dimazen Mar 16, 2019
ab0c144
Add priority evaluation for user input files and inferred input files
dimazen Mar 16, 2019
ed8f05a
Extract input files inferring to separate class; Add spec for inferring
dimazen Mar 24, 2019
7b9b856
Add excluding of the user input files from inferred files to prevent …
dimazen Mar 24, 2019
b683b99
Update CopyFrameworks to use new InputFilesInferrer
dimazen Mar 24, 2019
89888aa
Update otool response handling to handle Mac frameworks and c99 ident…
dimazen Mar 24, 2019
2ad8830
Remove dashes from otool response handling
dimazen Mar 24, 2019
faac09d
Add doc for automatic copying to README
dimazen Mar 25, 2019
44000e4
Add `verbose` mode and shorten `automatic` to `auto` for copy-framewo…
dimazen Mar 25, 2019
7726b85
Fix README commands for Automatic copy-frameworks
dimazen Mar 26, 2019
62f4e2e
Add FRAMEWORK_SEARCH_PATHS usage by InputFilesInferrer
dimazen Apr 14, 2019
7dd48cf
Add expansion of the recursive framework search path entry
dimazen Apr 14, 2019
f75fa80
Add tests for path resolving by InputFilesInferrer
dimazen Apr 21, 2019
3434045
Add tests for path resolving duplicates / standartization
dimazen Apr 21, 2019
efd3173
Move FRAMEWORK_SEARCH_PATHS mapping to InputFilesInferrer
dimazen Apr 22, 2019
e94ee95
Add ignorance of the directory descendants based on extension; Add al…
dimazen May 1, 2019
e956858
Add spec for InputFilesInferrer framework search paths resolution
dimazen May 1, 2019
5b59eef
Add skipIfOutdated for copyProduct
dimazen May 3, 2019
ba50f98
Fix modification date comparison key to prevent attributes modification
dimazen May 3, 2019
2229832
Fix tests for InputFileInferrer to skip framework file type check
dimazen May 3, 2019
99b1f48
Replace "Nested" by "Transient" dependency
dimazen May 21, 2019
3661c19
Add path quoting and escaping for otool invocation
dimazen May 23, 2019
d779680
Add copy skip before framework stripping / processing
dimazen Jun 2, 2019
241525c
Update logging for automatic frameworks copying to prevent threading …
dimazen Jun 2, 2019
51836c9
Align logging of copy-frameworks command
dimazen Jun 3, 2019
689bea3
Merge branch 'master' of https://github.com/Carthage/Carthage
dimazen Jun 3, 2019
1d25d71
Minor verbose logging improvements for copy-frameworks
dimazen Jun 3, 2019
308ad18
PR review fixes
dimazen Jun 22, 2019
9dbe871
Fix typos
dimazen Jul 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 79 additions & 0 deletions Source/CarthageKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,44 @@ public final class Project { // swiftlint:disable:this type_body_length
return incompatibilities.isEmpty ? .init(value: ()) : .init(error: .invalidResolvedCartfile(incompatibilities))
}
}

func findLinkedFrameworks(
for executableURL: URL,
existingFrameworksEnumerator: SignalProducer<URL, CarthageError>
) -> SignalProducer<String, CarthageError> {
let existingFrameworksMap = existingFrameworksEnumerator
.filter { url in
// We need to filter out any static frameworks to not accidentally copy then for the dynamically linked ones.
let components = url.pathComponents
let staticFolderIndex = components.index(components.endIndex, offsetBy: -2)
return staticFolderIndex >= 0 && components[staticFolderIndex] != FrameworkType.staticFolderName
}
.reduce(into: [String: URL]()) { (map, frameworkURL) in
let name = frameworkURL.deletingPathExtension().lastPathComponent
map[name] = frameworkURL
}

return sharedLinkedFrameworks(for: executableURL).flatMap(.latest) { frameworks -> SignalProducer<String, CarthageError> in
if frameworks.isEmpty {
return .empty
}

return existingFrameworksMap.flatMap(.latest) { builtFrameworks -> SignalProducer<String, CarthageError> in
return SignalProducer(frameworks).filterMap { builtFrameworks[$0]?.path }
}
}
}

/// Finds Carthage's frameworks that are linked against a given executable.
///
/// - Parameters:
/// - executableURL: Path to a executable of the Project. See `xcodebuild` settings `TARGET_BUILD_DIR` and `EXECUTABLE_PATH` for more info.
/// - platform: Platform of the executable.
/// - Returns: Stream of Path for each linked framework for a given `platform` that was build by Carthage.
public func findLinkedFrameworks(for executableURL: URL, platform: Platform) -> SignalProducer<String, CarthageError> {
let frameworksDirectory = directoryURL.appendingPathComponent(platform.relativePath, isDirectory: true)
return findLinkedFrameworks(for: executableURL, existingFrameworksEnumerator: frameworksInDirectory(frameworksDirectory))
}
}

/// Creates symlink between the dependency build folder and the root build folder
Expand Down Expand Up @@ -1521,3 +1559,44 @@ public func cloneOrFetch(
}
}
}

/// Invokes otool -L for a given executable URL.
///
/// - Parameter executableURL: URL to a valid executable.
/// - Returns: Array of the Shared Library ID that are linked against given executable (`Alamofire`, `Realm`, etc).
/// System libraries and dylibs are omited.
internal func sharedLinkedFrameworks(for executableURL: URL) -> SignalProducer<[String], CarthageError> {
return Task("/usr/bin/xcrun", arguments: ["otool", "-L", executableURL.path.spm_shellEscaped()])
.launch()
.mapError(CarthageError.taskError)
.ignoreTaskData()
.filterMap { data -> String? in
return String(data: data, encoding: .utf8)
}
.map(sharedLinkedFrameworkIDs(from:))
}

/// Stripping linked shared frameworks from
/// @rpath/Alamofire.framework/Alamofire (compatibility version 1.0.0, current version 1.0.0)
/// to Alamofire as well as filtering out system frameworks and various dylibs.
/// Static frameworks and libraries won't show up here, so we can ignore them.
///
/// - Parameter input: Output of the otool -L
/// - Returns: Array of Shared Framework IDs.
internal func sharedLinkedFrameworkIDs(from input: String) -> [String] {
guard let regex = try? NSRegularExpression(pattern: "@rpath.*\\.framework\\/([\\w]+)") else {
dimazen marked this conversation as resolved.
Show resolved Hide resolved
return []
}
return input.components(separatedBy: "\n").compactMap { value in
let fullNSRange = NSRange(value.startIndex..<value.endIndex, in: value)
if
let match = regex.firstMatch(in: value, range: fullNSRange),
match.numberOfRanges > 1,
match.range(at: 1).length > 0
{
return Range(match.range(at: 1), in: value).map { String(value[$0]) }
}
return nil
}
}

27 changes: 26 additions & 1 deletion Source/carthage/CopyFrameworks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ private func targetBuildFolder() -> Result<URL, CarthageError> {
.map { URL(fileURLWithPath: $0, isDirectory: true) }
}

private func executablePath() -> Result<URL, CarthageError> {
return appropriateDestinationFolder().flatMap { url in
return getEnvironmentVariable("EXECUTABLE_PATH").map { path in
return url.appendingPathComponent(path)
}
}
}

private func frameworksFolder() -> Result<URL, CarthageError> {
return appropriateDestinationFolder()
.flatMap { url -> Result<URL, CarthageError> in
Expand All @@ -163,6 +171,11 @@ private func frameworksFolder() -> Result<URL, CarthageError> {
}
}

private func projectDirectory() -> Result<URL, CarthageError> {
return getEnvironmentVariable("PROJECT_FILE_PATH")
.map { URL(fileURLWithPath: $0, isDirectory: false).deletingLastPathComponent() }
}

private func validArchitectures() -> Result<[String], CarthageError> {
return getEnvironmentVariable("VALID_ARCHS").map { architectures -> [String] in
return architectures.components(separatedBy: " ")
Expand All @@ -175,7 +188,7 @@ private func buildActionIsArchiveOrInstall() -> Bool {
}

private func inputFiles() -> SignalProducer<String, CarthageError> {
return SignalProducer(values: scriptInputFiles(), scriptInputFileLists())
return SignalProducer(values: scriptInputFiles(), scriptInputFileLists(), inferredInputFiles())
.flatten(.merge)
.uniqueValues()
}
Expand Down Expand Up @@ -213,3 +226,15 @@ private func scriptInputFileLists() -> SignalProducer<String, CarthageError> {
return .empty
}
}

private func inferredInputFiles() -> SignalProducer<String, CarthageError> {
if
let directory = projectDirectory().value,
let platformName = getEnvironmentVariable("PLATFORM_NAME").value,
let platform = BuildPlatform.from(string: platformName)?.platforms.first,
let executable = executablePath().value
{
return Project(directoryURL: directory).findLinkedFrameworks(for: executable, platform: platform)
}
return .empty
}