Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question about using pyver for versioning a package #2

Closed
ceball opened this issue Dec 3, 2017 · 19 comments
Closed

Question about using pyver for versioning a package #2

ceball opened this issue Dec 3, 2017 · 19 comments
Labels

Comments

@ceball
Copy link
Member

ceball commented Dec 3, 2017

There are a few things I'd really like from a versioning package like pyver, but I can't quite see how pyver would provide them for me. I'll try to describe what it is that I want, and maybe someone could tell me how to achieve them with pyver, or suggest an alternative thing to do because what I want is something bad and I need to be saved from myself!

I want...

  • To be able to produce a package at any time (even between "official releases", e.g. to test) without having to do anything except run the packaging command (e.g. python setup.py sdist), and for that package to include the correct version in its name, and to have the same correct version in the imported python package.

  • To automatically get a version that matches the style currently expected for python packages (PEP440), including what I think is important info like e.g. git hash and number of commits on master since last tag if created from a git repo master between tagged releases.

  • To write the release version once via e.g. git tag -a v1.2.3 -m "Release something", and then run the packaging command, and get a release with version 1.2.3 (in package name, and in imported package). Without having to do anything else such as setting one or more other version numbers scattered throughout the source code.

  • To believe the version data from the python package when I ask for it, and to believe the version data in the name of a package, and to know pretty certainly how to regenerate that package if I wanted to do so.

Is it possible to achieve these with pyver? Alternatively, am I wrong for wanting the things described above?

The behavior described above comes out of wanting the following kind of scheme for packaging/releasing:

  1. Say I just released 1.5.1 of my package.
  2. I then immediately tag the repo as e.g. 1.5.2.dev0, which is "pre" version 1.5.2 i.e. 1.5.2 will be considered later than 1.5.2.dev0; additionally, python tools will not install 1.5.2.dev0 unless requested to install pre-releases (pip install --pre [...]).
  3. Then say I do some development on my package and make a PR, and it gets merged. At this point git describe will return something like v1.5.2.dev0-1-g813a2fd. If I import the package from github master (e.g. maybe I pip install -e my clone, or I pip install [github master.zip]), I will get a __version__ of 1.5.2.dev0+1.g813a2fd. If I create a package, e.g. python setup.py bdist_wheel, I will get e.g. package-1.5.2.dev0+1.g813a2fd-py2.py3-none-any.whl. If I install and import that, I will get a __version__ of 1.5.2.dev0+1.g813a2fd. If I conda build conda.recipe/, I will get a conda package noarch/package-1.5.2.dev0_g813a2fd-py_1.tar.bz2 (not totally sure about abusing the build number to store the commit number, but it works) and a __version__ of 1.5.2.dev0+1.g813a2fd when I import the package.
  4. Travis will be set so that commits to master result in a package being uploaded to anaconda.org [channel]/label/dev, meaning the latest master is always easily available.
  5. It's release time, so I git tag -a v1.5.2 -m "Finally made release" and push the tag.
  6. Travis will be set so that pushing a tag like v1.5.2 results in an sdist and wheel being uploaded to pypi (will automatically look like e.g. package-1.5.2-py2.py3-none-any.whl), and a conda package being uploaded to anaconda.org [channel]/label/main (will automatically look like e.g. noarch/package-1.5.2-py_0.tar.bz2). Importing the resulting python package will give a __version__ of 1.5.2.

I’m not certain the scheme above is right, but I think it works (I concretely implemented it to test, and it seems really great). I think such a scheme would help reduce time and willpower required to make a release, reduce confusion about what versions of packages people really have, reduce packaging errors, improve package reproducibility, improve package quality (no stray files - not that it’s a big deal), reduce time to make features and fixes available to others (risk takers!) between releases, and reduce the amount of time spent thinking about versioning and packaging and releasing. I think the scheme depends on the desired behaviors of a versioning tool that I described at the start of this issue, but I might be wrong. I also think the benefits to packaging/releasing would outweigh even quite bad/sneaky code required to achieve the behaviors.

But surely there are downsides to the scheme that I don’t see, so fire away :)

@ceball ceball added the question label Dec 3, 2017
@jbednar
Copy link
Contributor

jbednar commented Dec 5, 2017

I think all four of the things in the first list above are both reasonable requests and important to achieve. I couldn't quite follow everything in the second list, but overall it sounds reasonable as well. In particular, why do you / how can you tag 1.5.2.dev0 immediately? At that point, how do you know that the next release is going to be 1.5.2 as opposed to 1.6.0? What's wrong with leaving the tag at 1.5.1?

@ceball
Copy link
Member Author

ceball commented Dec 5, 2017

