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

Add -F flag for xcframework dependency #7998

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

Kyle-Ye
Copy link
Contributor

@Kyle-Ye Kyle-Ye commented Sep 25, 2024

Close #7580

Motivation:

If a target has a dependency on binaryTarget. Then the call of PackageManager.getSymbolGraph to that target will get a directory contains no symbol graph.

It will break PackageManager.getSymbolGraph's client such as swift-docc-plugin.

The downstream user can generate doc using swift build and call docc convert to generate documentation. But if they migrate to swift-docc-plugin or swift-docc-plugin based service like SwiftPackageIndex, the documentation build will fail.

// ✅ Build Package Documentation via Xcode GUI

// ✅ Build Package Documentation manually
swift build --target DemoKit \
  -Xswiftc -emit-symbol-graph \
  -Xswiftc -emit-symbol-graph-dir -Xswiftc "./symbol-graph"

docc convert --additional-symbol-graph-dir ./symbol-graph ...

// ❌ Build Package Documentation via swift-docc-plugin
swift package generate-documentation --target DemoKit

The underlying issue is that xcframework dependencies are being passed to swift-symbolgraph-extract with -I paths, rather than -F paths. (Thanks @QuietMisdreavus)

Modifications:

Adding -F related additionalFlag for binary swift target.

Replace -I with -F is enough to fix the issue. Keeping -I for compatibility reason in case there are other cases where -I is needed

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 25, 2024

@swift-ci please test

Copy link
Contributor

@MaxDesiatov MaxDesiatov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update issue description with more details, otherwise it's unclear what the effect of this PR is, what exactly it's trying to solve, and what's the desired outcome. Additionally, it needs test coverage to prevent future regressions.

@MaxDesiatov MaxDesiatov added the needs tests This change needs test coverage label Sep 25, 2024
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 25, 2024

Please update issue description with more details, otherwise it's unclear what the effect of this PR is, what exactly it's trying to solve, and what's the desired outcome.

The detail can be found on the issue link. If you do not mind duplication, I can re-summary the issue we encounter in this PR too. (Update: Done)

what exactly it's trying to solve, and what's the desired outcome.

Close the issue linked above.

Additionally, it needs test coverage to prevent future regressions.

Yes. We need to add some test case to prevent future regressions before merging it.

I'm struggling with the test infra codebase here and wondering is it acceptable to add a small simple xcframework binary file into the codebase.

@rauhul
Copy link
Member

rauhul commented Sep 25, 2024

This does need a test, but I also have no idea where it would go.

I am curious, do xcframeworks need -I flags at all and should they use -F exclusively?

@MaxDesiatov
Copy link
Contributor

MaxDesiatov commented Sep 25, 2024

Build planning has its own BuildPlanTests: XCTestCase, but I'd advise against adding new test functions to that same class, since it's already too big and should be split up (even disregarding readability, it needs that purely for more parallelizable test runs). If no other test that cover build planning fit your change, creating new one is fine, taking the same testing approach that BuildPlanTests took.

Copy link

@QuietMisdreavus QuietMisdreavus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basic code change looks good. Having both sets of flags should be fine. Were you able to test this on your demo project?

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 27, 2024

Basic code change looks good. Having both sets of flags should be fine. Were you able to test this on your demo project?

Yeah. I have tested it by setting working directory and arguments in Xcode Scheme Editor.

image

The things I'm working on:

  1. Familiar with SwiftPM's testing infra to find a good place to write a test case to cover it.
// Current plan: PackagePluginAPITests/PackageManagerProxyTests.swift
import PackagePlugin
import XCTest
import _InternalTestSupport

final class PackageManagerProxyTests: XCTestCase {
    func testBinarySwiftTargetSymbolGraph() async throws {
        struct SymbolGraphPlugin: CommandPlugin {
            func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws {
                for target in context.package.targets {
                    let result = try packageManager.getSymbolGraph(for: target, options: .init())
                    let url = result.directoryPath
                    ...
                }
            }
        }
        
        try fixture(name: "BinaryTarget") { path in
            // Construct a package which has a binary dependency and perform the plugin
            let package = ...
            let context: PluginContext = ...
            let plugin = SymbolGraphPlugin()
            plugin.performCommand(context: context, arguments: [])
            ...
        }
    }
}
  1. Add a simple/small xcframework in Fixture/BinaryTarget folder. I'm also plan to add the source package and script to generate the xcframework. It is easy to make one for Xcode project. But I have not found a way for pure Swift package yet.

