Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Summary

This release migrates lightweight GitHub Actions workflow jobs to use the new cost-effective `ubuntu-slim` runner.
It also updates cookiecutter pyproject license metadata to SPDX expressions to avoid setuptools deprecation warnings.

## Upgrading

Expand Down Expand Up @@ -41,4 +42,5 @@ But you might still need to adapt your code:

### Cookiecutter template

<!-- Here bug fixes for cookiecutter specifically -->
- Switched `project.license` to SPDX expressions and added `project.license-files`.
This removes deprecated setuptools license metadata and avoids build warnings.
120 changes: 120 additions & 0 deletions cookiecutter/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import hashlib
import json
import os
import re
import subprocess
import tempfile
from pathlib import Path
Expand All @@ -36,6 +37,9 @@ def main() -> None:
print("Migrating workflows to use ubuntu-slim runner for lightweight jobs...")
migrate_to_ubuntu_slim()
print("=" * 72)
print("Migrating pyproject license metadata to SPDX format...")
migrate_pyproject_license()
print("=" * 72)
print("Migration script finished. Remember to follow any manual instructions.")
print("=" * 72)

Expand Down Expand Up @@ -198,6 +202,81 @@ def migrate_to_ubuntu_slim() -> None:
)


def migrate_pyproject_license() -> None: # pylint: disable=too-many-branches
"""Migrate pyproject license metadata to SPDX expressions."""
pyproject_path = Path("pyproject.toml")
if not pyproject_path.exists():
print(" Skipping pyproject.toml (file not found)")
return

content = pyproject_path.read_text(encoding="utf-8")
new_content = content
updated = False

license_expression = None
for old_license, new_license in (
("MIT", "MIT"),
("Proprietary", "LicenseRef-Proprietary"),
("Propietary", "LicenseRef-Proprietary"),
):
old_line = f'license = {{ text = "{old_license}" }}'
if old_line in new_content:
new_content = new_content.replace(old_line, f'license = "{new_license}"', 1)
license_expression = new_license
updated = True
break

if license_expression is None:
for existing_license in ("MIT", "LicenseRef-Proprietary"):
if f'license = "{existing_license}"' in new_content:
license_expression = existing_license
break

if license_expression is None:
cookiecutter_license = read_cookiecutter_license()
if cookiecutter_license == "MIT":
license_expression = "MIT"
elif cookiecutter_license == "Proprietary":
license_expression = "LicenseRef-Proprietary"

if license_expression is None:
manual_step(
"Unable to detect project license in pyproject.toml. Please set "
"`project.license` to a SPDX expression and add "
'`project.license-files = ["LICENSE"]`.'
)
return

license_line = f'license = "{license_expression}"'
if "license-files" not in new_content and license_line in new_content:
new_content = new_content.replace(
license_line, f'{license_line}\nlicense-files = ["LICENSE"]', 1
)
updated = True

for classifier in (
"License :: OSI Approved :: MIT License",
"License :: Other/Proprietary License",
):
classifier_line = f' "{classifier}",\n'
if classifier_line in new_content:
new_content = new_content.replace(classifier_line, "", 1)
updated = True

setuptools_version = parse_setuptools_version(new_content)
if setuptools_version is not None and setuptools_version < 77:
new_content, replaced = replace_setuptools_pin(new_content, "80.9.0")
if replaced:
updated = True

if not updated or new_content == content:
print(" Skipped pyproject.toml (already up to date)")
return

replace_file_contents_atomically(pyproject_path, content, new_content, count=1)
print(" Updated pyproject.toml: migrated license metadata")


def read_project_type() -> str | None:
"""Read the cookiecutter project type from the replay file."""
replay_path = Path(".cookiecutter-replay.json")
Expand All @@ -220,6 +299,47 @@ def read_project_type() -> str | None:
return project_type


def read_cookiecutter_license() -> str | None:
"""Read the cookiecutter license from the replay file."""
replay_path = Path(".cookiecutter-replay.json")
if not replay_path.exists():
return None

