diff --git a/.github/workflows/build-wheel-release-publish.yml b/.github/workflows/build-wheel-release-publish.yml new file mode 100644 index 0000000..7c11c4b --- /dev/null +++ b/.github/workflows/build-wheel-release-publish.yml @@ -0,0 +1,84 @@ +name: Release +on: + workflow_dispatch: + push: + tags: + - '*' +permissions: + contents: write +jobs: + build-package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + - uses: hynek/build-and-inspect-python-package@v2 + + update-changelog: + needs: [build-package] + if: "!contains(github.ref, 'rc')" + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Update CHANGELOG.rst wit the latest news + run: python .github/workflows/update-changelog.py "${{ github.ref_name }}" + + - name: Commite the changes in CHANGELOG.rst + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: update changelog + branch: pypi-build # FIXME: change to main + + github-pre-release: + needs: [build-package] + if: contains(github.ref, 'rc') + runs-on: ubuntu-latest + steps: + - name: Generate GH release notes for pre-release + uses: softprops/action-gh-release@v2 + with: + draft: true + prerelease: true + generate_release_notes: true + + github-release: + needs: [build-package, update-changelog] + if: "!contains(github.ref, 'rc')" + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + ref: pypi-build # FIXME: change to main + - name: Generate GH release notes for release + run: python .github/workflows/get-latest-changelog.py "${{ github.ref_name }}" + - name: Release + uses: softprops/action-gh-release@v2 + with: + body_path: CHANGELOG.txt + draft: true + + pypi-publish: + needs: [build-package] + environment: + name: testpypi # FIXME: change to pypi + url: https://test/pypi.org/p/diffpy.snmf # FIXME: get the PyPI URL + permissions: + id-token: write + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ # FIXME: remove test. + verbose: true diff --git a/.github/workflows/get-latest-changelog.py b/.github/workflows/get-latest-changelog.py new file mode 100644 index 0000000..67d5f4d --- /dev/null +++ b/.github/workflows/get-latest-changelog.py @@ -0,0 +1,58 @@ +import re +import sys + + +def get_tag_news_items(tag, filepath): + """Collect news items after the specified tag until the next version is found.""" + collect = False + collected_lines = [] + # Regex to match version numbers + version_pattern = re.compile(r"^\d+\.\d+\.\d+") + + with open(filepath, "r") as file: + for line in file: + if line.strip().startswith(tag): + collect = True + continue + elif collect and version_pattern.match(line.strip()): + break + elif collect: + collected_lines.append(line.rstrip()) + + return collected_lines + + +def remove_two_lines(lines): + """Remove two lines after the tag in CHANGELOG.rst.""" + if lines: + # Remove the first line containing "====" + if "====" in lines[0]: + lines.pop(0) + # Remove the second empty line + if lines[1] == "": + lines.pop(1) + return lines + + +def save_to_txt_file(lines, filename): + """Save collected lines to a .txt file used for GH release notes.""" + output = "\n".join(lines) + with open(filename, "w") as file: + file.write(output) + return output + + +if __name__ == "__main__": + if len(sys.argv) < 2: + assert ( + False + ), "No tag has been provided. Please provide a tag by running python get-latest-changelog.py " + + tag = sys.argv[1] + CHANGELOG_PATH = "CHANGELOG.rst" + LATEST_CHANGELOG_PATH = "CHANGELOG.txt" + + collected_lines = get_tag_news_items(tag, CHANGELOG_PATH) + cleaned_lines = remove_two_lines(collected_lines) + latest_changelog_output = save_to_txt_file(cleaned_lines, LATEST_CHANGELOG_PATH) + print(f"CHANGELOG for {tag}:\n{latest_changelog_output}") diff --git a/.github/workflows/update-changelog.py b/.github/workflows/update-changelog.py new file mode 100644 index 0000000..46094e0 --- /dev/null +++ b/.github/workflows/update-changelog.py @@ -0,0 +1,92 @@ +import os +import sys +from glob import glob + +# Get the GitHub reference passed as an argument +tag = sys.argv[1] + +# Store category data +news_items = { + "Added": [], + "Changed": [], + "Deprecated": [], + "Removed": [], + "Fixed": [], + "Security": [], +} + + +def extract_news_items(file_path): + """Extract news bullet points under each category for each .rst file.""" + with open(file_path, "r") as file: + for line in file: + line = line.strip() + + # Check if the line is a category header + if line.startswith("**") and line.endswith(":**"): + current_category = line.strip("**:").strip() + + # Only add if the line is not empty and not a category header + elif current_category and line and not line.startswith("* "): + news_items[current_category].append(line) + + +def write_merged_file(): + """Add the news items under the ".. current developments" section.""" + CHANGELOG_PATH = "CHANGELOG.rst" + CHANGELOG_HEADER = ".. current developments" + + # Insert news + new_news_content = f"\n{tag}\n=====\n\n" + for category_name in sorted(news_items.keys()): + items = news_items[category_name] + if items: + # Add category name e.g. Added, Changed, etc. + new_news_content += f"**{category_name}:**\n\n" + for item in items: + # Add each item in the category + new_news_content += f"{item}\n" + new_news_content += "\n" + + # Read CHANGELOG.rst + with open(CHANGELOG_PATH, "r") as file: + current_content = file.read() + + # Find the position to insert news after ".. current developments" + insert_position = current_content.find(CHANGELOG_HEADER) + len(CHANGELOG_HEADER) + 1 + final_content = current_content[:insert_position] + new_news_content + current_content[insert_position:] + + # Write the updated content back to the file + with open(CHANGELOG_PATH, "w") as file: + file.write(final_content) + + return new_news_content + + +def remove_news_rst_files(news_dir_path): + """Remove .rst files in the news directory except TEMPLATE.rst""" + rst_files = os.listdir(news_dir_path) + for file_name in rst_files: + rst_file_path = os.path.join(news_dir_path, file_name) + if file_name.endswith(".rst") and file_name != "TEMPLATE.rst": + os.remove(rst_file_path) + + +if __name__ == "__main__": + NEWS_DIR_PATH = "news" + + # Get all news .rst files + news_rst_files = glob(os.path.join(NEWS_DIR_PATH, "*.rst")) + + # Extract and store news items into a single dictionary + for rst_file in news_rst_files: + extract_news_items(rst_file) + + # Add news under ".. current developments" + new_news_content = write_merged_file() + + # Remove all .rst files in the news directory except TEMPLATE.rst + remove_news_rst_files(NEWS_DIR_PATH) + + # Print for debugging + print(new_news_content) diff --git a/news/gh-workflow.rst b/news/gh-workflow.rst new file mode 100644 index 0000000..801db3b --- /dev/null +++ b/news/gh-workflow.rst @@ -0,0 +1,23 @@ +**Added:** + +* GitHub workflow files for PyPI and GH release + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +*