Referring to libxml2 in Swift Package Description

At least since WWDC'19, many devs have been turning their former Cocoapods into full Swift Packages thanks to the SPM being integrated into Xcode. My current implementation issue is the following: I'm migrating the Fuzi Pod from Cocoapods to SPM (see Adding support for Swift Package Manager by thebluepotato · Pull Request #101 · cezheng/Fuzi · GitHub). This package wraps libxml2 to provide an excellent HTML parser for Swift. To make things simple, I create a Package with a Fuzi target with the following:

.target(name: "Fuzi",
    path: "Sources",
    cSettings: [.headerSearchPath("$(SDKROOT)/usr/include/libxml2")],
    linkerSettings: [.linkedLibrary("xml2")]
)

This worked up until Xcode 11b4 because it did not yet enforce the .headerSearchPath to be exclusively relative. After that, it seemed impossible to point to libxml2 in iOS projects because $(SDKROOT) could not be used anymore. It goes without saying that all unsafe options were looked at but not considered since Xcode won't even look at packages using them.

My next idea was using a .systemLibrary target. For context, the SPM PackageDescription has been updated for Swift 5/5.1 as well, including changes to how "System libraries" should be declared but this has not followed the new availability of SPM for iOS projects. Indeed, Usage.md still refers to system packages which have been deprecated some time ago (swift-package-manager/Usage.md at main · apple/swift-package-manager · GitHub). In the description for PackageDescription, the .systemLibrary target type is referred to but just as the system packages of yore, require to be installed via homebrew (swift-package-manager/PackageDescription.md at main · apple/swift-package-manager · GitHub). This makes no sense on non-macOS or Linux platforms. And it does not make any sense either that such system libraries should be nearly inaccessible under iOS. My newest try is having that target:

.systemLibrary(
            name: "libxmlFuzi",
            path: "Module")

Of course the main Fuzi target depends on libxmlFuzi. The Module folder contains this modulemap:

module libxmlFuzi [system] {
    link "xml2"
    umbrella header "libxml2-fuzi.h"
    export *
    module * { export * }
}

And the following umbrella header:

#import <libxml2/libxml/xmlreader.h>
#import <libxml2/libxml/xpath.h>
#import <libxml2/libxml/xpathInternals.h>
#import <libxml2/libxml/HTMLparser.h>
#import <libxml2/libxml/HTMLtree.h>

My question to the Swift community is the following: how can I possibly get this to build and be used in other projects? It did build at random once or twice, but hasn't since, and never built when include in another Package/project. Any help is greatly appreciated!

EDIT: To clarify, the build errors when building Fuzi are the following, for all Swift files contained in the Package:

<module-includes>:1:9: note: in file included from <module-includes>:1:
#import "libxml/HTMLparser.h"
        ^
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.sdk/usr/include/libxml2/libxml/HTMLparser.h:15:10: error: 'libxml/xmlversion.h' file not found
#include <libxml/xmlversion.h>
         ^
/Users/xxx/Documents/Projects/Fuzi/Sources/Document.swift:23:8: error: could not build Objective-C module 'libxml2'
import libxml2
       ^
2 Likes

Unfortunately there is no way to properly reference libxml2 in a Swift package, but we are aware of this issue.

I am afraid the only workaround right now is copying and patching the libxml headers but that might cause other issues if your copy of headers get out-of-sync with the ones in SDK :confused:

That would indeed be a solution, but a very dirty one. IMO the SPM should have .systemLibrary usable in broader contexts such as iOS. Using .systemLibrary(name: "libxml2") would for example point to the headers of libxml2 in each SDK and also incorporate the linking steps.

Is there a swift-evolution proposal already or a pitch at least?

For the record, here is a previous discussion: Dependency only for Linux? - #9 by mickael

are there any updates regarding this issue? is it possible to add libxml2 as dependency to package made with Swift Package Manager ?

2 Likes

Will this be fixed at some point? If not, I'm going to have to discontinue my OSS project which depends heavily on libxml2, every release of Xcode since 8 or 9 has broken it because of libxml2 changes, frankly, I'm tired of trying to support Swift, when other languages like Rust or Go actually acknowledge the outer world exists.

I'm happy to say that this issue has now been fixed as of Xcode 11.4 beta 1. All you need to do is import libxml2 just as you would any other module from the SDK such as Foundation or UIKit -- no additional search paths or configuration are needed.

For C targets you can also change the import style from #import <libxml2/libxml/*.h> to just #import <libxml/*.h> for compatibility with the more common header layout on Linux.

11 Likes