I guess I'll end up using a pure small xcframework build in my local without package source or a remote one with checksum in the end. (Any recommendation here? eg. Apple's official xcframework example will be great. But I can't find one)

Close #7580

Replace -I with -F is enough to fix the issue. Keeping -I for compatibility reason in case there are other cases where -I is needed.
@Kyle-Ye Kyle-Ye force-pushed the bugfix/kyle/xcframework_symbol_graph branch from 0cbb6ef to 68c6b28 Compare September 29, 2024 15:58
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 29, 2024

@swift-ci please test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 29, 2024

@swift-ci test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Sep 29, 2024

@swift-ci test

@Kyle-Ye Kyle-Ye force-pushed the bugfix/kyle/xcframework_symbol_graph branch from 6aa51d8 to 502bc8e Compare October 8, 2024 04:19
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 8, 2024

@swift-ci test

2 similar comments
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 8, 2024

@swift-ci test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 8, 2024

@swift-ci test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 8, 2024

Have you tried either modifying the existing project file or using one of the community-maintained Xcode project generation tools?

Tried to create it via Xcode 15.0.1 / Xcode 15.1 / Xcode 15.1 Beta 2 and modifying the existing project file. They all end up with a strange crash which I can't reproduce locally. (Even with the same Xcode version - 15.1b2)

error: unexpected service error: The Xcode build system has crashed. Build again to continue.

There is already one xcframework zip in the repo. But I can't use it since it only contains a x86_64 one - IntegrationTests/Fixtures/BinaryTargets/TestBinary/SwiftFramework.zip.

I suggest we either bump the current Xcode 15.1b2 on CI to any release version of Xcode / just use a xcframework zip (like the existing SwiftFramework.zip) / keep it as it is but mark it as skip like the previous binary test case. @MaxDesiatov

Or I'd be appreciated if you have other suggestions which may help push the PR to the next level.

