[Warning: Apologies ahead of time. This reply message is super long, but hopefully everything I've said is relevant.]
Please correct me if I'm wrong here, but it seems like the "mutating port" that you talk about would probably be referred to by various *nix package management systems as a "metapackage" or a "virtual package". For example, a couple of years ago I worked with @Gcenx to modify the MoltenVK port to behave like a virtual package, which attempts to select the correct versioned subport based on the user's OS version. The unversioned MoltenVK port doesn't install any binary itself, it only mutates which versioned subport gets specified as the 'depends_run'. ========== I have long advocated for moving MacPorts to be a fully version-aware package management system. All modern PMSes allow specifying version requirements when listing dependencies. I particularly like the robust way that Debian implemented it when they rewrote Apt from the ground up for version 2.0 (https://www.debian.org/doc/debian-policy/ch-relationships.html), e.g.: Package: mutt Version: 1.3.17-1 Depends: libc6 (>= 2.2.1), default-mta | mail-transport-agent (Here's a more complicated example involving ranges: https://askubuntu.com/questions/766846/version-range-in-debian-control) Even FreeBSD's Ports has implemented the ability to specify dependency version requirements ( https://docs.freebsd.org/en/books/porters-handbook/makefiles/#makefile-version-dependency ): p5-Spiffy>=0.26:devel/p5-Spiffy I'm fully cognizant that this would involve a major re-architecting of macports-base and probably also the server-side infrastructure, but I continue to feel that, at some point, the MacPorts project _must_ implement this sort of a feature in order to continue to be viable as a modern PMS far into the future. ========== Sometimes the single mutating port strategy doesn't work so well. The go > port is an example. Many go modules fail to build on older systems because > they require a newer go than the latest one that works on the older system. > But this wouldn't be solved by switching to multiple versioned go ports. > Each go module port would still need to know its minimum go version which > it currently doesn't. I believe this problem would be completely solved by having a version-aware PMS. First, let's assume that all ports are versioned, because we have a version-aware PMS. When an older system is asked to install a particular go module, it will automatically know that it needs to install an older version of the module, because the module itself is an unversioned virtual package that selects the go module version based on OS version. The versioned (sub)port of the module will attempt to install the go compiler as a dependency; this dependency line in the Portfile has a version range specified, i.e. 'depends_build: go (>= 2.6.1 && <= 3.1.7)', which means that the newest version of go within that version range will get added to the dependency tree. Thus, an old version of go that is compatible with the old version of the module will get installed, followed by the old version of the go module that is compatible with the old OS version. The separate versioned ports strategy also doesn't always work well because > it takes continual effort to keep them in sync. Not necessarily. If you consider the versioned ports to basically be immutable after they have been fully packaged, then you can consider them to be historical archived snapshots of each version of the port. (Okay, immutable is not really the right word, because Portfiles might need to be altered with minor fixes and revbumps.) Really the only thing that would take continual effort to keep in sync would be the Portfiles for the top-level "unversioned" virtual packages. If we go back to the go and go-module example, then this is what the various Portfiles would look like (I'm going to use pseudocode here, please don't hate me): In the go module's unversioned virtual package (ports/go/go-module/Portfile): if {${os.major} <= 15} { depends_run go-module (>= 14.1.7 && <= 16.2.2) } elseif {${os.major} == 16} { depends_run go-module (>= 16.3.0 && <= 17.4.4) } else { depends_run go-module (>= 18.0.0) } In the go module's OLD versioned (sub)port (ports/go/go-module/16.2.2/Portfile): depends_build go (>= 3.0.0 && <= 4.3.7) In the module's NEW versioned (sub)port (ports/go/go-module/18.0.0/Portfile): depends_build go (>= 6.0.0 && <= 7.1.4) As you can see, because the Portfile for go-module v16.2.2 exists at the same time as the Portfile for go-module v18.0.0, and the valid version ranges for each version of the go module will always be specified in the versioned (sub)port's Portfile, installing that particular version of the go module will always specify a valid version of the go compiler. Note that this also elegantly takes care of your example of For example, if I later figure out a way to offer version 5 to some older > OS versions, how does the user who installed cliclick4 learn of that? With a version-aware PMS, let's say that you figure out how to get v5.0.0 of the go compiler to work with Darwin 15. Then in the go module's old versioned (sub)port (ports/go/go-module/16.2.2/Portfile), you would update it to be this: revision 5 # revbump depends_build go (>= 3.0.0 && <= 5.0.0) It wouldn't disturb go-module v18.0.0 in any way, because both of their Portfiles exist at the same time in the ports tree. I know what you're thinking. The logical next question would be: "What about all of the Portfiles for go-module from versions 14.1.7 through 16.2.1? Would those now also need to be changed so that the 'depends_build' line for go is also 5.0.0?" And my answer to that would be: You could if you wanted to, but I don't think it would be absolutely necessary. Because what's the likelihood that someone would want to use go-module v16.2.1, or v15.y.z, or v14.1.7, when v16.2.2 is available? On the one hand, in the scenario where you don't change those other Portfiles, they would still work fine, it's just that they would still be dependent on the go compiler being (>= 3.0.0 && <= 4.3.7). It wouldn't break anything, it's just that anyone who specifically wants to install one of those particular older versions of the module wouldn't receive the benefit of your efforts on getting go-compiler v5.0.0 to work on Darwin 15. On the other hand, if you wanted to risk it, you could mass update all of the Portfiles for go-module v14.1.7 through v16.2.2 to use go-compiler v5.0.0 by using sed or awk commands. ========== I think one trade-off of a fully version-aware PMS might be that it could explode the amount of storage needed, if you were to start retaining all of the pre-compiled binaries for versioned ports. But, other PMSes seem to be able to handle this somehow, so I'm sure there must be a way to balance the trade-offs. Perhaps you could have some way to specify which versions of old pre-compiled binaries to keep on the servers, and leave the other unused versioned ports with only the control files (i.e. the Portfile) present, forcing the user to build no-longer used versioned ports from source locally on their own machines. -- Jason Liu On Fri, Aug 29, 2025 at 10:27 AM Ryan Carsten Schmidt < [email protected]> wrote: > Should we continue to allow changing the port version based on OS version? > Currently we do this in several ports e.g. to offer the last compatible > version to old OS versions. Josh brought up in a ticket that we should > consider not doing that, and instead create separate versioned ports. I > thought we should discuss because there are pros and cons to each approach > and currently we have a mix of strategies. > > For example a user who wants cliclick just installs the port. It picks the > right version for the OS version. This is simple for the user. The > alternative is that a user of an old OS would have to know to install a > hypothetical cliclick4 instead. And the situation becomes complicated if > the criteria change. For example, if I later figure out a way to offer > version 5 to some older OS versions, how does the user who installed > cliclick4 learn of that? > > But such ports that mutate to fit the environment are more complex to > write and maintain. And we have to remember that we can only change the > version (and any other property that goes into the portindex) based on the > segmentation of our server-side portindexes. We maintain separate indexes > for each OS version/arch combination so we can change a port version based > on OS version or arch but not based on other criteria like Xcode version. > > Sometimes the single mutating port strategy doesn't work so well. The go > port is an example. Many go modules fail to build on older systems because > they require a newer go than the latest one that works on the older system. > But this wouldn't be solved by switching to multiple versioned go ports. > Each go module port would still need to know its minimum go version which > it currently doesn't. Or, depending on how common it is for modules to > require newer go versions, we could dispense with the idea of go supporting > old systems entirely. > > The separate versioned ports strategy also doesn't always work well > because it takes continual effort to keep them in sync. The postgresql > ports for example are all a little different from one another because at > various times a fix or a reformat only got applied to one of them but not > the others. > > >