try:
data = json.loads(replay_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None

cookiecutter_data = data.get("cookiecutter")
if not isinstance(cookiecutter_data, dict):
return None

license_value = cookiecutter_data.get("license")
if not isinstance(license_value, str):
return None

return license_value


def parse_setuptools_version(content: str) -> int | None:
"""Parse the setuptools major version from pyproject content."""
match = re.search(r'"setuptools\s*==\s*([0-9]+)(?:\.[0-9]+)*"', content)
if not match:
return None
return int(match.group(1))


def replace_setuptools_pin(content: str, new_version: str) -> tuple[str, bool]:
"""Replace the setuptools pin with a new version."""
new_content, count = re.subn(
r'("setuptools\s*==\s*)[0-9]+(?:\.[0-9]+)*("\s*,?)',
rf"\1{new_version}\2",
content,
count=1,
)
return new_content, count > 0


def apply_patch(patch_content: str) -> None:
"""Apply a patch using the patch utility."""
subprocess.run(["patch", "-p1"], input=patch_content.encode(), check=True)
Expand Down
16 changes: 9 additions & 7 deletions cookiecutter/{{cookiecutter.github_repo_name}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[build-system]
requires = [
"setuptools == 75.8.0",
"setuptools == 80.9.0",
"setuptools_scm[toml] == 8.1.0",
"frequenz-repo-config[{{cookiecutter.type}}] == 0.14.0",
{%- if cookiecutter.type == "api" %}
Expand All @@ -22,17 +22,19 @@ build-backend = "setuptools.build_meta"
name = "{{cookiecutter.pypi_package_name}}"
description = "{{cookiecutter.description}}"
readme = "README.md"
license = { text = "{{cookiecutter.license}}" }
{%- if cookiecutter.license == "MIT" %}
license = "MIT"
{%- elif cookiecutter.license == "Proprietary" %}
license = "LicenseRef-Proprietary"
{%- else %}
license = "{{cookiecutter.license}}"
{%- endif %}
license-files = ["LICENSE"]
keywords = {{cookiecutter | keywords}}
# TODO(cookiecutter): Remove and add more classifiers if appropriate
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
{%- if cookiecutter.license == "MIT" %}
"License :: OSI Approved :: MIT License",
{%- elif cookiecutter.license == "Propietary" %}
"License :: Other/Proprietary License",
{%- endif %}
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
{%- if cookiecutter.type != "app" %}
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ build-backend = "setuptools.build_meta"
name = "frequenz-repo-config"
description = "Frequenz repository setup tools and common configuration"
readme = "README.md"
license = { text = "MIT" }
license = "MIT"
license-files = ["LICENSE"]
keywords = [
"config",
"frequenz",
Expand All @@ -29,7 +30,6 @@ keywords = [
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[build-system]
requires = [
"setuptools == 75.8.0",
"setuptools == 80.9.0",
"setuptools_scm[toml] == 8.1.0",
"frequenz-repo-config[actor] == 0.14.0",
]
Expand All @@ -13,13 +13,13 @@ build-backend = "setuptools.build_meta"
name = "frequenz-actor-test"
description = "Test description"
readme = "README.md"
license = { text = "MIT" }
license = "MIT"
license-files = ["LICENSE"]
keywords = ["frequenz", "python", "actor", "test"]
# TODO(cookiecutter): Remove and add more classifiers if appropriate
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[build-system]
requires = [
"setuptools == 75.8.0",
"setuptools == 80.9.0",
"setuptools_scm[toml] == 8.1.0",
"frequenz-repo-config[api] == 0.14.0",
# We need to pin the protobuf, grpcio and grpcio-tools dependencies to make
Expand All @@ -20,13 +20,13 @@ build-backend = "setuptools.build_meta"
name = "frequenz-api-test"
description = "Test description"
readme = "README.md"
license = { text = "MIT" }
license = "MIT"
license-files = ["LICENSE"]
keywords = ["frequenz", "python", "api", "grpc", "protobuf", "rpc", "test"]
# TODO(cookiecutter): Remove and add more classifiers if appropriate
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[build-system]
requires = [
"setuptools == 75.8.0",
"setuptools == 80.9.0",
"setuptools_scm[toml] == 8.1.0",
"frequenz-repo-config[app] == 0.14.0",
]
Expand All @@ -13,13 +13,13 @@ build-backend = "setuptools.build_meta"
name = "frequenz-app-test"
description = "Test description"
readme = "README.md"
license = { text = "MIT" }
license = "MIT"
license-files = ["LICENSE"]
keywords = ["frequenz", "python", "app", "application", "test"]
# TODO(cookiecutter): Remove and add more classifiers if appropriate
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Typing :: Typed",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[build-system]
requires = [
"setuptools == 75.8.0",
"setuptools == 80.9.0",
"setuptools_scm[toml] == 8.1.0",
"frequenz-repo-config[lib] == 0.14.0",
]
Expand All @@ -13,13 +13,13 @@ build-backend = "setuptools.build_meta"
name = "frequenz-test"
description = "Test description"
readme = "README.md"
license = { text = "MIT" }
license = "MIT"
license-files = ["LICENSE"]
keywords = ["frequenz", "python", "lib", "library", "test"]
# TODO(cookiecutter): Remove and add more classifiers if appropriate
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[build-system]
requires = [
"setuptools == 75.8.0",
"setuptools == 80.9.0",
"setuptools_scm[toml] == 8.1.0",
"frequenz-repo-config[model] == 0.14.0",
]
Expand All @@ -13,13 +13,13 @@ build-backend = "setuptools.build_meta"
name = "frequenz-model-test"
description = "Test description"
readme = "README.md"
license = { text = "MIT" }
license = "MIT"
license-files = ["LICENSE"]
keywords = ["frequenz", "python", "model", "ai", "ml", "machine-learning", "test"]
# TODO(cookiecutter): Remove and add more classifiers if appropriate
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries",
Expand Down