beets icon indicating copy to clipboard operation
beets copied to clipboard

Add a workflow for easily creating releases

Open Serene-Arc opened this issue 8 months ago • 28 comments

I humbly present a solution our lack of releases: a workflow that can be triggered to automatically create one. This workflow builds the project, creates a GitHub release, and publishes beets to PyPi, for a one-stop solution.

@sampsyo this would make it much easier to create releases, as it requires only one little interaction: going to the actions tab and entering a version number. Once that's done, the workflow should take care of the rest.

I have only tested the build job so far, since I can't do anything about the pypi or do a release just to test, but the code is lifted from other similar actions and should work fine.

It also requires one piece of setup. This is that PyPi must be set up with a trusted publisher to receive the new package. Once that's done, the process should go off automatically.

Serene-Arc avatar Oct 16 '23 04:10 Serene-Arc

For testing the actual push to pypi you could use test.pypi.org

JOJ0 avatar Oct 16 '23 11:10 JOJ0

Another idea for the manual intervention step: the workflow could be triggered by something like

on: tag-push

fixme: let me give you the exact syntax later today....on the go atm

like that also a human could decide for the version number

JOJ0 avatar Oct 16 '23 11:10 JOJ0

It could, but that would require exactly the same amount of intervention. I can make it trigger the same thing on a tag push to the repository but this workflow has the same amount of user input and does the tag. It's just a choice of whether to do it through git or the GitHub interface.

My goal is to make releases a one-click affair. Ultimately that's on @sampsyo as to what he finds easier: pushing a tag or running an action.

Serene-Arc avatar Oct 16 '23 12:10 Serene-Arc

You are right, it's actually a matter of taste. Just wanted to mention as often triggering things via git tags is what I see other projects do and also what I use myself in other projects.

A second topic to think of at this point is permissions. Who should hit that release button? Or push that tag?

JOJ0 avatar Oct 16 '23 17:10 JOJ0

A second topic to think of at this point is permissions. Who should hit that release button? Or push that tag?

But let me pull back here a little, maybe that's not of too much importance atm. I think my mind was going more of what happens if you push a faulty release to pypi for some reason. But also that's not the end of the world. Worst case is you push a broken version x.x and, thanks to everything is automated, quickly are able to push a bugfix release x.x.1 afterwards :-)

So back to the actual topic...

What do you think of including bumping version in files to that workflow? Sphinx doc version, setup.py and wherever else beets version needs to be increased.

The script in extra/release.py did all kinds of stuff to prep a release. We could brainstorm which stuff of that is absolutely required and what could be left out.

JOJ0 avatar Oct 16 '23 22:10 JOJ0

We can run that script as part of the workflow easily enough. I can integrate it in.

Serene-Arc avatar Oct 16 '23 23:10 Serene-Arc

Ah, I see that it makes a GitHub release itself. We can strip it down to the bare minimum of changing the version numbers, and then add a new commit as part of the jobs.

Serene-Arc avatar Oct 16 '23 23:10 Serene-Arc

Ah, I see that it makes a GitHub release itself. We can strip it down to the bare minimum of changing the version numbers, and then add a new commit as part of the jobs.

