Skip to content

Commit a927cef

Browse files
authored
Merge pull request #218 from mitre-attack/semantic-release
Migrate to uv and integrate PSR for automated releases
2 parents 7b5b8c6 + b14107c commit a927cef

File tree

11 files changed

+2370
-2539
lines changed

11 files changed

+2370
-2539
lines changed

.github/workflows/ci.yml

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
# Validate the squash merge commit message on pushes to main.
14+
# PR title validation is handled separately in pr-title.yml.
15+
commitlint:
16+
if: github.event_name == 'push'
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v6
21+
with:
22+
fetch-depth: 2
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v7
26+
with:
27+
version: "latest"
28+
29+
- name: Install Python and dependencies
30+
run: |
31+
uv python install 3.11
32+
uv sync --all-extras
33+
34+
- name: Validate commit message
35+
run: uv run cz check --rev-range HEAD~1..HEAD
36+
37+
lint:
38+
runs-on: ubuntu-latest
39+
strategy:
40+
matrix:
41+
python-version: ["3.11"] # TODO see https://github.com/mitre-attack/mitreattack-python/issues/176
42+
steps:
43+
- uses: actions/checkout@v6
44+
45+
- name: Install the latest version of uv
46+
uses: astral-sh/setup-uv@v7
47+
with:
48+
python-version: ${{ matrix.python-version }}
49+
50+
- name: Install dependencies
51+
run: uv sync --all-extras
52+
53+
- name: Lint with ruff
54+
run: uv run ruff check --output-format github
55+
56+
- name: Check formatting with ruff
57+
run: uv run ruff format --check
58+
59+
test:
60+
runs-on: ubuntu-latest
61+
strategy:
62+
matrix:
63+
python-version: ["3.11"] # TODO see https://github.com/mitre-attack/mitreattack-python/issues/176
64+
steps:
65+
- uses: actions/checkout@v6
66+
67+
- name: Install the latest version of uv
68+
uses: astral-sh/setup-uv@v7
69+
with:
70+
python-version: ${{ matrix.python-version }}
71+
72+
- name: Install dependencies
73+
run: uv sync --all-extras
74+
75+
- name: Run pytest
76+
run: uv run pytest --cov=mitreattack
77+
78+
release:
79+
needs: [commitlint, lint, test]
80+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
81+
strategy:
82+
matrix:
83+
python-version: ["3.11"] # TODO see https://github.com/mitre-attack/mitreattack-python/issues/176
84+
runs-on: ubuntu-latest
85+
# The concurrency block prevents multiple release jobs from running simultaneously for the same branch.
86+
# This is particularly useful for releases since you typically want sequential deployments (finish
87+
# current release before starting next) rather than canceling in-progress releases or running them
88+
# in parallel.
89+
concurrency:
90+
# Creates a concurrency group keyed by workflow name + "release" + branch name (e.g., "Continuous Delivery-release-main")
91+
group: ${{ github.workflow }}-release-${{ github.ref_name }}
92+
# If a release is already running and a new push triggers another, the new job waits rather than canceling the running one
93+
# If you changed cancel-in-progress to true, a new push would cancel the currently running release job.
94+
cancel-in-progress: false
95+
96+
permissions:
97+
contents: write
98+
99+
steps:
100+
# Note: We checkout the repository at the branch that triggered the workflow.
101+
# Python Semantic Release will automatically convert shallow clones to full clones
102+
# if needed to ensure proper history evaluation. However, we forcefully reset the
103+
# branch to the workflow sha because it is possible that the branch was updated
104+
# while the workflow was running, which prevents accidentally releasing un-evaluated
105+
# changes.
106+
- name: Setup | Checkout Repository on Release Branch
107+
uses: actions/checkout@v6
108+
with:
109+
fetch-depth: 0
110+
ref: ${{ github.ref_name }}
111+
112+
- name: Setup | Force release branch to be at workflow sha
113+
run: |
114+
git reset --hard ${{ github.sha }}
115+
116+
- name: Setup | Install uv
117+
uses: astral-sh/setup-uv@v7
118+
with:
119+
python-version: ${{ matrix.python-version }}
120+
121+
- name: Setup | Install Python
122+
run: uv python install ${{ matrix.python-version }}
123+
124+
- name: Setup | Install dependencies
125+
run: uv sync --all-extras
126+
127+
- name: Semantic Release
128+
id: release
129+
uses: python-semantic-release/python-semantic-release@v10.5.3
130+
with:
131+
github_token: ${{ secrets.GITHUB_TOKEN }}
132+
# NOTE: git_committer_name and git_committer_email are optional
133+
# We omit them because, if set, they must be associated with the provided token
134+
# and we don't really care to have a specific committer for automated releases.
135+
136+
- name: Upload to GitHub Release Assets
137+
uses: python-semantic-release/publish-action@v10.5.3
138+
if: steps.release.outputs.released == 'true'
139+
with:
140+
github_token: ${{ secrets.GITHUB_TOKEN }}
141+
tag: ${{ steps.release.outputs.tag }}
142+
143+
- name: Upload distribution artifacts
144+
uses: actions/upload-artifact@v6
145+
if: steps.release.outputs.released == 'true'
146+
with:
147+
name: distribution-artifacts
148+
path: dist
149+
if-no-files-found: error
150+
151+
outputs:
152+
released: ${{ steps.release.outputs.released || 'false' }}
153+
154+
publish:
155+
# 1. Separate out the deploy step from the publish step to run each step at
156+
# the least amount of token privilege
157+
# 2. Also, deployments can fail, and its better to have a separate job if you need to retry
158+
# and it won't require reversing the release.
159+
needs: release
160+
if: needs.release.outputs.released == 'true'
161+
runs-on: ubuntu-latest
162+
environment: release
163+
164+
permissions:
165+
contents: read
166+
id-token: write
167+
168+
steps:
169+
- name: Download build artifacts
170+
uses: actions/download-artifact@v7
171+
id: artifact-download
172+
with:
173+
name: distribution-artifacts
174+
path: dist
175+
176+
- name: Publish to PyPI
177+
uses: pypa/gh-action-pypi-publish@release/v1
178+
with:
179+
packages-dir: dist
180+
print-hash: true
181+
verbose: true

