Introduction
HomeBrew is a popular package manager solution on macOS, but some people still prefer to use MacPorts for package management, and SwiftPM should also add support for it.
Motivation
There are people keeps submitting feedback that they need support of MacPorts in SwiftPM both on the GitHub issue of SwiftPM and the Swift Forums.
See
- MacPorts and pkg-config
- Please add support for MacPorts in SystemPackageProvider. · Issue #6716 · apple/swift-package-manager · GitHub
- Vapor 4 and MacPorts - #6 by IOOI
Proposed solution
Add a new systemPackageProvider type almost the same as we do for HomeBrew, apt-get, yum and nuget.
public enum SystemPackageProvider {
/// Packages installable by the MacPorts package manager.
@available(_PackageDescription, introduced: 999.0)
case portItem([String])
/// Creates a system package provider with a list of installable packages
/// for people who use the MacPorts package manager on macOS.
///
/// - Parameter packages: The list of package names.
///
/// - Returns: A package provider.
public static func port(_ packages: [String]) -> SystemPackageProvider {
return .portItem(packages)
}
}
See the full implementation at Add MacPorts package manager support by Kyle-Ye · Pull Request #6756 · apple/swift-package-manager · GitHub
Detailed Design
For homebrew provider, we simply use $(brew --prefix)/lib/pkgconfig for a search path at first.
"$(brew --prefix)" will be evaluated to be
/opt/homebrewon Apple Silicon Macs and/usr/localon Intel Macs.MacPorts will always be
/opt/local/by default
But since Homebrew can have multiple versions of the same package, we then use a more accurate path - /usr/local/opt/(NAME)/lib/pkgconfig in SwiftPM.
I don't know if MacPorts supports similar features. For now I'll simply use the /opt/local/lib/pkgconfig first and we can improve it later.
case .brew(let packages):
let brewPrefix: String
if let brewPrefixOverride {
brewPrefix = brewPrefixOverride.pathString
} else {
// Homebrew can have multiple versions of the same package. The
// user can choose another version than the latest by running
// ``brew switch NAME VERSION``, so we shouldn't assume to link
// to the latest version. Instead use the version as symlinked
// in /usr/local/opt/(NAME)/lib/pkgconfig.
struct Static {
static let value = { try? TSCBasic.Process.checkNonZeroExit(args: "brew", "--prefix").spm_chomp() }()
}
if let value = Static.value {
brewPrefix = value
} else {
return []
}
}
return try packages.map({ try AbsolutePath(validating: brewPrefix).appending(components: "opt", $0, "lib", "pkgconfig") })
case .port:
return [try AbsolutePath(validating: "/opt/local/lib/pkgconfig")]
Impact on existing packages
Since the new API are entirely additive, there's no impact on existing packages.
And if you'd like to choose MacPorts as your provider instead of HomeBrew, simply replace it with .port.
.systemLibrary(
name: "Clibxml",
pkgConfig: "libxml-2.0",
providers: [
- .brew(["libxml2"]),
+ .port(["libxml2"]),
]
)
Currently we can't make a package supporting them on macOS at the same time. See Future Directions for more information.
Alternatives considered
Asking people to switch from MacPorts to HomeBrew
Asking people to switch from MacPorts to HomeBrew on macOS platform.
Using other name instead of port
@Max_Desiatov is suggesting we use macPorts instead of port across the PR.
On the macPorts side:
- potential conflict with other package manager named
porton BSD systems.
Sticking to the name of a CLI command, e.g.
breworaptthere was a mistake. IMO we should introduce cleaner names and deprecate old names based on short CLI command names. In fact, the type is namedSystemPackageProvider, notSystemPackageCommand. There are enough package manager CLI commands namedportorpkgon BSD systems for example. By squattingporthere we'll create a mess for possible future BSD support. I'm not sure how exactly limited means of namespacing in Swift would help.
portis a ambiguous in the context
Additionally, using plain
portcreates confusion for a reader. I'm skimming through a package and see.portthere without any disambiguation. Is that a Mac port, FreeBSD port, or OpenBSD port? Brevity and consistency is good, but not at the cost of reduced readability and increased chance for confusion.
On the port side:
- We should use a consist API for
SystemPackageProvider
- For
HomeBrewpackage manager (brew install xx), we usebrewhere. - For
apt-getpackage manager (apt install xx), we useapthere. - So for
MacPortspackage manager (port install xx), we should useporthere.
- Swift have a namespace system.
- It is expected we only use it in the right place. We can only use
.port(["xx"])if the callee accepts aSystemPackageProviderparams.
Future Directions
The current implementation of pkgConfigArgs will only pick the first provider available for the platform.
It means we can't set more than 1 provider on a given platform.
For example, both provider A and provider B are available for platform P. If we put provider A in front of provider B, then in fact, provider B will be useless on this platform. If A can provide it, it will be successful, otherwise SwiftPM will tell you to use A to install the corresponding package, and B will not be given a chance to try.
We can fix it in this PR or leave it to a future one.