final class SwiftPMTests: XCTestCase {
func testBinaryTargets() throws {
try XCTSkip("FIXME: ld: warning: dylib (/../BinaryTargets.6YVYK4/TestBinary/.build/x86_64-apple-macosx/debug/SwiftFramework.framework/SwiftFramework) was built for newer macOS version (10.15) than being linked (10.10)")
#if !os(macOS)
try XCTSkip("Test requires macOS")
#endif
try binaryTargetsFixture { fixturePath in
.

@Kyle-Ye Kyle-Ye force-pushed the bugfix/kyle/xcframework_symbol_graph branch from 478babc to b3aa099 Compare October 12, 2024 13:31
@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 12, 2024

@swift-ci test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 12, 2024

@swift-ci test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 12, 2024

@swift-ci test

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 13, 2024

Remove xcodeproj and use pure Swift package to generate xcframework still fails on macOS+smoke test CI for an unknown error.

Command line invocation:
    /Applications/Xcode-beta.app/Contents/Developer/usr/bin/xcodebuild archive -scheme FooKit -archivePath /private/var/folders/bb/hcrjxg1s0b96pfst0ymhmp240000gn/T/Miscellaneous_Plugins_SymbolGraphForBinaryDependency.vYoYE9/Miscellaneous_Plugins_SymbolGraphForBinaryDependency/FooKit/.build/FooKit-macosx.xcarchive -derivedDataPath /private/var/folders/bb/hcrjxg1s0b96pfst0ymhmp240000gn/T/Miscellaneous_Plugins_SymbolGraphForBinaryDependency.vYoYE9/Miscellaneous_Plugins_SymbolGraphForBinaryDependency/FooKit/.build/xcodebuild/DerivedData -sdk macosx -destination generic/platform=macOS BUILD_LIBRARY_FOR_DISTRIBUTION=YES INSTALL_PATH=Library/Frameworks OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface

User defaults from command line:
    IDEArchivePathOverride = /private/var/folders/bb/hcrjxg1s0b96pfst0ymhmp240000gn/T/Miscellaneous_Plugins_SymbolGraphForBinaryDependency.vYoYE9/Miscellaneous_Plugins_SymbolGraphForBinaryDependency/FooKit/.build/FooKit-macosx.xcarchive
    IDEDerivedDataPathOverride = /private/var/folders/bb/hcrjxg1s0b96pfst0ymhmp240000gn/T/Miscellaneous_Plugins_SymbolGraphForBinaryDependency.vYoYE9/Miscellaneous_Plugins_SymbolGraphForBinaryDependency/FooKit/.build/xcodebuild/DerivedData
    IDEPackageSupportToolchainOverrideForManifestLoading = com.apple.dt.toolchain.XcodeDefault
    IDEPackageSupportUseBuiltinSCM = YES

Build settings from command line:
    BUILD_LIBRARY_FOR_DISTRIBUTION = YES
    INSTALL_PATH = Library/Frameworks
    OTHER_SWIFT_FLAGS = -no-verify-emitted-module-interface
    SDKROOT = macosx14.2
    TOOLCHAINS = default

Resolve Package Graph

<unknown>:0: warning: legacy driver is now deprecated; consider avoiding specifying '-disallow-use-new-driver'
<unknown>:0: error: unable to execute command: <unknown>

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 16, 2024

This does need a test, but I also have no idea where it would go.

I am curious, do xcframeworks need -I flags at all and should they use -F exclusively?

Maybe -I is needed for the header search I guess. But anyway I suggest we just keep it for compatibility. @rauhul

@rauhul
Copy link
Member

rauhul commented Oct 21, 2024

Maybe -I is needed for the header search I guess

I think I'm missing how this has been determined, is there a message above about how the build fails without the flag?

My goal here is so minimize the CLI flags because it makes debugging SwiftPM builds much easier.

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 21, 2024

Maybe -I is needed for the header search I guess

I think I'm missing how this has been determined, is there a message above about how the build fails without the flag?

My goal here is so minimize the CLI flags because it makes debugging SwiftPM builds much easier.

Not very familiar with such flags. So I can't confidently remove it.

But I tried to remove -I and kick off the test case with a C target. It still success.

image

@xedin
Copy link
Contributor

xedin commented Oct 29, 2024

I have a couple of thoughts here:

  1. @Kyle-Ye I think you misunderstand what @MaxDesiatov was trying to say initially. I don't think we need an xcframework test here, it would be enough to either modify an existing test in a BuildPlanTests to make sure that we pass the flags to binary targets correct (for example there might be a test that does a similar check but doesn't check compiler arguments related to binary target dependencies) or add a new test (in a separate file i.e. BuildPlanBinaryTargetTests.swift) which build a plan with a binary target dependency and checks output of compileArguments and symbolGraphExtractArguments for correctness.

  2. To @rauhul's question in Add -F flag for xcframework dependency #7998 (comment). I don't think we need to use -I here at all because correctly emitted -F should handle includes and libraries correctly.

To that end, I think we should remove the code that adds -I and -Xcc -I from BuildPlan+Swift.swift and replace it with

library.headersPaths.forEach {
  swiftTarget.libraryBinaryPaths.insert($0)
}

and modify https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift#L522-L525 to emit -F and -Xcc -F to each library in the list.

  1. We need to add -F for each libary in libraryBinaryPaths to SwiftModuleBuildDescription.symbolGraphExtractArguments.

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 29, 2024

  1. @Kyle-Ye I think you misunderstand what @MaxDesiatov was trying to say initially. I don't think we need an xcframework test here, it would be enough to either modify an existing test in a BuildPlanTests to make sure that we pass the flags to binary targets correct (for example there might be a test that does a similar check but doesn't check compiler arguments related to binary target dependencies) or add a new test (in a separate file i.e. BuildPlanBinaryTargetTests.swift) which build a plan with a binary target dependency and checks output of compileArguments and symbolGraphExtractArguments for correctness.

Got it. And huge thanks for the point out.

@Kyle-Ye
Copy link
Contributor Author

Kyle-Ye commented Oct 29, 2024

  1. To @rauhul's question in Add -F flag for xcframework dependency #7998 (comment). I don't think we need to use -I here at all because correctly emitted -F should handle includes and libraries correctly.

To that end, I think we should remove the code that adds -I and -Xcc -I from BuildPlan+Swift.swift and replace it with

library.headersPaths.forEach {
  swiftTarget.libraryBinaryPaths.insert($0)
}

and modify https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift#L522-L525 to emit -F and -Xcc -F to each library in the list.

