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/homebrew
on Apple Silicon Macs and/usr/local
on 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
port
on BSD systems.
Sticking to the name of a CLI command, e.g.
brew
orapt
there 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 namedport
orpkg
on BSD systems for example. By squattingport
here we'll create a mess for possible future BSD support. I'm not sure how exactly limited means of namespacing in Swift would help.
port
is a ambiguous in the context
Additionally, using plain
port
creates confusion for a reader. I'm skimming through a package and see.port
there 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
HomeBrew
package manager (brew install xx
), we usebrew
here. - For
apt-get
package manager (apt install xx
), we useapt
here. - So for
MacPorts
package manager (port install xx
), we should useport
here.
- 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 aSystemPackageProvider
params.
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.