In particular, why do you /how can you tag 1.5.2.dev0 immediately? At that point, how do you know that the next release is going to be 1.5.2 as opposed to 1.6.0? What's wrong with leaving the tag at 1.5.1?

Yeah, I'm not sure about that bit. I've never actually tried it out in a real project, just when I implemented the test scheme above.

It's to do with saying "understand you're not using an official release" when someone installs/imports the package between releases, while also not having to do anything other than tagging to make that happen. Having "dev" in the version indicates clearly it's not an official/final release. Sorry, that's still not very clear. I'll try to explain, but only to help you understand what I'm trying to achieve so you can suggest what's better (because I also don't like the idea of tagging straight after a release).

Say I release 1.5.1. I then leave the tag that way and keep working, getting a commit on master. git describe will return something like v1.5.1-1-g813a2fd. If I import from master, I will get a __version__ something like 1.5.1+1.g813a2fd. If I make a package, it will be called something like
noarch/package-1.5.1_g813a2fd-py_1.tar.bz2. On their own, those don't seem obviously enough like unofficial versions made between releases. Or maybe it seems ok to others and I'm worried about nothing? Or maybe the default package version could have 'dev' or similar inserted if not currently exactly on a v1.2.3 type of tag?

As an aside, I did also previously consider a scheme where the commit number N (on master, number of commits since last tag) appears as devN. So rather than e.g. 1.5.2.dev0+1.g813a2fd you would have 1.5.2.dev1+g813a2fd.

Anyway, I am trying to find a way of making clear that the between-release packages are not 'official releases'. Because I am conflicted between wanting to make latest work always available for people (including myself) to try out and use in projects (where 'latest work' means PRs that are merged to master, so reviewed and accepted as a good idea, but maybe not perfect and possibly subject to change once tested in the real world a bit), while also wanting to cling to the idea that I can still make releases which have some guarantee of being better than taking some random point from master.

@jbednar
Copy link
Contributor

jbednar commented Dec 5, 2017

If I make a package, it will be called something like
noarch/package-1.5.1_g813a2fd-py_1.tar.bz2. On their own, those don't seem obviously enough like unofficial versions made between releases. Or maybe it seems ok to others and I'm worried about nothing?

I don't think you are worried about nothing, especially now that conda versions typically include a hash, which means that the g... notation is very likely not to get noticed.

Looking over PEP440, it just seems like they really didn't consider the idea of continuous releases, i.e. things that need to be always available with no human intervention. I certainly can't find any category that sorts correctly and conveys what we would be trying to do here. Using .dev to indicate a pre release requires knowledge of the future, which humans may know as they gear up for a manual release but which is entirely unknowable automatically. .post is promising, in that it's explicitly meant to be after a release (which can be known causally!), but the clear implication of .post is that it's for trivial updates, whereas here we have no idea how different a given automatic build could be from the previous release.

What about X.Y.postN.devM, nominally a "Developmental release of a post-release"? I think it works semantically and for sorting, i.e. it's after release X.Y, it's clearly marked dev, and it's clearly not the main release. It's clunky, but that's not necessarily a bad thing; it's definitely a version string that would make you think twice.

@jlstevens
Copy link
Contributor

Discussing with Chris, why not X.Y.postN e.g 1.5.2.post1+g813a2fd? This would be the first development release post 1.5.2.

@jbednar
Copy link
Contributor

jbednar commented Jan 10, 2018

That seems allowed by PEP440 syntactically, but violates the stated semantics of a post release being a trivial modification of a full release:
image
Some of our in-between releases could be like that, but many of them will have new features, bug fixes, etc., and thus don't fit the stated purpose of .post, hence the .post.dev proposal above (which indicates both that it's post, and also that it's dev and thus could contain anything.

That said, I don't object to abusing .post in this way, as long as we know we are deliberately doing it and acknowledge that it's against the spirit of PEP440.

@ceball
Copy link
Member Author

ceball commented Jan 11, 2018

Just to clarify, there's a decision to make for the python package and the conda package name.

Conda isn't a python packager specifically, but according to https://conda.io/docs/user-guide/tasks/build-packages/package-spec.html#info-index-json, "Conda acknowledges PEP 440". Maybe there's a better page than that, or we should ask conda (build) developers what to do?

Anyway, to try to summarize the question we're asking...

