Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
24 changes: 18 additions & 6 deletions .github/scripts/guide_ci_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class GuideMetadata:
slug: str
publish: bool
base: str | None
python_app: bool = False


def kebab_case_to_gradle_name(name: str) -> str:
Expand Down Expand Up @@ -50,6 +51,7 @@ def load_guides(guides_dir: Path) -> dict[str, GuideMetadata]:
slug=slug,
publish=metadata.get("publish", True) is not False,
base=metadata.get("base"),
python_app="PYTHON" in (metadata.get("languages") or []) and metadata.get("apps") != [],
)
return guides

Expand All @@ -75,12 +77,18 @@ def resolve_impacted_guide_slugs(
)


def tasks_for_changed_files(changed_files: Iterable[str], guides_dir: Path) -> list[str]:
def tasks_for_changed_files(
changed_files: Iterable[str], guides_dir: Path, *, jvm_only: bool = False,
) -> list[str]:
guides = load_guides(guides_dir)
return [
f"{kebab_case_to_gradle_name(slug)}Build"
for slug in resolve_impacted_guide_slugs(changed_files, guides)
]
tasks = []
for slug in resolve_impacted_guide_slugs(changed_files, guides):
task_slug = kebab_case_to_gradle_name(slug)
task = f"{task_slug}Build"
if jvm_only and guides[slug].python_app:
task += f" -x {task_slug}RunPythonTestScript"
tasks.append(task)
return tasks


def matrix_for_tasks(tasks: Iterable[str]) -> dict[str, list[str]]:
Expand Down Expand Up @@ -124,6 +132,10 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--guides-dir", help="Guides directory. Defaults to <repo>/guides.")
parser.add_argument("--base", help="Base commit for git diff.")
parser.add_argument("--head", default="HEAD", help="Head commit for git diff.")
parser.add_argument(
"--jvm-only", action="store_true",
help="Exclude only Python runtime test tasks; retain JVM tests and guide generation.",
)
parser.add_argument(
"--diff-mode",
choices=("range", "merge-base"),
Expand Down Expand Up @@ -153,7 +165,7 @@ def main() -> int:
if args.changed_files is not None
else git_changed_files(repo, args.base, args.head, args.diff_mode)
)
tasks = tasks_for_changed_files(changed_files, guides_dir)
tasks = tasks_for_changed_files(changed_files, guides_dir, jvm_only=args.jvm_only)
matrix = matrix_for_tasks(tasks)

print(f"Changed files: {json.dumps(changed_files)}", file=sys.stderr)
Expand Down
109 changes: 109 additions & 0 deletions .github/scripts/test_guide_ci_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from __future__ import annotations

import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
Expand Down Expand Up @@ -78,13 +80,116 @@ def test_non_guide_changes_return_empty_task_list(self) -> None:

self.assertEqual([], tasks)