.github/workflows/lint-publish.yml

Lines changed: 0 additions & 60 deletions
This file was deleted.

.github/workflows/pr-title.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PR Title
2+
3+
# Validate that PR titles follow Conventional Commits format.
4+
# Since we squash merge PRs, the PR title becomes the merge commit message,
5+
# which Python Semantic Release uses to determine version bumps.
6+
#
7+
# This avoids burdening external contributors with conventional commit enforcement
8+
# on every individual commit. See: https://www.conventionalcommits.org/en/v1.0.0/
9+
#
10+
# NOTE: pull_request_target is used instead of pull_request because it runs in the
11+
# context of the base branch and has reliable access to the GITHUB_TOKEN for reading
12+
# PR metadata.
13+
14+
on:
15+
pull_request_target:
16+
types: [opened, edited, synchronize, reopened]
17+
branches: [main]
18+
19+
permissions:
20+
pull-requests: read
21+
22+
jobs:
23+
validate:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: Validate PR title
27+
uses: amannn/action-semantic-pull-request@v5
28+
env:
29+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
with:
31+
# For work-in-progress PRs you can typically use draft pull requests from GitHub.
32+
# This action allows us to use the special "[WIP]" prefix to indicate a draft state
33+
# without actually flagging the PR as a draft.
34+
# Example:
35+
# `[WIP] feat: Add support for Node.js 18` <--- will not be validated!
36+
wip: true

.pre-commit-config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.14.10
4+
hooks:
5+
# Run the linter (disabled for now)
6+
# - id: ruff-check
7+
# args: [--fix]
8+
# Run the formatter
9+
- id: ruff-format
10+
11+
- repo: https://github.com/commitizen-tools/commitizen
12+
rev: v4.10.1
13+
hooks:
14+
- id: commitizen
15+
stages: [commit-msg]

.readthedocs.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ build:
44
os: ubuntu-24.04
55
tools:
66
python: "3.11"
7+
# For details on how to customize the build process, see:
8+
# https://docs.readthedocs.com/platform/stable/builds.html
9+
# https://docs.readthedocs.com/platform/stable/build-customization.html
710
jobs:
8-
post_create_environment:
9-
- pip install poetry
10-
post_install:
11-
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs
11+
install:
12+
- pip install uv
13+
- uv sync --extra docs
14+
build:
15+
- uv run sphinx-build -b html docs $READTHEDOCS_OUTPUT/html
1216

1317
sphinx:
1418
configuration: docs/conf.py

docs/CONTRIBUTING.md

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,65 @@
11
# Contributors
22

33
## Reporting Issues
4-
If you encounter an issue with the `mitreattack-python` library, please let us know by filing a [Github issue](https://github.com/mitre-attack/mitreattack-python/issues). When doing so, please make sure you provide the following information:
5-
* Describe (in detail as possible) what occurred and what you were expecting to occur. Any information you can provide, such stack traces or errors are very helpful.
6-
* Describe the steps necessary to replicate the issue.
7-
* Indicate your OS and python versions.
4+
5+
If you encounter an issue with the `mitreattack-python` library, please let us know by filing a [GitHub issue](https://github.com/mitre-attack/mitreattack-python/issues). When doing so, please make sure you provide the following information:
6+
7+
- Describe (in as much detail as possible) what occurred and what you were expecting to occur. Any information you can provide, such as stack traces or errors, is very helpful.
8+
- Describe the steps necessary to replicate the issue.
9+
- Indicate your OS and Python versions.
810

911
## Suggested New Features
10-
If you have an idea for a new feature for `mitreattack-python`, please let us know by filing a [Github issue](https://github.com/mitre-attack/mitreattack-python/issues). When doing so, please make sure you provide the following information:
11-
* Explain the functionality you are proposing, and its use case - what would it be useful for or allow you to do?
12-
* List what existing ATT&CK tools or resources map to the proposed functionality
13-
* If applicable, provide examples of other requests for the proposed functionality
12+
13+
If you have an idea for a new feature for `mitreattack-python`, please let us know by filing a [GitHub issue](https://github.com/mitre-attack/mitreattack-python/issues). When doing so, please make sure you provide the following information:
14+
15+
- Explain the functionality you are proposing, and its use case — what would it be useful for or allow you to do?
16+
- List what existing ATT&CK tools or resources map to the proposed functionality.
17+
- If applicable, provide examples of other requests for the proposed functionality.
1418

1519
## Developing
16-
If you want to work on the `mitreattack-python` library and contribute to its ongoing development, we welcome merge requests! You can set up an environment for development by following this process:
17-
1. Clone the repository - `git clone https://github.com/mitre-attack/mitreattack-python`.
18-
2. Create a virtual environment, and activate it - `python3 -m venv venv`/`. venv/bin/activate`.
19-
3. Install the appropriate python modules via pip - `pip install -r requirements-dev.txt`.
2020

21-
### Merge Requests
22-
When making a merge request, please make sure to include a summary of what the changes are intended to do, functionality wise, and the testing performed to validate the changes (ideally in the form of new pytests integrated into the `tests/` collection, though this is not strictly required). In addition, the complete pytest test battery `tests/` must be passed without errors in order for any code to actually be merged.
21+
We welcome pull requests! To set up a local development environment:
22+
23+
### Prerequisites
24+
25+
- [Python 3.11+](https://www.python.org/downloads/)
26+
- [uv](https://docs.astral.sh/uv/getting-started/installation/) — fast Python package manager
27+
- [just](https://github.com/casey/just#installation) — command runner (optional, but recommended)
28+
29+
### Setup
30+
31+
```bash
32+
# Clone the repository
33+
git clone https://github.com/mitre-attack/mitreattack-python
34+
cd mitreattack-python
35+
36+
# Install all dependencies (including dev and docs extras)
37+
just install
38+
# or without just:
39+
uv sync --all-extras
40+
41+
# (Optional) Install pre-commit hooks for local linting and commit message validation
42+
just setup-hooks
43+
# or without just:
44+
uv run pre-commit install
45+
uv run pre-commit install --hook-type commit-msg
46+
```
47+
48+
### Common Commands
49+
50+
Run `just` with no arguments to see all available commands. Here are the most common ones:
51+
52+
```bash
53+
just lint # Run pre-commit hooks (ruff format) on all files
54+
just test # Run tests
55+
just test-cov # Run tests with coverage report
56+
just build # Build the package
57+
```
58+
59+
### Pull Requests
60+
61+
When making a pull request, please make sure to:
62+
63+
- Include a summary of what the changes are intended to do and the testing performed to validate them (ideally in the form of new pytests in the `tests/` collection, though this is not strictly required).
64+
- **Use a [Conventional Commits](https://www.conventionalcommits.org) formatted PR title** (e.g., `feat: add new export format`, `fix: handle missing data sources`). PRs are squash-merged, so the PR title becomes the merge commit message — individual commit messages within the PR do not need to follow any convention.
65+
- All pytest tests in `tests/` must pass, and ruff linting/formatting checks must be clean.

0 commit comments

Comments
 (0)