├── .editorconfig
├── .gitattributes
├── .github
├── actions
│ └── build
│ │ └── action.yml
├── link-check-config.json
├── renovate.json5
├── scripts
│ ├── replace_string.py
│ ├── requirements.txt
│ ├── test_replace_string.py
│ ├── test_resources
│ │ └── api_manual.html
│ ├── test_update_api_spec_version.py
│ └── update_api_spec_version.py
└── workflows
│ ├── pr.yml
│ ├── publish-javadoc.yml
│ ├── publish-library.yml
│ ├── submit-dependencies.yml
│ ├── test-readme-links.yml
│ ├── update-api-spec.yml
│ └── update-examples.yml
├── .gitignore
├── LICENSE
├── README.md
├── build-logic
├── build.gradle.kts
├── gradle.properties
├── settings.gradle.kts
└── src
│ ├── functionalTest
│ └── kotlin
│ │ └── com
│ │ └── gabrielfeo
│ │ └── task
│ │ └── PostProcessGeneratedApiTest.kt
│ └── main
│ └── kotlin
│ └── com
│ └── gabrielfeo
│ ├── develocity-api-code-generation.gradle.kts
│ ├── examples-test-suite.gradle.kts
│ ├── integration-test-suite.gradle.kts
│ ├── kotlin-jvm-library.gradle.kts
│ ├── no-op.gradle.kts
│ ├── published-kotlin-jvm-library.gradle.kts
│ ├── task
│ └── PostProcessGeneratedApi.kt
│ └── test-fixtures.gradle.kts
├── build.gradle.kts
├── docs
├── AccessKeys.md
├── Logging.md
└── media
│ ├── AccessPage.png
│ ├── AnonymousAccessPage.png
│ ├── IntelliJKernelLogs.png
│ └── IntelliJKernelSettings.png
├── examples
├── example-gradle-task
│ ├── README.md
│ ├── build.gradle.kts
│ ├── buildSrc
│ │ ├── build.gradle.kts
│ │ ├── settings.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── kotlin
│ │ │ └── build
│ │ │ └── logic
│ │ │ ├── DevelocityApiService.kt
│ │ │ ├── PerformanceMetricsTask.kt
│ │ │ └── performance-metrics-plugin.gradle.kts
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle.kts
├── example-notebooks
│ ├── Logging.ipynb
│ ├── MostFrequentBuilds.ipynb
│ └── requirements.txt
├── example-project
│ ├── build.gradle.kts
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── settings.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── com
│ │ └── gabrielfeo
│ │ └── develocity
│ │ └── api
│ │ └── example
│ │ ├── Main.kt
│ │ └── analysis
│ │ └── MostFrequentBuilds.kt
└── example-scripts
│ └── example-script.main.kts
├── gradle.properties
├── gradle
├── gradle-daemon-jvm.properties
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .openapi-generator-ignore
├── api
│ └── library.api
├── build.gradle.kts
└── src
│ ├── examplesTest
│ ├── kotlin
│ │ └── com
│ │ │ └── gabrielfeo
│ │ │ └── develocity
│ │ │ └── api
│ │ │ └── example
│ │ │ ├── JsonParser.kt
│ │ │ ├── Queries.kt
│ │ │ ├── Shell.kt
│ │ │ ├── gradle
│ │ │ ├── ExampleGradleTaskTest.kt
│ │ │ ├── ExampleProjectTest.kt
│ │ │ └── ResourceInitScripts.kt
│ │ │ ├── notebook
│ │ │ ├── Jupyter.kt
│ │ │ ├── NotebookJson.kt
│ │ │ ├── NotebooksTest.kt
│ │ │ └── PythonVenv.kt
│ │ │ └── script
│ │ │ └── ScriptsTest.kt
│ └── resources
│ │ ├── force-snapshot-library.init.gradle.kts
│ │ ├── preprocessors.py
│ │ └── require-java-11-compatibility.init.gradle
│ ├── integrationTest
│ ├── kotlin
│ │ └── com
│ │ │ └── gabrielfeo
│ │ │ └── develocity
│ │ │ └── api
│ │ │ ├── DevelocityApiIntegrationTest.kt
│ │ │ ├── InMemoryLogRecorder.kt
│ │ │ ├── LibraryApiNamingTest.kt
│ │ │ ├── LoggingIntegrationTest.kt
│ │ │ ├── SmokeTest.kt
│ │ │ ├── extension
│ │ │ ├── BuildsApiExtensionsIntegrationTest.kt
│ │ │ └── RequestRecorder.kt
│ │ │ └── internal
│ │ │ └── jupyter
│ │ │ └── DevelocityApiJupyterIntegrationTest.kt
│ └── resources
│ │ ├── logback-test.xml
│ │ └── response
│ │ └── api
│ │ └── builds
│ │ └── 5-builds.json
│ ├── main
│ ├── kotlin
│ │ └── com
│ │ │ └── gabrielfeo
│ │ │ └── develocity
│ │ │ └── api
│ │ │ ├── Config.kt
│ │ │ ├── DevelocityApi.kt
│ │ │ ├── extension
│ │ │ ├── BuildAttributesValueExtensions.kt
│ │ │ ├── BuildsApiExtensions.kt
│ │ │ └── Mapping.kt
│ │ │ └── internal
│ │ │ ├── ApiConstants.kt
│ │ │ ├── Env.kt
│ │ │ ├── OkHttpClient.kt
│ │ │ ├── OkHttpClientBuilderFactory.kt
│ │ │ ├── Retrofit.kt
│ │ │ ├── SystemProperties.kt
│ │ │ ├── auth
│ │ │ ├── AccessKeyResolver.kt
│ │ │ └── HostAccessKeyEntry.kt
│ │ │ ├── caching
│ │ │ ├── CacheEnforcingInterceptor.kt
│ │ │ └── CacheHitLoggingInterceptor.kt
│ │ │ └── jupyter
│ │ │ └── DevelocityApiJupyterIntegration.kt
│ └── resources
│ │ └── META-INF
│ │ └── kotlin-jupyter-libraries
│ │ └── libraries.json
│ ├── test
│ ├── kotlin
│ │ └── com
│ │ │ └── gabrielfeo
│ │ │ └── develocity
│ │ │ └── api
│ │ │ ├── CacheConfigTest.kt
│ │ │ ├── ConfigTest.kt
│ │ │ ├── DevelocityApiExtensionsTest.kt
│ │ │ ├── DevelocityApiTest.kt
│ │ │ ├── FakeBuildsApi.kt
│ │ │ ├── OkHttpClientTest.kt
│ │ │ ├── RetrofitTest.kt
│ │ │ ├── TestResourceUtils.kt
│ │ │ ├── extension
│ │ │ ├── BuildsApiExtensionsTest.kt
│ │ │ └── MappingTest.kt
│ │ │ ├── internal
│ │ │ ├── auth
│ │ │ │ └── AccessKeyResolverTest.kt
│ │ │ └── caching
│ │ │ │ └── CacheEnforcingInterceptorTest.kt
│ │ │ └── model
│ │ │ ├── BuildAttributesValueExtensionsTest.kt
│ │ │ └── FakeBuild.kt
│ └── resources
│ │ └── gradle-attributes-response.json
│ └── testFixtures
│ └── kotlin
│ └── com
│ └── gabrielfeo
│ └── develocity
│ └── api
│ ├── FakeDevelocityApiScaffold.kt
│ ├── Resources.kt
│ └── internal
│ ├── FakeEnv.kt
│ └── FakeSystemProperties.kt
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.kt, *.kts]
4 | indent_style = space
5 | indent_size = 4
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # Linux start script should use lf
5 | /gradlew text eol=lf
6 |
7 | # These are Windows script files and should use crlf
8 | *.bat text eol=crlf
9 |
10 |
--------------------------------------------------------------------------------
/.github/actions/build/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Build'
2 | description: 'Run a Gradle build'
3 | inputs:
4 | args:
5 | description: "Gradle args"
6 | required: false
7 | artifact-name:
8 | description: "Artifact name"
9 | required: false
10 | path-to-upload:
11 | description: "Path to upload as artifact"
12 | required: false
13 | dry-run:
14 | description: "Whether to --dry-run"
15 | type: boolean
16 | default: false
17 | runs:
18 | using: "composite"
19 | steps:
20 | - name: Set up Java
21 | uses: actions/setup-java@v5
22 | with:
23 | distribution: liberica
24 | java-version: |
25 | 24
26 | 17
27 | - name: Setup Python
28 | uses: actions/setup-python@v6
29 | with:
30 | python-version: '3.14'
31 | cache: 'pip'
32 | - name: Set up Gradle
33 | uses: gradle/actions/setup-gradle@v5
34 | with:
35 | validate-wrappers: true
36 | add-job-summary-as-pr-comment: 'on-failure'
37 | # Disable cache for releases, disable write for bot branches, enable write everywhere else
38 | cache-disabled: ${{ startsWith('refs/tags', github.ref) }}
39 | cache-read-only: ${{ contains('renovate/', github.ref) || contains('bot/', github.ref) }}
40 | - name: Run Gradle
41 | shell: bash
42 | run: |
43 | if [[ "${{ inputs.dry-run }}" == "true" ]]; then
44 | ./gradlew --dry-run ${{ inputs.args }}
45 | else
46 | ./gradlew ${{ inputs.args }}
47 | fi
48 | - name: Upload
49 | if: ${{ inputs.path-to-upload }}
50 | uses: actions/upload-artifact@v4
51 | with:
52 | name: ${{ inputs.artifact-name }}
53 | path: ${{ inputs.path-to-upload }}
54 | if-no-files-found: warn
55 |
--------------------------------------------------------------------------------
/.github/link-check-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "retryCount": 3,
3 | "fallbackRetryDelay": "120s",
4 | "replacementPatterns": [
5 | {
6 | "pattern": "^https://nbviewer.org/github/",
7 | "replacement": "https://github.com/"
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended",
5 | ],
6 | "schedule": [
7 | // Runs once, despite '*' minutes (https://docs.renovatebot.com/configuration-options/#schedule)
8 | "* 0-6 * * 1",
9 | ],
10 | "ignorePaths": [
11 | "**/test_resources/**",
12 | ],
13 | "dependencyDashboard": true,
14 | "branchConcurrentLimit": 0,
15 | "prConcurrentLimit": 0,
16 | "prHourlyLimit": 0,
17 | "rebaseWhen": "behind-base-branch",
18 | // Remove configDescription from PR body
19 | "prBodyTemplate": "{{{header}}}{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}{{{controls}}}{{{footer}}}",
20 | // Remove "dependency " prefix which makes message subjects too long
21 | "commitMessageTopic": "{{depName}}",
22 | "packageRules": [
23 | // Add a changelog link to Develocity plugin PRs
24 | {
25 | "matchDepNames": ["com.gradle.develocity"],
26 | "prBodyNotes": ["https://docs.gradle.com/develocity/gradle-plugin/current#release_history"],
27 | },
28 | // Group Kotlin/Jupyter artifact bumps
29 | {
30 | "matchDepNames": [
31 | "org.jetbrains.kotlinx:kotlin-jupyter-test-kit",
32 | "org.jetbrains.kotlinx:kotlin-jupyter-api",
33 | // Two possible names for the plugin (GAV or plugin ID)
34 | "org.jetbrains.kotlinx:kotlin-jupyter-api-gradle-plugin",
35 | "org.jetbrains.kotlin.jupyter.api",
36 | ],
37 | "groupName": "Kotlin/Jupyter",
38 | "groupSlug": "kotlin-jupyter",
39 | },
40 | // Group bumps of .github/scripts dependencies
41 | {
42 | "matchFileNames": [".github/scripts/**"],
43 | "groupName": ".github/scripts dependencies",
44 | },
45 | // Group bumps of examples/ dependencies
46 | {
47 | "matchFileNames": ["examples/**"],
48 | "groupName": "examples dependencies",
49 | },
50 | ],
51 | }
52 |
--------------------------------------------------------------------------------
/.github/scripts/replace_string.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # pylint: disable=missing-function-docstring,missing-module-docstring
3 |
4 | import argparse
5 | from pathlib import Path
6 | import re
7 | import sys
8 | import git
9 |
10 |
11 | def main() -> None:
12 | parser = argparse.ArgumentParser()
13 | parser.add_argument("path", type=Path, help="Path to root directory")
14 | parser.add_argument("old", type=str, help="Old string to be replaced")
15 | parser.add_argument(
16 | "new", type=str, help="New string to replace old string")
17 | args = parser.parse_args()
18 | replace_string(args.path, args.old, args.new)
19 |
20 |
21 | def replace_string(path: Path, old: str, new: str) -> None:
22 | repo = git.Repo(path, search_parent_directories=True)
23 | print(f'Replacing {old} for {new}...')
24 | for file in path.glob('**/*'):
25 | if not _should_replace_in(repo, file):
26 | continue
27 | try:
28 | text = file.read_text()
29 | text = _replace_badge_versions(text, old, new)
30 | text = _replace_non_badge_versions(text, old, new)
31 | file.write_text(text)
32 | print(f'Replaced in file {file}')
33 | except UnicodeError as e:
34 | print(f'Error processing file {file}:', e, file=sys.stderr)
35 |
36 |
37 | def _replace_badge_versions(text: str, old: str, new: str) -> str:
38 | return re.sub(
39 | rf'''https://img\.shields\.io/badge/(.+?)-{_badge_version(old)}-(\w+)''',
40 | rf'''https://img.shields.io/badge/\1-{_badge_version(new)}-\2''',
41 | text
42 | )
43 |
44 |
45 | def _replace_non_badge_versions(text: str, old: str, new: str) -> str:
46 | return re.sub(rf'(?!https://img\.shields\.io/badge/){old}', new, text)
47 |
48 |
49 | def _badge_version(version: str) -> str:
50 | return version.replace('-', '--')
51 |
52 |
53 | def _should_replace_in(repo, file: Path) -> bool:
54 | return file.is_file() \
55 | and not repo.ignored(file) \
56 | and file.parts[0] != '.git' \
57 | and file.name != 'test_replace_string.py'
58 |
59 |
60 | if __name__ == "__main__":
61 | main()
62 |
--------------------------------------------------------------------------------
/.github/scripts/requirements.txt:
--------------------------------------------------------------------------------
1 | certifi==2025.10.5
2 | charset-normalizer==3.4.4
3 | gitdb==4.0.12
4 | GitPython==3.1.45
5 | idna==3.11
6 | requests==2.32.5
7 | smmap==5.0.2
8 | urllib3==2.5.0
9 |
--------------------------------------------------------------------------------
/.github/scripts/test_replace_string.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # pylint: disable=missing-function-docstring,missing-module-docstring,missing-class-docstring
3 |
4 | from textwrap import dedent
5 | import unittest
6 | from unittest import mock
7 | from pathlib import Path
8 | from tempfile import TemporaryDirectory
9 | from replace_string import replace_string
10 |
11 | class TestReplaceString(unittest.TestCase):
12 |
13 | @mock.patch('git.Repo')
14 | def test_stable_to_stable(self, repo):
15 | self._test_replace_string(repo, [
16 | ('2024.1.0', '2024.2.0'),
17 | ('2024.1.0', '2024.1.1'),
18 | ('2024.4.2', '2025.1.0')
19 | ])
20 |
21 | @mock.patch('git.Repo')
22 | def test_stable_to_pre_release(self, repo):
23 | self._test_replace_string(repo, [
24 | ('2024.1.0', '2024.1.1-alpha01'),
25 | ('2024.1.0', '2024.1.1-beta01'),
26 | ('2024.1.0', '2024.2.0-alpha01'),
27 | ('2024.1.0', '2024.2.0-beta01'),
28 | ('2024.4.2', '2025.1.0-alpha01'),
29 | ])
30 |
31 | @mock.patch('git.Repo')
32 | def test_pre_release_to_stable(self, repo):
33 | self._test_replace_string(repo, [
34 | ('2024.1.0-alpha01', '2024.1.0'),
35 | ('2024.1.0-beta01', '2024.1.0'),
36 | ('2024.1.0-rc01', '2024.1.0'),
37 | ])
38 |
39 | @mock.patch('git.Repo')
40 | def test_pre_release_to_pre_release(self, repo):
41 | self._test_replace_string(repo, [
42 | ('2024.1.0-alpha01', '2024.1.0-alpha02'),
43 | ('2024.1.0-alpha02', '2024.1.0-beta01'),
44 | ('2024.1.0-beta01', '2024.1.1-rc01'),
45 | ])
46 |
47 | def _test_replace_string(self, repo, replacements):
48 | repo.return_value.ignored.return_value = False
49 | for old, new in replacements:
50 | with TemporaryDirectory() as temp_dir:
51 | target = Path(temp_dir)
52 | write_test_files(target, old)
53 | replace_string(target, old, new)
54 | for file in target.glob('**/*'):
55 | self._assert_replaced(target / file, old, new)
56 |
57 | def _assert_replaced(self, file, old, new):
58 | content = file.read_text()
59 | self._assert_regular_versions_replaced(content, old, new)
60 | self._assert_badge_versions_replaced(content, old, new)
61 |
62 | def _assert_regular_versions_replaced(self, content, old, new):
63 | self.assertIn(new, content)
64 | self.assertNotIn(old, content)
65 |
66 | def _assert_badge_versions_replaced(self, content, old, new):
67 | if "badge/" not in content:
68 | return
69 | self.assertIn(
70 | f"badge/Maven%20Central-{new.replace('-', '--')}-blue", content)
71 | self.assertNotIn(
72 | f"badge/Maven%20Central-{old.replace('-', '--')}-blue", content)
73 |
74 |
75 | def write_test_files(target_dir, old):
76 | (target_dir / 'README.md').write_text(dedent(f'''
77 | # Title
78 |
79 | [}-blue)][14]
80 |
81 | ```kotlin
82 | @file:DependsOn("com.gabrielfeo:develocity-api-kotlin:{old}")
83 | implementation("com.gabrielfeo:develocity-api-kotlin:{old}")
84 | ```
85 |
86 | ```
87 | %use develocity-api-kotlin(version={old})
88 | ```
89 |
90 | [14]: https://central.sonatype.com/artifact/com.gabrielfeo/develocity-api-kotlin/{old}
91 | '''))
92 | (target_dir / 'build.gradle.kts').write_text(dedent(f'''
93 | dependencies {'{'}
94 | implementation("com.gabrielfeo:develocity-api-kotlin:{old}")
95 | {'}'}
96 | '''))
97 | (target_dir / 'notebook.ipynb').write_text(dedent('''
98 | {
99 | "metadata": {},
100 | "nbformat": 4,
101 | "nbformat_minor": 2,
102 | "cells": [
103 | {
104 | "cell_type": "code",
105 | "execution_count": 1,
106 | "source": [
107 | "%use gradle-enterprise-api-kotlin(version={{VERSION}})"
108 | ],
109 | "outputs": []
110 | }
111 | ]
112 | }
113 | ''').replace('{{VERSION}}', old))
114 |
115 |
116 | if __name__ == "__main__":
117 | unittest.main()
118 |
--------------------------------------------------------------------------------
/.github/scripts/test_update_api_spec_version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from pathlib import Path
4 | from update_api_spec_version import main
5 | from tempfile import NamedTemporaryFile
6 | import unittest
7 | from unittest import mock
8 |
9 | TEST_RESOURCES = Path(__file__).parent / 'test_resources'
10 | TEST_API_MANUAL_HTML = TEST_RESOURCES / 'api_manual.html'
11 | LATEST_VERSION = '2024.1' # Same as HTML
12 |
13 |
14 | class TestCheckForNewApiSpec(unittest.TestCase):
15 |
16 | @mock.patch('builtins.print')
17 | @mock.patch('requests.get')
18 | def test_main_with_update_available(self, mock_get, _):
19 | mock_get.return_value.status_code = 200
20 | mock_get.return_value.text = TEST_API_MANUAL_HTML.read_text()
21 | with self.properties_file(version='2022.4') as file:
22 | main(properties_file=file.name)
23 | self.assert_properties_version(file, LATEST_VERSION)
24 |
25 | @mock.patch('builtins.print')
26 | @mock.patch('requests.get')
27 | def test_main_without_update_available(self, mock_get, _):
28 | mock_get.return_value.status_code = 200
29 | mock_get.return_value.text = TEST_API_MANUAL_HTML.read_text()
30 | with self.properties_file(version=LATEST_VERSION) as file:
31 | with self.assertRaises(SystemExit):
32 | main(properties_file=file.name)
33 | self.assert_properties_version(file, LATEST_VERSION)
34 |
35 | def assert_properties_version(self, file, version):
36 | with open(file.name) as file:
37 | expected = f"develocity.version={version}\nversion={version}.0\n1=2\n"
38 | self.assertEqual(file.read(), expected)
39 |
40 | def properties_file(self, version):
41 | file = NamedTemporaryFile()
42 | content = f"develocity.version={version}\nversion={version}.0\n1=2\n"
43 | file.write(content.encode())
44 | file.flush()
45 | return file
46 |
47 |
48 | if __name__ == '__main__':
49 | unittest.main()
50 |
--------------------------------------------------------------------------------
/.github/scripts/update_api_spec_version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import requests
4 | import re
5 | import fileinput
6 | import sys
7 |
8 | VERSIONS_URL = "https://docs.gradle.com/enterprise/api-manual/"
9 | LATEST_VERSION_REGEX = r']*href="ref/develocity-([\d.]+)-api\.yaml">Specification'
10 |
11 |
12 | def main(properties_file='gradle.properties'):
13 | current = get_current_api_spec_version(properties_file)
14 | print(f"Current spec version in", properties_file, "is", current, file=sys.stderr)
15 | latest = extract_latest_version()
16 | if current == latest:
17 | exit(1)
18 | update_version(properties_file, latest)
19 | print(latest)
20 |
21 |
22 | def get_current_api_spec_version(properties_file) -> str:
23 | with open(properties_file, mode='r') as file:
24 | for line in file.readlines():
25 | if '=' not in line:
26 | continue
27 | k, v = line.strip().split('=', maxsplit=2)
28 | if k == 'develocity.version':
29 | return v
30 |
31 |
32 | def extract_latest_version():
33 | resp = requests.get(VERSIONS_URL)
34 | resp.raise_for_status()
35 | match = re.search(LATEST_VERSION_REGEX, resp.text)
36 | if not match:
37 | raise RuntimeError(f"Failed to get latest version from {VERSIONS_URL} \
38 | with /{LATEST_VERSION_REGEX}/")
39 | print("First match in HTML of ", VERSIONS_URL, "is", match, file=sys.stderr)
40 | return match.group(1)
41 |
42 |
43 | def update_version(properties_file, new_version):
44 | for line in fileinput.input(properties_file, inplace=True):
45 | if '=' in line:
46 | k, v = line.strip().split('=', maxsplit=2)
47 | # Update target API spec version
48 | if k == 'develocity.version':
49 | line = f"{k}={new_version}\n"
50 | # Update library version
51 | if k == 'version':
52 | line = f"{k}={new_version}.0\n"
53 | sys.stdout.write(line)
54 |
55 |
56 | if __name__ == '__main__':
57 | main()
58 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yml:
--------------------------------------------------------------------------------
1 | name: 'Check PR'
2 |
3 | on:
4 | pull_request
5 |
6 | defaults:
7 | run:
8 | shell: bash
9 |
10 | jobs:
11 |
12 | kotlin-tests:
13 | runs-on: ubuntu-latest
14 | env:
15 | DEVELOCITY_URL: "${{ vars.DEVELOCITY_URL }}"
16 | DEVELOCITY_ACCESS_KEY: "${{ secrets.DEVELOCITY_ACCESS_KEY }}"
17 | DEVELOCITY_API_CACHE_ENABLED: "false"
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v5
21 | - name: gradle check
22 | uses: ./.github/actions/build
23 | with:
24 | args: check
25 |
26 | python-tests:
27 | runs-on: ubuntu-latest
28 | steps:
29 | - name: Checkout
30 | uses: actions/checkout@v5
31 | - name: Setup Python
32 | uses: actions/setup-python@v6
33 | with:
34 | python-version: '3.14'
35 | cache: 'pip'
36 | - run: pip install -r .github/scripts/requirements.txt
37 | - name: 'unittest discover'
38 | run: python3 -m unittest discover -bs .github/scripts
39 |
40 | readme-links-test:
41 | uses: ./.github/workflows/test-readme-links.yml
42 |
43 | dry-run-publish-javadoc:
44 | uses: ./.github/workflows/publish-javadoc.yml
45 | with:
46 | dry_run: true
47 | secrets: inherit
48 |
49 | dry-run-publish-library:
50 | uses: ./.github/workflows/publish-library.yml
51 | with:
52 | dry_run: true
53 | secrets: inherit
54 |
55 | dry-run-update-api-spec:
56 | uses: ./.github/workflows/update-api-spec.yml
57 | with:
58 | dry_run: true
59 |
--------------------------------------------------------------------------------
/.github/workflows/publish-javadoc.yml:
--------------------------------------------------------------------------------
1 | name: 'Publish javadoc'
2 |
3 | on:
4 | push:
5 | tags: ['*']
6 | workflow_dispatch:
7 | inputs:
8 | dry_run:
9 | description: 'Dry run'
10 | type: boolean
11 | default: false
12 | workflow_call:
13 | inputs:
14 | dry_run:
15 | description: 'Dry run'
16 | type: boolean
17 | default: false
18 |
19 | defaults:
20 | run:
21 | shell: bash
22 |
23 | jobs:
24 |
25 | build-javadoc:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - name: Checkout
29 | uses: actions/checkout@v5
30 | - name: Build javadoc
31 | uses: ./.github/actions/build
32 | with:
33 | args: >-
34 | dokkaGenerate
35 | '-Pversion=${{ github.ref_name }}'
36 | artifact-name: 'docs'
37 | path-to-upload: "library/build/dokka/html/**/*"
38 |
39 | publish-javadoc:
40 | needs: [build-javadoc]
41 | runs-on: ubuntu-latest
42 | permissions:
43 | contents: write
44 | steps:
45 | - name: Checkout
46 | uses: actions/checkout@v5
47 | with:
48 | ref: gh-pages
49 | - name: Delete current javadoc
50 | run: rm -rf docs
51 | - name: Download new javadoc
52 | uses: actions/download-artifact@v5
53 | with:
54 | path: docs
55 | - name: Commit
56 | run: |
57 | git config user.name github-actions
58 | git config user.email github-actions@github.com
59 | git add docs
60 | git commit --allow-empty -m "Add ${{ github.ref_name }} javadoc"
61 | - name: Push
62 | run: |
63 | if [[ "${{ inputs.dry_run }}" == 'true' ]]; then
64 | args=' --dry-run'
65 | fi
66 | git push $args
67 |
--------------------------------------------------------------------------------
/.github/workflows/publish-library.yml:
--------------------------------------------------------------------------------
1 | name: 'Publish library to Central'
2 |
3 | on:
4 | push:
5 | tags: ['*']
6 | workflow_dispatch:
7 | inputs:
8 | dry_run:
9 | description: 'Dry run'
10 | type: boolean
11 | default: false
12 | workflow_call:
13 | inputs:
14 | dry_run:
15 | description: 'Dry run'
16 | type: boolean
17 | default: false
18 |
19 | defaults:
20 | run:
21 | shell: bash
22 |
23 | jobs:
24 |
25 | build-and-publish:
26 | runs-on: ubuntu-latest
27 | env:
28 | DEVELOCITY_URL: "${{ vars.DEVELOCITY_URL }}"
29 | DEVELOCITY_ACCESS_KEY: "${{ secrets.DEVELOCITY_ACCESS_KEY }}"
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v5
33 | - name: Verify that version property matches tag
34 | if: ${{ inputs.dry_run != true }}
35 | run: grep -qP '^version=${{ github.ref_name }}$' gradle.properties
36 | - name: gradle publish
37 | uses: ./.github/actions/build
38 | with:
39 | dry-run: ${{ inputs.dry_run }}
40 | args: >-
41 | check
42 | publishDevelocityApiKotlinPublicationToMavenCentralRepository
43 | publishRelocationPublicationToMavenCentralRepository
44 | --rerun-tasks
45 | '-PmavenCentralUsername=${{ secrets.MAVEN_CENTRAL_USERNAME }}'
46 | '-PmavenCentralPassword=${{ secrets.MAVEN_CENTRAL_PASSWORD }}'
47 | '-Psigning.password=${{ secrets.GPG_PASSWORD }}'
48 | '-Psigning.secretKey=${{ secrets.GPG_SECRET_KEY }}'
49 | artifact-name: 'outputs'
50 | path-to-upload: |
51 | library/build/*-api.yaml
52 | library/build/post-processed-api/**/*
53 | library/build/publications/**/*
54 | library/build/libs/**/*
55 |
--------------------------------------------------------------------------------
/.github/workflows/submit-dependencies.yml:
--------------------------------------------------------------------------------
1 | name: 'Submit dependencies'
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | workflow_call:
8 | workflow_dispatch:
9 |
10 | defaults:
11 | run:
12 | shell: bash
13 |
14 | jobs:
15 |
16 | submit-dependencies:
17 | runs-on: ubuntu-latest
18 | permissions: # The Dependency Submission API requires write permission
19 | contents: write
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v5
23 | - name: Submit Gradle dependencies
24 | uses: gradle/actions/dependency-submission@v5
25 | env:
26 | DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS: '.*[Tt]est(Compile|Runtime)Classpath'
27 |
--------------------------------------------------------------------------------
/.github/workflows/test-readme-links.yml:
--------------------------------------------------------------------------------
1 | name: 'Test README links'
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - gh-pages
8 | workflow_call:
9 | workflow_dispatch:
10 |
11 | defaults:
12 | run:
13 | shell: bash
14 |
15 | jobs:
16 |
17 | test-readme-links:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v5
22 | - name: Check README links
23 | uses: gaurav-nelson/github-action-markdown-link-check@v1
24 | with:
25 | folder-path: .
26 | max-depth: 1
27 | use-verbose-mode: yes
28 | config-file: .github/link-check-config.json
29 |
--------------------------------------------------------------------------------
/.github/workflows/update-api-spec.yml:
--------------------------------------------------------------------------------
1 | name: 'Update API spec'
2 |
3 | on:
4 | schedule:
5 | - cron: '0 6 * * *'
6 | workflow_dispatch:
7 | inputs:
8 | dry_run:
9 | description: 'Dry run'
10 | type: boolean
11 | default: false
12 | workflow_call:
13 | inputs:
14 | dry_run:
15 | description: 'Dry run'
16 | type: boolean
17 | default: false
18 |
19 | defaults:
20 | run:
21 | shell: bash
22 |
23 | jobs:
24 |
25 | update-api-spec:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - name: 'Checkout'
29 | uses: actions/checkout@v5
30 | - name: 'Update API spec version'
31 | run: |
32 | if ./.github/scripts/update_api_spec_version.py | tee new.txt; then
33 | echo "NEW_VERSION=$(cat new.txt)" >> $GITHUB_ENV
34 | fi
35 | rm new.txt || true
36 | - name: gradle :library:apiDump
37 | uses: ./.github/actions/build
38 | with:
39 | dry-run: ${{ inputs.dry_run }}
40 | args: :library:apiDump
41 | - name: Check for existing PR
42 | if: ${{ env.NEW_VERSION }}
43 | run: |
44 | set -u
45 | update_branch="feature/api-spec-$NEW_VERSION"
46 | echo "UPDATE_BRANCH=$update_branch" >> $GITHUB_ENV
47 | if git ls-remote --exit-code --heads origin "$update_branch"; then
48 | echo "EXISTING_PR=true" >> $GITHUB_ENV
49 | fi
50 | - name: 'Create PR'
51 | if: ${{ inputs.dry_run != true && env.NEW_VERSION && !env.EXISTING_PR }}
52 | uses: peter-evans/create-pull-request@v7
53 | with:
54 | branch: "${{ env.UPDATE_BRANCH }}"
55 | commit-message: "Bump Develocity API spec version to ${{ env.NEW_VERSION }}"
56 | title: |
57 | Bump Develocity API spec version to ${{ env.NEW_VERSION }}
58 |
59 | Generated from workflow run [${{ github.run_id }}][1].
60 |
61 | [1]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
62 | body: "https://docs.gradle.com/enterprise/api-manual/#release_history"
63 | author: "github-actions "
64 | committer: "github-actions "
65 | add-paths: "gradle.properties"
66 | delete-branch: true
67 |
--------------------------------------------------------------------------------
/.github/workflows/update-examples.yml:
--------------------------------------------------------------------------------
1 | name: 'Update examples'
2 |
3 | on:
4 | push:
5 | tags: [ '*' ]
6 | workflow_dispatch:
7 | inputs:
8 | old:
9 | description: |
10 | git ref of version to be replaced with 'new'. Defaults to the first git tag lower than 'new' in v:refname sorting.
11 | required: false
12 | new:
13 | description: |
14 | git ref of version to replace 'old'. Defaults to the current git ref (github.ref_name).
15 | required: false
16 |
17 | defaults:
18 | run:
19 | shell: bash
20 |
21 | jobs:
22 |
23 | update-examples:
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: 'Checkout'
27 | uses: actions/checkout@v5
28 | with:
29 | fetch-depth: 0
30 | - name: Setup Python
31 | uses: actions/setup-python@v6
32 | with:
33 | python-version: '3.14'
34 | cache: 'pip'
35 | - run: pip install -r .github/scripts/requirements.txt
36 | - name: 'Get versions'
37 | run: |
38 | old="${{ inputs.old }}"
39 | new="${{ inputs.new || github.ref_name }}"
40 | echo "NEW_VERSION=$new" | tee -a $GITHUB_ENV
41 | if [[ "$old" == "" ]]; then
42 | echo "Auto-detecting old version from git tags"
43 | # Get previous version from descending tag list
44 | old="$(git tag --sort=-v:refname | grep -A1 "$new" | tail -1)"
45 | fi
46 | echo "OLD_VERSION=$old" | tee -a $GITHUB_ENV
47 | - name: 'Update version in all files'
48 | run: ./.github/scripts/replace_string.py ./ "$OLD_VERSION" "$NEW_VERSION"
49 | - name: 'Create PR'
50 | uses: peter-evans/create-pull-request@v7
51 | with:
52 | base: 'main'
53 | branch: "replace-${{ env.OLD_VERSION }}-${{ env.NEW_VERSION }}"
54 | title: "Bump examples and badges to ${{ env.NEW_VERSION }}"
55 | author: "github-actions "
56 | committer: "github-actions "
57 | body: "Bump versions from ${{ env.OLD_VERSION }} to ${{ env.NEW_VERSION }}."
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .kotlin
3 | out
4 | !*/src/**/out
5 |
6 | gradle-user-home
7 | profile-out*
8 |
9 | build
10 | !.github/actions/build
11 | !**/src/**/build
12 | .ipynb_checkpoints
13 |
14 | .venv
15 | __pycache__
16 |
17 | **/.log
18 | .DS_Store
19 |
20 | .idea
21 | local.properties
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Gabriel Feo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build-logic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | `kotlin-dsl`
5 | }
6 |
7 | java {
8 | toolchain {
9 | languageVersion = JavaLanguageVersion.of(17)
10 | vendor = JvmVendorSpec.AZUL
11 | }
12 | }
13 |
14 | testing {
15 | suites {
16 | register("functionalTest") {
17 | useJUnitJupiter()
18 | }
19 | }
20 | }
21 |
22 | gradlePlugin {
23 | testSourceSets(sourceSets["functionalTest"])
24 | }
25 |
26 | tasks.named("check") {
27 | dependsOn(tasks.withType())
28 | }
29 |
30 | dependencies {
31 | implementation(libs.kotlin.plugin)
32 | implementation(libs.kotlin.binary.compatibility.validator.plugin)
33 | implementation(libs.dokka.plugin)
34 | implementation(libs.openapi.generator.plugin)
35 | implementation(libs.vanniktech.mavenPublishPlugin)
36 | "functionalTestImplementation"(project)
37 | }
38 |
--------------------------------------------------------------------------------
/build-logic/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.daemon.jvmargs=-Xmx1g
2 | org.gradle.configuration-cache=true
3 | org.gradle.configuration-cache.parallel=true
4 |
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0")
5 | }
6 |
7 | dependencyResolutionManagement {
8 | repositories {
9 | mavenCentral()
10 | gradlePluginPortal()
11 | }
12 | versionCatalogs {
13 | create("libs") {
14 | from(files("../gradle/libs.versions.toml"))
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo
2 |
3 | import com.gabrielfeo.task.PostProcessGeneratedApi
4 | import org.gradle.kotlin.dsl.*
5 |
6 | plugins {
7 | id("com.gabrielfeo.kotlin-jvm-library")
8 | id("org.openapi.generator")
9 | }
10 |
11 | val downloadApiSpec by tasks.registering {
12 | val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse(
13 | providers.gradleProperty("develocity.version").map { geVersion ->
14 | val majorVersion = geVersion.substringBefore('.').toInt()
15 | val specName = when {
16 | majorVersion <= 2023 -> "gradle-enterprise-$geVersion-api.yaml"
17 | else -> "develocity-$geVersion-api.yaml"
18 | }
19 | "https://docs.gradle.com/enterprise/api-manual/ref/$specName"
20 | }
21 | )
22 | val spec = resources.text.fromUri(remoteSpecUrl)
23 | val specName = remoteSpecUrl.map { it.substringAfterLast('/') }
24 | val outFile = project.layout.buildDirectory.file(specName)
25 | inputs.property("Spec URL", remoteSpecUrl)
26 | outputs.file(outFile)
27 | doLast {
28 | logger.info("Downloaded API spec from ${remoteSpecUrl.get()}")
29 | spec.asFile().renameTo(outFile.get().asFile)
30 | }
31 | }
32 |
33 | openApiGenerate {
34 | generatorName = "kotlin"
35 | inputSpec = downloadApiSpec.map { it.outputs.files.first().absolutePath }
36 | val generateDir = project.layout.buildDirectory.dir("generated-api")
37 | .map { it.asFile.absolutePath }
38 | outputDir = generateDir
39 | val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore")
40 | ignoreFileOverride.set(ignoreFile.asFile.absolutePath)
41 | apiPackage = "com.gabrielfeo.develocity.api"
42 | modelPackage = "com.gabrielfeo.develocity.api.model"
43 | packageName = "com.gabrielfeo.develocity.api.internal"
44 | invokerPackage = "com.gabrielfeo.develocity.api.internal"
45 | additionalProperties.put("library", "jvm-retrofit2")
46 | additionalProperties.put("useCoroutines", true)
47 | additionalProperties.put("enumPropertyNaming", "camelCase")
48 | additionalProperties.put("useResponseAsReturnType", false)
49 | cleanupOutput = true
50 | }
51 |
52 | val postProcessGeneratedApi by tasks.registering(PostProcessGeneratedApi::class) {
53 | val generatedSrc = tasks.openApiGenerate
54 | .flatMap { it.outputDir }
55 | .map { File(it) }
56 | originalFiles.convention(project.layout.dir(generatedSrc))
57 | postProcessedFiles.convention(project.layout.buildDirectory.dir("post-processed-api"))
58 | modelsPackage.convention(tasks.openApiGenerate.flatMap { it.modelPackage })
59 | }
60 |
61 | sourceSets {
62 | main {
63 | java {
64 | srcDir(postProcessGeneratedApi)
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/examples-test-suite.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.gabrielfeo
4 |
5 | plugins {
6 | id("org.jetbrains.kotlin.jvm")
7 | }
8 |
9 | testing {
10 | suites {
11 | register("examplesTest") {
12 | useKotlinTest()
13 | }
14 | }
15 | }
16 |
17 | kotlin {
18 | target {
19 | val main by compilations.getting
20 | val examplesTest by compilations.getting
21 | examplesTest.associateWith(main)
22 | }
23 | }
24 |
25 | val examples = fileTree(rootDir) {
26 | include("examples/**")
27 | exclude {
28 | it.isDirectory
29 | && (it.name == "build" || it.name.startsWith("."))
30 | && !it.path.endsWith("buildSrc/src/main/kotlin/build")
31 | }
32 | }
33 |
34 | tasks.named("processExamplesTestResources", ProcessResources::class) {
35 | from(examples)
36 | }
37 |
38 | val downloadPipRequirements by tasks.registering(Exec::class) {
39 | val requirementsFiles = examples.filter { it.name == "requirements.txt" }
40 | inputs.files(requirementsFiles)
41 | .withPropertyName("requirementsFiles")
42 | .withPathSensitivity(PathSensitivity.NONE)
43 | .skipWhenEmpty()
44 | val downloadDir = layout.buildDirectory.dir("pip-requirements")
45 | outputs.dir(downloadDir)
46 | commandLine("pip3", "download")
47 | workingDir(downloadDir)
48 | argumentProviders += CommandLineArgumentProvider {
49 | requirementsFiles.files.flatMap { listOf("-r", it.absolutePath) }
50 | }
51 | }
52 |
53 | tasks.named("examplesTest") {
54 | inputs.files(downloadPipRequirements)
55 | .withPropertyName("downloadedPipRequirements")
56 | .withPathSensitivity(PathSensitivity.NONE)
57 | systemProperty(
58 | "downloaded-requirements-path",
59 | downloadPipRequirements.map { it.outputs.files.singleFile }.get().relativeTo(workingDir).path,
60 | )
61 | }
62 |
63 | tasks.named("check") {
64 | dependsOn("examplesTest")
65 | }
66 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/integration-test-suite.gradle.kts:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo
2 |
3 | plugins {
4 | id("org.jetbrains.kotlin.jvm")
5 | `java-test-fixtures`
6 | }
7 |
8 | testing {
9 | suites {
10 | register("integrationTest") {
11 | useKotlinTest()
12 | }
13 | }
14 | }
15 |
16 | kotlin {
17 | target {
18 | val main by compilations.getting
19 | val integrationTest by compilations.getting
20 | integrationTest.associateWith(main)
21 | }
22 | }
23 |
24 | tasks.named("check") {
25 | dependsOn("integrationTest")
26 | }
27 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.gabrielfeo
4 |
5 | plugins {
6 | id("org.jetbrains.kotlin.jvm")
7 | `java-library`
8 | }
9 |
10 | java {
11 | toolchain {
12 | languageVersion = JavaLanguageVersion.of(17)
13 | vendor = JvmVendorSpec.AZUL
14 | }
15 | consistentResolution {
16 | useRuntimeClasspathVersions()
17 | }
18 | }
19 |
20 | testing {
21 | suites {
22 | named("test") {
23 | useKotlinTest()
24 | }
25 | }
26 | }
27 |
28 | val testTasks = tasks.named {
29 | it == "check" || it.contains("test", ignoreCase = true)
30 | }
31 |
32 | tasks.named { it.startsWith("publish") }.configureEach {
33 | shouldRunAfter(testTasks)
34 | }
35 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo
2 |
3 | // Plugin is only used to test the tasks logic. Applying it adds the task classes to the classpath.
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/published-kotlin-jvm-library.gradle.kts:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo
2 |
3 | import org.jetbrains.dokka.gradle.DokkaExtension
4 | import org.jetbrains.dokka.gradle.engine.parameters.VisibilityModifier
5 | import java.net.URI
6 |
7 | plugins {
8 | id("com.gabrielfeo.kotlin-jvm-library")
9 | `java-library`
10 | `maven-publish`
11 | signing
12 | id("com.vanniktech.maven.publish.base")
13 | id("org.jetbrains.kotlinx.binary-compatibility-validator")
14 | id("org.jetbrains.dokka")
15 | }
16 |
17 | java {
18 | withSourcesJar()
19 | withJavadocJar()
20 | }
21 |
22 |
23 | configure {
24 | val kotlinSourceRoot = file("src/main/kotlin")
25 | val repoUrlSuffix = "/blob/$version/${kotlinSourceRoot.relativeTo(rootDir)}"
26 | val repoUrl = providers.gradleProperty("repo.url")
27 | .map { URI("$it$repoUrlSuffix").toString() }
28 | dokkaSourceSets.configureEach {
29 | sourceRoots.from(kotlinSourceRoot)
30 | sourceLink {
31 | localDirectory.set(kotlinSourceRoot)
32 | remoteUrl(repoUrl)
33 | remoteLineSuffix = "#L"
34 | }
35 | jdkVersion = java.toolchain.languageVersion.map { it.asInt() }
36 | documentedVisibilities.add(VisibilityModifier.Public)
37 | suppressGeneratedFiles = false
38 | perPackageOption {
39 | matchingRegex = """.*\.internal.*"""
40 | suppress = true
41 | }
42 | listOf(
43 | "https://kotlinlang.org/api/kotlinx.coroutines",
44 | "https://square.github.io/okhttp/5.x/okhttp",
45 | "https://square.github.io/retrofit/2.x/retrofit",
46 | "https://square.github.io/moshi/1.x/moshi",
47 | "https://square.github.io/moshi/1.x/moshi-kotlin",
48 | ).forEach { url ->
49 | val name = url.trim('/').substringAfterLast('/')
50 | externalDocumentationLinks.register(name) {
51 | url(url)
52 | packageListUrl("$url/package-list")
53 | }
54 | }
55 | }
56 | }
57 |
58 | tasks.named("javadocJar") {
59 | from(tasks.dokkaGenerate)
60 | }
61 |
62 | mavenPublishing {
63 | publishToMavenCentral()
64 | }
65 |
66 | fun isCI() = System.getenv("CI").toBoolean()
67 |
68 | signing {
69 | val signedPublications = publishing.publications.matching {
70 | !it.name.contains("unsigned", ignoreCase = true)
71 | }
72 | sign(signedPublications)
73 | if (isCI()) {
74 | useInMemoryPgpKeys(
75 | project.properties["signing.secretKey"] as String?,
76 | project.properties["signing.password"] as String?,
77 | )
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.task
2 |
3 | import org.gradle.api.DefaultTask
4 | import org.gradle.api.file.DirectoryProperty
5 | import org.gradle.api.file.FileSystemOperations
6 | import org.gradle.api.provider.Property
7 | import org.gradle.api.tasks.*
8 | import org.gradle.kotlin.dsl.withGroovyBuilder
9 | import java.io.File
10 | import javax.inject.Inject
11 |
12 | @CacheableTask
13 | abstract class PostProcessGeneratedApi @Inject constructor(
14 | private val fsOperations: FileSystemOperations,
15 | ) : DefaultTask() {
16 |
17 | @get:InputDirectory
18 | @get:PathSensitive(PathSensitivity.RELATIVE)
19 | abstract val originalFiles: DirectoryProperty
20 |
21 | @get:Input
22 | abstract val modelsPackage: Property
23 |
24 | @get:OutputDirectory
25 | abstract val postProcessedFiles: DirectoryProperty
26 |
27 | @TaskAction
28 | fun doWork() {
29 | postProcessedFiles.get().asFile.deleteRecursively()
30 | fsOperations.copy {
31 | from(originalFiles)
32 | into(postProcessedFiles)
33 | }
34 | postProcess(
35 | srcDir = postProcessedFiles.get().dir("src/main/kotlin").asFile,
36 | modelsPackage = modelsPackage.get()
37 | )
38 | }
39 |
40 | private fun postProcess(srcDir: File, modelsPackage: String) {
41 | // Add @JvmSuppressWildcards to avoid square/retrofit#3275
42 | replaceAll(
43 | match = "interface",
44 | replace = "@JvmSuppressWildcards\ninterface",
45 | dir = srcDir,
46 | includes = "com/gabrielfeo/develocity/api/*Api.kt",
47 | )
48 | // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes
49 | replaceAll(
50 | match = "Minus",
51 | replace = "",
52 | dir = srcDir,
53 | includes = "com/gabrielfeo/develocity/api/model/BuildModelName.kt",
54 | )
55 | // Fix mapping of GradleConfigurationCacheResult.Outcome: hIT -> hit
56 | val file = "com/gabrielfeo/develocity/api/model/GradleConfigurationCacheResult.kt"
57 | replaceAll("hIT", "hit", dir = srcDir, includes = file)
58 | replaceAll("mISS", "miss", dir = srcDir, includes = file)
59 | replaceAll("fAILED", "failed", dir = srcDir, includes = file)
60 |
61 | // Fix mapping of MavenExtension.Type: lIBEXT -> core
62 | val mavenExtensionFile = "com/gabrielfeo/develocity/api/model/MavenExtension.kt"
63 | mapOf(
64 | "cORE" to "core",
65 | "mAVENEXTCLASSPATH" to "mavenExtClasspath",
66 | "lIBEXT" to "libExt",
67 | "pROJECT" to "project",
68 | "pOM" to "pom",
69 | "uNKNOWN" to "unknown",
70 | ).forEach { (match, replace) ->
71 | replaceAll(match, replace, dir = srcDir, includes = mavenExtensionFile)
72 | }
73 | }
74 |
75 | private fun replaceAll(
76 | match: String,
77 | replace: String,
78 | dir: File,
79 | includes: String,
80 | ) {
81 | ant.withGroovyBuilder {
82 | "replaceregexp"(
83 | "match" to match,
84 | "replace" to replace,
85 | "flags" to "mg",
86 | ) {
87 | "fileset"(
88 | "dir" to dir,
89 | "includes" to includes,
90 | )
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/build-logic/src/main/kotlin/com/gabrielfeo/test-fixtures.gradle.kts:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo
2 |
3 | plugins {
4 | id("org.jetbrains.kotlin.jvm")
5 | `java-test-fixtures`
6 | }
7 |
8 | kotlin {
9 | target {
10 | val main by compilations.getting
11 | val testFixtures by compilations.getting
12 | testFixtures.associateWith(main)
13 | compilations.named { it.endsWith("test", ignoreCase = true) }.configureEach {
14 | associateWith(testFixtures)
15 | }
16 | }
17 | }
18 |
19 | components.named("java") {
20 | withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() }
21 | withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() }
22 | }
23 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.gabrielfeo.kotlin-jvm-library") apply false
3 | }
4 |
5 | tasks.register("check") {
6 | dependsOn(gradle.includedBuilds.map { it.task(":check") })
7 | }
8 |
--------------------------------------------------------------------------------
/docs/AccessKeys.md:
--------------------------------------------------------------------------------
1 | # Access keys
2 |
3 | API requests may require [authentication][1].
4 |
5 | ## Anonymous access
6 |
7 | If your Develocity server is configured to allow [anonymous access][2] for permission "Access build data via the API", then an access key is not required to use this library.
8 | This is often the case for a server only accessible from a private network.
9 |
10 | 
11 |
12 | ## Authenticated access
13 |
14 | The library will automatically resolve the access key using the same conventions as official Develocity tooling, in order:
15 |
16 | - Environment variable `DEVELOCITY_ACCESS_KEY`
17 | - Environment variable `GRADLE_ENTERPRISE_ACCESS_KEY`
18 | - File `$GRADLE_USER_HOME/.gradle/develocity/keys.properties` (or `~/.gradle/develocity/keys.properties` if `GRADLE_USER_HOME` is not set)
19 | - File `~/.m2/.develocity/keys.properties`
20 |
21 | Please check if you already have an access key set up in your build environment for the Develocity server you want to query. The first key for a matching host will be used, if found.
22 |
23 | See the official manuals for instructions on how to set up a new access key in one of these locations:
24 |
25 | - [Develocity Gradle Plugin User Manual][3]
26 | - [Develocity Maven Extension User Manual][4]
27 | - [Develocity sbt Plugin User Manual][5]
28 | - [Develocity npm Agent User Manual][6]
29 | - [Develocity Python Agent User Manual][7]
30 |
31 | ### User permissions
32 |
33 | To call the API, the user from which the access key was generated must have the "Access build data via the API" permission.
34 |
35 | 
36 |
37 | [1]: https://docs.gradle.com/enterprise/api-manual/#access_control
38 | [2]: https://docs.gradle.com/develocity/helm-admin/current/#_anonymous_access
39 | [3]: https://docs.gradle.com/develocity/gradle-plugin/current/#manual_access_key_configuration
40 | [4]: https://docs.gradle.com/develocity/maven-extension/current/#manual_access_key_configuration
41 | [5]: https://gradle.com/help/sbt-plugin-authenticating
42 | [6]: https://gradle.com/help/npm-agent-authenticating
43 | [7]: https://gradle.com/help/python-agent-authenticating
44 |
--------------------------------------------------------------------------------
/docs/Logging.md:
--------------------------------------------------------------------------------
1 | # Logging
2 |
3 | This library uses [SLF4J][1] but does not bundle an SLF4J implementation.
4 |
5 | - [Notebooks](#notebooks)
6 | - [Scripts](#scripts)
7 | - [`simple-logger`](#simple-logger)
8 | - [Projects](#projects)
9 | - [`simple-logger`](#simple-logger-1)
10 |
11 | ## Notebooks
12 |
13 | 1. Set `%logLevel ` in a code cell, e.g., `%logLevel debug`.
14 |
15 | Logs appear in the Kotlin Jupyter kernel logs:
16 |
17 | - In Jupyter and JupyterLab, logs appear in the shell that owns the Jupyter process
18 | - In IntelliJ, view logs in the Kotlin Notebook logs tool window
19 |
20 | 
21 |
22 | ⚠️ Older versions of the Kotlin Jupyter kernel had issues with logging. Kernel version `0.15.0-598` and higher are known to work. In IntelliJ, configure to use a later version than bundled. In pip and conda, update the kernel package.
23 |
24 | 
25 |
26 | See the example notebook [Logging.ipynb](../examples/example-notebooks/Logging.ipynb).
27 |
28 | ## Scripts
29 |
30 | 1. Add an SLF4J implementation (e.g., `slf4j-simple`, `logback-classic`, etc.)
31 | 2. Set the log level for the package `com.gabrielfeo.develocity.api` using your chosen logging framework's configuration
32 |
33 | ### `simple-logger`
34 |
35 | Adding `simple-logger` to your classpath is the easiest way to get logging in scripts. You can do this by adding the following line to your script:
36 |
37 | ```kotlin
38 | @file:DependsOn("org.slf4j:slf4j-simple:2.0.17")
39 | ```
40 |
41 | Then set the log level for `com.gabrielfeo.develocity.api` using system properties. For example:
42 |
43 | - from script code
44 |
45 | ```kotlin
46 | @file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2025.1.1")
47 | @file:DependsOn("org.slf4j:slf4j-simple:2.0.17")
48 |
49 | System.setProperty("org.slf4j.simpleLogger.log.com.gabrielfeo.develocity.api", "debug")
50 |
51 | // ...
52 | ```
53 |
54 | - from the shebang line
55 |
56 | ```kotlin
57 | #!/usr/bin/env kotlin -script -J-Dorg.slf4j.simpleLogger.log.com.gabrielfeo.develocity.api=debug
58 |
59 | @file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2025.1.1")
60 | @file:DependsOn("org.slf4j:slf4j-simple:2.0.17")
61 |
62 | // ...
63 | ```
64 |
65 | - from `JAVA_OPTS`
66 |
67 | ```bash
68 | export JAVA_OPTS="-Dorg.slf4j.simpleLogger.log.com.gabrielfeo.develocity.api=debug"
69 | kotlin -script example-script.main.kts
70 | ```
71 |
72 | ## Projects
73 |
74 | 1. Add an SLF4J implementation (e.g., `slf4j-simple`, `logback-classic`, etc.) to your classpath
75 | 2. Set the log level for the package `com.gabrielfeo.develocity.api` using your chosen logging framework's configuration
76 |
77 | ### `simple-logger`
78 |
79 | Adding `simple-logger` to your classpath is the easiest way to get logging in projects. You can do this by adding the following dependency to your build file:
80 |
81 | ```kotlin
82 | // build.gradle.kts
83 | dependencies {
84 | implementation("com.gabrielfeo:develocity-api-kotlin:2025.1.1")
85 | runtimeOnly("org.slf4j:slf4j-simple:2.0.17")
86 | }
87 | ```
88 |
89 | Then set the system property when running your application. If using the Gradle `run` task, it can be declared in your build:
90 |
91 | ```kotlin
92 | tasks.named("run") {
93 | // ...
94 | systemProperty("org.slf4j.simpleLogger.log.com.gabrielfeo.develocity.api", "debug")
95 | }
96 | ```
97 |
98 | [0]: https://www.slf4j.org/
99 |
--------------------------------------------------------------------------------
/docs/media/AccessPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/docs/media/AccessPage.png
--------------------------------------------------------------------------------
/docs/media/AnonymousAccessPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/docs/media/AnonymousAccessPage.png
--------------------------------------------------------------------------------
/docs/media/IntelliJKernelLogs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/docs/media/IntelliJKernelLogs.png
--------------------------------------------------------------------------------
/docs/media/IntelliJKernelSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/docs/media/IntelliJKernelSettings.png
--------------------------------------------------------------------------------
/examples/example-gradle-task/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Example usage in build logic
4 |
5 | This example shows how to create a reusable Gradle plugin that adds a `userBuildPerformanceMetrics` task to fetch and print build performance metrics for a specific user, using the Develocity API Kotlin client.
6 |
7 | ## Core files
8 |
9 | - [`build-logic/src/main/kotlin/build/logic/PerformanceMetricsTask.kt`](./build-logic/src/main/kotlin/build/logic/PerformanceMetricsTask.kt): Implements a custom Gradle task that fetches and prints build performance metrics of a given user.
10 | - [`build-logic/src/main/kotlin/build/logic/DevelocityApiService.kt`](./build-logic/src/main/kotlin/build/logic/DevelocityApiService.kt): Defines a shared build service containing the Develocity API client, which could be used by multiple tasks while ensuring a singleton client per build.
11 | - [`build-logic/performance-metrics-plugin.gradle.kts`](./build-logic/performance-metrics-plugin.gradle.kts): A plugin which registers the task where it's applied.
12 | - [`build.gradle.kts`](./build.gradle.kts): Applies `performance-metrics-plugin`, making the task available for this build.
13 |
14 | ## Usage
15 |
16 | Run:
17 |
18 | ```sh
19 | ./gradlew userBuildPerformanceMetrics [--user=foo] --period=[-1d|-7d|-14d|-30d|...]
20 | ```
21 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("build.logic.performance-metrics-plugin")
3 | }
4 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | dependencies {
6 | implementation("com.gabrielfeo:develocity-api-kotlin:2025.1.1")
7 | implementation("org.apache.commons:commons-math3:3.6.1")
8 | }
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/buildSrc/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "dak-example-gradle-task-build-src"
2 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/buildSrc/src/main/kotlin/build/logic/DevelocityApiService.kt:
--------------------------------------------------------------------------------
1 | package build.logic
2 |
3 | import com.gabrielfeo.develocity.api.Config
4 | import com.gabrielfeo.develocity.api.DevelocityApi
5 | import org.gradle.api.services.BuildService
6 | import org.gradle.api.services.BuildServiceParameters
7 | import okhttp3.OkHttpClient
8 |
9 | abstract class DevelocityApiService
10 | : DevelocityApi by DevelocityApi.newInstance(),
11 | BuildService,
12 | AutoCloseable {
13 |
14 | override fun close() {
15 | shutdown()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/buildSrc/src/main/kotlin/build/logic/PerformanceMetricsTask.kt:
--------------------------------------------------------------------------------
1 | package build.logic
2 |
3 | import com.gabrielfeo.develocity.api.DevelocityApi
4 | import kotlinx.coroutines.runBlocking
5 | import com.gabrielfeo.develocity.api.model.BuildModelName
6 | import com.gabrielfeo.develocity.api.model.Build
7 | import com.gabrielfeo.develocity.api.model.GradleBuildCachePerformance
8 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics
9 | import org.gradle.api.DefaultTask
10 | import org.gradle.api.provider.Property
11 | import org.gradle.api.tasks.Input
12 | import org.gradle.api.tasks.Optional
13 | import org.gradle.api.tasks.TaskAction
14 | import org.gradle.api.tasks.options.Option
15 | import org.gradle.api.services.ServiceReference
16 | import java.time.Duration
17 |
18 |
19 | abstract class PerformanceMetricsTask(
20 |
21 | ) : DefaultTask() {
22 |
23 | @get:Optional
24 | @get:Input
25 | @get:Option(
26 | option = "user",
27 | description = "The user to query builds for. Defaults to the current OS username."
28 | )
29 | abstract val user: Property
30 |
31 | @get:Optional
32 | @get:Input
33 | @get:Option(
34 | option = "period",
35 | description = "The period to query builds for (e.g. -14d, -30d, etc). Default: -14d."
36 | )
37 | abstract val period: Property
38 |
39 | @get:ServiceReference
40 | abstract val api: Property
41 |
42 | @TaskAction
43 | fun run() {
44 | val user = user.getOrElse(System.getProperty("user.name"))
45 | val startTime = period.getOrElse("-14d")
46 | val metrics = runBlocking {
47 | getUserBuildsPerformanceMetrics(api.get(), user, startTime)
48 | }
49 | logger.quiet(metrics)
50 | }
51 |
52 | suspend fun getUserBuildsPerformanceMetrics(
53 | api: DevelocityApi,
54 | user: String,
55 | startTime: String,
56 | ): String {
57 | val query = """user:"$user" buildStartTime>$startTime"""
58 | val buildsPerformanceData = fetchBuildsPerformanceData(api, query)
59 | val serializationFactors = buildsPerformanceData
60 | .map { it.serializationFactor }
61 | .let { DescriptiveStatistics(it.toDoubleArray()) }
62 | val avoidanceSavings = buildsPerformanceData
63 | .map { it.workUnitAvoidanceSavingsSummary.ratio }
64 | .let { DescriptiveStatistics(it.toDoubleArray()) }
65 | val heading = "Build performance overview for $user since $startTime (powered by Develocity®)"
66 | return """
67 | |
68 | |${"\u001B[1;36m"}$heading${"\u001B[0m"}
69 | | ▶︎ Serialization factor: %.1fx
70 | | (Gradle's parallel execution)
71 | | ⏩︎ Avoidance savings: %.1f%% (mean) ~ %.1f%% (p95)
72 | | (Gradle and Develocity's mechanisms, incl. incremental build and remote cache)
73 | """.trimMargin().format(
74 | serializationFactors.mean,
75 | avoidanceSavings.mean,
76 | avoidanceSavings.getPercentile(95.0),
77 | )
78 | }
79 |
80 | private suspend fun fetchBuildsPerformanceData(
81 | api: DevelocityApi,
82 | query: String,
83 | ): List {
84 | return api.buildsApi.getBuilds(
85 | fromInstant = 0,
86 | query = query,
87 | models = listOf(BuildModelName.gradleBuildCachePerformance),
88 | ).mapNotNull { build ->
89 | build.models?.gradleBuildCachePerformance?.model
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/buildSrc/src/main/kotlin/build/logic/performance-metrics-plugin.gradle.kts:
--------------------------------------------------------------------------------
1 | package build.logic
2 |
3 | gradle.sharedServices.registerIfAbsent("develocityApiService", DevelocityApiService::class)
4 |
5 | tasks.register("userBuildPerformanceMetrics") {
6 | group = "Develocity"
7 | description = "Retrieves performance metrics for the user's builds from Develocity API."
8 | }
9 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/examples/example-gradle-task/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/examples/example-gradle-task/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/examples/example-gradle-task/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "dak-example-gradle-task-main-build"
2 |
--------------------------------------------------------------------------------
/examples/example-notebooks/Logging.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "metadata": {},
5 | "cell_type": "code",
6 | "outputs": [],
7 | "execution_count": null,
8 | "source": [
9 | "%useLatestDescriptors\n",
10 | "%use develocity-api-kotlin(version=2025.1.1)\n",
11 | "%use coroutines(v=1.7.1)\n",
12 | "\n",
13 | "val api = DevelocityApi.newInstance(config = Config(cacheConfig = Config.CacheConfig(cacheEnabled = false)))"
14 | ]
15 | },
16 | {
17 | "metadata": {},
18 | "cell_type": "code",
19 | "outputs": [],
20 | "execution_count": null,
21 | "source": [
22 | "%logLevel debug\n",
23 | "\n",
24 | "runBlocking {\n",
25 | " api.buildsApi.getBuildsFlow(\n",
26 | " fromInstant = 0,\n",
27 | " query = \"\"\"buildStartTime>-7d buildTool:gradle\"\"\",\n",
28 | " ).last()\n",
29 | "}"
30 | ]
31 | },
32 | {
33 | "metadata": {},
34 | "cell_type": "markdown",
35 | "source": [
36 | "Expect logs such as these in kernel logs. See [docs/Logging.md][1] for more details.\n",
37 | "\n",
38 | "##### Cache hits\n",
39 | "\n",
40 | "```\n",
41 | "3764 [Execution of code '%logLevel debug...'] DEBUG c.gabrielfeo.develocity.api.Cache - HTTP cache dir: /Users/gfeo/.develocity-api-kotlin-cache (max 1000000000B)\n",
42 | "4053 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.gabrielfeo.develocity.api.Cache - Cache hit: https://ge.solutions-team.gradle.com/api/builds?fromInstant=0&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false\n",
43 | "4072 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.gabrielfeo.develocity.api.Cache - Cache hit: https://ge.solutions-team.gradle.com/api/builds?fromBuild=qcku2w347d5dy&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false\n",
44 | "4072 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.gabrielfeo.develocity.api.OkHttpClient - Cache hit: https://ge.solutions-team.gradle.com/api/builds?fromBuild=qcku2w347d5dy&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false\n",
45 | "```\n",
46 | "\n",
47 | "##### Cache misses\n",
48 | "\n",
49 | "```\n",
50 | "3447 [Execution of code '%logLevel debug...'] DEBUG c.gabrielfeo.develocity.api.Cache - HTTP cache is disabled\n",
51 | "3853 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.g.develocity.api.OkHttpClient - --> GET https://ge.solutions-team.gradle.com/api/builds?fromInstant=0&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false h2\n",
52 | "4208 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.g.develocity.api.OkHttpClient - <-- 200 https://ge.solutions-team.gradle.com/api/builds?fromInstant=0&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false (355ms, unknown-length body)\n",
53 | "4230 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.g.develocity.api.OkHttpClient - --> GET https://ge.solutions-team.gradle.com/api/builds?fromBuild=qcku2w347d5dy&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false h2\n",
54 | "4424 [OkHttp https://ge.solutions-team.gradle.com/...] DEBUG c.g.develocity.api.OkHttpClient - <-- 200 https://ge.solutions-team.gradle.com/api/builds?fromBuild=qcku2w347d5dy&maxBuilds=1000&query=buildStartTime%3E-7d%20buildTool%3Agradle&allModels=false (193ms, unknown-length body)\n",
55 | "```\n",
56 | "\n",
57 | "[1]: https://github.com/gabrielfeo/develocity-api-kotlin/blob/main/docs/Logging.md"
58 | ]
59 | }
60 | ],
61 | "metadata": {
62 | "kernelspec": {
63 | "display_name": "Kotlin",
64 | "language": "kotlin",
65 | "name": "kotlin"
66 | },
67 | "language_info": {
68 | "codemirror_mode": "text/x-kotlin",
69 | "file_extension": ".kt",
70 | "mimetype": "text/x-kotlin",
71 | "name": "kotlin",
72 | "nbconvert_exporter": "",
73 | "pygments_lexer": "kotlin",
74 | "version": "2.2.20-Beta2"
75 | }
76 | },
77 | "nbformat": 4,
78 | "nbformat_minor": 0
79 | }
80 |
--------------------------------------------------------------------------------
/examples/example-notebooks/requirements.txt:
--------------------------------------------------------------------------------
1 | jupyterlab==4.4.8
2 | kotlin-jupyter-kernel==0.15.0.598
3 |
--------------------------------------------------------------------------------
/examples/example-project/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm") version "2.2.10"
3 | application
4 | }
5 |
6 | application {
7 | mainClass = "com.gabrielfeo.develocity.api.example.MainKt"
8 | }
9 |
10 | dependencies {
11 | implementation("com.gabrielfeo:develocity-api-kotlin:2025.1.1")
12 | }
13 |
14 | repositories {
15 | mavenCentral()
16 | }
17 |
--------------------------------------------------------------------------------
/examples/example-project/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/examples/example-project/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/examples/example-project/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/examples/example-project/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/examples/example-project/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "dak-example-gradle-project"
2 |
--------------------------------------------------------------------------------
/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/Main.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example
2 |
3 | import com.gabrielfeo.develocity.api.Config
4 | import com.gabrielfeo.develocity.api.DevelocityApi
5 | import com.gabrielfeo.develocity.api.example.analysis.mostFrequentBuilds
6 | import okhttp3.OkHttpClient
7 |
8 | /*
9 | * Example main that runs all API analysis at once. In projects, you can share an
10 | * OkHttpClient.Builder between DevelocityApi and your own project classes, in order to
11 | * save resources.
12 | */
13 |
14 | val clientBuilder = OkHttpClient.Builder()
15 |
16 | suspend fun main(args: Array) {
17 | val newConfig = Config(
18 | clientBuilder = clientBuilder,
19 | )
20 | val develocityApi = DevelocityApi.newInstance(newConfig)
21 | val query = args.getOrElse(0) { "buildStartTime>-1d buildTool:gradle" }
22 | runAllAnalysis(develocityApi, query)
23 | develocityApi.shutdown()
24 | }
25 |
26 | private suspend fun runAllAnalysis(develocityApi: DevelocityApi, query: String) {
27 | mostFrequentBuilds(api = develocityApi.buildsApi, query)
28 | }
29 |
--------------------------------------------------------------------------------
/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/analysis/MostFrequentBuilds.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example.analysis
2 |
3 | import com.gabrielfeo.develocity.api.*
4 | import com.gabrielfeo.develocity.api.extension.*
5 | import com.gabrielfeo.develocity.api.model.*
6 | import kotlinx.coroutines.flow.*
7 | import java.util.LinkedList
8 |
9 | /**
10 | * See what builds are most commonly invoked by developers, e.g. 'clean assemble',
11 | * 'test' or 'check'. You can set up the URL and a token for your Gradle
12 | * Enterprise instance and run this notebook as-is for your own project. This is a
13 | * simple example of something you can do with the API. It could bring insights,
14 | * for example:
15 | *
16 | * - "Our developers frequently clean together with assemble. We should ask them why,
17 | * because they shouldn't have to. Just an old habit from Maven or are they working
18 | * around a build issue we don't know about?"
19 | *
20 | * - "Some are doing check builds locally, which we set up to trigger our notably slow
21 | * legacy tests. We should suggest they run test instead, leaving check for CI to run."
22 | */
23 | suspend fun mostFrequentBuilds(
24 | api: BuildsApi,
25 | query: String,
26 | ) {
27 | // Fetch builds from the API
28 | val builds: List = api.getBuildsFlow(
29 | fromInstant = 0,
30 | query = query,
31 | models = listOf(BuildModelName.gradleAttributes),
32 | ).map {
33 | it.models!!.gradleAttributes!!.model!!
34 | }.toList(LinkedList())
35 |
36 | // Process builds and count how many times each was invoked
37 | check(builds.isNotEmpty()) { "No builds found. Adjust query and try again." }
38 | val buildCounts = builds.groupBy { build ->
39 | val tasks = build.requestedTasks.joinToString(" ").trim(':')
40 | tasks.ifBlank { "IDE sync" }
41 | }.mapValues { (_, builds) ->
42 | builds.size
43 | }.entries.sortedByDescending { (_, count) ->
44 | count
45 | }
46 |
47 | // Print the top 5 as a pretty table
48 | val table = buildCounts.take(5).joinToString("\n") { (tasks, count) ->
49 | "${tasks.padEnd(100)} | $count"
50 | }
51 | println(
52 | """
53 | |---------------------
54 | |Most frequent builds:
55 | |
56 | |$table
57 | """.trimMargin()
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/examples/example-scripts/example-script.main.kts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env kotlinc -script
2 |
3 | /*
4 | * See what builds are most commonly invoked by developers, e.g. 'clean assemble',
5 | * 'test' or 'check'. You can set up the URL and a token for your Gradle
6 | * Enterprise instance and run this notebook as-is for your own project. This is a
7 | * simple example of something you can do with the API. It could bring insights,
8 | * for example:
9 | *
10 | * - "Our developers frequently clean together with assemble. We should ask them why,
11 | * because they shouldn't have to. Just an old habit from Maven or are they working
12 | * around a build issue we don't know about?"
13 | *
14 | * - "Some are doing check builds locally, which we set up to trigger our notably slow
15 | * legacy tests. We should suggest they run test instead, leaving check for CI to run."
16 | *
17 | * Run this with at least 1GB of heap to accommodate the fetched data: JAVA_OPTS=-Xmx1g
18 | */
19 |
20 | @file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2025.1.1")
21 |
22 | import com.gabrielfeo.develocity.api.*
23 | import com.gabrielfeo.develocity.api.model.*
24 | import com.gabrielfeo.develocity.api.extension.*
25 | import kotlinx.coroutines.*
26 | import kotlinx.coroutines.flow.*
27 | import java.util.LinkedList
28 |
29 | // Parameters
30 | val query = args.getOrElse(0) { "buildStartTime>-7d buildTool:gradle" }
31 |
32 | // Fetch builds from the API
33 | val api = DevelocityApi.newInstance()
34 | val builds: List = runBlocking {
35 | api.buildsApi.getBuildsFlow(
36 | fromInstant = 0,
37 | query = query,
38 | models = listOf(BuildModelName.gradleAttributes),
39 | ).map {
40 | it.models!!.gradleAttributes!!.model!!
41 | }.toList(LinkedList())
42 | }
43 |
44 | // Process builds and count how many times each was invoked
45 | check(builds.isNotEmpty()) { "No builds found. Adjust query and try again." }
46 | val buildCounts = builds.groupBy { build ->
47 | val tasks = build.requestedTasks.joinToString(" ").trim(':')
48 | tasks.ifBlank { "IDE sync" }
49 | }.mapValues { (_, builds) ->
50 | builds.size
51 | }.entries.sortedByDescending { (_, count) ->
52 | count
53 | }
54 |
55 | // Print the top 5 as a pretty table
56 | val table = buildCounts.take(5).joinToString("\n") { (tasks, count) ->
57 | "${tasks.padEnd(100)} | $count"
58 | }
59 | println(
60 | """
61 | |---------------------
62 | |Most frequent builds:
63 | |
64 | |$table
65 | """.trimMargin()
66 | )
67 |
68 | // Shutdown to end background threads and allow script to exit earlier (see README)
69 | api.shutdown()
70 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | group=com.gabrielfeo
2 | artifact=develocity-api-kotlin
3 | version=2025.2.0
4 | develocity.version=2025.2
5 | repo.url=https://github.com/gabrielfeo/develocity-api-kotlin
6 | org.gradle.parallel=true
7 | org.gradle.jvmargs=-Xmx5g
8 | org.gradle.caching=true
9 | org.gradle.configuration-cache=true
10 | org.gradle.configuration-cache.parallel=true
11 | # Becomes default in Gradle 9.0
12 | org.gradle.kotlin.dsl.skipMetadataVersionCheck=false
13 | # Explicitly added at different versions, due to Kotlin/kotlin-jupyter#462
14 | kotlin.jupyter.add.api=false
15 | kotlin.jupyter.add.scanner=false
16 | kotlin.jupyter.add.testkit=false
17 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
18 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
19 |
--------------------------------------------------------------------------------
/gradle/gradle-daemon-jvm.properties:
--------------------------------------------------------------------------------
1 | #This file is generated by updateDaemonJvm
2 | toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6fb1b1f1c4016d56e240f38e31b0615f/redirect
3 | toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/bebd8b980a4cdad3dd6f9d1c3f194c08/redirect
4 | toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6fb1b1f1c4016d56e240f38e31b0615f/redirect
5 | toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/bebd8b980a4cdad3dd6f9d1c3f194c08/redirect
6 | toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/35ea6c5d874634e79bd0dcf2704df0e8/redirect
7 | toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/bdb01d23d4d09037d47c81bbe1b947e1/redirect
8 | toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/6fb1b1f1c4016d56e240f38e31b0615f/redirect
9 | toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/bebd8b980a4cdad3dd6f9d1c3f194c08/redirect
10 | toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/fa5701aaf7265fd19b6f642c567a4fbb/redirect
11 | toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/74794a7f60a73cdd790f439c37c7a608/redirect
12 | toolchainVendor=BELLSOFT
13 | toolchainVersion=24
14 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | kotlin = "2.2.10"
3 | kotlin-stdlib = "2.0.0"
4 | dokka = "2.0.0"
5 | openapi-generator = "7.16.0"
6 | jupyter = "0.15.0-650"
7 | okio = "3.16.1"
8 | moshi = "1.15.2"
9 | okhttp = "4.12.0"
10 | retrofit = "3.0.0"
11 | kotlin-coroutines = "1.10.2"
12 | kotlin-binary-compatibility-validator = "0.18.1"
13 | slf4j = "2.0.17"
14 | logback = "1.5.19"
15 | guava = "33.5.0-jre"
16 | maven-publish-plugin = "0.34.0"
17 |
18 | [libraries]
19 | junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params" }
20 | okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
21 | okio-fakeFileSystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okio" }
22 | moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
23 | moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
24 | okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
25 | okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
26 | okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
27 | retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
28 | retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
29 | retrofit-converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
30 | kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
31 | kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
32 | kotlin-jupyter-api = { module = "org.jetbrains.kotlinx:kotlin-jupyter-api", version.ref = "jupyter" }
33 | kotlin-jupyter-testkit = { module = "org.jetbrains.kotlinx:kotlin-jupyter-test-kit", version.ref = "jupyter" }
34 | kotlin-binary-compatibility-validator-plugin = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "kotlin-binary-compatibility-validator" }
35 | kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
36 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-stdlib" }
37 | dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
38 | openapi-generator-plugin = { module = "org.openapitools:openapi-generator-gradle-plugin", version.ref = "openapi-generator" }
39 | slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
40 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
41 | logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
42 | guava = { module = "com.google.guava:guava", version.ref = "guava" }
43 | vanniktech-mavenPublishPlugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "maven-publish-plugin" }
44 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrielfeo/develocity-api-kotlin/c50cd98e0f9ac69f1b1386c92ea92c1b6537614a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 |
118 |
119 | # Determine the Java command to use to start the JVM.
120 | if [ -n "$JAVA_HOME" ] ; then
121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
122 | # IBM's JDK on AIX uses strange locations for the executables
123 | JAVACMD=$JAVA_HOME/jre/sh/java
124 | else
125 | JAVACMD=$JAVA_HOME/bin/java
126 | fi
127 | if [ ! -x "$JAVACMD" ] ; then
128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
129 |
130 | Please set the JAVA_HOME variable in your environment to match the
131 | location of your Java installation."
132 | fi
133 | else
134 | JAVACMD=java
135 | if ! command -v java >/dev/null 2>&1
136 | then
137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
138 |
139 | Please set the JAVA_HOME variable in your environment to match the
140 | location of your Java installation."
141 | fi
142 | fi
143 |
144 | # Increase the maximum file descriptors if we can.
145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
146 | case $MAX_FD in #(
147 | max*)
148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
149 | # shellcheck disable=SC2039,SC3045
150 | MAX_FD=$( ulimit -H -n ) ||
151 | warn "Could not query maximum file descriptor limit"
152 | esac
153 | case $MAX_FD in #(
154 | '' | soft) :;; #(
155 | *)
156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
157 | # shellcheck disable=SC2039,SC3045
158 | ulimit -n "$MAX_FD" ||
159 | warn "Could not set maximum file descriptor limit to $MAX_FD"
160 | esac
161 | fi
162 |
163 | # Collect all arguments for the java command, stacking in reverse order:
164 | # * args from the command line
165 | # * the main class name
166 | # * -classpath
167 | # * -D...appname settings
168 | # * --module-path (only if needed)
169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
170 |
171 | # For Cygwin or MSYS, switch paths to Windows format before running java
172 | if "$cygwin" || "$msys" ; then
173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 |
74 |
75 | @rem Execute Gradle
76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
77 |
78 | :end
79 | @rem End local scope for the variables with windows NT shell
80 | if %ERRORLEVEL% equ 0 goto mainEnd
81 |
82 | :fail
83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
84 | rem the _cmd.exe /c_ return code!
85 | set EXIT_CODE=%ERRORLEVEL%
86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
88 | exit /b %EXIT_CODE%
89 |
90 | :mainEnd
91 | if "%OS%"=="Windows_NT" endlocal
92 |
93 | :omega
94 |
--------------------------------------------------------------------------------
/library/.openapi-generator-ignore:
--------------------------------------------------------------------------------
1 | build/generated-api/*
2 | build/generated-api/.*
3 | build/generated-api/.github/*
4 | build/generated-api/api/*
5 | build/generated-api/gradle/**/*
6 | build/generated-api/docs/*
7 | build/generated-api/src/main/AndroidManifest.xml
8 | build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/internal/infrastructure/ApiClient.kt
9 | build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/GradleEnterpriseApi.kt
10 | build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt
11 | build/generated-api/src/test/**/*
12 |
--------------------------------------------------------------------------------
/library/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 | import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
4 |
5 | plugins {
6 | id("com.gabrielfeo.published-kotlin-jvm-library")
7 | id("com.gabrielfeo.develocity-api-code-generation")
8 | id("com.gabrielfeo.integration-test-suite")
9 | id("com.gabrielfeo.examples-test-suite")
10 | id("com.gabrielfeo.test-fixtures")
11 | }
12 |
13 | // Order matters as this library is used as a Kotlin Jupyter kernel dependency (see #440)
14 | dependencies {
15 | constraints {
16 | implementation(libs.okio)
17 | }
18 | // Set fixed version of stdlib for compatibility with the version of Kotlin
19 | // embedded in earlier versions of Gradle
20 | implementation(libs.kotlin.stdlib)
21 | api(libs.okhttp)
22 | implementation(libs.okhttp.logging.interceptor)
23 | api(libs.retrofit)
24 | implementation(libs.retrofit.converter.moshi)
25 | implementation(libs.retrofit.converter.scalars)
26 | api(libs.moshi)
27 | implementation(libs.moshi.kotlin)
28 | api(libs.kotlin.coroutines)
29 | implementation(libs.slf4j.api)
30 | compileOnly(libs.kotlin.jupyter.api)
31 | testImplementation(libs.okhttp.mockwebserver)
32 | testImplementation(libs.okio.fakeFileSystem)
33 | testImplementation(libs.okio)
34 | testImplementation(libs.kotlin.coroutines.test)
35 | testImplementation(libs.junit.jupiter.params)
36 | integrationTestImplementation(libs.kotlin.coroutines.test)
37 | integrationTestImplementation(libs.guava)
38 | integrationTestImplementation(libs.kotlin.jupyter.testkit)
39 | integrationTestImplementation(libs.logback.core)
40 | integrationTestImplementation(libs.logback.classic)
41 | integrationTestImplementation(libs.okhttp.mockwebserver)
42 | }
43 |
44 | val libraryPom = Action {
45 | name = "Develocity API Kotlin"
46 | description = "A library to use the Develocity API in Kotlin"
47 | val repoUrl = providers.gradleProperty("repo.url")
48 | url = repoUrl
49 | licenses {
50 | license {
51 | name = "MIT"
52 | url = "https://spdx.org/licenses/MIT.html"
53 | distribution = "repo"
54 | }
55 | }
56 | developers {
57 | developer {
58 | id = "gabrielfeo"
59 | name = "Gabriel Feo"
60 | email = "gabriel@gabrielfeo.com"
61 | }
62 | }
63 | scm {
64 | val basicUrl = repoUrl.map { it.substringAfter("://") }
65 | connection = basicUrl.map { "scm:git:git://$it.git" }
66 | developerConnection = basicUrl.map { "scm:git:ssh://$it.git" }
67 | url = basicUrl.map { "https://$it/" }
68 | }
69 | }
70 |
71 | publishing {
72 | publications {
73 | register("develocityApiKotlin") {
74 | artifactId = "develocity-api-kotlin"
75 | from(components["java"])
76 | pom(libraryPom)
77 | }
78 | // For occasional maven local publishing
79 | register("unsignedDevelocityApiKotlin") {
80 | artifactId = "develocity-api-kotlin"
81 | from(components["java"])
82 | pom(libraryPom)
83 | }
84 | register("unsignedSnapshotDevelocityApiKotlin") {
85 | artifactId = "develocity-api-kotlin"
86 | version = "SNAPSHOT"
87 | from(components["java"])
88 | pom(libraryPom)
89 | }
90 | register("relocation") {
91 | artifactId = "gradle-enterprise-api-kotlin"
92 | pom {
93 | libraryPom(this)
94 | distributionManagement {
95 | relocation {
96 | groupId = project.group.toString()
97 | artifactId = "develocity-api-kotlin"
98 | message = "artifactId has been changed. Part of the rename to Develocity."
99 | }
100 | }
101 | }
102 | }
103 | }
104 | }
105 |
106 | tasks.named("compileJava", JavaCompile::class) {
107 | sourceCompatibility = JavaVersion.VERSION_11.majorVersion
108 | targetCompatibility = JavaVersion.VERSION_11.majorVersion
109 | }
110 |
111 | tasks.processIntegrationTestResources {
112 | dependsOn(tasks.apiCheck)
113 | from(project.layout.projectDirectory.dir("api/library.api"))
114 | }
115 |
116 | tasks.named("compileKotlin", KotlinCompile::class) {
117 | compilerOptions {
118 | languageVersion = KotlinVersion.KOTLIN_1_8
119 | jvmTarget = JvmTarget.JVM_11
120 | }
121 | }
122 |
123 | tasks.withType().configureEach {
124 | systemProperty("junit.jupiter.execution.parallel.enabled", "true")
125 | systemProperty("junit.jupiter.execution.parallel.mode.default", "same_thread")
126 | val cleanupMode = System.getProperty("junit.jupiter.tempdir.cleanup.mode.default")
127 | ?: "always"
128 | systemProperty("junit.jupiter.tempdir.cleanup.mode.default", cleanupMode)
129 | }
130 |
131 | tasks.named("test") {
132 | environment = emptyMap()
133 | }
134 |
135 | tasks.named("integrationTest") {
136 | maxParallelForks = 2
137 | environment = emptyMap()
138 | }
139 |
140 | val publishUnsignedSnapshotDevelocityApiKotlinPublicationToMavenLocal by tasks.getting
141 |
142 | tasks.named("examplesTest") {
143 | maxParallelForks = 4
144 | inputs.files(files(publishUnsignedSnapshotDevelocityApiKotlinPublicationToMavenLocal))
145 | .withPropertyName("snapshotPublicationArtifacts")
146 | .withNormalizer(ClasspathNormalizer::class)
147 | providers.environmentVariablesPrefixedBy("DEVELOCITY_API_").get().forEach { (name, value) ->
148 | inputs.property("${name}.hashCode", value.hashCode())
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/JsonParser.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example
2 |
3 | import com.squareup.moshi.Moshi
4 | import okio.buffer
5 | import okio.source
6 | import java.nio.file.Path
7 |
8 | object JsonAdapter {
9 |
10 | private val jsonAdapter = Moshi.Builder().build().adapter(Map::class.java)
11 |
12 | fun fromJson(path: Path): Map<*, *>? =
13 | jsonAdapter.fromJson(path.source().buffer())
14 |
15 | fun toJson(map: Map<*, *>?): String =
16 | jsonAdapter.toJson(map)
17 |
18 | fun toPrettyJson(map: Map<*, *>?): String =
19 | jsonAdapter.indent(" ").toJson(map)
20 | }
21 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/Queries.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example
2 |
3 | object BuildStartTime {
4 | const val RECENT = "-10h"
5 | }
6 |
7 | object Queries {
8 | const val FAST = "buildStartTime>${BuildStartTime.RECENT} buildTool:gradle"
9 | }
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/Shell.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example
2 |
3 | import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
4 | import kotlinx.coroutines.async
5 | import kotlinx.coroutines.launch
6 | import kotlinx.coroutines.runBlocking
7 | import java.nio.file.Path
8 |
9 | fun runInShell(workDir: Path, vararg command: String) =
10 | runInShell(workDir, command.joinToString(" "))
11 |
12 | fun runInShell(workDir: Path, command: String): OutputStreams {
13 | val process = ProcessBuilder("bash", "-c", command).apply {
14 | directory(workDir.toFile())
15 | // Ensure the test's build toolchain is used (not whatever JAVA_HOME is set to)
16 | environment()["JAVA_HOME"] = System.getProperty("java.home")
17 | }.start()
18 | val streams = runBlocking {
19 | OutputStreams(
20 | stderr = async(start = UNDISPATCHED) {
21 | process.errorStream.bufferedReader().lineSequence()
22 | .onEach(System.err::println)
23 | .joinToString("\n")
24 | }.await(),
25 | stdout = async(start = UNDISPATCHED) {
26 | process.inputStream.bufferedReader().lineSequence()
27 | .onEach(System.out::println)
28 | .joinToString("\n")
29 | }.await(),
30 | )
31 | }
32 | val exitCode = process.waitFor()
33 | check(exitCode == 0) { "Exit code '$exitCode' for command: $command" }
34 | return streams
35 | }
36 |
37 | class OutputStreams(val stdout: String, val stderr: String)
38 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/gradle/ExampleGradleTaskTest.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example.gradle
2 |
3 | import org.junit.jupiter.api.Assertions.assertFalse
4 | import org.junit.jupiter.api.Assertions.assertTrue
5 | import org.junit.jupiter.api.Test
6 | import org.junit.jupiter.api.io.TempDir
7 | import java.nio.file.Path
8 | import com.gabrielfeo.develocity.api.copyFromResources
9 | import com.gabrielfeo.develocity.api.example.BuildStartTime
10 | import com.gabrielfeo.develocity.api.example.runInShell
11 | import org.junit.jupiter.api.parallel.Execution
12 | import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
13 | import kotlin.io.path.div
14 |
15 | @Execution(CONCURRENT)
16 | class ExampleGradleTaskTest {
17 |
18 | class TestPaths(val rootDir: Path) {
19 | val initScriptsDir = rootDir
20 | val projectDir = rootDir / "examples/example-gradle-task"
21 | }
22 |
23 | @Test
24 | fun ensureRunBuildUsesSnapshotDependency(@TempDir tempDir: Path) = with(setup(tempDir)) {
25 | val dependencies = runBuild(":buildSrc:dependencies --configuration runtimeClasspath").stdout
26 | val libraryMatches = dependencies.lines().filter { "develocity-api-kotlin" in it }
27 | assertTrue(libraryMatches.isNotEmpty())
28 | assertTrue(libraryMatches.all { "-> SNAPSHOT" in it && "FAILED" !in it }) {
29 | "Expected forced SNAPSHOT versions, but found [${libraryMatches.joinToString(", ")}]"
30 | }
31 | }
32 |
33 | private fun setup(tempDir: Path): TestPaths {
34 | copyFromResources("/examples", tempDir)
35 | copyFromResources("/${ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}", tempDir)
36 | copyFromResources("/${ResourceInitScripts.REQUIRE_JAVA_11_COMPATIBILITY}", tempDir)
37 | return TestPaths(tempDir)
38 | }
39 |
40 | @Test
41 | fun testBuildPerformanceMetricsTask(@TempDir tempDir: Path) = with(setup(tempDir)) {
42 | val args = "--user runner --period=${BuildStartTime.RECENT}"
43 | val output = runBuild("userBuildPerformanceMetrics $args").stdout
44 | assertPerformanceMetricsOutput(output, user = "runner", period = BuildStartTime.RECENT)
45 | }
46 |
47 | @Test
48 | fun testJavaVersionCompatibility(@TempDir tempDir: Path) = with(setup(tempDir)) {
49 | val initScript = initScriptsDir / ResourceInitScripts.REQUIRE_JAVA_11_COMPATIBILITY
50 | val output = runBuild("-p buildSrc :generateExternalPluginSpecBuilders -I '$initScript'").stdout
51 | assertFalse(Regex("""FAILED|Could not resolve|No matching variant""").containsMatchIn(output))
52 | }
53 |
54 | private fun TestPaths.runBuild(gradleArgs: String) =
55 | runInShell(
56 | projectDir,
57 | "./gradlew --stacktrace --no-daemon",
58 | "-I ${initScriptsDir / ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}",
59 | gradleArgs,
60 | )
61 |
62 | @Suppress("SameParameterValue")
63 | private fun assertPerformanceMetricsOutput(
64 | output: String,
65 | user: String,
66 | period: String,
67 | ) {
68 | val expectedHeading = "Build performance overview for $user since $period (powered by Develocity®)"
69 | assertTrue(output.contains(expectedHeading))
70 | assertTrue(output.contains("▶︎ Serialization factor:"))
71 | assertTrue(output.contains("⏩︎ Avoidance savings:"))
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/gradle/ExampleProjectTest.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example.gradle
2 |
3 | import com.gabrielfeo.develocity.api.example.Queries
4 | import org.junit.jupiter.api.Assertions.assertTrue
5 | import org.junit.jupiter.api.Test
6 | import org.junit.jupiter.api.io.TempDir
7 | import java.nio.file.Path
8 | import kotlin.io.path.div
9 | import com.gabrielfeo.develocity.api.copyFromResources
10 | import com.gabrielfeo.develocity.api.example.runInShell
11 | import org.junit.jupiter.api.parallel.Execution
12 | import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
13 |
14 | @Execution(CONCURRENT)
15 | class ExampleProjectTest {
16 |
17 | class TestPaths(val rootDir: Path) {
18 | val projectDir = rootDir / "examples/example-project"
19 | val initScriptPath = rootDir / ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY
20 | }
21 |
22 | @Test
23 | fun ensureRunBuildUsesSnapshotDependencies(@TempDir tempDir: Path) = with(setup(tempDir)) {
24 | val dependencies = runBuild("dependencies --configuration runtimeClasspath").stdout
25 | val libraryMatches = dependencies.lines().filter { "develocity-api-kotlin" in it }
26 | assertTrue(libraryMatches.isNotEmpty())
27 | assertTrue(libraryMatches.all { "-> SNAPSHOT" in it && "FAILED" !in it }) {
28 | "Expected forced SNAPSHOT versions, but found [${libraryMatches.joinToString(", ")}]"
29 | }
30 | }
31 |
32 | private fun setup(tempDir: Path): TestPaths {
33 | copyFromResources("/examples", tempDir)
34 | copyFromResources("/${ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}", tempDir)
35 | return TestPaths(tempDir)
36 | }
37 |
38 | @Test
39 | fun testExampleProject(@TempDir tempDir: Path) = with(setup(tempDir)) {
40 | val output = runBuild("""run --args '"${Queries.FAST}"'""").stdout
41 | val tableRegex = Regex("""(?ms)^[-]+\nMost frequent builds:\n\s*\n(.+\|\s*\d+\s*\n?)+""")
42 | assertTrue(tableRegex.containsMatchIn(output)) {
43 | "Expected match for pattern '$tableRegex' in output '$output'"
44 | }
45 | }
46 |
47 | private fun TestPaths.runBuild(gradleArgs: String) =
48 | runInShell(
49 | projectDir,
50 | "./gradlew --stacktrace --no-daemon",
51 | "-I $initScriptPath",
52 | gradleArgs,
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/gradle/ResourceInitScripts.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example.gradle
2 |
3 | object ResourceInitScripts {
4 | const val FORCE_SNAPSHOT_LIBRARY = "force-snapshot-library.init.gradle.kts"
5 | const val REQUIRE_JAVA_11_COMPATIBILITY = "require-java-11-compatibility.init.gradle"
6 | }
7 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/notebook/Jupyter.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example.notebook
2 |
3 | import com.gabrielfeo.develocity.api.copyFromResources
4 | import com.gabrielfeo.develocity.api.example.OutputStreams
5 | import com.gabrielfeo.develocity.api.example.runInShell
6 | import java.nio.file.Path
7 | import kotlin.io.path.div
8 | import kotlin.io.path.nameWithoutExtension
9 | import kotlin.io.path.notExists
10 |
11 | class Jupyter(
12 | val workDir: Path,
13 | val venv: Path,
14 | ) {
15 | class Execution(
16 | val outputStreams: OutputStreams,
17 | val outputNotebook: Path,
18 | )
19 |
20 | fun executeNotebook(path: Path): Execution {
21 | val outputPath = path.parent / "${path.nameWithoutExtension}-executed.ipynb"
22 | val outputStreams = runInShell(
23 | workDir,
24 | "source '${venv / "bin/activate"}' &&",
25 | "jupyter nbconvert '$path'",
26 | "--to ipynb",
27 | "--execute",
28 | "--output='$outputPath'",
29 | )
30 | return Execution(outputStreams, outputPath)
31 | }
32 |
33 | fun replacePattern(
34 | path: Path,
35 | pattern: Regex,
36 | replacement: String,
37 | ): Path {
38 | if ((workDir / "preprocessors.py").notExists()) {
39 | copyFromResources("/preprocessors.py", workDir)
40 | }
41 | val outputPath = path.parent / "${path.nameWithoutExtension}-replaced.ipynb"
42 | runInShell(
43 | workDir,
44 | "source '${venv / "bin/activate"}' &&",
45 | "jupyter nbconvert '$path'",
46 | "--to ipynb",
47 | "--output='$outputPath'",
48 | "--NotebookExporter.preprocessors=preprocessors.ReplacePatternPreprocessor",
49 | "--ReplacePatternPreprocessor.pattern='$pattern'",
50 | "--ReplacePatternPreprocessor.replacement='$replacement'",
51 | )
52 | return outputPath
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/notebook/NotebookJson.kt:
--------------------------------------------------------------------------------
1 | package com.gabrielfeo.develocity.api.example.notebook
2 |
3 | class NotebookJson(
4 | val properties: Map,
5 | ) {
6 |
7 | val cells = properties["cells"] as List