def test_default_python_build_remains_unchanged(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
guides_dir = Path(temp_dir)
write_metadata(guides_dir, "python-guide", languages=["JAVA", "PYTHON"])

self.assertEqual(
["pythonGuideBuild"],
guide_ci_tasks.tasks_for_changed_files(
["guides/python-guide/metadata.json"], guides_dir,
),
)

def test_jvm_only_excludes_python_runner_for_implicit_apps(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
guides_dir = Path(temp_dir)
write_metadata(guides_dir, "python-guide", languages=["JAVA", "PYTHON"])

self.assertEqual(
["pythonGuideBuild -x pythonGuideRunPythonTestScript"],
guide_ci_tasks.tasks_for_changed_files(
["guides/python-guide/metadata.json"], guides_dir, jvm_only=True,
),
)

def test_jvm_only_excludes_python_runner_for_explicit_apps(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
guides_dir = Path(temp_dir)
write_metadata(
guides_dir, "python-guide", languages=["JAVA", "PYTHON"],
apps=[{"name": "books"}, {"name": "inventory"}],
)

self.assertEqual(
["pythonGuideBuild -x pythonGuideRunPythonTestScript"],
guide_ci_tasks.tasks_for_changed_files(
["guides/python-guide/python/tests/test_books.py"], guides_dir, jvm_only=True,
),
)

def test_jvm_only_docs_only_guide_has_no_absent_runner_exclusion(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
guides_dir = Path(temp_dir)
write_metadata(guides_dir, "docs-guide", languages=["JAVA", "PYTHON"], apps=[])

self.assertEqual(
["docsGuideBuild"],
guide_ci_tasks.tasks_for_changed_files(
["guides/docs-guide/docs-guide.adoc"], guides_dir, jvm_only=True,
),
)

def test_jvm_only_retains_jvm_guide_build(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
guides_dir = Path(temp_dir)
write_metadata(guides_dir, "jvm-guide", languages=["JAVA", "GROOVY", "KOTLIN"])

self.assertEqual(
["jvmGuideBuild"],
guide_ci_tasks.tasks_for_changed_files(
["guides/jvm-guide/metadata.json"], guides_dir, jvm_only=True,
),
)

def test_jvm_only_preserves_transitive_base_impacts_and_deduplication(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
guides_dir = Path(temp_dir)
write_metadata(guides_dir, "root-base", publish=False)
write_metadata(guides_dir, "middle-base", publish=False, base="root-base")
write_metadata(
guides_dir, "python-guide", base="middle-base", languages=["JAVA", "PYTHON"],
)

self.assertEqual(
["pythonGuideBuild -x pythonGuideRunPythonTestScript"],
guide_ci_tasks.tasks_for_changed_files(
["guides/root-base/common.adoc", "guides/python-guide/metadata.json"],
guides_dir, jvm_only=True,
),
)

def test_cli_jvm_only_flag(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
repo = Path(temp_dir)
guides_dir = repo / "guides"
guides_dir.mkdir()
write_metadata(guides_dir, "python-guide", languages=["JAVA", "PYTHON"])

for flags, task in (
([], "pythonGuideBuild"),
(["--jvm-only"], "pythonGuideBuild -x pythonGuideRunPythonTestScript"),
):
with self.subTest(flags=flags):
result = subprocess.run(
[sys.executable, str(Path(guide_ci_tasks.__file__).resolve()),
"--repo", str(repo), "--changed-file", "guides/python-guide/metadata.json", *flags],
capture_output=True, text=True,
)

self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual({"group_test_tasks": [task]}, json.loads(result.stdout))


def write_metadata(
guides_dir: Path,
slug: str,
*,
publish: bool = True,
base: str | None = None,
languages: list[str] | None = None,
apps: list[dict[str, str]] | None = None,
) -> None:
guide_dir = guides_dir / slug
guide_dir.mkdir()
Expand All @@ -93,6 +198,10 @@ def write_metadata(
metadata["publish"] = publish
if base is not None:
metadata["base"] = base
if languages is not None:
metadata["languages"] = languages
if apps is not None:
metadata["apps"] = apps
(guide_dir / "metadata.json").write_text(json.dumps(metadata), encoding="utf-8")


Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
- uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
- name: Validate guide matrix selector
run: python3 -m unittest discover -s .github/scripts -p 'test_*.py'
- name: Generate Guide Test Matrix
id: test_matrix_step
env:
Expand All @@ -25,6 +27,7 @@ jobs:
DIFF_MODE: ${{ github.event_name == 'pull_request' && 'merge-base' || 'range' }}
run: |
python3 .github/scripts/guide_ci_tasks.py \
--jvm-only \
--repo "$GITHUB_WORKSPACE" \
--base "$BASE_SHA" \
--head "$HEAD_SHA" \
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/python-guides.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Validate Python Guides
on:
push:
branches:
- master
- python
- python-guide-support
paths:
- '.github/workflows/python-guides.yml'
- 'buildSrc/**'
- 'guides/**'
- 'settings.gradle'
- 'src/docs/common/**'
pull_request:
branches:
- master
- python
- python-guide-support
paths:
- '.github/workflows/python-guides.yml'
- 'buildSrc/**'
- 'guides/**'
- 'settings.gradle'
- 'src/docs/common/**'

jobs:
validate:
name: Generate Python Guides
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/setup-java@v5.2.0
with:
distribution: adopt
java-version: '25'
- uses: actions/cache@v5.0.5
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Validate build infrastructure
run: ./gradlew buildSrc:test --stacktrace
- name: Generate Python guides
run: ./gradlew generateAllPythonGuideTestScripts --stacktrace
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ To build a single guide, run the dynamic task created by `GuidesPlugin`; convert
./gradlew micronautHttpClientBuild
```

### Use local composite builds

Local Micronaut Core, Pyronaut, and Starter checkouts can be included with
either the matching Gradle property or environment variable:

| Checkout | Gradle property | Environment variable |
| --- | --- | --- |
| Micronaut Core | `local.git.micronaut-core` | `LOCAL_GIT_MICRONAUT_CORE` |
| Pyronaut | `local.git.pyronaut` | `LOCAL_GIT_PYRONAUT` |
| Micronaut Starter | `local.git.micronaut-starter` | `LOCAL_GIT_MICRONAUT_STARTER` |

For example: `./gradlew -Plocal.git.pyronaut=/path/to/pyronaut help`.
Gradle properties take precedence when both forms are set. Pyronaut's GraalPy
bundle repository is discovered from a local Core or Pyronaut checkout when
available; set `micronaut.graalpy.bundle.repo` to override it.

## Create a new guide

For a high-level overview of the guides infrastructure, take a look at this [blog post](https://micronaut.io/2021/04/12/improving-the-micronaut-guides-infrastructure/).
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ repositories {
}

ext {
micronautStarterVersion = "5.2.0"
micronautStarterVersion = "5.2.1"
}

dependencies {
Expand Down Expand Up @@ -83,5 +83,5 @@ tasks.named('test', Test) {
}

tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-Amicronaut.jsonschema.baseUri=https://micronaut-projects.github.io/micronaut-guides/schemas") // (1)
options.compilerArgs.add("-Amicronaut.jsonschema.baseUri=https://guides.micronaut.io/schemas") // (1)
}
10 changes: 10 additions & 0 deletions buildSrc/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

rootProject.name = "micronaut-guides-buildsrc"

apply from: file('../gradle/local-composite-builds.gradle')
Loading
Loading