We're talking here about packages that get built automatically for every merge to master. For the first commit to master after release 1.5.2:

  • For python, the version could be 1.5.2.post1.dev+g813a2fd or 1.5.2.post1+g813a2fd. (Unless someone has a better suggestion.)

  • For conda, (??? not sure...) maybe the package will look something like noarch/package-1.5.2.post1.dev+g813a2fd-pyh0b9a878_0.tar.bz2 or noarch/package-1.5.2.post1+g813a2fd-pyh0b9a878_0.tar.bz2? (I lost track of what's happening with the build strings including hash or not). Or abuse the build number for commit count maybe?

@ceball
Copy link
Member Author

ceball commented Jan 11, 2018

To clarify more (not sure if it's helping...), a project doesn't have to produce a package for every commit to master. The main thing is that packages can be generated in an automated way.

Producing a package for every commit to master is something I'm probably proposing for e.g. param 2 development, but other projects might well prefer to do something different. E.g. a project might want to produce packages only on pushing a tag, e.g. git tag -a v1.5.2.dev0 -m ... && git push --tags would result in packages like package-1.5.2.dev0-py2.py3-none-any.whl being produced.

@jlstevens
Copy link
Contributor

jlstevens commented Jan 11, 2018

I have no strong feelings about this and the tyeps of workflow Chris just described is something you can do with autover. Right now, I just want to decide on the version string format (and maybe this discussion means it has to be more configurable?).

@jbednar I don't mind the X.Y.postN.devM suggestion as long as the integers N and M are both meaningful. Looking at your screenshot describing PEP440, the concept of a post release makes sense (the sort of quick bugfix releases we tend to make after a release!) but I don't yet see how it combines with the notion of a dev release.

One idea might be that if we automate packaging when PRs are merged to master, we no longer need a separate notion of a 'dev' releases. These could just be lightweight tags linking to the corresponding, automatically generated package from the last PR merge, using whatever version format we settle on.

@ceball
Copy link
Member Author

ceball commented Jan 11, 2018

X.Y.postN.devM suggestion as long as the integers N and M are both meaningful

Are they both required? Could it be just N (for commit count), and no M?

@jlstevens
Copy link
Contributor

I think that would be ok. I feel having both post and dev is a bit redundant, i.e I would prefer simply dev1, dev2 etc,the way I'm already used to but if that is what PEP440 requires then so be it.

@jbednar
Copy link
Contributor

jbednar commented Jan 11, 2018

Unfortunately, I think we have to abuse PEP440 somehow, because it was apparently not anticipating the desire to have a complete spectrum of interim releases, so it doesn't have any tag that actually means what we want to mean. post means "after the stated release", but only "just after", dev means "before the stated release" (which is only possible just before due to lack of knowledge of the future). "post+dev" isn't clearly defined, which is why I proposed using it, because at least it (a) is not incorrect given PEP440, (b) appears to sort correctly (after the stated release), and (c) is clearly labeled as a dev release (in a way that a .post is not). I'm slightly uncomfortable with just using .post, because the clear implication of PEP440 is that people should use .post releases as being slightly fixed but otherwise precisely the same as the corresponding true release, which is really not going to be true for the packages we are discussing. I do think we are required to have both N and M by PEP440; the regular expression doesn't allow them to be empty ([N!]N(.N)*[{a|b|rc}N][.postN][.devN]).

@jbednar
Copy link
Contributor

jbednar commented Jan 11, 2018

For the record, PEP440 says Developmental releases of post-releases are also strongly discouraged, but they may be appropriate for projects which use the post-release notation for full maintenance releases which may include code changes. I think this statement gets the closest to what we are doing of anything listed in PEP440, despite the strong discouragement.

@jlstevens
Copy link
Contributor

Why don't we just use the normal dev semantics, i.e bump the version and then make dev versions till it is released? E.g after releasing 1.9.1, immediately bump it so you get 1.9.2.dev1, 1.9.2.dev2 etc until 1.9.2 comes out?

If this is still confused, I'll have to give in and actually read PEP440! ;-p

@jbednar
Copy link
Contributor

jbednar commented Jan 11, 2018

That's a valid option. Calling it that isn't necessarily actually accurate, i.e. the next genuine release after 1.9.1 might be 2.0.0, making 1.9.2.dev29 confusing in retrospect. But sure, maybe that's the least of our worries, i.e. we could simply accept that dev releases labelled 1.9.2 really mean "post-1.9.1 dev releases", because then everything sorts properly and is less complex than .post.dev. We could end up with release histories like:

  • 1.9.1
  • 1.9.2dev0
  • 1.9.2dev1
  • 1.9.2dev2
  • 1.9.2dev3
  • 1.9.2dev4
  • 2.0.0dev0 (if we explicitly tag a dev release prior to the main release as we gear up for it?)
  • 2.0.0dev1 (next automatic release after 2.0.0dev0)
  • 2.0.0
  • 2.0.1dev0

etc. All things considered, maybe that's the least confusing option. It's true there's never any 1.9.2 real release, but by the time we have 2.0, maybe no one cares? I'm ok with that.

@jlstevens
Copy link
Contributor

What do you think @ceball @philippjfr ?

I will need to think a bit about the implications for autover. You don't want to make a '1.9.2' tag to define '1.9.2dev0' when '1.9.2' isn't actually released yet. I suppose you can just tag '1.9.2dev0' when it is ready though I'll need to make sure autover.Version supports this.

@jbednar
Copy link
Contributor

jbednar commented Jan 11, 2018

I don't think this would work if it required a manual tag for 1.9.2x to exist. Instead, it would just be defined that the current dev version is 1.9.N+1devM, for 1.9.N last tagged as non-dev. Any explicit tagging of dev versions would be optional and would need to be consistent with such automatic versions (or I guess the CI system could automatically tag?).

@ceball
Copy link
Member Author

ceball commented Jan 12, 2018

I'm not sure if I'm helping here, but these are two "workflows" in which I used/tested versioneer:

  1. Git tag to generate a package on demand, where the version is the git tag. That might be to make a version of the package you want willing victims to try out, or it might be a release candidate, or it might be the package you intend to release and you want to get it for inspection (e.g. from test pypi), or it might even be the package you release and upload to pypi. E.g. I've been generating packages of nbsite on pypi on demand ("this version might be ok - let's try it!") by doing git tag -a 0.1.2 -m ... && git push --tags (getting a package like nbsite-0.1.2, plus updated website). The package version is the git tag. If you want e.g. a 1.5.2.dev3 package, you just tag 1.5.2.dev3. It's great that the packaging and uploading is automated by travis, but it could be done on someone's local machine in just the same way because after git tag 1.5.2.dev3 it's just a standard command like python setup.py sdist to make the package. (Note: a package like package-1.5.2.dev3 can be uploaded to pypi and installed via pip --pre install, but is ignored by plain pip install.)

  2. Having an informative version between tags, whether in generated packages or when using the code from the git repository. If the latest tag is 1.5.1, and the current commit is 813a2fd, and there's been 1 commit since the tag, asking versioneer for the version with "pep440" (default) style will give you 1.5.1+1.g813a2fd. This happens whether you import from a git repository, or you generate a python package via setup.py (the package gets a name like package-1.5.1+1.g813a2fd, and when you import you see a __version__ of 1.5.1+1.g813a2fd). This version name is informative, and complies with pep440, but it's got a "local" (+) component so can't be uploaded to pypi (or test pypi). If you wanted to upload to pypi, you could instead specify the pypi-compatible "pep440-pre" style and get a package like package-1.5.1.post0.dev1. Or "pep440-old" to get package-1.5.1.post1. Or maybe other styles, I don't know.

I think that automatically generating and uploading packages from master (between tags) would be useful for some projects. E.g. it could be good for a project that's quite stable, and which only has PRs merged after they've been accepted as a good idea and have been reviewed, and has reasonable tests, and is thus expected to work all the time - but which might only release after several such PRs. (Releasing generally requiring quite a lot of extra work beyond just making a package, at least in the current world - although the amount of work is falling with automated documentation etc.)

@ceball
Copy link
Member Author

ceball commented Jan 12, 2018

I think I don't have much of an opinion about the versioning scheme to use, i.e. whether it's "post release" versioning vs "predicted next release" versioning.

E.g. to make recent work available, you could deliberately generate (tag) 1.5.2.dev1 or 1.5.1.post1dev0 depending which versioning scheme you prefer. Any number of commits later you could then deliberately generate 1.5.2.dev2 or 1.5.1.post2.dev0. And so on.

Alternatively, to make recent work available, you could automatically generate 1.5.2.dev1 or 1.5.1.post1dev0 from the 1st commit to master since 1.5.1. The 2nd commit, you could generate 1.5.2.dev2 or 1.5.1.post2.dev0. And so on.

(In both cases, whether you only ever deliberately make packages available, or "latest master" packages are automatically available, someone would still deliberately make certain "release" packages e.g. by tagging 1.5.2 or 1.5.2.rc1 or whatever.)

There seem to be tradeoffs with both versioning schemes. Maybe it depends what you want your versions to mean. I guess I've only ever thought about a version as an identifier, a way of ordering, and some kind of weak indicator of how big my problems will be when I try to upgrade to a new version.

@ceball
Copy link
Member Author

ceball commented Jan 23, 2018

This issue contains useful discussion, but no actions, so I'll close it.

My 4 "wants" in the original comment are I hope covered more clearly by issues #12, #13, and #8. I've tried to start adding tests in #11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants