[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.
>
>
>

Reply via email to