This script seems to be a very handy tool - I use something like that but not so sophisticated in my own projects where frequent releases are required. Basically what I do - and I call it "half automation", is:

  • bump the version with a shellscript that basically uses a tool named `bump2version", which increases version in all relevant files and then commits it to git and adds a git-tag locally.
  • I then push the tag to githu, which triggers a workflow that creates a draft for a new release
  • I edit my changelog in that draft-release manually (also using the "what's new" button which gives my all the latest PR's as a checklist already) and set the release to "publish"
  • As a final step I run another shellscript that creates a wheel and pushes to pypi.

Now looking at that script we have in beets, it does something very similar, but in a much more sophisticated way, and also has some more features (for example translating our changelog written in ReST to markdown, probably so it can be used as the changelog text within the actual github release)

It's a python cli-tool using the Click framework. The subcommands it provides give a clue of what it does exactly:

./release.py --help
Usage: release.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  bump       Bump the version number.
  changelog  Get the most recent version's changelog as Markdown.
  datestamp  Enter today's date as the release date in the changelog.
  ghrelease  Create a GitHub release using the `github-release`...
  prep       Run all steps to prepare a release.
  publish    Unleash a release unto the world.
  version    Display the current version.

We might not even have to strip down that script but since it can run "single tasks" we could try to incorporate only some of those into the gh-actions workflow.

The thing is: Is it easily possible to run this program within a gh-actions workflow? You have experience with that @Serene-Arc ?

If you think it's easily possible to run some of those subcommands in our gh-workflow, I think the next step should be to brainstorm which of those things listed under "Commands:" are absolutely required for our needs.

JOJ0 avatar Oct 18 '23 13:10 JOJ0

Ok, so to summarise: What in my opinion is required for a release and what the workflow you provide supports so far (and please do correct me if I'm wrong!):

  • [x] 1. Graphical way to input desired release version
  • [x] 2. Set version in project files and manifest changes as a commit
  • [x] 3. Create a git tag with the version
  • [x] 4. Create a GitHub release from that tag
  • [x] 5. Add the latest changelog to that GitHub release
  • [x] 6. Build the package
  • [x] 7. Publish the package to PyPi

JOJ0 avatar Oct 18 '23 15:10 JOJ0

  1. Add the latest changelog to that GitHub release

extra/release.py does a lot of crazy stuff to make that happen! Do we really need that? A very simple way of adding the release notes to that auto-generated GitHub release could be to just simply state

beets version x.x.x was released on $timestamp, find all the details in "link to docs/changelog.rest"

Wouldn't that be sufficient? Stuff like that can be easily done with gh-actions already. No crazy magic required. For example the release action I used once supports a body statement which could als be filles with version number, timestamp and so on: https://github.com/JOJ0/synadm/blob/382664db21a1776b6e39e5a82cc646cba6a7362c/.github/workflows/release.yaml#L78-L94

JOJ0 avatar Oct 18 '23 16:10 JOJ0

Awesome!!! I love the idea of having this be as automated as possible. The high-level vision would be (a) move release.py into GitHub actions, so it's as easy as possible to trigger, and (b) giving up on all manual steps, such as manual changelog cleanup, which was nice to do when the project was smaller but is no longer really sustainable.

@JOJ0, you mention the issue with GitHub releases. I have lovingly crafted a script to fill out a Markdown changelog for exactly this purpose, as you noted. But that is probably not worth it. We could keep the messy conversion thing just for fun or throw it out and use the simple "link to the docs" approach you propose.

sampsyo avatar Oct 20 '23 19:10 sampsyo

I think the best option would be to strip the release script we currently have down to just changing the version numbers, then use that in an action that also commits it. So it would still only be triggered from the action.

We can also use the markdown conversion in the script automatically and put it into the release. That really isn't a difficult thing. We can capture the output and add it to the release information extremely easily.

Serene-Arc avatar Oct 21 '23 00:10 Serene-Arc

I've changed the workflow and the script so that, hopefully, it changes the version numbers, commits it, and then uses the output to use the changelog in the release.

I'm a little perplexed how to test this. Maybe running it on my own fork would work for most of it? That would check everything except the pushing to pypi and the release I think

Serene-Arc avatar Oct 21 '23 10:10 Serene-Arc

Right, well I just spent the last few hours testing this action and it works, but there is one issue with the changelog @sampsyo @JOJ0 . I tried dynamically including the changelog in the release but it just wasn't working. If you really want I can keep troubleshooting, but right now it's a hard coded link to the changelog in the docs.

Right now, I just need to modify the script so that it changes the changelog again so that the latest release version and date is added as a header. Once that is done, the workflow should be finished.

Serene-Arc avatar Oct 21 '23 12:10 Serene-Arc

I've changed the workflow and the script so that, hopefully, it changes the version numbers, commits it, and then uses the output to use the changelog in the release.

I'm a little perplexed how to test this. Maybe running it on my own fork would work for most of it? That would check everything except the pushing to pypi and the release I think

Awesome work you are doing here, it's just so so cool to finally see a future with realeases! Thanks so much! Love it!

I have one idea for testing: Yes do everything on your own fork and then for testing add one more commit where you change the actual pushing to pypi, to test.pypi.org - that could work, given that the pypi action you are using can be configured to push to that one instead of "real" pypi.org. HTH

JOJ0 avatar Oct 21 '23 12:10 JOJ0

Right, well I just spent the last few hours testing this action and it works, but there is one issue with the changelog @sampsyo @JOJ0 . I tried dynamically including the changelog in the release but it just wasn't working. If you really want I can keep troubleshooting, but right now it's a hard coded link to the changelog in the docs.

Right now, I just need to modify the script so that it changes the changelog again so that the latest release version and date is added as a header. Once that is done, the workflow should be finished.

As already mentionend I think having a super-nice changelog within the realease would be a "nice to have feature", not a "must have feature". So if you ask me, screw this for now and go the easy way. If someone wants to play around with this later on, they can do it. Let's focus on a working solution for now. Tidying up later :-)

JOJ0 avatar Oct 21 '23 12:10 JOJ0

Agreed with @JOJ0 here: awesome that this works at all! IMO, we can move ahead with the simple "see elsewhere" changelogs on GitHub and revisit populating those in the future (in some other PR).

sampsyo avatar Oct 21 '23 20:10 sampsyo

@JOJ0 I have completely tested everything, and it all works, bar the Pypi one, which is copied line for line from their docs, so I don't doubt that it's fine. Once @sampsyo sets up the trusted publisher stuff, it'll work.

Do we want the script to do anything to the changelog? Like add a header to the changelog with the version and the release data, similar to how it is formatted now?

Serene-Arc avatar Oct 21 '23 23:10 Serene-Arc

I think if you name the release title something like beets 1.6.0 (2021-01-01) it will show up similar to what we had here: https://github.com/beetbox/beets/releases/tag/v1.6.0

But actually you could even spare including the date with the title, it's sitting right next to the title in the releases list anyway: https://github.com/beetbox/beets/releases as well as in the actual release view.

For the text, actually just the link to the changelog, something like Find out what's new in [the changleog]<link to changelog>

JOJ0 avatar Oct 22 '23 18:10 JOJ0

I've done that, the releases are fine. I mean changes to the changelog file. What do we want to do for that? Are there changes that need to be made? Do we need to rename the most recent section?

Serene-Arc avatar Oct 22 '23 23:10 Serene-Arc

Ah gotcha! Sorry, I misunderstood, so you mean "the changelog" changelog ;-) Ok so what do we have here.... Screenshot 2023-10-23 at 19 52 51

Hmmm could be a tricky task.

  • Removal of (in development)
  • The line Changelog goes here... should actually stay and below that a new changelog starts....
  • Then preparing for the next version:
    • A h1 heading with the next version number, but we might not know it at this point, maybe just make the top headline always the same: In development
    • then the basic lists "major features", "features", "bug fixes" would need to be created.

Hmm I am really not sure if this all can be automated like this. We might have to simplify how this changelog looks in the future. For example those "how to and where to write my line things" should maybe go into a tutorial/the docs. somewhere else. And the changelog is consisting purely "of data" - descriptions somewhere else.

Sidenote:

I am not sure if 1.6.1 should be the next version number. I think we could skip that and make one (maybe still manual? Or even trying out this workflow and see what's missing") release. And I would call it 1.7.0 - there is dozens of new things in beets since the Nov 2021 release 1.6.0 ;-)

Usually semver says: Major is for breaking changes -> 2.0.0, New features are Minor -> 1.7.0, and only fixes would be -> 1.6.1

But as always there is no-one-answer on how to put what semver suggests (and my idea of it is very very simplified I guess). I think we need @sampsyo here for some thoughts on how to proceed. And let's brainstorm more of what are the essentials of changes to the format of the changelog that could be easily automated and how would we need to adapt it - maybe even now! Before the auto-release-era begins!

JOJ0 avatar Oct 23 '23 18:10 JOJ0

We are definitely doing 1.7.0, the project uses semantic versioning and we're way past a patch. Providing the version number is the only input required for the workflow, so I think we should stick with keeping that as the only intervention required.

I'll try restoring the functionality of the script to change that file. Hopefully it'll do the changes necessary to do all of the changed.

Serene-Arc avatar Oct 24 '23 00:10 Serene-Arc

Yes, manual intervention to pick the version number seems perfectly good to me! FWIW, I find strict SemVer to be an awkward fit for end-user applications (like beets), as opposed to libraries with an API (where the concept of "breaking changes" is easier to define). So I think it's inevitably somewhat fuzzy.

Once @sampsyo sets up the trusted publisher stuff, it'll work.

Happy to do this! Shall we first merge the basic mechanics in this PR, and then pull the trigger on actual PyPI uploads in the next one (since that seems worth being very careful about)?

sampsyo avatar Oct 25 '23 19:10 sampsyo

It's all one thing, if we merge this then it will do the Pypi uploads as long as it's working. Right now it'll fail because the trusted publisher stuff isn't set up but there shouldn't be any problems whatsoever with them. The workflow is what they recommend, I really didn't change the code.

There's something I want to do before we merge. I need to reactivate the code that changes the changelog in the right way. I feel that this workflow should be the absolute minimum intervention: you provide the version number and that's it. So I need to propagate that back to the changelog so it doesn't need to be done manually.

Note also that there are no conditions (at this time) where this release will be run automatically. It will always need human intervention to kick things off. Now if you'd like to switch to some kind of weekly release @sampsyo with the date as the version, we can also do that very easily. youtube-dl and yt-dlp do that. Then it would really be automatic.

Serene-Arc avatar Oct 26 '23 01:10 Serene-Arc

@sampsyo this is ready to merge! I highly recommend setting up the PyPi trusted publisher stuff as that is part of this PR and the workflows as submitted. Nothing can really go wrong with what it is right now. I've tested everything up to the PyPi and the worst that can happen is that it just doesn't work, in which case everything up to that (including making a GitHub release, which I have tested) will have worked and for the first release we'll have to do that manually. But I really don't think that's going to happen, since it's very basic and copied from their docs.

I'm quite sure that everything is working as intended, as it went fine in my own fork. Once this is merged, we should think about doing a release (1.7.0!) unless you'd like to consider changing beets to a date-based versioning system. But if not, then this PR is complete :)

Serene-Arc avatar Oct 27 '23 08:10 Serene-Arc

@JOJ0 I think we should wait until @sampsyo sets up the pypi trusted publishing. It won't work until that's done.

Serene-Arc avatar Oct 30 '23 10:10 Serene-Arc

This PR is a big improvement after nearly two years in 1.6.0, great work guys. But how is this going? Is there something I can help with?

vicholp avatar Dec 05 '23 03:12 vicholp

@vicholp Thanks but we're good. We're just waiting on @sampsyo to do the final step.

Serene-Arc avatar Dec 05 '23 05:12 Serene-Arc