  1. We need to add -F for each libary in libraryBinaryPaths to SwiftModuleBuildDescription.symbolGraphExtractArguments.

d57ee08 is what I got. Correct me if I mistake something.

                    for library in libraries {
                        library.headersPaths.forEach {
-                            swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString]
+                            swiftTarget.libraryBinaryPaths.insert($0)
                        }
                        swiftTarget.libraryBinaryPaths.insert(library.libraryPath)
                    }

And it will be the following using the DemoKit.zip provided by the issue.

(lldb) po library.libraryPath
▿ <AbsolutePath:"/<redacted>/DemoKit/.build/artifacts/demokit/MMKV/MMKV.xcframework/macos-arm64_x86_64/MMKV.framework">
  ▿ underlying : <AbsolutePath:"/<redacted>/DemoKit/.build/artifacts/demokit/MMKV/MMKV.xcframework/macos-arm64_x86_64/MMKV.framework">
    ▿ _impl : UNIXPath
      - string : "/<redacted>/DemoKit/.build/artifacts/demokit/MMKV/MMKV.xcframework/macos-arm64_x86_64/MMKV.framework"
(lldb) po library.headersPaths
▿ 1 element
  ▿ 0 : <AbsolutePath:"/<redacted>/DemoKit/.build/artifacts/demokit/MMKV/MMKV.xcframework/macos-arm64_x86_64">
    ▿ underlying : <AbsolutePath:"/<redacted>/DemoKit/.build/artifacts/demokit/MMKV/MMKV.xcframework/macos-arm64_x86_64">
      ▿ _impl : UNIXPath
        - string : "/<redacted>/DemoKit/.build/artifacts/demokit/MMKV/MMKV.xcframework/macos-arm64_x86_64"

@xedin
Copy link
Contributor

xedin commented Oct 29, 2024

The line that adds libraryPath needs to be removed.

@@ -43,7 +43,7 @@ extension BuildPlan {
let libraries = try self.parseXCFramework(for: target, triple: swiftTarget.buildParameters.triple)
for library in libraries {
library.headersPaths.forEach {
swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString]
swiftTarget.libraryBinaryPaths.insert($0)
Copy link
Contributor Author

@Kyle-Ye Kyle-Ye Oct 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#7998 (comment)

Do you mean Line 46 or Line 48 here? @xedin

Considering the po result, I guess you mean Line 46. (Personally I do not get the reason to add it here.) But if I remove the line, it would conflict with your previous comment/suggestion here.

To that end, I think we should remove the code that adds -I and -Xcc -I from BuildPlan+Swift.swift and replace it with

library.headersPaths.forEach {
swiftTarget.libraryBinaryPaths.insert($0)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean swiftTarget.libraryBinaryPaths.insert(library.libraryPath) on line 48, libraryPath doesn't actually point to the place that -F would recognize anyway.

Copy link
Contributor Author

@Kyle-Ye Kyle-Ye Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird. I do not know what "-F" should recognize.

But according to the testDocCPluginForBinaryDependency test case, deleting Line 46 is fine while deleting Line 48 would make the test case fail with "No such module FooKit".

Saying that, I do think it is valuable we add real xcframework test case (via a trusted local one or fix the CI to build it on the fly). The simple flags check may be doubtful here.

There is other reviewer have a +1 for the real test case.

https://github.com/swiftlang/swift-package-manager/pull/7998/files?w=1#r1822483995

@@ -521,7 +521,10 @@ public final class SwiftModuleBuildDescription {

// Only add the build path to the framework search path if there are binary frameworks to link against.
if !self.libraryBinaryPaths.isEmpty {
args += ["-F", self.buildParameters.buildPath.pathString]
args += [
Copy link
Contributor

@bkhouri bkhouri Oct 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Does this if block and the one here always need to match?

  • If so (this comment becomes blocking), can we refactor by moving the common code into its own function so that updating in one place will automatically update all usage?
  • if not, from my knowledge, can you disciple the use cases where they would differ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should. I'll refactor it to a small helper function here.

Copy link
Contributor

@bkhouri bkhouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi.

I provided comments using Conventional Comments.

I'm happy to have a discussion if my comments are invalid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs tests This change needs test coverage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PackagePlugin's PackageManager.getSymbolGraph does not support binaryTarget
7 participants