I recently had occasion to begin creating proper releases for a few Python packages I’ve written, and have been relying on my project’s Makefile to to things like upload the package to PyPi, or tag the release in source control, and then cut a release from that tag locally — I thought I’d share what I’m doing in this fairly common use case, if you are new to make, rather than some of the more complex things you might be able to get done using it. You can do anything you can do in your terminal with Make with a lot of additional programmability beyond just wrapping your one-liners, but lately, I’ve found myself relying on it for projects like these where there are manual steps that I don’t necessarily need or want a whole CI/CD pipeline to release them, or can be used (if part of the repo) to streamline CI steps (having it run the Makefile to test, cut releases, etc.)
For example, my Makefile includes pretty straightforward instructions like install :
install:
pip3 install -e .
or dist :
dist:
rm -rf dist/ ; python3 setup.py bdist_wheel --universal
but in this case, I’m using it to enforce a rule that my Git tags must, first, match the v?.?.?? format before pushing said tag after confirming:
.SILENT:
tag-release:
if [[$(TAG) == v?.?.?]]; then echo "Tagging $(TAG)"; elif [[$(TAG) == v?.?.??]]; then echo "Tagging $(TAG)"; else echo "Bad Tag Format: $(TAG)"; exit 1; fi && git tag -a $(TAG) -m "Releasing $(TAG)" ; read -p "Push tag: $(TAG)? " push_tag ; if ["${push_tag}"="yes"]; then git push self $(TAG); fi
which when run, looks like this:
and then you can see the new tag on the Releases page of your Gitea repo:
turning this tag into a proper Release requires the use of the Gitea API.
First, the difference between the two is that a tag is just a reference to a specific commit in the log, which is what makes it a great pointer for use in a higher level concept like a Release. As Github put it when they originally introduced the feature to Github: “Releases are first-class objects with changelogs and binary assets that present a full project history beyond Git artifacts.”
So, in our case, you will need an Access Token for your own Gitea environment, which you can find on https://{Your_Git_Host}/user/settings/applications.
Once you have that token, let’s look at the next step of the Makefile:
.SILENT:
create-release:
if [[$(TAG) == v?.?.?]]; then echo "Cutting release from $(TAG)"; elif [[$(TAG) == v?.?.??]]; then echo "Cutting release from $(TAG)"; else echo "Bad Tag Format, cannot cut release: $(TAG)"; exit 1; fi && git tag -a $(TAG) -m "Releasing $(TAG)" ; read -p "Cut release from tag: $(TAG)? " push_tag ; if ["${push_tag}"="yes"]; then TAG=$(TAG) ./make-release.sh; fi
In this instruction, we’re using the same tag verification process, but this time handing off to a helper script (this was for my own convenience, you can do this inline to the Makefile), make-release.sh that takes a single argument, TAG and your GITEA_TOKEN (which I have set in my bash profile, so if you don’t do that, then you’ll need to include GITEA_TOKEN=$GITEA_TOKEN to the Make instruction as well):
#!/bin/bash
TAG=$TAG
TAG_COMMIT=$(git rev-parse --short ${TAG})
AUTH_TOKEN=$GITEA_TOKEN
curl -X POST "[**https://{Your\_Git\_Host}**](https://%7BYour_Git_Host%7D/user/settings/applications.)/api/v1/repos/ **{** You}/ **{** Your_Project}/releases" -H "Authorization: token $AUTH_TOKEN" \
-H "accept: application/json" -H "Content-Type: application/json" \
-d "{ \"body\": \"Cutting ${TAG} at ${TAG_COMMIT}\", \"draft\": false, \"name\": \"${TAG}\", \"prerelease\": false, \"tag_name\": \"${TAG}\", \"target_commitish\": \"${TAG_COMMIT}\"}" \
-ik
To use this flow with Github, just like you would for Gitea, update the endpoint URL and include an additional header, and you can use the same request body to create a release:
AUTH_TOKEN=$GITHUB_TOKEN
curl \
-X POST "https://api.github.com/repos/jmarhee/polly-textfile-cli/releases" -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d "{ \"body\": \"Cutting ${TAG} at ${TAG_COMMIT}\", \"draft\": false, \"name\": \"${TAG}\", \"prerelease\": false, \"tag_name\": \"${TAG}\" }" \
So, here, we’re taking the tag, and checking the revision history on the repo to find which git commit hash the tag maps to TAG_COMMIT (you can use git show to get other information like the commit message or whatever, which you can use in the release creation object as well), and then we are POST ing it to the Gitea API to tell it which commit to start the release at and convert that tag into a release.
TAG=v0.1.19 make create-release
In my case, I’m also pushing these projects to Python’s package registry, so once released, I can also do something like:
push-test:
make dist; python3 -m twine upload --repository testpypi dist/*
push:
make dist; python3 -m twine upload dist/*
and again, can use logic like the above to validate the tag (since these will match the version of the package in my setup.py for example) before doing so, if I’d like.
As I mentioned before, this is something that can also make creating your CI pipeline around these steps (to keep them more readable, to create some additional behavioral expectations, etc.)
If you’d like to read more about what you can do in Make, I very much enjoyed this guide:
Top comments (0)