Skip to content

Commit 777be23

Browse files
authored
Add GitHub workflow files for test PyPI and GitHub release (#95)
* Move tests folder to top * Revert commit * Add test pypi/gh release workflows * Add .rst news file * Apply pre-commit
1 parent e5999b7 commit 777be23

File tree

4 files changed

+257
-0
lines changed

4 files changed

+257
-0
lines changed

Diff for: .github/workflows/build-wheel-release-publish.yml

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Release
2+
on:
3+
workflow_dispatch:
4+
push:
5+
tags:
6+
- '*'
7+
permissions:
8+
contents: write
9+
jobs:
10+
build-package:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
ref: ${{ github.ref }}
16+
- uses: hynek/build-and-inspect-python-package@v2
17+
18+
update-changelog:
19+
needs: [build-package]
20+
if: "!contains(github.ref, 'rc')"
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Checkout the repository
24+
uses: actions/checkout@v4
25+
with:
26+
ref: ${{ github.ref }}
27+
28+
- name: Update CHANGELOG.rst wit the latest news
29+
run: python .github/workflows/update-changelog.py "${{ github.ref_name }}"
30+
31+
- name: Commite the changes in CHANGELOG.rst
32+
uses: stefanzweifel/git-auto-commit-action@v5
33+
with:
34+
commit_message: update changelog
35+
branch: pypi-build # FIXME: change to main
36+
37+
github-pre-release:
38+
needs: [build-package]
39+
if: contains(github.ref, 'rc')
40+
runs-on: ubuntu-latest
41+
steps:
42+
- name: Generate GH release notes for pre-release
43+
uses: softprops/action-gh-release@v2
44+
with:
45+
draft: true
46+
prerelease: true
47+
generate_release_notes: true
48+
49+
github-release:
50+
needs: [build-package, update-changelog]
51+
if: "!contains(github.ref, 'rc')"
52+
runs-on: ubuntu-latest
53+
steps:
54+
- name: Checkout the repository
55+
uses: actions/checkout@v4
56+
with:
57+
ref: pypi-build # FIXME: change to main
58+
- name: Generate GH release notes for release
59+
run: python .github/workflows/get-latest-changelog.py "${{ github.ref_name }}"
60+
- name: Release
61+
uses: softprops/action-gh-release@v2
62+
with:
63+
body_path: CHANGELOG.txt
64+
draft: true
65+
66+
pypi-publish:
67+
needs: [build-package]
68+
environment:
69+
name: testpypi # FIXME: change to pypi
70+
url: https://test/pypi.org/p/diffpy.snmf # FIXME: get the PyPI URL
71+
permissions:
72+
id-token: write
73+
runs-on: ubuntu-latest
74+
steps:
75+
- uses: actions/download-artifact@v4
76+
with:
77+
name: Packages
78+
path: dist
79+
80+
- name: Publish package distributions to PyPI
81+
uses: pypa/gh-action-pypi-publish@release/v1
82+
with:
83+
repository-url: https://test.pypi.org/legacy/ # FIXME: remove test.
84+
verbose: true

Diff for: .github/workflows/get-latest-changelog.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import re
2+
import sys
3+
4+
5+
def get_tag_news_items(tag, filepath):
6+
"""Collect news items after the specified tag until the next version is found."""
7+
collect = False
8+
collected_lines = []
9+
# Regex to match version numbers
10+
version_pattern = re.compile(r"^\d+\.\d+\.\d+")
11+
12+
with open(filepath, "r") as file:
13+
for line in file:
14+
if line.strip().startswith(tag):
15+
collect = True
16+
continue
17+
elif collect and version_pattern.match(line.strip()):
18+
break
19+
elif collect:
20+
collected_lines.append(line.rstrip())
21+
22+
return collected_lines
23+
24+
25+
def remove_two_lines(lines):
26+
"""Remove two lines after the tag in CHANGELOG.rst."""
27+
if lines:
28+
# Remove the first line containing "===="
29+
if "====" in lines[0]:
30+
lines.pop(0)
31+
# Remove the second empty line
32+
if lines[1] == "":
33+
lines.pop(1)
34+
return lines
35+
36+
37+
def save_to_txt_file(lines, filename):
38+
"""Save collected lines to a .txt file used for GH release notes."""
39+
output = "\n".join(lines)
40+
with open(filename, "w") as file:
41+
file.write(output)
42+
return output
43+
44+
45+
if __name__ == "__main__":
46+
if len(sys.argv) < 2:
47+
assert (
48+
False
49+
), "No tag has been provided. Please provide a tag by running python get-latest-changelog.py <tag>"
50+
51+
tag = sys.argv[1]
52+
CHANGELOG_PATH = "CHANGELOG.rst"
53+
LATEST_CHANGELOG_PATH = "CHANGELOG.txt"
54+
55+
collected_lines = get_tag_news_items(tag, CHANGELOG_PATH)
56+
cleaned_lines = remove_two_lines(collected_lines)
57+
latest_changelog_output = save_to_txt_file(cleaned_lines, LATEST_CHANGELOG_PATH)
58+
print(f"CHANGELOG for {tag}:\n{latest_changelog_output}")

Diff for: .github/workflows/update-changelog.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import os
2+
import sys
3+
from glob import glob
4+
5+
# Get the GitHub reference passed as an argument
6+
tag = sys.argv[1]
7+
8+
# Store category data
9+
news_items = {
10+
"Added": [],
11+
"Changed": [],
12+
"Deprecated": [],
13+
"Removed": [],
14+
"Fixed": [],
15+
"Security": [],
16+
}
17+
18+
19+
def extract_news_items(file_path):
20+
"""Extract news bullet points under each category for each .rst file."""
21+
with open(file_path, "r") as file:
22+
for line in file:
23+
line = line.strip()
24+
25+
# Check if the line is a category header
26+
if line.startswith("**") and line.endswith(":**"):
27+
current_category = line.strip("**:").strip()
28+
29+
# Only add if the line is not empty and not a category header
30+
elif current_category and line and not line.startswith("* <news item>"):
31+
news_items[current_category].append(line)
32+
33+
34+
def write_merged_file():
35+
"""Add the news items under the ".. current developments" section."""
36+
CHANGELOG_PATH = "CHANGELOG.rst"
37+
CHANGELOG_HEADER = ".. current developments"
38+
39+
# Insert news
40+
new_news_content = f"\n{tag}\n=====\n\n"
41+
for category_name in sorted(news_items.keys()):
42+
items = news_items[category_name]
43+
if items:
44+
# Add category name e.g. Added, Changed, etc.
45+
new_news_content += f"**{category_name}:**\n\n"
46+
for item in items:
47+
# Add each item in the category
48+
new_news_content += f"{item}\n"
49+
new_news_content += "\n"
50+
51+
# Read CHANGELOG.rst
52+
with open(CHANGELOG_PATH, "r") as file:
53+
current_content = file.read()
54+
55+
# Find the position to insert news after ".. current developments"
56+
insert_position = current_content.find(CHANGELOG_HEADER) + len(CHANGELOG_HEADER) + 1
57+
final_content = current_content[:insert_position] + new_news_content + current_content[insert_position:]
58+
59+
# Write the updated content back to the file
60+
with open(CHANGELOG_PATH, "w") as file:
61+
file.write(final_content)
62+
63+
return new_news_content
64+
65+
66+
def remove_news_rst_files(news_dir_path):
67+
"""Remove .rst files in the news directory except TEMPLATE.rst"""
68+
rst_files = os.listdir(news_dir_path)
69+
for file_name in rst_files:
70+
rst_file_path = os.path.join(news_dir_path, file_name)
71+
if file_name.endswith(".rst") and file_name != "TEMPLATE.rst":
72+
os.remove(rst_file_path)
73+
74+
75+
if __name__ == "__main__":
76+
NEWS_DIR_PATH = "news"
77+
78+
# Get all news .rst files
79+
news_rst_files = glob(os.path.join(NEWS_DIR_PATH, "*.rst"))
80+
81+
# Extract and store news items into a single dictionary
82+
for rst_file in news_rst_files:
83+
extract_news_items(rst_file)
84+
85+
# Add news under ".. current developments"
86+
new_news_content = write_merged_file()
87+
88+
# Remove all .rst files in the news directory except TEMPLATE.rst
89+
remove_news_rst_files(NEWS_DIR_PATH)
90+
91+
# Print for debugging
92+
print(new_news_content)

Diff for: news/gh-workflow.rst

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* GitHub workflow files for PyPI and GH release
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

0 commit comments

Comments
 (0)