Post and developmental release version specifier handling in distros

At least with the distro I help maintain the Python package build infrastructure for (really primary maintainer at this point), the .post and .dev segments in this thread title are problematic to support.

In my distro, the conversion from pre-release version specifiers work correctly, where {a|b|rc}N are converted to our .{a|b|r}N scheme seamlessly during the stage and package processes. However, the logic breaks for .post and .dev, since they would result in distro package versions being older than the referenced final release. I’ve seen other distros manually work around each such case per package, but I would prefer to keep things consistent without workarounds, or better yet, disallow these version segments altogether.

Isn’t that what .post is supposed to do?

It is not clear what sort of response you are looking for. Are you asking for help with distro maintenance? For a change of some kind in the packaging ecosystem? Which change?

Either some guidance on how to make it make sense in a general case for distro package building frameworks or discuss removing the two segments entirely. I was in a bit of a rush to get the thread started before needing to be on the move so I understand how it sounded unclear.

As it stands right now, in order to support those two segments, each package that we have containing those segments would need to be special cased to hell on an individual basis. I would very much like to not do that any longer. Our fetch (mostly from PyPI) phase is tied directly between the package name and version specified on our side to the exactly matching sdist name. Our package manager has its own rules wrt permissible version specifiers and their semantics, namely that any version ending with [DOT][letter]* is older than a version without (ie final release). Pre-releases are seamlessly converted to our convention, as they should be semantically older than a final, but this breaks for .post or .dev. Removing the [DOT] before post or dev does not work, as the sdist with that name will not exist, and no processing of the initial package name and version declarations are allowed.

Both .post and .dev are supposed to be semantically newer than their referenced final release, as they are meant to signify bug-fix releases supposedly not warranting another final release, or number of commits after a final release, amongst other meanings.

This is hard to provide without some specifics on which distro you are maintaining, which package manager it is, …

Yes, I know you asked for “general” guidance, but I don’t see how this could be generalized since package managers vary widely.

I honestly don’t think this has any chance to happen.

3 Likes

This is FreeBSD, but I don’t see how specifically this part varies widely between distro package managers. Maybe how any [DOT][letter] suffix is considered older than the final release, but no [DOT] (without conversion) is newer.

.post and .dev are already strongly discouraged in the final PEP and living standard, but I don’t think this is strong enough deterrence when it comes to version semantics almost anywhere else.

For RPM, we use the tilde (~) for pre-releases and caret (^) for post-releases. Perhaps your versioning scheme has something similar?
The code to convert PyPA versions to RPM versions is hairy, but it works.

1 Like

We don’t have or allow either of those whatsoever.

Is there any chance of adding such a feature to your package manager?

1 Like

Other than the risk of future collisions, could you add another version field? So map 1.0.post1 as 1.0.1 (or even 1.0.0.0.1 if you want to allow a buffer for the project to release 1.0.1 themselves).

Technically, Python package versions can have any number of fields, so you can’t really protect against a project adding a new field on each release. But in general, most don’t do it.

The number of fields isn’t the problem, it’s specifically handling of .post and .dev. Your mapping suggestion was also my first thought but also introduces other issues.

With the addition of PEP 517 support into our framework (by yours truly), this was a golden opportunity to more strictly enforce metadata consistency all the way through sdist, bdist and distro package. As currently implemented, whatever package name and version specifier on our end must match exactly that of the sdist else fetching fails. In your example, specifying 1.0.1 on our end to as a map to 1.0.post1 would fail as the sdist doesn’t exist. Some packages even do 1.0.post0 which has its own issues. While we have a source file override parameter, it is not for cases like this that are apparently numerous. Said parameter also has no effect on the stage/package process, where we specify the exact package and version specifiers from the generated bdist to installer without any override option (and I do not ever intend to consider one).

I can see about entertaining the discussion, but probably not, especially since it would most likely involve a rather heavy redo of at least the version parsing architecture (with just the package manager, another question for the ports framework). As mentioned before, [DOT][any letter] is always considered older than versions without (ie final release). However, [letter] without [DOT] is newer than versions without. Such can only be specified in our RPM spec-equivalent form as the “port version”. Otherwise, “distribution version” is the preferred way to specify versions, which gets converted into our general scheme for package generation only.

For better visualisation, running through some examples using literally the package manager’s in-built version testing function:

% pkg version -t 1.0.1 1.0
>
% pkg version -t 1.0.post1 1.0
<
% pkg version -t 1.0.p1 1.0
<
% pkg version -t 1.0.p0 1.0
<
% pkg version -t 1.0.0 1.0
=
% pkg version -t 1.0p1 1.0
>
% pkg version -t 1.0post0 1.0
>

I agree. The current specification for Python package versions (PEP 440) has been around for nearly 10 years, and removing segments that are currently valid, and are used in real projects, would be massively disruptive, for essentially no benefit to the Python packaging ecosystem.

2 Likes

You mean Version specifiers - Python Packaging User Guide :wink:

3 Likes

…which also states

The use of post-releases to publish maintenance releases containing actual bug fixes is strongly discouraged. In general, it is better to use a longer release number and increment the final component for each maintenance release.

which apparently isn’t strong enough. The benefit lies in external consumers of this ecosystem that have to consider a case in a general way that is oddball almost anywhere else.

So… what do your propose to do, given that removing this part of the spec is not feasible?

post releases are acceptable for (for example) metadata fixes. That’s the point of the phrasing “actual bug fixes”.

Anyway, it’s a moot point. Those version segments aren’t going to go away in any sort of timescale that will make a difference to you.

2 Likes

If my phrasing gave the impression that the affected version segments should go away immediately, understand that this was more about bringing awareness to external consumers of the ecosystem not having good ways to deal with them, even if these have been valid within this ecosystem for as long as they have. Special casing every individual package with these version segments is not a good way when such cases exist systemically. As in, sudo with their patch-level releases is an isolated case, but multiple Python packages using something allowed but heavily discouraged is systemic.

If you are going to strongly discourage (as the living standard says) using these version specifiers whilst still allowing them, then build backends (and any lint-type tooling) should display a warning similar to how setuptools alerts against direct setup.py execution. As long as those projects will entertain patches to that effect, I am willing to implement them.

Am I wrong in thinking that post-releases are in fact recommended to mark old releases as incompatible with new Python versions? For example, if v0.1.4 of mypackage depends on a feature removed in Python 3.12, mypackage maintainers should release v0.1.4post1 with an upper bound on the Python version. If that was the case, and everyone maintained their packages rigorously, it would mean post-releases are far from an edge case (as every versions would have a post-release eventually).

1 Like