diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..474f37b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.kt, *.kts] +indent_style = space +indent_size = 4 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 333f7f1d..d472299b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,10 +20,6 @@ updates: retrofit: patterns: - "com.squareup.retrofit2:*" - - package-ecosystem: "gradle" - directory: "/examples/example-project/" - schedule: - interval: "daily" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index af1a79b5..f7660f2b 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -1 +1,8 @@ -GitPython==3.1.42 +certifi==2024.2.2 +charset-normalizer==3.3.2 +gitdb==4.0.11 +GitPython==3.1.43 +idna==3.6 +requests==2.31.0 +smmap==5.0.1 +urllib3==2.2.1 diff --git a/.github/scripts/test_resources/README.md b/.github/scripts/test_resources/README.md index e78a5d40..7d0e0794 100644 --- a/.github/scripts/test_resources/README.md +++ b/.github/scripts/test_resources/README.md @@ -3,15 +3,15 @@ [![Maven Central](https://img.shields.io/badge/Maven%20Central-0.17.0-blue)][14] ```kotlin -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:0.17.0") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:0.17.0") ``` ```kotlin -implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:0.17.0") +implementation("com.gabrielfeo:develocity-api-kotlin:0.17.0") ``` ``` -%use gradle-enterprise-api-kotlin(version=0.17.0) +%use develocity-api-kotlin(version=0.17.0) ``` -[14]: https://central.sonatype.com/artifact/com.gabrielfeo/gradle-enterprise-api-kotlin/0.17.0 +[14]: https://central.sonatype.com/artifact/com.gabrielfeo/develocity-api-kotlin/0.17.0 diff --git a/.github/scripts/test_resources/api_manual.html b/.github/scripts/test_resources/api_manual.html index 8a2d9f59..68aac1bc 100644 --- a/.github/scripts/test_resources/api_manual.html +++ b/.github/scripts/test_resources/api_manual.html @@ -1,366 +1,3472 @@ - - - - - - - Gradle Enterprise API User Manual | Gradle Enterprise Docs - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - -
-
-
-
-

The Gradle Enterprise API allows programmatic interaction with various aspects of Gradle Enterprise, from configuration to inspecting build data.

-
-
-
-
-

Fundamentals

-
-
-

The Gradle Enterprise API is a REST-style API using JSON as the data format.

-
-
-

OpenAPI

-
-

The API is defined using the OpenAPI standard, which is a declarative specification that allows tools and libraries to generate client code. The specification can be found in the Reference section.

-
-
-
-

Versioning and compatibility

-
-

The API is generally backwards compatible.

-
-
-

New functionality (e.g. endpoints, response attributes) may be added in new major versions (i.e. 2022.1, 2022.2).

-
-
-

Breaking changes will occur infrequently if at all, and only after a reasonable deprecation period. Deprecations will be communicated by:

-
-
-
    -
  • The release notes for the major Gradle Enterprise version introducing the deprecation.

  • -
  • The deprecated annotation within the OpenAPI specification.

  • -
  • The Deprecated HTTP response header (indicating when the operation was deprecated).

  • -
  • The Sunset HTTP response header (indicating when the operation will be removed).

  • -
-
-
-

API documentation may be refined in patch releases, along with implementation defects that cause deviation from the specification.

-
-
+ $(window).scroll(adjustTOC).click(adjustTOC); + $(adjustTOC); + + + + -
-
-

Getting started

-
-
-

Access control

-
-

All requests must provide a Gradle Enterprise access key as “bearer token”.

-
-
-

To create an access key:

-
-
-
    -
  1. Sign in to Gradle Enterprise.

  2. -
  3. Access "My settings" from the user menu in the top right-hand corner of the page.

  4. -
  5. Access "Access keys" from the left-hand menu.

  6. -
  7. Click "Generate" on the right-hand side and copy the generated access key.

  8. -
-
-
-

Different requests require different user permissions, as describe via the API specification.

-
-
-

To view the permissions assigned to you:

-
-
-
    -
  1. Sign in to Gradle Enterprise.

  2. -
  3. Access "My settings" from the user menu in the top right-hand corner of the page.

  4. -
  5. Access "Permissions" from the left-hand menu.

  6. -
  7. Check if you have the required permissions.

  8. -
-
-
-

If you do not have the required permission, contact your Gradle Enterprise administrator.

-
-
-
-

Making a request

-
-

The following example demonstrates using cURL to make a request to the builds endpoint, which requires the “Access build data via the API” permission.

-
-
-
-
$ curl -H "Authorization: Bearer 7asejatf24zun43yshqufp7qi4ovcefxpykbwzqbzilcpwzb52ja" "https://ge.mycompany.com/api/builds?since=0&maxBuilds=3"
-
-
-
-
-
[
-  {
-    "id": "7asfp6iy3d5ey",
-    "availableAt": 1645452789334,
-    "buildToolType": "gradle",
-    "buildToolVersion": "7.4",
-    "buildAgentVersion": "3.8.1"
-  },
-  {
-    "id": "1saxebtd5d4xs",
-    "availableAt": 1645452795414,
-    "buildToolType": "maven",
-    "buildToolVersion": "3.8.4",
-    "buildAgentVersion": "1.12.4"
-  },
-  {
-    "id": "hx4k23knk64ri",
-    "availableAt": 1645453205526,
-    "buildToolType": "gradle",
-    "buildToolVersion": "7.4",
-    "buildAgentVersion": "3.8.1"
-  }
-]
-
-
-
-

The builds endpoint can be used to discover the builds observed by the system. This example fetches the 3 oldest builds due to the since=0&maxBuilds=3 parameters.

-
-
-
-

Next steps

-
-

Check out the sample project.

-
-
-

To learn more about the functionality provided by the API and what you can do with it, see Reference.

-
-
-
-
-
-

Reference

-
-
-

The reference documentation is useful for discovering the functionality of the API.

-
-
-

The reference specification is the API described using the OpenAPI standard and is useful for generating client code with OpenAPI-based tooling.

-
- - -
-
-
-

Appendix A: About the Export API

-
-
-

The Gradle Enterprise API described in this document will eventually replace the Gradle Enterprise Export API. At this time, the Gradle Enterprise API does not allow consuming the raw events of a build as the Export API does.

-
-
-

If your existing usage of the Export API can be replaced with the easier-to-use Gradle Enterprise API, please consider migrating. For assistance, please contact Gradle Enterprise support.

-
+
+
+
+
+

The Develocity API allows programmatic interaction with various aspects of Develocity, from configuration to inspecting build data.

+
+
+
+
+

+ + Fundamentals +

+
+
+

The Develocity API is a REST-style API using JSON as the data format.

+
+
+

+ + OpenAPI +

+
+

+ The API is defined using the + OpenAPI standard + , which is a declarative specification that allows + tools and libraries + to generate client code. The specification can be found in the + Reference + section. +

+
+
+
+

+ + Versioning and compatibility +

+
+

The API is generally backwards compatible.

+
+
+

New functionality (e.g. endpoints, response attributes) may be added in new major versions (i.e. 2022.1, 2022.2).

+
+
+

Breaking changes will occur infrequently if at all, and only after a reasonable deprecation period. Deprecations will be communicated by:

+
+
+
    +
  • +

    The release notes for the major Develocity version introducing the deprecation.

    +
  • +
  • +

    + The + + deprecated + + annotation within the OpenAPI specification. +

    +
  • +
  • +

    + The + + Deprecated + + HTTP response header (indicating when the operation was deprecated). +

    +
  • +
  • +

    + The + + Sunset + + HTTP response header (indicating when the operation will be removed). +

    +
  • +
+
+
+

API documentation may be refined in patch releases, along with implementation defects that cause deviation from the specification.

+
+
+
+
+
+

+ + Getting started +

+
+
+

+ + Access control +

+
+

All requests must provide a Develocity access key as “bearer token”.

+
+
+

To create an access key:

+
+
+
    +
  1. +

    Sign in to Develocity.

    +
  2. +
  3. +

    Access "My settings" from the user menu in the top right-hand corner of the page.

    +
  4. +
  5. +

    Access "Access keys" from the left-hand menu.

    +
  6. +
  7. +

    Click "Generate" on the right-hand side and copy the generated access key.

    +
  8. +
+
+
+

Different requests require different user permissions, as describe via the API specification.

+
+
+

To view the permissions assigned to you:

+
+
+
    +
  1. +

    Sign in to Develocity.

    +
  2. +
  3. +

    Access "My settings" from the user menu in the top right-hand corner of the page.

    +
  4. +
  5. +

    Access "Permissions" from the left-hand menu.

    +
  6. +
  7. +

    Check if you have the required permissions.

    +
  8. +
+
+
+

If you do not have the required permission, contact your Develocity administrator.

+
+
+

+ + Short-lived access tokens +

+
+

Develocity access keys are long-lived, creating risks if they are leaked. To avoid this, users can use short-lived access tokens to authenticate with Develocity. Access tokens can be used wherever an access key would be used. Access tokens are only valid for the Develocity instance that created them.

+
+
+ + + + + + + +
+ + Develocity server version 2024.1+ supports access tokens.
+
+
+ + + + + + + +
+ + Changing a Develocity instance’s hostname will cause all existing access tokens to become invalid.
+
+
+

To create an access token:

+
+
+
    +
  1. +

    Get an access key or access token for the user you wish to create a token for.

    +
  2. +
  3. +

    Decide which permissions the new access token should have.

    +
  4. +
  5. +

    If project-level access control is enabled, decide which projects the new access token should be able to access.

    +
  6. +
  7. +

    Decide how long the new access token should live.

    +
  8. +
  9. +

    + Make a POST request to + /api/auth/token + , optionally with the following parameters. The response will be the access token. +

    +
    +
      +
    1. +

      + A + permissions= + query parameter with the + config values + of each permission you wish to grant. By default, all permissions for the credential used to authenticate the token request are granted to the created token. +

      +
    2. +
    3. +

      + If project-level access control is enabled, a + projectIds= + query parameter with the ID of each project you wish to grant access to. By default, all projects for the credential used to authenticate the token request are granted to the created token. +

      +
    4. +
    5. +

      + An + expiresInHours= + query parameter with the token’s intended lifetime in hours, with a maximum of 24. The default is two hours, or the remaining lifetime of the credential used to authenticate the request, whichever is smaller. +

      +
    6. +
    +
    +
  10. +
+
+
+

+ The requested permissions and project ids can be specified as comma-seperated lists or repeated parameters. For example, + ?projectIds=a,b&projectIds=c + is valid and will request projects + a + , + b + , and + c + . +

+
+
+ + + + + + + +
+ + If project-level access control is not enabled, all access tokens will be granted the “Access all data without an associated project” permission even if it is not explicitly requested.
+
+
+

+ If the user creating the token does not have one of the requested permissions or projects, Develocity will respond with a + 403 Forbidden + error. If an access token is used to authenticate the creation request, its permissions and projects will be used for this check instead of the user’s. The request will also error if the requested lifetime would cause the new access token to expire after the one used to authenticate the request. Together, this means you cannot create an access token with more access or a later expiration than the credentials used to authenticate the request. +

+
+
+ + + + + + + +
+ + + See the + API documentation + for more details on the + /api/auth/token + endpoint. +
+
+
+

Here is an example using CURL to create an access token:

+
+
+
+
+                                    $ curl -X POST https://ge.mycompany.com/api/auth/token?permissions=publishScan,writeCache,accessDataWithoutAssociatedProject&projectIds=project-a,project-b&expiresInHours=1 \
+                                        -H "Authorization: Bearer 7asejatf24zun43yshqufp7qi4ovcefxpykbwzqbzilcpwzb52ja"
+
+                                    eyJraWQiOiJ0ZXN0LWtleSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc19hbm9ueW1vdXMiOmZhbHNlLCJwZXJtaXNzaW9ucyI6WyJSRUFEX1ZFUlNJT04iLCJFWFBPUlRfREFUQSIsIkFDQ0VTU19EQVRBX1dJVEhPVVRfQVNTT0NJQVRFRF9QUk9KRUNUIl0sInByb2plY3RzIjp7ImEiOjEsImIiOjJ9LCJ1c2VyX2lkIjoic29tZS1pZCIsInVzZXJuYW1lIjoidGVzdCIsImZpcnN0X25hbWUiOiJhIiwibGFzdF9uYW1lIjoidXNlciIsImVtYWlsIjoiYkBncmFkbGUuY29tIiwic3ViIjoidGVzdCIsImV4cCI6NzIwMCwibmJmIjowLCJpYXQiOjAsImF1ZCI6ImV4YW1wbGUuZ3JhZGxlLmNvbSIsImlzcyI6ImV4YW1wbGUuZ3JhZGxlLmNvbSIsInRva2VuX3R5cGUiOiJhY2Nlc3NfdG9rZW4ifQ.H1_NEG1xuleP-WIAY_uvSmdd2o7i_-Ko3qhlo04zvCgrElJe7_F5jNuqsyDfnb5hvKlOe5UKG_7QPTgY9-3pFQ
+                                
+
+
+
+

The resulting token would have the following permissions:

+
+
+
    +
  • +

    “Publish build scans”

    +
  • +
  • +

    “Read and write build cache data”

    +
  • +
  • +

    “Access all data without an associated project”

    +
  • +
+
+
+

And it would have access to these projects:

+
+
+
    +
  • +

    “project-a”

    +
  • +
  • +

    “project-b”

    +
  • +
+
+
+

The token would only be usable for one hour.

+
+
+
+
+

+ + Making a request +

+
+

+ The following example demonstrates using + + cURL + + to make a request to the + builds + endpoint, which requires the “Access build data via the API” permission. +

+
+
+
+
+                                $ curl -H "Authorization: Bearer <access key or access token>" "https://ge.mycompany.com/api/builds?since=0&maxBuilds=3"
+                            
+
+
+
+
+
[
+                              {
+                                "id": "7asfp6iy3d5ey",
+                                "availableAt": 1645452789334,
+                                "buildToolType": "gradle",
+                                "buildToolVersion": "7.4",
+                                "buildAgentVersion": "3.8.1"
+                              },
+                              {
+                                "id": "1saxebtd5d4xs",
+                                "availableAt": 1645452795414,
+                                "buildToolType": "maven",
+                                "buildToolVersion": "3.8.4",
+                                "buildAgentVersion": "1.12.4"
+                              },
+                              {
+                                "id": "hx4k23knk64ri",
+                                "availableAt": 1645453205526,
+                                "buildToolType": "gradle",
+                                "buildToolVersion": "7.4",
+                                "buildAgentVersion": "3.8.1"
+                              }
+                            ]
+
+
+
+

+ The + builds + endpoint can be used to discover the builds observed by the system. This example fetches the 3 oldest builds due to the + since=0&maxBuilds=3 + parameters. +

+
+
+
+

+ + Next steps +

+
+

+ Check out the + sample project + . +

+
+
+

+ To learn more about the functionality provided by the API and what you can do with it, see + Reference + . +

+
+
+
+
+
+

+ + Reference +

+
+
+

The reference documentation is useful for discovering the functionality of the API.

+
+
+

+ The reference specification is the API described using the OpenAPI standard and is useful for generating client code with + OpenAPI-based tooling + . +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

2024.1

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2023.4

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2023.3

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2023.2

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2023.1

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2022.4

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2022.3

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2022.2

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+

2022.1

+
+

+ Documentation +

+
+

+ Specification + ( + SHA-256 checksum + , + PGP signature + , + PGP signature SHA-256 checksum + ) +

+
+
+
+
+

+ + Appendix A: About the Export API +

+
+
+

+ The Develocity API described in this document will eventually replace the + Develocity Export API + . At this time, the Develocity API does not allow consuming the + raw events of a build as the Export API does + . +

+
+
+

If your existing usage of the Export API can be replaced with the easier-to-use Develocity API, please consider migrating. For assistance, please contact Develocity support.

+
+
+
+
+

+ + Appendix B: Release history +

+
+

2024.1

+
+
+ 2nd April 2024 +
+
    +
  • +

    + Add new + /api/builds/{id}/gradle-artifact-transform-executions + endpoint +

    +
  • +
  • +

    + Add new + /api/builds/{id}/gradle-deprecations + endpoint +

    +
  • +
  • +

    + Add + allModels + query parameter to + /api/build/{id} + and + /api/builds + endpoint for requesting all build models +

    +
  • +
+
+

2023.4

+
+
+ 5th December 2023 +
+
    +
  • +

    + Add new + /api/builds/{id}/gradle-network-activity + endpoint +

    +
  • +
  • +

    + Add new + /api/builds/{id}/maven-dependency-resolution + endpoint +

    +
  • +
  • +

    + Add new + /api/tests/cases + endpoint +

    +
  • +
  • +

    + Add new + /api/tests/containers + endpoint +

    +
  • +
  • +

    + Add + develocitySettings + attribute on the + /api/builds/{id}/gradle-attributes + and + /api/builds/{id}/maven-attributes + operation responses. This value is identical to + gradleEnterpriseSettings + and should be used instead +

    +
  • +
  • +

    + Add + workUnitFingerprintingSummary + and + workUnitAvoidanceSavingsSummary + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + cacheKey + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + cacheKey + to + /api/builds/{id}/maven-build-cache-performance + operation response +

    +
  • +
  • +

    + Make + isDisabledDueToError + nullable on + /api/builds/{id}/gradle-build-cache-performance + and + /api/builds/{id}/maven-build-cache-performance + responses +

    +
  • +
  • +

    + Deprecate + rerunGoals + in favor of + rerunGoalsEnabled + within the + buildOptions + property on the + /api/builds/{id}/maven-attributes + operation responses +

    +
  • +
  • +

    + Deprecate + avoidanceSavingsSummary + in favor of + taskAvoidanceSavingsSummary + from + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
+
+

2023.3

+
+
+ 13th September 2023 +
+
    +
  • +

    + Add + hasVerificationFailure + and + hasNonVerificationFailure + attributes on the + /api/builds/{id}/gradle-attributes + and + /api/builds/{id}/maven-attributes + operation response +

    +
  • +
  • +

    + Introduce optional + query + query parameter for the on the + /api/builds + endpoint, which can be used to filter the set of returned builds. This parameter’s value should be a URL encoded text query written in the advanced query language (the same advanced query language that is used in the scans list UI) +

    +
  • +
+
+

2023.2

+
+
+ 18th July 2023 +
+
    +
  • +

    + Add + effectiveWorkUnitExecutionTime + and + serialWorkUnitExecutionTime + on the + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Exclude potential artifact transforms from + effectiveTaskExecutionTime + and + serialTaskExecutionTime + on the + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + cacheArtifactRejectedReason + to + /api/builds/{id}/gradle-build-cache-performance + and + /api/builds/{id}/maven-build-cache-performance + operation response +

    +
  • +
+
+

2023.1

+
+
+ 12th April 2023 +
+
    +
  • +

    + Add + cacheArtifactSize + to + /api/builds/{id}/gradle-build-cache-performance + and + /api/builds/{id}/maven-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + excludedTasks + to + /api/builds/{id}/gradle-attributes + operation response +

    +
  • +
+
+

2022.4

+
+
+ 8th December 2022 +
+
    +
  • +

    + Add new + /api/test-distribution/* + endpoints for managing API keys and agent pools +

    +
  • +
  • +

    + Add new + /api/builds/{id}/gradle-projects + endpoint +

    +
  • +
  • +

    + Add new + /api/builds/{id}/maven-modules + endpoint +

    +
  • +
  • +

    + Introduce + fromInstant + , + fromBuild + , + reverse + query parameters on the + /api/builds + operation query +

    +
  • +
  • +

    + Deprecate + since + and + sinceBuild + query parameters on the + /api/builds + operation query +

    +
  • +
+
+

2022.3

+
+
+ 10th August 2022 +
+
    +
  • +

    + Add + taskExecution.nonCacheabilityCategory + and + taskExecution.nonCacheabilityReason + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + goalExecution.nonCacheabilityCategory + and + goalExecution.nonCacheabilityReason + to + /api/builds/{id}/maven-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + taskExecution.taskType + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + goalExecution.mojoType + to + /api/builds/{id}/maven-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + buildCaches.remote.type + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add + buildCaches.remote.className + to + /api/builds/{id}/gradle-build-cache-performance + operation response +

    +
  • +
  • +

    + Add a + title + to + /api/builds + inlined query parameters schema +

    +
  • +
+
+

2022.2

+
+
+ 19th April 2022 +
+
    +
  • +

    Improve OpenAPI specification examples

    +
  • +
  • +

    + Add + buildCaches.local.isPushEnabled + to + /api/builds/{id}/maven-build-cache-performance + operation response +

    +
  • +
+
+

2022.1

+
+
+ 17th March 2022 +
+
    +
  • +

    Initial release

    +
  • +
+
+
+
+
+

+ + Appendix C: Advanced search syntax +

+
+
+

Develocity supports the use of advanced search for filtering Build Scan data in the dashboards from version 2022.4, and in the Enterprise API from version 2023.3.

+
+
+

+ Each query can be made from one or more + terms + separated by spaces. Each term is a field name and search pattern: + fieldName:pattern + . For example, + project:my-project + is equivalent to entering + my-project + in the + Project + field in the basic search. +

+
+
+

+ + Supported fields +

+
+

+ + Generic fields +

+
+

These fields are applicable for builds from all supported build tools.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionData typeVersion availabilityExample
+

user

+
+

Username of the OS account that executed the build.

+
+

String

+
+

>=2022.4

+
+

user:dylan

+
+

hostname

+
+

Public hostname of the build machine.

+
+

String

+
+

>=2022.4

+
+

hostname:*.somedomain.com

+
+

project

+
+

Name of the Gradle root project, Maven top level module, Bazel workspace, or Sbt root project.

+
+

String

+
+

>=2023.3

+
+

project:my-project

+
+

rootProjectName

+
+

Name of the Gradle root project, Maven top level module, Bazel workspace, or Sbt root project.

+
+

String

+
+

2022.4 to 2023.2.3

+
+

rootProjectName:my-project

+
+

requested

+
+

Tasks, goals, or targets that were requested when the build was invoked.

+
+

String

+
+

>=2022.4

+
+

requested:":my-project:*"

+
+

buildTool

+
+

+ Must be one of + gradle + , + maven + , or + bazel + . In the Build Scan dashboards, but not in the Enterprise API, you can also use the value + sbt + . +

+
+

String

+
+

>=2022.4

+
+

buildTool:gradle

+
+

buildToolVersion

+
+

Version of the build tool that executed the build.

+
+

String

+
+

>=2022.4

+
+

buildToolVersion:8.*

+
+

value

+
+

+ Custom values + added to the Build Scan in the form + name=value + . +

+
+

Key-value pair

+
+

>=2022.4

+
+

value:"Git branch=abc/my-feature"

+
+

tag

+
+

+ Tags + added to the Build Scan. +

+
+

String

+
+

>=2022.4

+
+

tag:CI

+
+

buildOutcome

+
+

+ succeeded + or + failed + . +

+
+

String

+
+

>=2022.4

+
+

buildOutcome:failed

+
+

buildDuration

+
+

Duration of the build.

+
+

Duration

+
+

>=2022.4

+
+

buildDuration>30m

+
+

buildStartTime

+
+

Start time of the build. This field should only be used when filtering Build Scan data from the Develocity API.

+
+

Timestamp

+
+

>=2022.4

+
+
+
+
    +
  • +

    buildStartTime:[2023-01-01 to 2023-02-01]

    +
  • +
  • +

    buildStartTime:[2023-09-01T13:00:00 to 2023-09-01T15:00:00]

    +
  • +
  • +

    buildStartTime>-5d

    +
  • +
+
+
+
+

hasVerificationFailure

+
+

True if the build fails and at least one of the failures is classified as "Verification". Otherwise, false.

+
+

Boolean

+
+

>=2024.1

+
+

hasVerificationFailure

+
+

hasNonVerificationFailure

+
+

True if the build fails and at least one of the failures is classified as "Non-verification". Otherwise, false.

+
+

Boolean

+
+

>=2024.1

+
+

hasNonVerificationFailure

+
+
+
+

+ + Gradle-specific fields +

+
+

+ Fields whose name is prefixed with " + gradle. + " are specific to Gradle and select only Gradle builds. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionData typeVersion availability
+

gradle.pluginVersion

+
+

The applied version of the Develocity Gradle plugin.

+
+

String

+
+

>=2023.3

+
+

gradle.requestedTasks

+
+

The resolved set of requested tasks for the Gradle build.

+
+

String

+
+

>=2023.3

+
+

gradle.rootProjectName

+
+

The root project name for the Gradle build.

+
+

String

+
+

>=2023.3

+
+

gradle.buildCache.hasError

+
+

Whether any build cache error or artifact rejections occurred during the build.

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.buildCacheEnabled

+
+

+ Whether the + Gradle build cache + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.configurationCacheEnabled

+
+

+ Whether the + configuration cache + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.configurationOnDemandEnabled

+
+

+ Whether + configuration on demand + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.continueOnFailureEnabled

+
+

+ Whether + continue on failure + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.continuousBuildEnabled

+
+

+ Whether + continuous build execution + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.daemonEnabled

+
+

+ Whether the + Gradle daemon + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.dryRunEnabled

+
+

+ Whether + dry run mode + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.fileSystemWatchingEnabled

+
+

+ Whether + file system watching + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.maxNumberOfGradleWorkers

+
+

+ Maximum number of build workers + configured for the Gradle build. +

+
+

Number

+
+

>=2023.4

+
+

gradle.buildOptions.offlineModeEnabled

+
+

+ Whether + offline mode + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.parallelProjectExecution

+
+

+ Whether + parallel project execution + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.refreshDependenciesEnabled

+
+

+ Whether the build was configured to + refresh all dependencies + . +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.buildOptions.rerunTasksEnabled

+
+

+ Whether + rerun tasks + was enabled. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.develocitySettings.backgroundPublicationEnabled

+
+

+ Whether build scan data was + published in the background + . +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.develocitySettings.buildOutputCapturingEnabled

+
+

+ Whether the build was configured to + capture logging output + for the build. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.develocitySettings.taskInputsFileCapturingEnabled

+
+

+ Whether the build was configured to + capture task input files + . +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.develocitySettings.testOutputCapturingEnabled

+
+

+ Whether the build was configured to + capture test logging output + for the build. +

+
+

Boolean

+
+

>=2023.4

+
+

gradle.environment.jreVersion

+
+

Version of the Java runtime that executed the build.

+
+

String

+
+

>=2023.4

+
+

gradle.environment.jvmCharset

+
+

Default charset of the build JVM.

+
+

String

+
+

>=2023.4

+
+

gradle.environment.jvmLocale

+
+

Locale of the build JVM.

+
+

String

+
+

>=2023.4

+
+

gradle.environment.jvmMaxMemoryHeapSize

+
+

Maximum heap memory available to the build JVM.

+
+

StorageSize

+
+

>=2023.4

+
+

gradle.environment.jvmVersion

+
+

Version of the Java Virtual Machine that executed the build.

+
+

String

+
+

>=2023.4

+
+

gradle.environment.localIPAddress

+
+

Local IP addresses of the build machine.

+
+

String

+
+

>=2023.4

+
+

gradle.environment.localHostname

+
+

Hostname of the build machine, as specified by itself.

+
+

String

+
+

>=2023.4

+
+

gradle.environment.numberOfCpuCores

+
+

Number of cores available to the build JVM.

+
+

Number

+
+

>=2023.4

+
+

gradle.environment.operatingSystem

+
+

Operating system of the build machine.

+
+

String

+
+

>=2023.4

+
+
+
+

+ + Maven-specific fields +

+
+

+ Fields whose name is prefixed with " + maven. + " are specific to Maven and select only Maven builds. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionData typeVersion availability
+

maven.extensionVersion

+
+

The applied version of the Develocity Maven extension.

+
+

String

+
+

>=2023.3

+
+

maven.requestedGoals

+
+

The resolved set of requested goals for the Maven build.

+
+

String

+
+

>=2023.3

+
+

maven.topLevelProjectName

+
+

The name of the top level Maven project.

+
+

String

+
+

>=2023.3

+
+

maven.buildCache.hasError

+
+

Whether any build cache error or artifact rejections occurred during the build.

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.batchModeEnabled

+
+

+ Whether the build was configured to run in + non-interactive (batch) mode + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.debugEnabled

+
+

+ Whether the build was configured to + produce execution debug output + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.errorsEnabled

+
+

+ Whether the build was configured to + produce execution error messages + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.failAtEndEnabled

+
+

+ Whether the build was configured to + only fail at the end + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.failFastEnabled

+
+

+ Whether the build was configured to + fail at the first error + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.failNeverEnabled

+
+

+ Whether the build was configured to + never fail, regardless of errors produced + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.laxChecksumsEnabled

+
+

+ Whether the build was configured to + only warn if checksums don’t match + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.maxNumberOfThreads

+
+

Maximum number of threads used when executing the build.

+
+

Number

+
+

>=2023.4

+
+

maven.buildOptions.nonRecursiveEnabled

+
+

+ Whether the build was configured to + not recurse into subprojects + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.noSnapshotUpdatesEnabled

+
+

+ Whether the build was configured to + suppress snapshot updates + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.offlineModeEnabled

+
+

+ Whether the build was configured to + run offline + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.quietEnabled

+
+

+ Whether the build was configured to + run in quiet mode + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.rerunGoalsEnabled

+
+

+ Whether the build was configured to + rerun goals without checking the build cache + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.strictChecksumsEnabled

+
+

+ Whether the build was configured to + fail if checksums don’t match + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.buildOptions.updateSnapshotsEnabled

+
+

+ Whether the build was configured to + force a check for missing releases and updated snapshots on remote repositories + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.develocitySettings.backgroundPublicationEnabled

+
+

+ Whether build scan data was + published in the background + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.develocitySettings.buildOutputCapturingEnabled

+
+

+ Whether the build was configured to + capture logging output + for the build. +

+
+

Boolean

+
+

>=2023.4

+
+

maven.develocitySettings.goalInputsFileCapturingEnabled

+
+

+ Whether the build was configured to + capture goal input files + . +

+
+

Boolean

+
+

>=2023.4

+
+

maven.develocitySettings.testOutputCapturingEnabled

+
+

+ Whether the build was configured to + capture test logging output + for the build. +

+
+

Boolean

+
+

>=2023.4

+
+

maven.environment.jreVersion

+
+

Version of the Java runtime that executed the build.

+
+

String

+
+

>=2023.4

+
+

maven.environment.jvmCharset

+
+

Default charset of the build JVM.

+
+

String

+
+

>=2023.4

+
+

maven.environment.jvmLocale

+
+

Locale of the build JVM.

+
+

String

+
+

>=2023.4

+
+

maven.environment.jvmMaxMemoryHeapSize

+
+

Maximum heap memory available to the build JVM.

+
+

StorageSize

+
+

>=2023.4

+
+

maven.environment.jvmVersion

+
+

Version of the Java Virtual Machine that executed the build.

+
+

String

+
+

>=2023.4

+
+

maven.environment.localIPAddress

+
+

Local IP addresses of the build machine.

+
+

String

+
+

>=2023.4

+
+

maven.environment.localHostname

+
+

Hostname of the build machine, as specified by itself.

+
+

String

+
+

>=2023.4

+
+

maven.environment.numberOfCpuCores

+
+

Number of cores available to the build JVM.

+
+

Number

+
+

>=2023.4

+
+

maven.environment.operatingSystem

+
+

Operating system of the build machine.

+
+

String

+
+

>=2023.4

+
+
+
+

+ + Bazel-specific fields +

+
+

+ Fields whose name is prefixed with " + bazel. + " are specific to Bazel and select only Bazel builds. +

+
+ + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionData typeVersion availability
+

bazel.requestedTargets

+
+

The resolved set of requested targets for the Bazel build.

+
+

String

+
+

>=2023.3

+
+
+
+

+ + sbt-specific fields +

+
+

+ Fields whose name is prefixed with " + sbt. + " are specific to sbt and select only sbt builds. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionData typeVersion availability
+

sbt.pluginVersion

+
+

The applied version of the Develocity sbt plugin.

+
+

String

+
+

>=2023.3

+
+

sbt.requestedTasks

+
+

The resolved set of requested tasks for the sbt build.

+
+

String

+
+

>=2023.3

+
+

sbt.rootProjectName

+
+

The name of the top level sbt project.

+
+

String

+
+

>=2023.3

+
+

sbt.buildOptions.interactiveModeEnabled

+
+

+ Whether the build was run + from the sbt shell + . +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.buildOptions.offlineModeEnabled

+
+

+ Whether the build was configured to + run offline + . +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.buildOptions.parallelExecutionEnabled

+
+

+ Whether the build was configured to use + parallel tasks execution + . +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.buildOptions.semanticDbScalacPluginEnabled

+
+

+ Whether the projects in the build were configured to use the + SemanticDB Scalac plugin + . +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.buildOptions.turboEnabled

+
+

+ Whether the build was configured to use + turbo mode + . +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.buildOptions.useCoursierEnabled

+
+

+ Whether + Coursier + was used for dependency resolution. +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.develocitySettings.backgroundPublicationEnabled

+
+

+ Whether build scan data was + published in the background + . +

+
+

Boolean

+
+

>=2023.4

+
+

sbt.environment.jreVersion

+
+

Version of the Java runtime that executed the build.

+
+

String

+
+

>=2023.4

+
+

sbt.environment.jvmCharset

+
+

Default charset of the build JVM.

+
+

String

+
+

>=2023.4

+
+

sbt.environment.jvmLocale

+
+

Locale of the build JVM.

+
+

String

+
+

>=2023.4

+
+

sbt.environment.jvmMaxMemoryHeapSize

+
+

Maximum heap memory available to the build JVM.

+
+

StorageSize

+
+

>=2023.4

+
+

sbt.environment.jvmVersion

+
+

Version of the Java Virtual Machine that executed the build.

+
+

String

+
+

>=2023.4

+
+

sbt.environment.localIPAddress

+
+

Local IP addresses of the build machine.

+
+

String

+
+

>=2023.4

+
+

sbt.environment.localHostname

+
+

Hostname of the build machine, as specified by itself.

+
+

String

+
+

>=2023.4

+
+

sbt.environment.numberOfCpuCores

+
+

Number of cores available to the build JVM.

+
+

Number

+
+

>=2023.4

+
+

sbt.environment.operatingSystem

+
+

Operating system of the build machine.

+
+

String

+
+

>=2023.4

+
+
+
+
+

+ + Field data types +

+
+

Every supported field in the advanced search query language is associated with one data type. A field’s data type indicates what values it accepts, and each has its own syntax. The data types are all described below.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Data typeDescription
+

String

+
+
+
+

Strings can be exact matches or use the * wildcard. Whitespace and special characters need to be double-quoted. Searches are case-insensitive.

+
+
+
    +
  • +

    project:my-project

    +
  • +
  • +

    tag:"Tag with spaces"

    +
  • +
  • +

    hostname:*.somedomain.com

    +
  • +
+
+
+
+

Key-value pair

+
+
+
+

These take the form fieldName:key=value, with wildcards allowed and double-quoting required for whitespace and special characters.

+
+
+
    +
  • +

    value:"Git branch=abc/my-feature"

    +
  • +
  • +

    value:ci-agent=*.subdomain.com

    +
  • +
+
+
+
+

Duration

+
+
+
+

+ Durations can be specified using + ISO 8601 duration syntax + or a simple syntax like + 5m + or + 24h + . +

+
+
+
    +
  • +

    buildDuration>30m

    +
  • +
  • +

    buildDuration<=PT2H5.123S

    +
  • +
+
+
+

The units supported for simple duration syntax are 'd' for days, 'h' for hours, 'm' for minutes, 's' for seconds, and 'ms' for milliseconds.

+
+
+
+

Timestamp

+
+
+
+

Timestamps can be specified in a few different ways.

+
+
+
    +
  • +

    YYYY-MM-DD - timestamp is at midnight on that day.

    +
  • +
  • +

    YYYY-MM-DDTHH:MM - seconds and milliseconds optional.

    +
  • +
  • +

    YYYY-MM-DDTHH:MM:SS.SSS

    +
  • +
+
+
+

+ All timestamps can have an optional timezone offset appended in the form + Z + or + +HH:SS + to be specific about which timezone the timestamp is being specified in. Default is timezone of the current web browser when using advanced search in Build Scan dashboards. Default is always UTC when using the Enterprise API. +

+
+
+

Timestamps in the form of a date can be queried using equality.

+
+
+
    +
  • +

    buildStartTime:2023-09-01 - builds that ran on this day, UTC.

    +
  • +
  • +

    buildStartTime:2023-09-01+01:00 - builds that ran on this day, BST (i.e. UTC+1).

    +
  • +
+
+
+

+ Timestamps can be queried using ranges in the form + [start to finish] + . +

+
+
+
    +
  • +

    buildStartTime:[2023-01-01 to 2023-02-01] - builds that ran from the Jan 1st - Feb 1st, inclusive.

    +
  • +
  • +

    buildStartTime:[2023-09-01T13:00:00 to 2023-09-01T15:00:00] - builds that ran on Sep 1st from 1pm to 3pm.

    +
  • +
+
+
+

A timestamp can also be specified relative to the current time using duration syntax.

+
+
+
    +
  • +

    buildStartTime>-5d - builds in the last 5 days.

    +
  • +
  • +

    buildStartTime:[2023-01-01T05:23:30Z to -12h] - builds that ran from the start timestamp, until 12 hours ago.

    +
  • +
+
+
+
+

Boolean

+
+
+
+

Boolean fields can be included as terms themselves, or they can have a value specified. If the term is used by itself, it is treated as if the value is "true", as is conventional.

+
+
+
    +
  • +

    gradle.buildOptions.buildCacheEnabled

    +
  • +
  • +

    gradle.buildOptions.buildCacheEnabled:true

    +
  • +
  • +

    gradle.buildOptions.buildCacheEnabled:false

    +
  • +
+
+
+

Some boolean fields distinguish between a false value, and an unknown value. You can use a wildcard to acknowledge the account for unknown booleans in your queries. The first query below will match all builds for which the build cache was known to be either enabled or disabled. The second will match all builds for which it is unknown whether the build cache was enabled or disabled.

+
+
+
    +
  • +

    gradle.buildOptions.buildCacheEnabled:*

    +
  • +
  • +

    !gradle.buildOptions.buildCacheEnabled:*

    +
  • +
+
+
+

The following term will match builds where the build cache was not known to be enabled, rather than builds where the build cache was known to be disabled.

+
+
+
    +
  • +

    !gradle.buildOptions.buildCacheEnabled

    +
  • +
+
+
+
+

Number

+
+
+
+

Numbers can be exact matches or use a range. They can be integers or decimals, although for some fields only integers really make sense, for example the max number of workers in a Gradle build is always an integer.

+
+
+
    +
  • +

    gradle.buildOptions.maxNumberOfGradleWorkers:4

    +
  • +
  • +

    gradle.buildOptions.maxNumberOfGradleWorkers>=8

    +
  • +
  • +

    gradle.buildOptions.maxNumberOfGradleWorkers:[3 to 6]

    +
  • +
  • +

    gradle.buildOptions.maxNumberOfGradleWorkers<13.5

    +
  • +
+
+
+
+

Storage size

+
+
+
+

Storage sizes represent a number of bytes. They are ultimately numbers, but they can have units, for convenience. As with numbers, they can be exact matches or use a range. If no unit is appended, the number value is interpreted as a number of bytes.

+
+
+
    +
  • +

    gradle.environment.jvmMaxMemoryHeapSize:3000000

    +
  • +
  • +

    gradle.environment.jvmMaxMemoryHeapSize>=5.3gi

    +
  • +
  • +

    gradle.environment.jvmMaxMemoryHeapSize:[8g to 12g]

    +
  • +
+
+
+

The units supported are:

+
+
+
    +
  • +

    'k' for 'kilobyte' representing 1000 bytes

    +
  • +
  • +

    'ki' for 'kibibyte' representing 1024 bytes

    +
  • +
  • +

    + 'm' for 'megabyte' representing 1000 + 2 + bytes +

    +
  • +
  • +

    + 'mi' for 'mebibyte' representing 1024 + 2 + bytes +

    +
  • +
  • +

    + 'g' for 'gigabyte' representing 1000 + 3 + bytes +

    +
  • +
  • +

    + 'gi' for 'gibibyte' representing 1024 + 3 + bytes +

    +
  • +
  • +

    + 't' for 'terabyte' representing 1000 + 4 + bytes +

    +
  • +
  • +

    + 'ti' for 'tebibyte' representing 1024 + 4 + bytes +

    +
  • +
+
+
+

Optionally, a 'b' may be appended to the end of these units. Thus, 500mb is the same as 500m, and 500mib is the same as 500mi. Units are also case-insensitive, so 500MiB is the same as 500mib.

+
+
+
+
+
+

+ + Combining terms +

+
+

By default, all terms must match.

+
+
+

+ Terms can be combined using boolean + and + and + or + operators, and grouped using parentheses. +

+
+
+
    +
  • +

    user:dylan or (tag:CI and value:branch=dylan/*)

    +
  • +
+
+
+

+ Terms can also be negated using + - + or + not + . +

+
+
+
    +
  • +

    project:my-project -tag:local

    +
  • +
  • +

    user:dylan or not (tag:CI and value:branch=dylan/*)

    +
  • +
+
+
+
+
-
-
-

Appendix B: Release history

-
-

2023.1

-
-
- 12th April 2023 -
-
    -
  • Add new cacheArtifactSize to /api/builds/{id}/gradle-build-cache-performance and /api/builds/{id}/maven-build-cache-performance operation response

  • -
  • Add new excludedTasks to /api/builds/{id}/gradle-attributes operation response

  • -
-
-

2022.4

-
-
- 8th December 2022 -
-
    -
  • Add new /api/test-distribution/* endpoints for managing API keys and agent pools

  • -
  • Add new /api/builds/{id}/gradle-projects endpoint

  • -
  • Add new /api/builds/{id}/maven-modules endpoint

  • -
  • Introduce fromInstant, fromBuild, reverse query parameters on the /api/builds operation query

  • -
  • Deprecate since and sinceBuild query parameters on the /api/builds operation query

  • -
-
-

2022.3

-
-
- 10th August 2022 -
-
    -
  • Add taskExecution.nonCacheabilityCategory and taskExecution.nonCacheabilityReason to /api/builds/{id}/gradle-build-cache-performance operation response

  • -
  • Add goalExecution.nonCacheabilityCategory and goalExecution.nonCacheabilityReason to /api/builds/{id}/maven-build-cache-performance operation response

  • -
  • Add taskExecution.taskType to /api/builds/{id}/gradle-build-cache-performance operation response

  • -
  • Add goalExecution.mojoType to /api/builds/{id}/maven-build-cache-performance operation response

  • -
  • Add buildCaches.remote.type to /api/builds/{id}/gradle-build-cache-performance operation response

  • -
  • Add buildCaches.remote.className to /api/builds/{id}/gradle-build-cache-performance operation response

  • -
  • Add a title to /api/builds inlined query parameters schema

  • -
-
-

2022.2

-
-
- 19th April 2022 -
-
    -
  • Improve OpenAPI specification examples

  • -
  • Add buildCaches.local.isPushEnabled to /api/builds/{id}/maven-build-cache-performance operation response

  • -
-
-

2022.1

-
-
- 17th March 2022 -
-
    -
  • Initial release

  • -
-
+ -
-
- - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/.github/scripts/test_resources/build.gradle.kts b/.github/scripts/test_resources/build.gradle.kts index 4bfbefee..813152ba 100644 --- a/.github/scripts/test_resources/build.gradle.kts +++ b/.github/scripts/test_resources/build.gradle.kts @@ -1,3 +1,3 @@ dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:0.17.0") + implementation("com.gabrielfeo:develocity-api-kotlin:0.17.0") } diff --git a/.github/scripts/test_update_api_spec_version.py b/.github/scripts/test_update_api_spec_version.py index 637a2242..47cbabe7 100755 --- a/.github/scripts/test_update_api_spec_version.py +++ b/.github/scripts/test_update_api_spec_version.py @@ -8,7 +8,7 @@ TEST_RESOURCES = Path(__file__).parent / 'test_resources' TEST_API_MANUAL_HTML = TEST_RESOURCES / 'api_manual.html' -LATEST_VERSION = '2023.1' # Same as HTML +LATEST_VERSION = '2024.1' # Same as HTML class TestCheckForNewApiSpec(unittest.TestCase): @@ -34,12 +34,12 @@ def test_main_without_update_available(self, mock_get, _): def assert_properties_version(self, file, version): with open(file.name) as file: - expected = f"gradle.enterprise.version={version}\n1=2\n" + expected = f"develocity.version={version}\nversion={version}.0\n1=2\n" self.assertEqual(file.read(), expected) def properties_file(self, version): file = NamedTemporaryFile() - content = f"gradle.enterprise.version={version}\n1=2\n" + content = f"develocity.version={version}\nversion={version}.0\n1=2\n" file.write(content.encode()) file.flush() return file diff --git a/.github/scripts/update_api_spec_version.py b/.github/scripts/update_api_spec_version.py index e30e02af..f3e8d253 100755 --- a/.github/scripts/update_api_spec_version.py +++ b/.github/scripts/update_api_spec_version.py @@ -6,7 +6,7 @@ import sys VERSIONS_URL = "https://docs.gradle.com/enterprise/api-manual/" -LATEST_VERSION_REGEX = r']*href="ref/gradle-enterprise-([\d.]+)-api\.yaml">Specification' +LATEST_VERSION_REGEX = r']*href="ref/develocity-([\d.]+)-api\.yaml">Specification' def main(properties_file='gradle.properties'): @@ -24,7 +24,7 @@ def get_current_api_spec_version(properties_file) -> str: if '=' not in line: continue k, v = line.strip().split('=', maxsplit=2) - if k == 'gradle.enterprise.version': + if k == 'develocity.version': return v @@ -41,8 +41,12 @@ def update_version(properties_file, new_version): for line in fileinput.input(properties_file, inplace=True): if '=' in line: k, v = line.strip().split('=', maxsplit=2) - if k == 'gradle.enterprise.version': + # Update target API spec version + if k == 'develocity.version': line = f"{k}={new_version}\n" + # Update library version + if k == 'version': + line = f"{k}={new_version}.0\n" sys.stdout.write(line) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d51e152b..3b31e96d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -30,8 +30,9 @@ jobs: - name: 'unittest discover' run: python3 -m unittest discover -bs .github/scripts - readme-links-test: - uses: ./.github/workflows/test-readme-links.yml + # TODO Re-enable once rename is done (too many dead links until new javadoc is published) + # readme-links-test: + # uses: ./.github/workflows/test-readme-links.yml generated-api-diff: runs-on: ubuntu-latest @@ -43,8 +44,8 @@ jobs: - name: gradle openApiGenerate (PR ref) uses: ./.github/actions/build with: - args: 'openApiGenerate' - - run: mv ./library/build/generated/openapi-generator ./pr-ref-api + args: 'openApiGenerate postProcessGeneratedApi' + - run: mv ./library/build/post-processed-api ./pr-ref-api - name: Checkout base ref uses: actions/checkout@v4 with: @@ -53,8 +54,8 @@ jobs: - name: gradle openApiGenerate (base ref) uses: ./.github/actions/build with: - args: '-p ./base-ref-checkout openApiGenerate' - - run: mv ./base-ref-checkout/library/build/generated/openapi-generator ./base-ref-api + args: '-p ./base-ref-checkout openApiGenerate postProcessGeneratedApi' + - run: mv ./base-ref-checkout/library/build/post-processed-api ./base-ref-api - name: Diff generated APIs run: | echo -e '### Generated API diff\n\n```diff' > comment.md diff --git a/.github/workflows/publish-library.yml b/.github/workflows/publish-library.yml index ea8df358..56b8becb 100644 --- a/.github/workflows/publish-library.yml +++ b/.github/workflows/publish-library.yml @@ -27,14 +27,17 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: gradle publishLibraryPublicationToMavenCentralRepository + - name: Verify that version property matches tag + if: ${{ inputs.dry_run != true }} + run: grep -qP '^version=${{ github.ref_name }}$' gradle.properties + - name: gradle publish uses: ./.github/actions/build with: dry-run: ${{ inputs.dry_run }} args: >- - publishLibraryPublicationToMavenCentralRepository + publishDevelocityApiKotlinPublicationToMavenCentralRepository + publishRelocationPublicationToMavenCentralRepository --rerun-tasks - '-Pversion=${{ github.ref_name }}' '-Pmaven.central.username=${{ secrets.MAVEN_CENTRAL_USERNAME }}' '-Pmaven.central.password=${{ secrets.MAVEN_CENTRAL_PASSWORD }}' '-Psigning.password=${{ secrets.GPG_PASSWORD }}' @@ -42,6 +45,6 @@ jobs: artifact-name: 'outputs' path-to-upload: | library/build/*-api.yaml - library/build/generated/open-api-generator/**/* + library/build/post-processed-api/**/* library/build/publications/**/* library/build/libs/**/* diff --git a/.github/workflows/submit-dependencies.yml b/.github/workflows/submit-dependencies.yml index d564f1da..3bcedd99 100644 --- a/.github/workflows/submit-dependencies.yml +++ b/.github/workflows/submit-dependencies.yml @@ -20,13 +20,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Set up Gradle - uses: ./.github/actions/build - name: Submit Gradle dependencies - uses: mikepenz/gradle-dependency-submission@v0.9.2 - with: - use-gradlew: false - include-build-environment: true - gradle-build-configuration-mapping: | - :library|compileClasspath - :library|runtimeClasspath + uses: gradle/actions/dependency-submission@v3 + env: + DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS: '.*[Tt]est(Compile|Runtime)Classpath' diff --git a/.github/workflows/update-api-spec.yml b/.github/workflows/update-api-spec.yml index 8fe739a2..8828c131 100644 --- a/.github/workflows/update-api-spec.yml +++ b/.github/workflows/update-api-spec.yml @@ -47,8 +47,8 @@ jobs: uses: peter-evans/create-pull-request@v6 with: branch: "${{ env.UPDATE_BRANCH }}" - commit-message: "Bump GE API spec version to ${{ env.NEW_VERSION }}" - title: "Bump GE API spec version to ${{ env.NEW_VERSION }}" + commit-message: "Bump Develocity API spec version to ${{ env.NEW_VERSION }}" + title: "Bump Develocity API spec version to ${{ env.NEW_VERSION }}" body: "https://docs.gradle.com/enterprise/api-manual/#release_history" author: "github-actions " committer: "github-actions " diff --git a/.gitignore b/.gitignore index b360c326..90de942b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build !*/*/src/**/build .ipynb_checkpoints +.venv __pycache__ **/.log diff --git a/README.md b/README.md index 4b9f0493..4e18d325 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,42 @@ -# Gradle Enterprise API Kotlin +# Develocity API Kotlin -[![Maven Central](https://img.shields.io/badge/Maven%20Central-2023.3.1-blue)][14] -[![Javadoc](https://img.shields.io/badge/Javadoc-2023.3.1-orange)][7] +[![Maven Central](https://img.shields.io/badge/Maven%20Central-2024.1.0-blue)][14] +[![Javadoc](https://img.shields.io/badge/Javadoc-2024.1.0-orange)][7] -A Kotlin library to access the [Gradle Enterprise API][1], easy to use from: +(formerly `gradle-enterprise-api-kotlin`) + +A Kotlin library to access the [Develocity API][1], easy to use from: - [Jupyter notebooks with the Kotlin kernel][29] - [Kotlin scripts (`kts`)][27] - [Kotlin projects][28] ```kotlin -val api = GradleEnterpriseApi.newInstance() -api.buildsApi.getBuilds(since = yesterdayMilli).forEach { +val api = DevelocityApi.newInstance() +api.buildsApi.getBuildsFlow(fromInstant = 0, query = "buildStartTime<-1d").forEach { println(it) } ``` - The library takes care of caching under the hood (opt-in) and provides some convenience extensions. +The library takes care of caching under the hood (opt-in) and provides some convenience extensions. ## Setup -Set up once and use the library from anywhere in your machine: - -- [`GRADLE_ENTERPRISE_API_URL`][16] environment variable: the URL of your Gradle Enterprise instance -- [`GRADLE_ENTERPRISE_API_TOKEN`][17] environment variable: an API access token for the Gradle - Enterprise instance. - - Or a macOS keychain entry labeled `gradle-enterprise-api-token` (recommended). - -
- - How to get an API token - - The Gradle Enterprise user must have the “Export build data via the API” permission. - - 1. Sign in to Gradle Enterprise - 2. Go to "My settings" from the user menu in the top right-hand corner of the page - 3. Go to "Access keys" from the sidebar - 4. Click "Generate" on the right-hand side and copy the generated token. - -
+Set up environment variables and use the library from any notebook, script or project: -That's it! You can now use the library without any code configuration from notebooks, scripts or -projects. +- [`DEVELOCITY_API_URL`][16]: the URL of your Develocity instance +- [`DEVELOCITY_API_TOKEN`][17]: an [access key][31] for the Develocity instance +- [`DEVELOCITY_API_CACHE_ENABLED`][12] (optional, off by default): enables caching for some + requests (see [caveats][13]) ### Setup snippets -ℹ️ The library is now published to Maven Central under `com.gabrielfeo`. Maven Central is -recommended over JitPack. -
Add to a Jupyter notebook ``` %useLatestDescriptors -%use gradle-enterprise-api-kotlin(version=2023.3.1) +%use develocity-api-kotlin(version=2024.1.0) ```
@@ -61,7 +45,7 @@ recommended over JitPack. Add to a Kotlin script ```kotlin -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2024.1.0") ``` @@ -71,7 +55,7 @@ recommended over JitPack. ```kotlin dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") + implementation("com.gabrielfeo:develocity-api-kotlin:2024.1.0") } ``` @@ -79,15 +63,18 @@ dependencies { ## Usage -The [`GradleEnterpriseApi`][9] interface represents the Gradle Enterprise REST API. It contains -the 4 APIs exactly as listed in the [REST API Manual][5]: +The [`DevelocityApi`][9] interface represents the Develocity REST API. It contains +all the APIs exactly as listed in the [REST API Manual][5]: ```kotlin -interface GradleEnterpriseApi { +interface DevelocityApi { val buildsApi: BuildsApi + val testsApi: TestsApi val buildCacheApi: BuildCacheApi + val projectsApi: ProjectsApi val metaApi: MetaApi val testDistributionApi: TestDistributionApi + val authApi: AuthApi // ... } ``` @@ -105,38 +92,63 @@ For most cases like scripts and notebooks, simply use [runBlocking][30]: ```kotlin runBlocking { - val builds: List = api.buildsApi.getBuilds(since = yesterdayMilli) + val builds: List = api.buildsApi.getBuilds(fromInstant = 0, query = "...") } ``` -It's recommended to call [`GradleEnterpriseApi.shutdown()`][11] at the end of scripts to release -resources and let the program exit. Otherwise, it'll keep running for an extra ~60s after code -finishes, as an [expected behavior of OkHttp][4]. - ### Caching HTTP caching is available, which can speed up queries significantly, but is -off by default. Enable by simply setting [`GRADLE_ENTERPRISE_API_CACHE_ENABLED`][12] to `true`. See +off by default. Enable by simply setting [`DEVELOCITY_API_CACHE_ENABLED`][12] to `true`. See [`CacheConfig`][13] for caveats. ### Extensions Explore the library's convenience extensions: -[`com.gabrielfeo.gradle.enterprise.api.extension`][25]. +[`com.gabrielfeo.develocity.api.extension`][25]. -What you'll probably use the most is [`getGradleAttributesFlow`][24], which will call -`/api/builds` to get the list of build IDs since a given date and join each with -`/api/builds/{id}/gradle-attributes`, which contains tags and custom values on each build. It -also takes care of paging under-the-hood, returning a [`Flow`][26] of all builds since the given -date, so you don't have to worry about the REST API's limit of 1000 builds per request: +By default, the API's most common endpoint, `/api/builds`, is paginated. The library provides a +[`getBuildsFlow`][24] extension to handle paging under-the-hood and yield all builds as you collect +them: ```kotlin -val builds: Flow = api.buildsApi.getGradleAttributesFlow(since = lastYear) +val builds: Flow = api.buildsApi.getBuildsFlow(fromInstant = 0, query = "...") builds.collect { // ... } ``` +### Shutdown + +By default, the library keeps some of its resources (like threads) alive until idle, in +case they're needed again. This is an optimization of [OkHttp][4]. If you're working on a notebook +or have a long-living program that fetches builds continuosly, no shutdown is needed. + +```kotlin +val api = DevelocityApi.newInstance() +while (true) { + delay(2.minutes) + processNewBuilds(api.buildsApi.getBuildsFlow(query = "...")) + // Don't worry about shutdown +} +``` + +In other cases (i.e. fetching some builds and exiting), you might want to call +[`DevelocityApi.shutdown()`][11] so that the program exits immediately: + +```kotlin +val api = DevelocityApi.newInstance() +printMetrics(api.buildsApi.getBuildsFlow(query = "...")) +// Call shutdown if you expect the program to exit now +api.shutdown() +``` + +### Working samples + +- [Jupyter notebooks with the Kotlin kernel][29] +- [Kotlin scripts (`kts`)][27] +- [Kotlin projects][28] + ## Documentation [![Javadoc](https://img.shields.io/badge/javadoc-latest-orange)][7] @@ -148,7 +160,7 @@ from the same OpenAPI spec. ## Optional setup Creating a custom [`Config`][8] allows you to change library settings via code instead of -environment variables. It also lets you share resource between the library's `OkHttpClient` and +environment variables. It also lets you share resources between the library's `OkHttpClient` and your own. For example: ```kotlin @@ -157,25 +169,23 @@ val config = Config( apiToken = { vault.getGeApiToken() }, clientBuilder = existingClient.newBuilder(), ) -val api = GradleEnterpriseApi.newInstance(config) -api.buildsApi.getBuilds(since = yesterdayMilli) +val api = DevelocityApi.newInstance(config) +api.buildsApi.getBuilds(fromInstant = yesterdayMilli) ``` See the [`Config`][8] documentation for more. ## More info -- Currently built for Gradle Enterprise `2022.4`, but should work fine with previous and - future versions. The library will be updated regularly for new API versions. - Use JDK 8 or 14+ to run, if you want to avoid the ["illegal reflective access" warning about Retrofit][3] - All classes live in these packages. If you need to make small edits to scripts where there's - no auto-complete, wildcard imports can be used: + no auto-complete, wildcard imports can be used (in notebooks, they're added automatically): ```kotlin -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.model.* -import com.gabrielfeo.gradle.enterprise.api.model.extension.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.model.* +import com.gabrielfeo.develocity.api.model.extension.* ``` [1]: https://docs.gradle.com/enterprise/api-manual/ @@ -184,25 +194,27 @@ import com.gabrielfeo.gradle.enterprise.api.model.extension.* [4]: https://github.com/square/retrofit/issues/3144#issuecomment-508300518 [5]: https://docs.gradle.com/enterprise/api-manual/ref/2022.4.html [6]: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-gradle-plugin/README.adoc -[7]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/ -[8]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/index.html -[9]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/ -[11]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/shutdown.html -[12]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/-cache-config/cache-enabled.html -[13]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/-cache-config/index.html -[14]: https://central.sonatype.com/artifact/com.gabrielfeo/gradle-enterprise-api-kotlin/2023.3.1 -[16]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/api-url.html -[17]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-config/api-token.html -[18]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/index.html -[19]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.model/-gradle-attributes/index.html -[20]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/index.html -[21]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/get-builds.html -[22]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-builds-api/get-gradle-attributes.html -[23]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api/-gradle-enterprise-api/-default-instance/index.html -[24]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/get-gradle-attributes-flow.html -[25]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/library/com.gabrielfeo.gradle.enterprise.api.extension/index.html +[7]: https://gabrielfeo.github.io/develocity-api-kotlin/ +[8]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/index.html +[9]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-develocity-api/ +[11]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-develocity-api/shutdown.html +[12]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/-cache-config/cache-enabled.html +[13]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/-cache-config/index.html +[14]: https://central.sonatype.com/artifact/com.gabrielfeo/develocity-api-kotlin/2023.4.0 +[16]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/api-url.html +[17]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-config/api-token.html +[18]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/index.html +[19]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.model/-gradle-attributes/index.html +[20]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/index.html +[21]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/get-builds.html +[22]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-builds-api/get-gradle-attributes.html +[23]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api/-develocity-api/-default-instance/index.html +[24]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.extension/get-builds-flow.html +[25]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.extension/index.html [26]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/ -[27]: ./examples/example-script.main.kts +[27]: ./examples/example-scripts/example-script.main.kts [28]: ./examples/example-project -[29]: https://nbviewer.org/github/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb +[29]: https://nbviewer.org/github/gabrielfeo/develocity-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb [30]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html +[31]: ./docs/AccessKeys.md +[32]: ./examples diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 00000000..3119e294 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,24 @@ +@file:Suppress("UnstableApiUsage") + +plugins { + `kotlin-dsl` +} + +testing { + suites { + register("functionalTest") { + useJUnitJupiter() + } + } +} + +gradlePlugin { + testSourceSets(sourceSets["functionalTest"]) +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.9.20") + implementation("org.openapitools:openapi-generator-gradle-plugin:7.4.0") + "functionalTestImplementation"(project) +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 00000000..90be858b --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,8 @@ +@file:Suppress("UnstableApiUsage") + +dependencyResolutionManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} diff --git a/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt b/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt new file mode 100644 index 00000000..02692a82 --- /dev/null +++ b/build-logic/src/functionalTest/kotlin/com/gabrielfeo/task/PostProcessGeneratedApiTest.kt @@ -0,0 +1,180 @@ +package com.gabrielfeo.task + +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.io.File + +class PostProcessGeneratedApiTest { + + // Prefer over TempDir to inspect files after tests when troubleshooting + private val tempDir: File = File("./build/test-workdir/") + + @BeforeEach + fun setup() { + tempDir.deleteRecursively() + tempDir.mkdirs() + } + + /** + * - Fixes missing model imports by replacing all with a wildcard (OpenAPITools/openapi-generator#14871) + * - Replaces return types of Response for X, for more idiomatic usage with coroutines + * - Adds @JvmSuppressWildcards to avoid square/retrofit#3275 + */ + @Test + fun apiInterfacePostProcessing() = testPostProcessing( + inputPath = "com/gabrielfeo/develocity/api/BuildsApi.kt", + inputContent = """ + package com.gabrielfeo.develocity.api + + import com.gabrielfeo.develocity.api.internal.infrastructure.CollectionFormats.* + import retrofit2.http.* + import retrofit2.Response + import okhttp3.RequestBody + import com.squareup.moshi.Json + + import com.gabrielfeo.develocity.api.model.ApiProblem + import com.gabrielfeo.develocity.api.model.Build + import com.gabrielfeo.develocity.api.model.BuildModelQuery + import com.gabrielfeo.develocity.api.model.BuildQuery + import com.gabrielfeo.develocity.api.model.BuildsQuery + import com.gabrielfeo.develocity.api.model.GradleAttributes + import com.gabrielfeo.develocity.api.model.GradleBuildCachePerformance + import com.gabrielfeo.develocity.api.model.GradleNetworkActivity + import com.gabrielfeo.develocity.api.model.GradleProject + import com.gabrielfeo.develocity.api.model.MavenAttributes + import com.gabrielfeo.develocity.api.model.MavenBuildCachePerformance + import com.gabrielfeo.develocity.api.model.MavenDependencyResolution + import com.gabrielfeo.develocity.api.model.MavenModule + + interface BuildsApi { + /** + * Get the common attributes of a Build Scan. + * The contained attributes are build tool agnostic. + * Responses: + * - 200: The common attributes of a Build Scan. + * - 400: The request cannot be fulfilled due to a problem. + * - 404: The referenced resource either does not exist or the permissions to know about it are missing. + * - 500: The server encountered an unexpected error. + * - 503: The server is not ready to handle the request. + * + * @param id The Build Scan ID. + * @param models The list of build models to return in the response for each build. If not provided, no models are returned. (optional) + * @param availabilityWaitTimeoutSecs The time in seconds the server should wait for ingestion before returning a wait timeout response. (optional) + * @return [Build] + */ + @GET("api/builds/{id}") + suspend fun getBuild(@Path("id") id: kotlin.String, @Query("models") models: kotlin.collections.List? = null, @Query("availabilityWaitTimeoutSecs") availabilityWaitTimeoutSecs: kotlin.Int? = null): Response + """.trimIndent(), + outputPath = "com/gabrielfeo/develocity/api/BuildsApi.kt", + outputContent = """ + package com.gabrielfeo.develocity.api + + import com.gabrielfeo.develocity.api.internal.infrastructure.CollectionFormats.* + import retrofit2.http.* + import retrofit2.Response + import okhttp3.RequestBody + import com.squareup.moshi.Json + + import com.gabrielfeo.develocity.api.model.* + + @JvmSuppressWildcards + interface BuildsApi { + /** + * Get the common attributes of a Build Scan. + * The contained attributes are build tool agnostic. + * Responses: + * - 200: The common attributes of a Build Scan. + * - 400: The request cannot be fulfilled due to a problem. + * - 404: The referenced resource either does not exist or the permissions to know about it are missing. + * - 500: The server encountered an unexpected error. + * - 503: The server is not ready to handle the request. + * + * @param id The Build Scan ID. + * @param models The list of build models to return in the response for each build. If not provided, no models are returned. (optional) + * @param availabilityWaitTimeoutSecs The time in seconds the server should wait for ingestion before returning a wait timeout response. (optional) + * @return [Build] + */ + @GET("api/builds/{id}") + suspend fun getBuild(@Path("id") id: kotlin.String, @Query("models") models: kotlin.collections.List? = null, @Query("availabilityWaitTimeoutSecs") availabilityWaitTimeoutSecs: kotlin.Int? = null): Build + """.trimIndent(), + ) + + /** + * - Fixes enum case names: gradleMinusAttributes -> gradleAttributes + */ + @Test + fun buildModelNameEnumPostProcessing() = testPostProcessing( + inputPath = "com/gabrielfeo/develocity/api/model/BuildModelName.kt", + inputContent = """ + @JsonClass(generateAdapter = false) + enum class BuildModelName(val value: kotlin.String) { + + @Json(name = "gradle-attributes") + gradleMinusAttributes("gradle-attributes"), + + @Json(name = "gradle-build-cache-performance") + gradleMinusBuildMinusCacheMinusPerformance("gradle-build-cache-performance"), + """.trimIndent(), + outputPath = "com/gabrielfeo/develocity/api/model/BuildModelName.kt", + outputContent = """ + @JsonClass(generateAdapter = false) + enum class BuildModelName(val value: kotlin.String) { + + @Json(name = "gradle-attributes") + gradleAttributes("gradle-attributes"), + + @Json(name = "gradle-build-cache-performance") + gradleBuildCachePerformance("gradle-build-cache-performance"), + """.trimIndent(), + ) + + private fun testPostProcessing( + inputPath: String, + inputContent: String, + outputPath: String, + outputContent: String, + ) { + val inputDir = File(tempDir, "input").also { it.mkdirs() } + val outputDir = File(tempDir, "output").also { it.mkdirs() } + File(inputDir, inputPath).apply { + parentFile.mkdirs() + writeText(inputContent) + } + val projectDir = writeTestProject(inputDir, outputDir) + runBuild(projectDir, listOf("postProcessGeneratedApi", "--stacktrace")) + assertEquals(outputContent, File(outputDir, outputPath).readText()) + } + + @Suppress("SameParameterValue") + private fun runBuild(projectDir: File, args: List) { + GradleRunner.create() + .withProjectDir(projectDir) + .withPluginClasspath() + .withArguments(args) + .build() + } + + private fun writeTestProject(inputDir: File, outputDir: File): File { + val projectDir = File(tempDir, "project").also { it.mkdirs() } + File(projectDir, "settings.gradle").writeText("") + File(projectDir, "build.gradle").writeText( + // language=groovy + """ + import com.gabrielfeo.task.PostProcessGeneratedApi + + plugins { + id("com.gabrielfeo.no-op") + } + + tasks.register("postProcessGeneratedApi", PostProcessGeneratedApi) { + originalFiles = new File("${inputDir.absolutePath}") + modelsPackage = "com.gabrielfeo.develocity.api.model" + postProcessedFiles = new File("${outputDir.absolutePath}") + } + """.trimIndent() + ) + return projectDir + } +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts new file mode 100644 index 00000000..a0e744ea --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/develocity-api-code-generation.gradle.kts @@ -0,0 +1,73 @@ +package com.gabrielfeo + +import com.gabrielfeo.task.PostProcessGeneratedApi +import org.gradle.kotlin.dsl.* + +plugins { + id("com.gabrielfeo.kotlin-jvm-library") + id("org.openapi.generator") +} + +val localSpecPath = providers.gradleProperty("localSpecPath") +val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse( + providers.gradleProperty("develocity.version").map { geVersion -> + val majorVersion = geVersion.substringBefore('.').toInt() + val specName = when { + majorVersion <= 2023 -> "gradle-enterprise-$geVersion-api.yaml" + else -> "develocity-$geVersion-api.yaml" + } + "https://docs.gradle.com/enterprise/api-manual/ref/$specName" + } +) + +val downloadApiSpec by tasks.registering { + onlyIf { !localSpecPath.isPresent() } + val spec = resources.text.fromUri(remoteSpecUrl) + val specName = remoteSpecUrl.map { it.substringAfterLast('/') } + val outFile = project.layout.buildDirectory.file(specName) + inputs.property("Spec URL", remoteSpecUrl) + outputs.file(outFile) + doLast { + logger.info("Downloaded API spec from ${remoteSpecUrl.get()}") + spec.asFile().renameTo(outFile.get().asFile) + } +} + +openApiGenerate { + generatorName.set("kotlin") + val spec = when { + localSpecPath.isPresent() -> localSpecPath.map { rootProject.file(it).absolutePath } + else -> downloadApiSpec.map { it.outputs.files.first().absolutePath } + } + inputSpec.set(spec) + val generateDir = project.layout.buildDirectory.dir("generated-api") + .map { it.asFile.absolutePath } + outputDir.set(generateDir) + val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore") + ignoreFileOverride.set(ignoreFile.asFile.absolutePath) + apiPackage.set("com.gabrielfeo.develocity.api") + modelPackage.set("com.gabrielfeo.develocity.api.model") + packageName.set("com.gabrielfeo.develocity.api.internal") + invokerPackage.set("com.gabrielfeo.develocity.api.internal") + additionalProperties.put("library", "jvm-retrofit2") + additionalProperties.put("useCoroutines", true) + additionalProperties.put("enumPropertyNaming", "camelCase") + cleanupOutput.set(true) +} + +val postProcessGeneratedApi by tasks.registering(PostProcessGeneratedApi::class) { + val generatedSrc = tasks.openApiGenerate + .flatMap { it.outputDir } + .map { File(it) } + originalFiles.convention(project.layout.dir(generatedSrc)) + postProcessedFiles.convention(project.layout.buildDirectory.dir("post-processed-api")) + modelsPackage.convention(tasks.openApiGenerate.flatMap { it.modelPackage }) +} + +sourceSets { + main { + java { + srcDir(postProcessGeneratedApi) + } + } +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts new file mode 100644 index 00000000..10a28d29 --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/kotlin-jvm-library.gradle.kts @@ -0,0 +1,49 @@ +package com.gabrielfeo + +import org.jetbrains.dokka.DokkaConfiguration.Visibility.PUBLIC +import org.jetbrains.dokka.gradle.DokkaTask +import java.net.URL + +plugins { + id("org.jetbrains.kotlin.jvm") + id("org.jetbrains.dokka") + `java-library` +} + +val repoUrl: Provider = providers.gradleProperty("repo.url") + +java { + withSourcesJar() + withJavadocJar() + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + vendor.set(JvmVendorSpec.AZUL) + } +} + +tasks.withType().configureEach { + dokkaSourceSets.all { + sourceRoot("src/main/kotlin") + sourceLink { + localDirectory.set(file("src/main/kotlin")) + remoteUrl.set(repoUrl.map { URL("$it/blob/$version/src/main/kotlin") }) + remoteLineSuffix.set("#L") + } + jdkVersion.set(11) + suppressGeneratedFiles.set(false) + documentedVisibilities.set(setOf(PUBLIC)) + perPackageOption { + matchingRegex.set(""".*\.internal.*""") + suppress.set(true) + } + externalDocumentationLink("https://kotlinlang.org/api/kotlinx.coroutines/") + externalDocumentationLink("https://square.github.io/okhttp/4.x/okhttp/") + externalDocumentationLink("https://square.github.io/retrofit/2.x/retrofit/") + externalDocumentationLink("https://square.github.io/moshi/1.x/moshi/") + externalDocumentationLink("https://square.github.io/moshi/1.x/moshi-kotlin/") + } +} + +tasks.named("javadocJar") { + from(tasks.dokkaHtml) +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts new file mode 100644 index 00000000..bc122ecc --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/no-op.gradle.kts @@ -0,0 +1,3 @@ +package com.gabrielfeo + +// Plugin is only used to test the tasks logic. Applying it adds the task classes to the classpath. \ No newline at end of file diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt b/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt new file mode 100644 index 00000000..e3775845 --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/task/PostProcessGeneratedApi.kt @@ -0,0 +1,99 @@ +package com.gabrielfeo.task + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.withGroovyBuilder +import java.io.File +import javax.inject.Inject + +@CacheableTask +abstract class PostProcessGeneratedApi @Inject constructor( + private val fsOperations: FileSystemOperations, +) : DefaultTask() { + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val originalFiles: DirectoryProperty + + @get:Input + abstract val modelsPackage: Property + + @get:OutputDirectory + abstract val postProcessedFiles: DirectoryProperty + + @TaskAction + fun doWork() { + postProcessedFiles.get().asFile.deleteRecursively() + fsOperations.copy { + from(originalFiles) + into(postProcessedFiles) + } + postProcess( + srcDir = postProcessedFiles.get().dir("src/main/kotlin").asFile, + modelsPackage = modelsPackage.get() + ) + } + + private fun postProcess(srcDir: File, modelsPackage: String) { + // Replace Response with X in every method return type of DevelocityApi.kt + ant.withGroovyBuilder { + "replaceregexp"( + "match" to ": Response<(.*?)>$", + "replace" to """: \1""", + "flags" to "gm", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/develocity/api/*Api.kt", + ) + } + } + // Add @JvmSuppressWildcards to avoid square/retrofit#3275 + ant.withGroovyBuilder { + "replaceregexp"( + "match" to "interface", + "replace" to """ + @JvmSuppressWildcards + interface + """.trimIndent(), + "flags" to "m", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/develocity/api/*Api.kt", + ) + } + } + // Workaround for missing imports of exploded queries + val escapedModelPackage = modelsPackage.replace(".", "\\.") + val lastModelImportInFilePattern = """(?:import $escapedModelPackage.[.\w]+\s)+""" + ant.withGroovyBuilder { + "replaceregexp"( + "match" to lastModelImportInFilePattern, + // Import all models instead: current + missing ones + "replace" to "import $modelsPackage.*\n", + "flags" to "m", + ) { + "fileset"( + "dir" to srcDir + ) + } + } + // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes + ant.withGroovyBuilder { + "replaceregexp"( + "match" to "Minus", + "replace" to "", + "flags" to "mg", + ) { + "fileset"( + "dir" to srcDir, + "includes" to "com/gabrielfeo/develocity/api/model/BuildModelName.kt", + ) + } + } + } +} diff --git a/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts b/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts new file mode 100644 index 00000000..7b0c5f70 --- /dev/null +++ b/build-logic/src/main/kotlin/com/gabrielfeo/test-suites.gradle.kts @@ -0,0 +1,40 @@ +package com.gabrielfeo + +plugins { + id("org.jetbrains.kotlin.jvm") + `java-test-fixtures` +} + +testing { + suites { + // 'test' is registered by default + register("integrationTest") + withType().configureEach { + useKotlinTest() + } + } +} + +tasks.named("check") { + dependsOn("integrationTest") +} + +kotlin { + target { + val main by compilations.getting + val integrationTest by compilations.getting + val test by compilations.getting + val testFixtures by compilations.getting + test.associateWith(main) + test.associateWith(testFixtures) + integrationTest.associateWith(main) + integrationTest.associateWith(testFixtures) + testFixtures.associateWith(main) + } +} + +// TODO Unapply test-fixtures and delete the source set, since we're not publishing it? +components.named("java") { + withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } + withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } +} diff --git a/build.gradle.kts b/build.gradle.kts index 954800a4..e69de29b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,15 +0,0 @@ -plugins { - id("org.jetbrains.kotlin.jvm") version "1.9.22" apply false - id("org.jetbrains.dokka") version "1.9.10" apply false - id("org.openapi.generator") version "7.2.0" apply false -} - -val group by project.properties -val artifact by project.properties - -project(":examples:example-project:app").configurations.configureEach { - resolutionStrategy.dependencySubstitution { - substitute(module("$group:$artifact")) - .using(project(":library")) - } -} diff --git a/docs/AccessKeys.md b/docs/AccessKeys.md new file mode 100644 index 00000000..b02562c7 --- /dev/null +++ b/docs/AccessKeys.md @@ -0,0 +1,32 @@ +# Access key / API token + +[All API requests require authentication][1]. Provide a valid access key of your Develocity instance +as the `DEVELOCITY_API_TOKEN` environment variable. + +## How to get an access key + +1. Sign in to Develocity (with a user that has “Export build data” permission) +2. Go to "My settings" from the user menu in the top right-hand corner of the page +3. Go to "Access keys" from the sidebar +4. Click "Generate" on the right-hand side +5. Set key as the `DEVELOCITY_API_TOKEN` environment variable when using the library + +## Migrating from macOS keychain support + +This library used to support storing the key in the macOS keychain as `gradle-enterprise-api-kotlin`. +This feature was deprecated in 2023.4.0, then removed in 2024.1.0. You may use the method of your choice +(secret managers, password manager CLIs, etc.) to store and retrieve the key to an environment. + +If you used the key from keychain and need a drop-in replacement: + +``` +# Create an alias in your shell to fetch the key from keychain +echo 'alias dat="security find-generic-password -w -a "$LOGNAME" -s gradle-enterprise-api-kotlin"' >> ~/.zshrc + +# Retrieve it to the environment variable before running the program +DEVELOCITY_API_TOKEN="$(dat)" ./my-script.main.kts +DEVELOCITY_API_TOKEN="$(dat)" jupyter lab +DEVELOCITY_API_TOKEN="$(dat)" idea my-project +``` + +[1]: https://docs.gradle.com/enterprise/api-manual/#access_control diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index f09bed60..41edd4be 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -4,20 +4,27 @@ plugins { base } +// Cross-configure so we don't pollute the example buildscript +project("example-project").configurations.configureEach { + resolutionStrategy.dependencySubstitution { + substitute(module("com.gabrielfeo:develocity-api-kotlin")) + .using(project(":library")) + } +} + val exampleTestTasks = ArrayList>() exampleTestTasks += tasks.register("runExampleScript") { group = "Application" - description = "Runs the 'example-script.main.kts' script" - commandLine("kotlinc", "-script", file("example-script.main.kts")) + description = "Runs the './example-scripts/example-script.main.kts' script" + commandLine("kotlinc", "-script", file("./example-scripts/example-script.main.kts")) environment("JAVA_OPTS", "-Xmx1g") } -exampleTestTasks += tasks.register("runExampleProject") { +exampleTestTasks += tasks.register("runExampleProject") { group = "Application" - description = "Runs examples/example-project as a standalone build" - dir = file("example-project") - tasks = listOf("run") + description = "Runs examples/example-project" + dependsOn(":examples:example-project:run") } val notebooks = fileTree(file("example-notebooks")) { diff --git a/examples/example-notebooks/MostFrequentBuilds.ipynb b/examples/example-notebooks/MostFrequentBuilds.ipynb index 1a1bfb65..721e2139 100644 --- a/examples/example-notebooks/MostFrequentBuilds.ipynb +++ b/examples/example-notebooks/MostFrequentBuilds.ipynb @@ -9,19 +9,19 @@ "source": [ "# Most frequent builds\n", "\n", - "See what builds are most commonly invoked by developers, e.g. `clean assemble`, `test` or `check`. You can [set up the URL and a token for your Gradle Enterprise instance](https://github.com/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/README.md#setup) and run this notebook as-is for your own project.\n", + "See what builds are most commonly invoked by developers, e.g. `clean assemble`, `test` or `check`. You can [set up the URL and a token for your Develocity instance](https://github.com/gabrielfeo/develocity-api-kotlin/blob/main/README.md#setup) and run this notebook as-is for your own project.\n", "\n", "This is a simple example of something you can do with the API. It could bring insights, for example:\n", "\n", "- \"Our developers frequently `clean` together with `assemble`. We should ask them why, because they shouldn't have to. Just an old habit from Maven or are they working around a build issue we don't know about?\"\n", "- \"Some are doing `check` builds locally, which we set up to trigger our notably slow legacy tests. We should suggest they run `test` instead, leaving `check` for CI to run.\"\n", "\n", - "This notebook will take you through using gradle-enterprise-api-kotlin in Jupyter, but it won't get into what a notebook is and how to run it. If you're not familiar with Jupyter:\n", + "This notebook will take you through using develocity-api-kotlin in Jupyter, but it won't get into what a notebook is and how to run it. If you're not familiar with Jupyter:\n", "\n", "- [Kotlin for data science overview](https://kotlinlang.org/docs/data-science-overview.html)\n", "- [Kotlin for Jupyter notebooks](https://github.com/cheptsov/kotlin-jupyter-demo/blob/master/index.ipynb)\n", "\n", - "Note: GitHub preview won't render tables or graphs. I recommend previewing this in the [online Jupyter nbviewer](https://nbviewer.org/github/gabrielfeo/gradle-enterprise-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb)." + "Note: GitHub preview won't render tables or graphs. I recommend previewing this in the [online Jupyter nbviewer](https://nbviewer.org/github/gabrielfeo/develocity-api-kotlin/blob/main/examples/example-notebooks/MostFrequentBuilds.ipynb)." ] }, { @@ -35,17 +35,17 @@ "Add libraries to use, via line magics. `%use` is a [line magic](https://github.com/Kotlin/kotlin-jupyter#line-magics) of the Kotlin kernel that can do much more than adding the library. To illustrate, this setup can be replaced with a single line magic.\n", "\n", "```kotlin\n", - "@file:DependsOn(\"com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1\")\n", + "@file:DependsOn(\"com.gabrielfeo:develocity-api-kotlin:2024.1.0\")\n", "\n", - "import com.gabrielfeo.gradle.enterprise.api.*\n", - "import com.gabrielfeo.gradle.enterprise.api.model.*\n", - "import com.gabrielfeo.gradle.enterprise.api.extension.*\n", + "import com.gabrielfeo.develocity.api.*\n", + "import com.gabrielfeo.develocity.api.model.*\n", + "import com.gabrielfeo.develocity.api.extension.*\n", "```\n", "\n", "is the same as:\n", "\n", "```\n", - "%use gradle-enterprise-api-kotlin(version=2023.3.1)\n", + "%use develocity-api-kotlin(version=2024.1.0)\n", "```" ] }, @@ -53,39 +53,19 @@ "cell_type": "code", "execution_count": 1, "id": "97aad45e-fdb0-4ca3-8049-cfa7ce934bbb", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:43.176052Z", + "start_time": "2024-04-01T20:24:41.907349Z" + } + }, "outputs": [], "source": [ "%useLatestDescriptors\n", - "%use gradle-enterprise-api-kotlin(version=2023.3.1)\n", - "%use coroutines(v=1.7.1)" - ] - }, - { - "cell_type": "markdown", - "id": "6d9bd2a4-dab0-4366-9afe-c5f9439cdc18", - "metadata": {}, - "source": [ - "## Parameters\n", - "\n", - "Change these to process a longer or shorter time range (and test faster)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cb319237-fb3d-4973-80ff-0a1ef508cfed", - "metadata": {}, - "outputs": [], - "source": [ - "import java.time.*\n", - "\n", - "val startDate = LocalDate.now().minusWeeks(1)\n", + "%use develocity-api-kotlin(version=2024.1.0)\n", + "%use coroutines(v=1.7.1)\n", "\n", - "val buildFilter: (GradleAttributes) -> Boolean = { build ->\n", - " \"LOCAL\" in build.tags\n", - " && !build.hasFailed\n", - "}" + "val api = DevelocityApi.newInstance()" ] }, { @@ -95,30 +75,53 @@ "source": [ "## Fetch builds\n", "\n", - "[getGradleAttributesFlow][1] is a utility to call `/api/builds` and join each ID with `/api/builds/{id}/gradle-attributes` at once.\n", + "Use [getBuildsFlow][1] to fetch all builds for a [query][2] with `/api/builds`.\n", + "\n", + "By default, \"builds\" from the API are just an ID and an upload time, but we can request more info to come in the same \n", + "response using the `models` parameter. \"Models\" are build details that would come from other endpoints. For example, \n", + "requesting models=[gradleAttributes][3] brings data from `/api/builds/{id}/gradle-attributes` in the same response.\n", "\n", - "[1]: https://gabrielfeo.github.io/gradle-enterprise-api-kotlin/gradle-enterprise-api-kotlin/com.gabrielfeo.gradle.enterprise.api/get-gradle-attributes-flow.html" + "[1]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api.extension/get-builds-flow.html\n", + "[2]: https://docs.gradle.com/enterprise/api-manual/#advanced_search_syntax \n", + "[3]: https://gabrielfeo.github.io/develocity-api-kotlin/library/com.gabrielfeo.develocity.api\n", + ".model/-build-model-name/gradle-attributes/index.html" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "588a699f", "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:45.458079Z", + "start_time": "2024-04-01T20:24:43.318588Z" + }, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "17907 builds\n" + ] + } + ], "source": [ "import java.time.temporal.*\n", "import java.util.LinkedList\n", "\n", - "val api = GradleEnterpriseApi.newInstance()\n", "val builds: List = runBlocking {\n", - " val startMilli = startDate.atStartOfDay(ZoneId.of(\"UTC\")).toInstant().toEpochMilli()\n", - " api.buildsApi.getGradleAttributesFlow(since = startMilli)\n", - " .filter(buildFilter)\n", - " .toList(LinkedList())\n", - "}" + " api.buildsApi.getBuildsFlow(\n", + " fromInstant = 0,\n", + " query = \"\"\"buildStartTime<-7d tag:local buildOutcome:failed\"\"\",\n", + " models = listOf(BuildModelName.gradleAttributes),\n", + " ).map {\n", + " it.models!!.gradleAttributes!!.model!!\n", + " }.toList(LinkedList())\n", + "}\n", + "\n", + "println(\"${builds.size} builds\")" ] }, { @@ -133,9 +136,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "5cd75a65-a819-497e-87c2-f0911cc1554f", - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:47.969270Z", + "start_time": "2024-04-01T20:24:45.457832Z" + } + }, "outputs": [ { "data": { @@ -347,7 +355,7 @@ } ], "source": [ - "%use dataframe(v=0.10.0)" + "%use dataframe(v=0.13.1)" ] }, { @@ -360,15 +368,108 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "adbd028f-d30a-489e-94bd-d8f968a659a3", "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:24:48.548709Z", + "start_time": "2024-04-01T20:24:47.958241Z" + }, "tags": [] }, "outputs": [ { "data": { - "application/kotlindataframe+json": "{\"nrow\":266,\"ncol\":2,\"columns\":[\"tasks\",\"count\"],\"kotlin_dataframe\":[{\"tasks\":\"app:assembleBrazilDebug\",\"count\":977},{\"tasks\":\"IDE sync\",\"count\":285},{\"tasks\":\"droid-cli:installDist\",\"count\":80},{\"tasks\":\"feature:order-delivery:waiting-platform-components:impl:compileReleaseSources\",\"count\":48},{\"tasks\":\"feature:home:impl:compileReleaseSources\",\"count\":17},{\"tasks\":\"feature:chat:impl:testReleaseUnitTest --tests br.com.ifood.chat.presentation.chat.viewModel.ChatViewModelTest.dispatchViewAction_withInitializeAction_andChatCreated_\",\"count\":16},{\"tasks\":\"clean\",\"count\":13},{\"tasks\":\"analytics-braze:testReleaseUnitTest --tests br.com.ifood.analytics.braze.AppBrazeAnalyticsProviderTest.sendEvent_featureFlagCanSendEvent_sendEvent\",\"count\":12},{\"tasks\":\"analytics-braze:testReleaseUnitTest --tests br.com.ifood.analytics.braze.AppBrazeAnalyticsProviderTest\",\"count\":12},{\"tasks\":\"app:assembleBrazilDebug :app:installBrazilDebug\",\"count\":11},{\"tasks\":\"buildPlugin\",\"count\":11},{\"tasks\":\"infrastructure:design-system:global-components-sample:assembleDebug\",\"count\":11},{\"tasks\":\"setupDependencies\",\"count\":10},{\"tasks\":\"feature:groceries:shopping-list:impl:testReleaseUnitTest --tests br.com.ifood.groceries.shoppinglist.presentation.shoppinglistvoice.ShoppingListVoiceSharedViewModelTest\",\"count\":8},{\"tasks\":\"feature:review:evaluating:impl:testReleaseUnitTest --tests br.com.ifood.review.presentation.viewmodel.ReviewViewModelTest\",\"count\":7},{\"tasks\":\"build-plugins:test --tests rasp.DexProtectorConfigManagerTest\",\"count\":7},{\"tasks\":\"build-plugins:functionalTest --tests rasp.AndroidRaspProtectionPluginTest\",\"count\":7},{\"tasks\":\"droid-cli:test --tests br.com.ifood.cli.util.RunCommandShellTest\",\"count\":7},{\"tasks\":\"feature:order-delivery:waiting:impl:testReleaseUnitTest\",\"count\":6},{\"tasks\":\"feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.contextcard.mapper.CartModelToMinimizedCartUiModelMapperTest\",\"count\":5}]}", + "application/kotlindataframe+json": { + "columns": [ + "tasks", + "count" + ], + "kotlin_dataframe": [ + { + "count": 4677, + "tasks": "app:assembleBrazilDebug" + }, + { + "count": 577, + "tasks": "kotlinLSPProjectDeps" + }, + { + "count": 420, + "tasks": "IDE sync" + }, + { + "count": 112, + "tasks": "feature:home:impl:testReleaseUnitTest --tests br.com.ifood.home.*" + }, + { + "count": 103, + "tasks": "feature:hits:cards:impl:testReleaseUnitTest --tests br.com.ifood.hits.communitybuytaxonomyitemslist.data.CommunityBuyTaxonomyItemsCardResponseToUiMapperTest" + }, + { + "count": 96, + "tasks": "feature:splash:impl:testReleaseUnitTest --tests br.com.ifood.splash.animation.CommemorativeCustomizationDefaultServiceTest" + }, + { + "count": 92, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.*" + }, + { + "count": 90, + "tasks": "feature:chat:core:impl:testReleaseUnitTest --tests br.com.ifood.chat.domain.service.ChatCallForegroundServiceTest" + }, + { + "count": 73, + "tasks": "feature:chat:core:impl:testReleaseUnitTest --tests br.com.ifood.chat.domain.service.ChatCallNotificationServiceTest" + }, + { + "count": 70, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.plugin.standard.items.item.ReplacementsControllerTest.initReplacements_forceUiModelRemap" + }, + { + "count": 58, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest" + }, + { + "count": 54, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.domain.factory.DefaultCartRemoteConfigsFactoryTest" + }, + { + "count": 54, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.domain.model.MerchantModelTest" + }, + { + "count": 51, + "tasks": "feature:chat:core:impl:testReleaseUnitTest --tests br.com.ifood.chat.domain.service.ChatCallForegroundServiceTest.onStartCommand_rejectCallIntent_stopsService" + }, + { + "count": 48, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.plugin.standard.clubpurchase.ClubPurchasePluginViewModelTest" + }, + { + "count": 48, + "tasks": "feature:hits:page:impl:testReleaseUnitTest --tests br.com.ifood.hits.page.presentation.HitsPageViewModelTest" + }, + { + "count": 45, + "tasks": "feature:discoverycards:impl:testReleaseUnitTest" + }, + { + "count": 44, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.domain.usecase.club.SetClubSummaryMetadataModelTest.invoke_withValidRulesAndRendering_returnCartWithUpdatedModel" + }, + { + "count": 43, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.processor.viewmodel.OrderPoolingProcessorTest.resumeOrderPolling_withPaymentDataEventAndPaymentEventNotAuthorized_handlePaymentActionData" + }, + { + "count": 41, + "tasks": "feature:checkout:core:impl:testReleaseUnitTest --tests br.com.ifood.checkout.presentation.processor.viewmodel.ClubSubscriptionProcessorTest" + } + ], + "ncol": 2, + "nrow": 2901 + }, "text/html": [ " \n", " \n", @@ -524,32 +625,180 @@ "}\n", "\n", "\n", + ":root {\n", + " --background: #fff;\n", + " --background-odd: #f5f5f5;\n", + " --background-hover: #d9edfd;\n", + " --header-text-color: #474747;\n", + " --text-color: #848484;\n", + " --text-color-dark: #000;\n", + " --text-color-medium: #737373;\n", + " --text-color-pale: #b3b3b3;\n", + " --inner-border-color: #aaa;\n", + " --bold-border-color: #000;\n", + " --link-color: #296eaa;\n", + " --link-color-pale: #296eaa;\n", + " --link-hover: #1a466c;\n", + "}\n", + "\n", + ":root[theme=\"dark\"], :root [data-jp-theme-light=\"false\"], .dataframe_dark{\n", + " --background: #303030;\n", + " --background-odd: #3c3c3c;\n", + " --background-hover: #464646;\n", + " --header-text-color: #dddddd;\n", + " --text-color: #b3b3b3;\n", + " --text-color-dark: #dddddd;\n", + " --text-color-medium: #b2b2b2;\n", + " --text-color-pale: #737373;\n", + " --inner-border-color: #707070;\n", + " --bold-border-color: #777777;\n", + " --link-color: #008dc0;\n", + " --link-color-pale: #97e1fb;\n", + " --link-hover: #00688e;\n", + "}\n", + "\n", + "p.dataframe_description {\n", + " color: var(--text-color-dark);\n", + "}\n", + "\n", + "table.dataframe {\n", + " font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n", + " font-size: 12px;\n", + " background-color: var(--background);\n", + " color: var(--text-color-dark);\n", + " border: none;\n", + " border-collapse: collapse;\n", + "}\n", + "\n", + "table.dataframe th, td {\n", + " padding: 6px;\n", + " border: 1px solid transparent;\n", + " text-align: left;\n", + "}\n", + "\n", + "table.dataframe th {\n", + " background-color: var(--background);\n", + " color: var(--header-text-color);\n", + "}\n", + "\n", + "table.dataframe td {\n", + " vertical-align: top;\n", + "}\n", + "\n", + "table.dataframe th.bottomBorder {\n", + " border-bottom-color: var(--bold-border-color);\n", + "}\n", + "\n", + "table.dataframe tbody > tr:nth-child(odd) {\n", + " background: var(--background-odd);\n", + "}\n", + "\n", + "table.dataframe tbody > tr:nth-child(even) {\n", + " background: var(--background);\n", + "}\n", + "\n", + "table.dataframe tbody > tr:hover {\n", + " background: var(--background-hover);\n", + "}\n", + "\n", + "table.dataframe a {\n", + " cursor: pointer;\n", + " color: var(--link-color);\n", + " text-decoration: none;\n", + "}\n", + "\n", + "table.dataframe tr:hover > td a {\n", + " color: var(--link-color-pale);\n", + "}\n", + "\n", + "table.dataframe a:hover {\n", + " color: var(--link-hover);\n", + " text-decoration: underline;\n", + "}\n", + "\n", + "table.dataframe img {\n", + " max-width: fit-content;\n", + "}\n", + "\n", + "table.dataframe th.complex {\n", + " background-color: var(--background);\n", + " border: 1px solid var(--background);\n", + "}\n", + "\n", + "table.dataframe .leftBorder {\n", + " border-left-color: var(--inner-border-color);\n", + "}\n", + "\n", + "table.dataframe .rightBorder {\n", + " border-right-color: var(--inner-border-color);\n", + "}\n", + "\n", + "table.dataframe .rightAlign {\n", + " text-align: right;\n", + "}\n", + "\n", + "table.dataframe .expanderSvg {\n", + " width: 8px;\n", + " height: 8px;\n", + " margin-right: 3px;\n", + "}\n", + "\n", + "table.dataframe .expander {\n", + " display: flex;\n", + " align-items: center;\n", + "}\n", + "\n", + "/* formatting */\n", + "\n", + "table.dataframe .null {\n", + " color: var(--text-color-pale);\n", + "}\n", + "\n", + "table.dataframe .structural {\n", + " color: var(--text-color-medium);\n", + " font-weight: bold;\n", + "}\n", + "\n", + "table.dataframe .dataFrameCaption {\n", + " font-weight: bold;\n", + "}\n", + "\n", + "table.dataframe .numbers {\n", + " color: var(--text-color-dark);\n", + "}\n", + "\n", + "table.dataframe td:hover .formatted .structural, .null {\n", + " color: var(--text-color-dark);\n", + "}\n", + "\n", + "table.dataframe tr:hover .formatted .structural, .null {\n", + " color: var(--text-color-dark);\n", + "}\n", "\n", "\n", " \n", " \n", " \n", - " \n", - "
\n", + "
\n", "\n", - "

... showing only top 20 of 266 rows

DataFrame: rowsCount = 266, columnsCount = 2

\n", + "

... showing only top 20 of 2901 rows

DataFrame: rowsCount = 2901, columnsCount = 2

\n", + "
taskscount
app:assembleBrazilDebug4677
kotlinLSPProjectDeps577
IDE sync420
feature:home:impl:testReleaseUnitTest...112
feature:hits:cards:impl:testReleaseUn...103
feature:splash:impl:testReleaseUnitTe...96
feature:checkout:core:impl:testReleas...92
feature:chat:core:impl:testReleaseUni...90
feature:chat:core:impl:testReleaseUni...73
feature:checkout:core:impl:testReleas...70
feature:checkout:core:impl:testReleas...58
feature:checkout:core:impl:testReleas...54
feature:checkout:core:impl:testReleas...54
feature:chat:core:impl:testReleaseUni...51
feature:checkout:core:impl:testReleas...48
feature:hits:page:impl:testReleaseUni...48
feature:discoverycards:impl:testRelea...45
feature:checkout:core:impl:testReleas...44
feature:checkout:core:impl:testReleas...43
feature:checkout:core:impl:testReleas...41
\n", " \n", " \n", " " ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -565,7 +814,7 @@ " count() into \"count\"\n", "}.sortByDesc(\"count\")\n", "\n", - "// Jupyter will render the last cell line\n", + "// Jupyter will render the last cell line (a String, an Int, a DataFrame, etc.)\n", "buildCounts" ] }, @@ -581,67 +830,28 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "8c28e684-0f3b-43d9-a3d2-7b1ef91fa6c4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - " " - ] - }, - "metadata": {}, - "output_type": "display_data" + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:25:13.190618Z", + "start_time": "2024-04-01T20:24:48.537811Z" } - ], + }, + "outputs": [], "source": [ - "%use kandy(v=0.4.1)" + "%use kandy(v=0.6.0)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 13, "id": "293b2b4d", "metadata": { + "ExecuteTime": { + "end_time": "2024-04-01T20:25:13.226464Z", + "start_time": "2024-04-01T20:24:50.790909Z" + }, "tags": [] }, "outputs": [ @@ -652,23 +862,19 @@ "output": { "data": { "count": [ - 977, - 285, - 80, - 48, - 17 + 4677, + 577, + 420 ], "tasks": [ "app:assembleBrazilDebug", - "IDE sync", - "droid-cli:installDist", - "feature:order-delivery:waiting-platform-components:impl:compileReleaseSources", - "feature:home:impl:compileReleaseSources" + "kotlinLSPProjectDeps", + "IDE sync" ] }, "ggsize": { "height": 250, - "width": 1000 + "width": 800 }, "kind": "plot", "layers": [ @@ -679,7 +885,7 @@ "y": "tasks" }, "orientation": "y", - "position": "identity", + "position": "dodge", "sampling": "none", "stat": "identity" } @@ -703,59 +909,373 @@ "swing_enabled": true }, "text/html": [ - " \n", - "
\n", - " " + " </script>\n", + " </body>\n", + "</html>\"> \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 1,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 2,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 3,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 4,000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 4,500\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " app:assembleBrazilDebug\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " kotlinLSPProjectDeps\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " IDE sync\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " tasks\n", + " \n", + " \n", + " \n", + " \n", + " count\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " " ] }, - "execution_count": 7, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "plot(buildCounts.take(5)) {\n", - " barsH { \n", + "plot(buildCounts.take(3)) {\n", + " barsH {\n", " x(\"count\")\n", " y(\"tasks\")\n", " }\n", - " layout.size = 1000 to 250\n", + " layout.size = 800 to 250\n", "}" ] } @@ -773,7 +1293,7 @@ "name": "kotlin", "nbconvert_exporter": "", "pygments_lexer": "kotlin", - "version": "1.8.20" + "version": "1.9.10" } }, "nbformat": 4, diff --git a/examples/example-project/.gitattributes b/examples/example-project/.gitattributes deleted file mode 100644 index 097f9f98..00000000 --- a/examples/example-project/.gitattributes +++ /dev/null @@ -1,9 +0,0 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# Linux start script should use lf -/gradlew text eol=lf - -# These are Windows script files and should use crlf -*.bat text eol=crlf - diff --git a/examples/example-project/.gitignore b/examples/example-project/.gitignore deleted file mode 100644 index 1b6985c0..00000000 --- a/examples/example-project/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore Gradle project-specific cache directory -.gradle - -# Ignore Gradle build output directory -build diff --git a/examples/example-project/app/build.gradle.kts b/examples/example-project/app/build.gradle.kts deleted file mode 100644 index e348acb4..00000000 --- a/examples/example-project/app/build.gradle.kts +++ /dev/null @@ -1,18 +0,0 @@ -plugins { - id("org.jetbrains.kotlin.jvm") - application -} - -application { - mainClass.set("com.gabrielfeo.gradle.enterprise.api.example.MainKt") -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) - } -} - -dependencies { - implementation("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") -} diff --git a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt b/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt deleted file mode 100644 index f21edf2b..00000000 --- a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/Main.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.example - -import com.gabrielfeo.gradle.enterprise.api.Config -import com.gabrielfeo.gradle.enterprise.api.GradleEnterpriseApi -import com.gabrielfeo.gradle.enterprise.api.example.analysis.mostFrequentBuilds -import okhttp3.OkHttpClient - -/* - * Example main that runs all API analysis at once. In projects, you can share an - * OkHttpClient.Builder between GradleEnterpriseApi and your own project classes, in order to - * save resources. - */ - -val clientBuilder = OkHttpClient.Builder() - -suspend fun main() { - val newConfig = Config( - clientBuilder = clientBuilder, - ) - val gradleEnterpriseApi = GradleEnterpriseApi.newInstance(newConfig) - runAllAnalysis(gradleEnterpriseApi) - gradleEnterpriseApi.shutdown() -} - -private suspend fun runAllAnalysis(gradleEnterpriseApi: GradleEnterpriseApi) { - mostFrequentBuilds(api = gradleEnterpriseApi.buildsApi) -} diff --git a/examples/example-project/build.gradle.kts b/examples/example-project/build.gradle.kts index 3039a50c..7c7daa35 100644 --- a/examples/example-project/build.gradle.kts +++ b/examples/example-project/build.gradle.kts @@ -1,3 +1,19 @@ plugins { - id("org.jetbrains.kotlin.jvm") version "1.9.22" apply false + // in your project, replace for id("org.jetbrains.kotlin.jvm") + id("com.gabrielfeo.kotlin-jvm-library") + application +} + +application { + mainClass.set("com.gabrielfeo.develocity.api.example.MainKt") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +dependencies { + implementation("com.gabrielfeo:develocity-api-kotlin:2024.1.0") } diff --git a/examples/example-project/gradle.properties b/examples/example-project/gradle.properties deleted file mode 100644 index 5b8859a1..00000000 --- a/examples/example-project/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -# This file was generated by the Gradle 'init' task. -# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties -org.gradle.jvmargs=-Xmx1g -org.gradle.parallel=true -org.gradle.caching=true - diff --git a/examples/example-project/gradle/wrapper/gradle-wrapper.jar b/examples/example-project/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d64cd491..00000000 Binary files a/examples/example-project/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/examples/example-project/gradle/wrapper/gradle-wrapper.properties b/examples/example-project/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 4baf5a11..00000000 --- a/examples/example-project/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,8 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/examples/example-project/gradlew b/examples/example-project/gradlew deleted file mode 100755 index 1aa94a42..00000000 --- a/examples/example-project/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/examples/example-project/gradlew.bat b/examples/example-project/gradlew.bat deleted file mode 100644 index 25da30db..00000000 --- a/examples/example-project/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/examples/example-project/settings.gradle.kts b/examples/example-project/settings.gradle.kts deleted file mode 100644 index 57a2ce80..00000000 --- a/examples/example-project/settings.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -rootProject.name = "example-project" - -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") -} - -dependencyResolutionManagement { - repositories { - mavenCentral() - } -} - -include(":app") diff --git a/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/Main.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/Main.kt new file mode 100644 index 00000000..37f23a7a --- /dev/null +++ b/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/Main.kt @@ -0,0 +1,27 @@ +package com.gabrielfeo.develocity.api.example + +import com.gabrielfeo.develocity.api.Config +import com.gabrielfeo.develocity.api.DevelocityApi +import com.gabrielfeo.develocity.api.example.analysis.mostFrequentBuilds +import okhttp3.OkHttpClient + +/* + * Example main that runs all API analysis at once. In projects, you can share an + * OkHttpClient.Builder between DevelocityApi and your own project classes, in order to + * save resources. + */ + +val clientBuilder = OkHttpClient.Builder() + +suspend fun main() { + val newConfig = Config( + clientBuilder = clientBuilder, + ) + val develocityApi = DevelocityApi.newInstance(newConfig) + runAllAnalysis(develocityApi) + develocityApi.shutdown() +} + +private suspend fun runAllAnalysis(develocityApi: DevelocityApi) { + mostFrequentBuilds(api = develocityApi.buildsApi) +} diff --git a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt b/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/analysis/MostFrequentBuilds.kt similarity index 70% rename from examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt rename to examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/analysis/MostFrequentBuilds.kt index 43af9a47..4e4d7990 100644 --- a/examples/example-project/app/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/example/analysis/MostFrequentBuilds.kt +++ b/examples/example-project/src/main/kotlin/com/gabrielfeo/develocity/api/example/analysis/MostFrequentBuilds.kt @@ -1,11 +1,9 @@ -package com.gabrielfeo.gradle.enterprise.api.example.analysis +package com.gabrielfeo.develocity.api.example.analysis -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.extension.* -import com.gabrielfeo.gradle.enterprise.api.model.* -import kotlinx.coroutines.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.extension.* +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.flow.* -import java.time.* import java.util.LinkedList /** @@ -24,16 +22,16 @@ import java.util.LinkedList */ suspend fun mostFrequentBuilds( api: BuildsApi, - startDate: LocalDate = LocalDate.now().minusWeeks(1), - buildFilter: (GradleAttributes) -> Boolean = { build -> - "LOCAL" in build.tags - }, + startTime: String = "-7d", ) { // Fetch builds from the API - val startMilli = startDate.atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli() - val builds: List = api.getGradleAttributesFlow(since = startMilli) - .filter(buildFilter) - .toList(LinkedList()) + val builds: List = api.getBuildsFlow( + fromInstant = 0, + query = """buildStartTime<$startTime tag:local buildOutcome:failed""", + models = listOf(BuildModelName.gradleAttributes), + ).map { + it.models!!.gradleAttributes!!.model!! + }.toList(LinkedList()) // Process builds and count how many times each was invoked val buildCounts = builds.groupBy { build -> diff --git a/examples/example-script.main.kts b/examples/example-scripts/example-script.main.kts similarity index 78% rename from examples/example-script.main.kts rename to examples/example-scripts/example-script.main.kts index 4abb8ed9..341c9634 100644 --- a/examples/example-script.main.kts +++ b/examples/example-scripts/example-script.main.kts @@ -17,11 +17,11 @@ * Run this with at least 1GB of heap to accomodate the fetched data: JAVA_OPTS=-Xmx1g */ -@file:DependsOn("com.gabrielfeo:gradle-enterprise-api-kotlin:2023.3.1") +@file:DependsOn("com.gabrielfeo:develocity-api-kotlin:2024.1.0") -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.model.* -import com.gabrielfeo.gradle.enterprise.api.extension.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.model.* +import com.gabrielfeo.develocity.api.extension.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import java.time.* @@ -34,12 +34,15 @@ val buildFilter: (GradleAttributes) -> Boolean = { build -> } // Fetch builds from the API -val api = GradleEnterpriseApi.newInstance() +val api = DevelocityApi.newInstance() val builds: List = runBlocking { - val startMilli = startDate.atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli() - api.buildsApi.getGradleAttributesFlow(since = startMilli) - .filter(buildFilter) - .toList(LinkedList()) + api.buildsApi.getBuildsFlow( + fromInstant = 0, + query = """buildStartTime<-7d tag:local buildOutcome:failed""", + models = listOf(BuildModelName.gradleAttributes), + ).map { + it.models!!.gradleAttributes!!.model!! + }.toList(LinkedList()) } // Process builds and count how many times each was invoked diff --git a/gradle.properties b/gradle.properties index 4aaaf10b..9d16b8c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,12 @@ +group=com.gabrielfeo +artifact=develocity-api-kotlin +version=2024.1.0 +develocity.version=2024.1 +repo.url=https://github.com/gabrielfeo/develocity-api-kotlin org.gradle.jvmargs=-Xmx5g org.gradle.caching=true # Becomes default in Gradle 9.0 org.gradle.kotlin.dsl.skipMetadataVersionCheck=false -version=SNAPSHOT -# Must be later than 2022.1 -gradle.enterprise.version=2023.4 -group=com.gabrielfeo -artifact=gradle-enterprise-api-kotlin +systemProp.scan.capture-build-logging=false +systemProp.scan.capture-test-logging=false +kotlin.jupyter.add.testkit=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491..e6441136 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4baf5a11..381baa9c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/library/.openapi-generator-ignore b/library/.openapi-generator-ignore index e0e41620..105d96e0 100644 --- a/library/.openapi-generator-ignore +++ b/library/.openapi-generator-ignore @@ -1,9 +1,11 @@ -build/generated/openapi-generator/* -build/generated/openapi-generator/.github/* -build/generated/openapi-generator/api/* -build/generated/openapi-generator/gradle/**/* -build/generated/openapi-generator/docs/* -build/generated/openapi-generator/src/main/AndroidManifest.xml -build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/infrastructure/ApiClient.kt -build/generated/openapi-generator/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt -build/generated/openapi-generator/src/test/**/* +build/generated-api/* +build/generated-api/.* +build/generated-api/.github/* +build/generated-api/api/* +build/generated-api/gradle/**/* +build/generated-api/docs/* +build/generated-api/src/main/AndroidManifest.xml +build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/internal/infrastructure/ApiClient.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/GradleEnterpriseApi.kt +build/generated-api/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt +build/generated-api/src/test/**/* diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 76a30146..250e7a34 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -5,230 +5,23 @@ import org.jetbrains.dokka.DokkaConfiguration.Visibility.PUBLIC import org.jetbrains.dokka.gradle.DokkaTask plugins { - id("org.jetbrains.kotlin.jvm") - id("org.jetbrains.dokka") - id("org.openapi.generator") + id("com.gabrielfeo.kotlin-jvm-library") + id("com.gabrielfeo.develocity-api-code-generation") + id("com.gabrielfeo.test-suites") `java-library` - `java-test-fixtures` `maven-publish` - `signing` + signing + kotlin("jupyter.api") version "0.12.0-181" } -val repoUrl = "https://github.com/gabrielfeo/gradle-enterprise-api-kotlin" - -val localSpecPath = providers.gradleProperty("localSpecPath") -val remoteSpecUrl = providers.gradleProperty("remoteSpecUrl").orElse( - providers.gradleProperty("gradle.enterprise.version").map { geVersion -> - val specName = "gradle-enterprise-$geVersion-api.yaml" - "https://docs.gradle.com/enterprise/api-manual/ref/$specName" - } -) - -val downloadApiSpec by tasks.registering { - onlyIf { !localSpecPath.isPresent() } - val spec = resources.text.fromUri(remoteSpecUrl) - val specName = remoteSpecUrl.map { it.substringAfterLast('/') } - val outFile = project.layout.buildDirectory.file(specName) - inputs.property("Spec URL", remoteSpecUrl) - outputs.file(outFile) - doLast { - logger.info("Downloaded API spec from ${remoteSpecUrl.get()}") - spec.asFile().renameTo(outFile.get().asFile) - } -} - -openApiGenerate { - generatorName.set("kotlin") - val spec = when { - localSpecPath.isPresent() -> localSpecPath.map { rootProject.file(it).absolutePath } - else -> downloadApiSpec.map { it.outputs.files.first().absolutePath } - } - inputSpec.set(spec) - val generateDir = project.layout.buildDirectory.file("generated/openapi-generator") - outputDir.set(generateDir.map { it.asFile.absolutePath }) - val ignoreFile = project.layout.projectDirectory.file(".openapi-generator-ignore") - ignoreFileOverride.set(ignoreFile.asFile.absolutePath) - apiPackage.set("com.gabrielfeo.gradle.enterprise.api") - modelPackage.set("com.gabrielfeo.gradle.enterprise.api.model") - packageName.set("com.gabrielfeo.gradle.enterprise.api.internal") - invokerPackage.set("com.gabrielfeo.gradle.enterprise.api.internal") - additionalProperties.put("library", "jvm-retrofit2") - additionalProperties.put("useCoroutines", true) -} - -tasks.openApiGenerate.configure { - val srcDir = File(outputDir.get(), "src/main/kotlin") - doFirst { - logger.info("Using API spec ${inputSpec.get()}") - } - // Replace Response with X in every method return type of GradleEnterpriseApi.kt - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to ": Response<(.*?)>$", - "replace" to """: \1""", - "flags" to "gm", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", - ) - } - } - } - // Add @JvmSuppressWildcards to avoid square/retrofit#3275 - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to "interface", - "replace" to """ - @JvmSuppressWildcards - interface - """.trimIndent(), - "flags" to "m", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/*Api.kt", - ) - } - } - } - // Workaround for properties generated with `arrayListOf(null,null)` as default value - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to """arrayListOf\(null,null\)""", - "replace" to """emptyList()""", - "flags" to "gm", - ) { - "fileset"( - "dir" to srcDir - ) - } - } - } - // Workaround for missing imports of exploded queries - doLast { - val modelPackage = openApiGenerate.modelPackage.get() - val modelPackagePattern = modelPackage.replace(".", "\\.") - ant.withGroovyBuilder { - "replaceregexp"( - "match" to """(?:import $modelPackagePattern.[.\w]+\s)+""", - "replace" to "import $modelPackage.*\n", - "flags" to "m", - ) { - "fileset"( - "dir" to srcDir - ) - } - } - } - // Fix mapping of BuildModelName: gradle-attributes -> gradleAttributes - doLast { - ant.withGroovyBuilder { - "replaceregexp"( - "match" to "Minus", - "replace" to "", - "flags" to "mg", - ) { - "fileset"( - "dir" to srcDir, - "includes" to "com/gabrielfeo/gradle/enterprise/api/model/BuildModelName.kt", - ) - } - } - } -} - -sourceSets { - main { - java { - srcDir(tasks.openApiGenerate) - } - } -} - -java { - withSourcesJar() - withJavadocJar() - toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) - vendor.set(JvmVendorSpec.AZUL) - } -} - -components.getByName("java").apply { - withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } - withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } -} - -tasks.withType().configureEach { - dokkaSourceSets.all { - sourceLink { - localDirectory.set(file("src/main/kotlin")) - remoteUrl.set(URL("$repoUrl/blob/$version/src/main/kotlin")) - remoteLineSuffix.set("#L") - } - jdkVersion.set(8) - suppressGeneratedFiles.set(false) - documentedVisibilities.set(setOf(PUBLIC)) - perPackageOption { - matchingRegex.set(""".*\.internal.*""") - suppress.set(true) - } - externalDocumentationLink("https://kotlinlang.org/api/kotlinx.coroutines/") - externalDocumentationLink("https://square.github.io/okhttp/4.x/okhttp/") - externalDocumentationLink("https://square.github.io/retrofit/2.x/retrofit/") - externalDocumentationLink("https://square.github.io/moshi/1.x/moshi/") - externalDocumentationLink("https://square.github.io/moshi/1.x/moshi-kotlin/") - } -} - -tasks.named("javadocJar") { - from(tasks.dokkaHtml) -} - -testing { - suites { - getByName("test") { - dependencies { - implementation("com.squareup.okhttp3:mockwebserver:4.12.0") - implementation("com.squareup.okio:okio:3.7.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") - } - } - register("integrationTest") { - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") - } - } - withType().configureEach { - useKotlinTest() - } - } -} - -kotlin { - target { - val main by compilations.getting - val integrationTest by compilations.getting - val test by compilations.getting - val testFixtures by compilations.getting - test.associateWith(main) - test.associateWith(testFixtures) - integrationTest.associateWith(main) - integrationTest.associateWith(testFixtures) - testFixtures.associateWith(main) - } -} - -tasks.named("check") { - dependsOn("integrationTest") +tasks.processJupyterApiResources { + libraryProducers = listOf( + "com.gabrielfeo.develocity.api.internal.jupyter.DevelocityApiJupyterIntegration", + ) } tasks.named("integrationTest") { - jvmArgs("-Xmx512m") + environment("DEVELOCITY_API_LOG_LEVEL", "DEBUG") } java { @@ -239,47 +32,77 @@ java { dependencies { constraints { - implementation("com.squareup.okio:okio:3.7.0") + implementation("com.squareup.okio:okio:3.9.0") } api("com.squareup.moshi:moshi:1.15.1") implementation("com.squareup.moshi:moshi-kotlin:1.15.1") api("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") - api("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-moshi:2.9.0") - implementation("com.squareup.retrofit2:converter-scalars:2.9.0") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + api("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-moshi:2.11.0") + implementation("com.squareup.retrofit2:converter-scalars:2.11.0") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0") + implementation("org.slf4j:slf4j-api:2.0.11") + implementation("ch.qos.logback:logback-classic:1.4.14") + testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") + testImplementation("com.squareup.okio:okio:3.9.0") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + integrationTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + integrationTestImplementation("com.google.guava:guava:33.1.0-jre") + integrationTestImplementation("org.jetbrains.kotlinx:kotlin-jupyter-test-kit:0.12.0-181") +} + +val libraryPom = Action { + name.set("Develocity API Kotlin") + description.set("A library to use the Develocity API in Kotlin") + val repoUrl = providers.gradleProperty("repo.url") + url.set(repoUrl) + licenses { + license { + name.set("MIT") + url.set("https://spdx.org/licenses/MIT.html") + distribution.set("repo") + } + } + developers { + developer { + id.set("gabrielfeo") + name.set("Gabriel Feo") + email.set("gabriel@gabrielfeo.com") + } + } + scm { + val basicUrl = repoUrl.map { it.substringAfter("://") } + connection.set(basicUrl.map { "scm:git:git://$it.git" }) + developerConnection.set(basicUrl.map { "scm:git:ssh://$it.git" }) + url.set(basicUrl.map { "https://$it/" }) + } } publishing { publications { - create("library") { - artifactId = "gradle-enterprise-api-kotlin" + register("develocityApiKotlin") { + artifactId = "develocity-api-kotlin" from(components["java"]) + pom(libraryPom) + } + // For occasional maven local publishing + register("unsignedDevelocityApiKotlin") { + artifactId = "develocity-api-kotlin" + from(components["java"]) + pom(libraryPom) + } + register("relocation") { + artifactId = "gradle-enterprise-api-kotlin" pom { - name.set("Gradle Enterprise API Kotlin") - description.set("A library to use the Gradle Enterprise REST API in Kotlin") - url.set("https://github.com/gabrielfeo/gradle-enterprise-api-kotlin") - licenses { - license { - name.set("MIT") - url.set("https://spdx.org/licenses/MIT.html") - distribution.set("repo") + libraryPom(this) + distributionManagement { + relocation { + groupId = project.group.toString() + artifactId = "develocity-api-kotlin" + message = "artifactId has been changed. Part of the rename to Develocity." } } - developers { - developer { - id.set("gabrielfeo") - name.set("Gabriel Feo") - email.set("gabriel@gabrielfeo.com") - } - } - scm { - val basicUrl = "github.com/gabrielfeo/gradle-enterprise-api-kotlin" - connection.set("scm:git:git://$basicUrl.git") - developerConnection.set("scm:git:ssh://$basicUrl.git") - url.set("https://$basicUrl/") - } } } } @@ -304,7 +127,10 @@ publishing { fun isCI() = System.getenv("CI").toBoolean() signing { - sign(publishing.publications["library"]) + val signedPublications = publishing.publications.matching { + !it.name.contains("unsigned", ignoreCase = true) + } + sign(signedPublications) if (isCI()) { useInMemoryPgpKeys( project.properties["signing.secretKey"] as String?, diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt new file mode 100644 index 00000000..41864a71 --- /dev/null +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/DevelocityApiIntegrationTest.kt @@ -0,0 +1,65 @@ +package com.gabrielfeo.develocity.api + +import com.gabrielfeo.develocity.api.internal.* +import com.google.common.reflect.ClassPath +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.reflect.KVisibility.PUBLIC +import kotlin.reflect.full.memberProperties +import kotlin.reflect.javaType +import kotlin.test.* + +@OptIn(ExperimentalStdlibApi::class) +class DevelocityApiIntegrationTest { + + @Test + fun canFetchBuildsWithDefaultConfig() = runTest { + env = RealEnv + val api = DevelocityApi.newInstance( + config = Config( + cacheConfig = Config.CacheConfig(cacheEnabled = false) + ) + ) + val builds = api.buildsApi.getBuilds( + since = 0, + maxBuilds = 5, + query = """tag:local value:"Email=gabriel.feo*"""" + ) + assertEquals(5, builds.size) + api.shutdown() + } + + @Test + fun canBuildNewInstanceWithPureCodeConfiguration() = runTest { + env = FakeEnv() + assertDoesNotThrow { + val config = Config( + apiUrl = "https://google.com/api/", + apiToken = { "" }, + ) + DevelocityApi.newInstance(config) + } + } + + @Test + fun mainApiInterfaceExposesAllGeneratedApiClasses() = runTest { + val generatedApiTypes = getGeneratedApiTypes() + val mainApiInterfaceProperties = getMainApiInterfaceProperties() + generatedApiTypes.forEach { + mainApiInterfaceProperties.singleOrNull { type -> type == it } + ?: fail("No property in DevelocityApi for $it") + } + } + + private fun getGeneratedApiTypes(): List { + val cp = ClassPath.from(this::class.java.classLoader) + return cp.getTopLevelClasses("com.gabrielfeo.develocity.api") + .filter { it.simpleName.endsWith("Api") } + .filter { !it.simpleName.endsWith("DevelocityApi") } + .map { it.name } + } + + private fun getMainApiInterfaceProperties() = DevelocityApi::class.memberProperties + .filter { it.visibility == PUBLIC } + .map { it.returnType.javaType.typeName } +} diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt similarity index 81% rename from library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt rename to library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt index 6eaa75be..3fc70699 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsIntegrationTest.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsIntegrationTest.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.internal.* -import com.gabrielfeo.gradle.enterprise.api.model.BuildModelName +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.internal.* +import com.gabrielfeo.develocity.api.model.BuildModelName import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.take import kotlinx.coroutines.test.runTest @@ -16,7 +16,6 @@ class BuildsApiExtensionsIntegrationTest { init { env = RealEnv - keychain = RealKeychain(RealSystemProperties) } private val recorder = RequestRecorder() @@ -28,29 +27,32 @@ class BuildsApiExtensionsIntegrationTest { } @Test - fun getBuildsFlowPreservesParamsAcrossRequests() = runTest(timeout = 3.minutes) { + fun getBuildsFlowPreservesParamsAcrossRequests() = runTest(timeout = 6.minutes) { api.buildsApi.getBuildsFlow( since = 0, query = "user:*", models = listOf(BuildModelName.gradleAttributes), + allModels = true, reverse = true, - ).take(2000).collect() + buildsPerPage = 2, + ).take(4).collect() recorder.requests.forEach { assertUrlParam(it, "query", "user:*") assertUrlParam(it, "models", "gradle-attributes") + assertUrlParam(it, "allModels", "true") assertUrlParam(it, "reverse", "true") } } @Test fun getBuildsFlowReplacesSinceForFromBuildAfterFirstRequest() = runTest { - api.buildsApi.getBuildsFlow(since = 1).take(2000).collect() + api.buildsApi.getBuildsFlow(since = 1, buildsPerPage = 2).take(10).collect() assertReplacedForFromBuildAfterFirstRequest(param = "since" to "1") } @Test fun getBuildsFlowReplacesFromInstantForFromBuildAfterFirstRequest() = runTest { - api.buildsApi.getBuildsFlow(fromInstant = 1).take(2000).collect() + api.buildsApi.getBuildsFlow(fromInstant = 1, buildsPerPage = 2).take(10).collect() assertReplacedForFromBuildAfterFirstRequest(param = "fromInstant" to "1") } @@ -69,7 +71,7 @@ class BuildsApiExtensionsIntegrationTest { } private fun buildApi(recorder: RequestRecorder) = - GradleEnterpriseApi.newInstance( + DevelocityApi.newInstance( config = Config( clientBuilder = recorder.clientBuilder(), cacheConfig = Config.CacheConfig(cacheEnabled = false), @@ -84,4 +86,4 @@ class BuildsApiExtensionsIntegrationTest { private fun assertUrlParamNotNull(request: Request, key: String) { assertNotNull(request.url.queryParameter(key), "Expected param $key, but was null (${request.url})") } -} \ No newline at end of file +} diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/RequestRecorder.kt similarity index 84% rename from library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt rename to library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/RequestRecorder.kt index a0679cfa..8c51b6f5 100644 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/RequestRecorder.kt +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/extension/RequestRecorder.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension import okhttp3.OkHttpClient import okhttp3.Request @@ -13,4 +13,4 @@ class RequestRecorder { requests += it.request() it.proceed(it.request()) } -} \ No newline at end of file +} diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt new file mode 100644 index 00000000..f4a6b170 --- /dev/null +++ b/library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt @@ -0,0 +1,46 @@ +package com.gabrielfeo.develocity.api.internal.jupyter + +import com.google.common.reflect.ClassPath +import com.google.common.reflect.ClassPath.ClassInfo +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlinx.jupyter.api.Code +import org.jetbrains.kotlinx.jupyter.testkit.JupyterReplTestCase +import kotlin.reflect.KVisibility +import kotlin.test.Test + +@ExperimentalStdlibApi +class DevelocityApiJupyterIntegrationTest : JupyterReplTestCase() { + + @Test + fun `imports all extensions`() = assertSucceeds(""" + com.gabrielfeo.develocity.api.BuildsApi::getGradleAttributesFlow + com.gabrielfeo.develocity.api.BuildsApi::getBuildsFlow + + val attrs = emptyList() + "custom value name" in attrs + attrs["custom value name"] + """) + + @Test + fun `imports all public classes`() { + val classes = allPublicClassesRecursive("com.gabrielfeo.develocity.api") + val references = classes.joinToString("\n") { "${it.name}::class" } + println("Running code:\n$references") + assertSucceeds(references) + } + + @Suppress("SameParameterValue") + private fun allPublicClassesRecursive(packageName: String): List { + val cp = ClassPath.from(this::class.java.classLoader) + return cp.getTopLevelClassesRecursive(packageName) + .filter { "internal" !in it.packageName } + .filter { !it.name.endsWith("Kt") } + .filter { Class.forName(it.name).kotlin.visibility == KVisibility.PUBLIC } + } + + private fun assertSucceeds(@Language("kts") code: Code) { + code.lines().forEach { + execRendered(it) + } + } +} \ No newline at end of file diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt deleted file mode 100644 index f9e52a41..00000000 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiIntegrationTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api - -import com.gabrielfeo.gradle.enterprise.api.internal.* -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.assertDoesNotThrow -import kotlin.test.* - -class GradleEnterpriseApiIntegrationTest { - - @Test - fun canFetchBuildsWithDefaultConfig() = runTest { - env = RealEnv - keychain = RealKeychain(RealSystemProperties) - val api = GradleEnterpriseApi.newInstance() - val builds = api.buildsApi.getBuilds( - since = 0, - maxBuilds = 5, - query = """tag:local value:"Email=gabriel.feo*"""" - ) - assertEquals(5, builds.size) - api.shutdown() - } - - @Test - fun canBuildNewInstanceWithPureCodeConfiguration() = runTest { - env = FakeEnv() - keychain = FakeKeychain() - assertDoesNotThrow { - val config = Config( - apiUrl = "https://google.com/api/", - apiToken = { "" }, - ) - GradleEnterpriseApi.newInstance(config) - } - } -} \ No newline at end of file diff --git a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt b/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt deleted file mode 100644 index 5cc94a5d..00000000 --- a/library/src/integrationTest/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/KeychainIntegrationTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - -import com.gabrielfeo.gradle.enterprise.api.internal.keychain -import kotlin.test.* - -internal class KeychainIntegrationTest { - - @Test - fun getApiToken() { - env = RealEnv - keychain = RealKeychain(RealSystemProperties) - val result = keychain.get("gradle-enterprise-api-token") - assertIs(result) - assertFalse(result.token.isNullOrBlank(), "Keychain returned null or blank") - } -} diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt similarity index 60% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt index 21d0a1ed..6aed79dd 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/Config.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/Config.kt @@ -1,10 +1,10 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.Config.CacheConfig +import com.gabrielfeo.develocity.api.internal.* import okhttp3.Dispatcher import okhttp3.OkHttpClient import java.io.File -import java.util.logging.Logger import kotlin.time.Duration.Companion.days /** @@ -14,26 +14,30 @@ import kotlin.time.Duration.Companion.days data class Config( /** - * Enables debug logging from the library. All logging is output to stderr. By default, uses - * environment variable `GRADLE_ENTERPRISE_API_DEBUG_LOGGING` or `false`. + * Forces a log level for internal library classes. By default, the library includes + * logback-classic with logging disabled, aimed at notebook and script usage. Logging may be + * enabled for troubleshooting by changing log level to "INFO", "DEBUG", etc. + * + * To use different SLF4J bindings, simply exclude the logback dependency. */ - val debugLoggingEnabled: Boolean = - env["GRADLE_ENTERPRISE_API_DEBUG_LOGGING"].toBoolean(), + val logLevel: String? = + env["DEVELOCITY_API_LOG_LEVEL"], /** - * Provides the URL of a Gradle Enterprise API instance REST API. By default, uses - * environment variable `GRADLE_ENTERPRISE_API_URL`. Must end with `/api/`. + * Provides the URL of a Develocity API instance REST API. By default, uses + * environment variable `DEVELOCITY_API_URL`. Must end with `/api/`. */ val apiUrl: String = - env["GRADLE_ENTERPRISE_API_URL"] - ?: error("GRADLE_ENTERPRISE_API_URL is required"), + env["DEVELOCITY_API_URL"] + ?: error("DEVELOCITY_API_URL is required"), /** - * Provides the access token for a Gradle Enterprise API instance. By default, uses keychain entry - * `gradle-enterprise-api-token` or environment variable `GRADLE_ENTERPRISE_API_TOKEN`. + * Provides the access token for a Develocity API instance. By default, uses environment + * variable `DEVELOCITY_API_TOKEN`. */ val apiToken: () -> String = { - requireEnvOrKeychainToken(debugLoggingEnabled = debugLoggingEnabled) + env["DEVELOCITY_API_TOKEN"] + ?: error("DEVELOCITY_API_TOKEN is required") }, /** @@ -50,7 +54,7 @@ data class Config( /** * Maximum amount of concurrent requests allowed. Further requests will be queued. By default, - * uses environment variable `GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS` or 5 (OkHttp's + * uses environment variable `DEVELOCITY_API_MAX_CONCURRENT_REQUESTS` or 5 (OkHttp's * default value of [Dispatcher.maxRequestsPerHost]). * * If set, will set [Dispatcher.maxRequests] and [Dispatcher.maxRequestsPerHost] of the @@ -58,16 +62,16 @@ data class Config( * if any. */ val maxConcurrentRequests: Int? = - env["GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS"]?.toInt(), + env["DEVELOCITY_API_MAX_CONCURRENT_REQUESTS"]?.toInt(), /** * Timeout for reading an API response, used for [OkHttpClient.readTimeoutMillis]. - * By default, uses environment variable `GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS` - * or 60_000. Keep in mind that GE API responses can be big and slow to send depending on + * By default, uses environment variable `DEVELOCITY_API_READ_TIMEOUT_MILLIS` + * or 60_000. Keep in mind that Develocity API responses can be big and slow to send depending on * the endpoint. */ val readTimeoutMillis: Long = - env["GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS"]?.toLong() + env["DEVELOCITY_API_READ_TIMEOUT_MILLIS"]?.toLong() ?: 60_000L, /** @@ -78,7 +82,7 @@ data class Config( ) { /** - * HTTP cache is off by default, but can speed up requests significantly. The Gradle Enterprise + * HTTP cache is off by default, but can speed up requests significantly. The Develocity * API disallows HTTP caching, but this library forcefully enables it by overwriting * cache-related headers in API responses. Enable with [cacheEnabled]. * @@ -98,12 +102,12 @@ data class Config( * all depends on whether it was matched by [shortTermCacheUrlPattern] or * [longTermCacheUrlPattern]. * - * Whenever GE is upgraded, cache should be [clear]ed. + * Whenever Develocity is upgraded, cache should be [clear]ed. * * ### Caveats * * While not encouraged by the API, caching shouldn't have any major downsides other than a - * time gap for certain queries, or having to reset cache when GE is upgraded. + * time gap for certain queries, or having to reset cache when Develocity is upgraded. * * #### Time gap * @@ -112,10 +116,10 @@ data class Config( * included in the query until the cache is invalidated 24h later. If that's a problem, * caching can be disabled for this `/api/builds` by changing [shortTermCacheUrlPattern]. * - * #### GE upgrades + * #### Develocity upgrades * - * When GE is upgraded, any API response can change. New data might be available in API - * endpoints such as `/api/build/{id}/gradle-attributes`. Thus, whenever the GE version + * When Develocity is upgraded, any API response can change. New data might be available in API + * endpoints such as `/api/build/{id}/gradle-attributes`. Thus, whenever the Develocity version * itself is upgraded, cache should be [clear]ed. */ @Suppress("MemberVisibilityCanBePrivate") @@ -123,30 +127,30 @@ data class Config( /** * Whether caching is enabled. By default, uses environment variable - * `GRADLE_ENTERPRISE_API_CACHE_ENABLED` or `false`. + * `DEVELOCITY_API_CACHE_ENABLED` or `false`. */ val cacheEnabled: Boolean = - env["GRADLE_ENTERPRISE_API_CACHE_ENABLED"].toBoolean(), + env["DEVELOCITY_API_CACHE_ENABLED"].toBoolean(), /** - * HTTP cache location. By default, uses environment variable `GRADLE_ENTERPRISE_API_CACHE_DIR` - * or the system temporary folder (`java.io.tmpdir` / gradle-enterprise-api-kotlin-cache). + * HTTP cache location. By default, uses environment variable `DEVELOCITY_API_CACHE_DIR` + * or the system temporary folder (`java.io.tmpdir` / develocity-api-kotlin-cache). */ val cacheDir: File = - env["GRADLE_ENTERPRISE_API_CACHE_DIR"]?.let(::File) - ?: File(systemProperties["user.home"], ".gradle-enterprise-api-kotlin-cache"), + env["DEVELOCITY_API_CACHE_DIR"]?.let(::File) + ?: File(systemProperties["user.home"], ".develocity-api-kotlin-cache"), /** * Max size of the HTTP cache. By default, uses environment variable - * `GRADLE_ENTERPRISE_API_MAX_CACHE_SIZE` or ~1 GB. + * `DEVELOCITY_API_MAX_CACHE_SIZE` or ~1 GB. */ - val maxCacheSize: Long = env["GRADLE_ENTERPRISE_API_MAX_CACHE_SIZE"]?.toLong() + val maxCacheSize: Long = env["DEVELOCITY_API_MAX_CACHE_SIZE"]?.toLong() ?: 1_000_000_000L, /** * Regex pattern to match API URLs that are OK to store long-term in the HTTP cache, up to * [longTermCacheMaxAge] (1y by default, max value). By default, uses environment variable - * `GRADLE_ENTERPRISE_API_LONG_TERM_CACHE_URL_PATTERN` or a pattern matching: + * `DEVELOCITY_API_LONG_TERM_CACHE_URL_PATTERN` or a pattern matching: * - {host}/api/builds/{id}/gradle-attributes * - {host}/api/builds/{id}/maven-attributes * - {host}/api/builds/{id}/gradle-build-cache-performance @@ -155,7 +159,7 @@ data class Config( * Use `|` to define multiple patterns in one, e.g. `.*gradle-attributes|.*test-distribution`. */ val longTermCacheUrlPattern: Regex = - env["GRADLE_ENTERPRISE_API_LONG_TERM_CACHE_URL_PATTERN"]?.toRegex() + env["DEVELOCITY_API_LONG_TERM_CACHE_URL_PATTERN"]?.toRegex() ?: Regex( """ .*/api/builds/[\d\w]+/(?:gradle|maven)-(?:attributes|build-cache-performance) @@ -164,46 +168,30 @@ data class Config( /** * Max age in seconds for URLs to be cached long-term (matched by [longTermCacheUrlPattern]). - * By default, uses environment variable `GRADLE_ENTERPRISE_API_LONG_TERM_CACHE_MAX_AGE` or 1 year. + * By default, uses environment variable `DEVELOCITY_API_LONG_TERM_CACHE_MAX_AGE` or 1 year. */ val longTermCacheMaxAge: Long = - env["GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() + env["DEVELOCITY_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() ?: 365.days.inWholeSeconds, /** * Regex pattern to match API URLs that are OK to store short-term in the HTTP cache, up to * [shortTermCacheMaxAge] (1d by default). By default, uses environment variable - * `GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_URL_PATTERN` or a pattern matching: + * `DEVELOCITY_API_SHORT_TERM_CACHE_URL_PATTERN` or a pattern matching: * - {host}/api/builds * * Use `|` to define multiple patterns in one, e.g. `.*gradle-attributes|.*test-distribution`. */ val shortTermCacheUrlPattern: Regex = - env["GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_URL_PATTERN"]?.toRegex() + env["DEVELOCITY_API_SHORT_TERM_CACHE_URL_PATTERN"]?.toRegex() ?: """.*/builds(?:\?.*|\Z)""".toRegex(), /** * Max age in seconds for URLs to be cached short-term (matched by [shortTermCacheUrlPattern]). - * By default, uses environment variable `GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_MAX_AGE` or 1 day. + * By default, uses environment variable `DEVELOCITY_API_SHORT_TERM_CACHE_MAX_AGE` or 1 day. */ val shortTermCacheMaxAge: Long = - env["GRADLE_ENTERPRISE_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() + env["DEVELOCITY_API_SHORT_TERM_CACHE_MAX_AGE"]?.toLong() ?: 1.days.inWholeSeconds, ) } - -internal fun requireEnvOrKeychainToken(debugLoggingEnabled: Boolean): String { - if (systemProperties["os.name"] == "Mac OS X") { - when (val result = keychain.get("gradle-enterprise-api-token")) { - is KeychainResult.Success -> return result.token - is KeychainResult.Error -> { - if (debugLoggingEnabled) { - val logger = Logger.getGlobal() - logger.info("Failed to get key from keychain (${result.description})") - } - } - } - } - return env["GRADLE_ENTERPRISE_API_TOKEN"] - ?: error("GRADLE_ENTERPRISE_API_TOKEN is required") -} diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt similarity index 57% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt index 1bbb1f16..a98cc73e 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApi.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/DevelocityApi.kt @@ -1,15 +1,16 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.buildOkHttpClient -import com.gabrielfeo.gradle.enterprise.api.internal.buildRetrofit -import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.Serializer +import com.gabrielfeo.develocity.api.internal.RealLoggerFactory +import com.gabrielfeo.develocity.api.internal.buildOkHttpClient +import com.gabrielfeo.develocity.api.internal.buildRetrofit +import com.gabrielfeo.develocity.api.internal.infrastructure.Serializer import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.create /** - * Gradle Enterprise API client. API endpoints are grouped exactly as in the - * [Gradle Enterprise API Manual](https://docs.gradle.com/enterprise/api-manual/#reference_documentation): + * Develocity API client. API endpoints are grouped exactly as in the + * [Develocity API Manual](https://docs.gradle.com/enterprise/api-manual/#reference_documentation): * * - [buildsApi] * - [buildCacheApi] @@ -19,7 +20,7 @@ import retrofit2.create * Create an instance with [newInstance]: * * ```kotlin - * val api = GradleEnterpriseApi.newInstance() + * val api = DevelocityApi.newInstance() * api.buildsApi.getBuilds(...) * ``` * @@ -27,14 +28,17 @@ import retrofit2.create * * ```kotlin * val options = Options(clientBuilder = myOwnOkHttpClient.newBuilder()) - * val api = GradleEnterpriseApi.newInstance(options) + * val api = DevelocityApi.newInstance(options) * api.buildsApi.getBuilds(...) * ``` */ -interface GradleEnterpriseApi { +interface DevelocityApi { + val authApi: AuthApi val buildsApi: BuildsApi val buildCacheApi: BuildCacheApi + val projectsApi: ProjectsApi + val testsApi: TestsApi val metaApi: MetaApi val testDistributionApi: TestDistributionApi @@ -51,21 +55,21 @@ interface GradleEnterpriseApi { companion object { /** - * Create a new instance of `GradleEnterpriseApi` with a custom `Config`. + * Create a new instance of `DevelocityApi` with a custom `Config`. */ - fun newInstance(config: Config = Config()): GradleEnterpriseApi { - return RealGradleEnterpriseApi(config) + fun newInstance(config: Config = Config()): DevelocityApi { + return RealDevelocityApi(config) } } } -internal class RealGradleEnterpriseApi( +internal class RealDevelocityApi( override val config: Config, -) : GradleEnterpriseApi { +) : DevelocityApi { private val okHttpClient by lazy { - buildOkHttpClient(config = config) + buildOkHttpClient(config = config, RealLoggerFactory(config)) } private val retrofit: Retrofit by lazy { @@ -76,8 +80,11 @@ internal class RealGradleEnterpriseApi( ) } + override val authApi: AuthApi by lazy { retrofit.create() } override val buildsApi: BuildsApi by lazy { retrofit.create() } override val buildCacheApi: BuildCacheApi by lazy { retrofit.create() } + override val projectsApi: ProjectsApi by lazy { retrofit.create() } + override val testsApi: TestsApi by lazy { retrofit.create() } override val metaApi: MetaApi by lazy { retrofit.create() } override val testDistributionApi: TestDistributionApi by lazy { retrofit.create() } diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildAttributesValueExtensions.kt similarity index 64% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildAttributesValueExtensions.kt index 197ae317..b8f29bfb 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildAttributesValueExtensions.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildAttributesValueExtensions.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.model.BuildAttributesValue +import com.gabrielfeo.develocity.api.model.BuildAttributesValue operator fun List.get(name: String): String? { return find { it.name == name }?.value diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensions.kt similarity index 87% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensions.kt index e5f7cd76..4f3d9338 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensions.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensions.kt @@ -1,11 +1,11 @@ @file:Suppress("unused") -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.Config -import com.gabrielfeo.gradle.enterprise.api.BuildsApi -import com.gabrielfeo.gradle.enterprise.api.internal.API_MAX_BUILDS -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.Config +import com.gabrielfeo.develocity.api.BuildsApi +import com.gabrielfeo.develocity.api.internal.API_MAX_BUILDS +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -18,7 +18,7 @@ import kotlinx.coroutines.flow.* * * - Will request from the API until results end, collection stops or an error occurs. * - Parameters same as [BuildsApi.getBuilds]. - * - Using [query] is highly recommended for server-side filtering (equivalent to GE advanced + * - Using [query] is highly recommended for server-side filtering (equivalent to Develocity advanced * query). * - `maxBuilds` is the only unsupported parameter, because this Flow will instead fetch * continously. Use [Flow.take] to stop collecting at a specific count. @@ -33,6 +33,7 @@ fun BuildsApi.getBuildsFlow( maxWaitSecs: Int? = null, buildsPerPage: Int = API_MAX_BUILDS, models: List? = null, + allModels: Boolean? = false, ): Flow { return flow { var builds = getBuilds( @@ -45,6 +46,7 @@ fun BuildsApi.getBuildsFlow( maxWaitSecs = maxWaitSecs, maxBuilds = buildsPerPage, models = models, + allModels = allModels, ) emitAll(builds.asFlow()) while (builds.isNotEmpty()) { @@ -55,6 +57,7 @@ fun BuildsApi.getBuildsFlow( maxWaitSecs = maxWaitSecs, maxBuilds = buildsPerPage, models = models, + allModels = allModels, ) emitAll(builds.asFlow()) } @@ -86,8 +89,8 @@ fun BuildsApi.getBuildsFlow( "getBuildsFlow(since, sinceBuild, fromInstant, fromBuild, query, reverse," + "maxWaitSecs, models = listOf(BuildModelName.gradleAttributes))", imports = [ - "com.gabrielfeo.gradle.enterprise.api.extension.getBuildsFlow", - "com.gabrielfeo.gradle.enterprise.api.model.BuildModelName", + "com.gabrielfeo.develocity.api.extension.getBuildsFlow", + "com.gabrielfeo.develocity.api.model.BuildModelName", ] ), ) diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/Mapping.kt similarity index 81% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/Mapping.kt index a4f2925b..d730844b 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/Mapping.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/extension/Mapping.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.* -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.* +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/ApiConstants.kt similarity index 70% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/ApiConstants.kt index 6354b399..a6396fe4 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/ApiConstants.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/ApiConstants.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal /** * Undocumented max value of `/api/builds?maxBuilds`. Last checked in 2022.4. diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Env.kt similarity index 79% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Env.kt index 9183a1b6..4c5108c7 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Env.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Env.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal internal var env: Env = RealEnv diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/LoggerFactory.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/LoggerFactory.kt new file mode 100644 index 00000000..061ad647 --- /dev/null +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/LoggerFactory.kt @@ -0,0 +1,22 @@ +package com.gabrielfeo.develocity.api.internal + +import ch.qos.logback.classic.Level +import com.gabrielfeo.develocity.api.Config +import org.slf4j.Logger +import kotlin.reflect.KClass + +interface LoggerFactory { + fun newLogger(cls: KClass<*>): Logger +} + +class RealLoggerFactory( + private val config: Config, +) : LoggerFactory { + + override fun newLogger(cls: KClass<*>): Logger { + val impl = org.slf4j.LoggerFactory.getLogger(cls.java) as ch.qos.logback.classic.Logger + return impl.apply { + level = Level.valueOf(config.logLevel) + } + } +} diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/OkHttpClient.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/OkHttpClient.kt new file mode 100644 index 00000000..6bf825a3 --- /dev/null +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/OkHttpClient.kt @@ -0,0 +1,92 @@ +package com.gabrielfeo.develocity.api.internal + +import com.gabrielfeo.develocity.api.Config +import com.gabrielfeo.develocity.api.internal.auth.HttpBearerAuth +import com.gabrielfeo.develocity.api.internal.caching.CacheEnforcingInterceptor +import com.gabrielfeo.develocity.api.internal.caching.CacheHitLoggingInterceptor +import okhttp3.Cache +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC +import okhttp3.logging.HttpLoggingInterceptor.Level.BODY +import java.time.Duration +import org.slf4j.Logger + +/** + * Base instance just so that multiple created [Config]s will share resources by default. + */ +internal val basicOkHttpClient by lazy { + OkHttpClient.Builder().build() +} + +/** + * Builds the final `OkHttpClient` with a `Config`. + */ +internal fun buildOkHttpClient( + config: Config, + loggerFactory: LoggerFactory, +) = with(config.clientBuilder) { + readTimeout(Duration.ofMillis(config.readTimeoutMillis)) + if (config.cacheConfig.cacheEnabled) { + cache(buildCache(config, loggerFactory)) + } + addInterceptors(config, loggerFactory) + addNetworkInterceptors(config, loggerFactory) + build().apply { + config.maxConcurrentRequests?.let { + dispatcher.maxRequests = it + dispatcher.maxRequestsPerHost = it + } + } +} + +private fun OkHttpClient.Builder.addInterceptors( + config: Config, + loggerFactory: LoggerFactory, +) { + if (config.cacheConfig.cacheEnabled) { + val logger = loggerFactory.newLogger(CacheHitLoggingInterceptor::class) + addInterceptor(CacheHitLoggingInterceptor(logger)) + } +} + +private fun OkHttpClient.Builder.addNetworkInterceptors( + config: Config, + loggerFactory: LoggerFactory, +) { + if (config.cacheConfig.cacheEnabled) { + addNetworkInterceptor(buildCacheEnforcingInterceptor(config)) + } + val httpLogger = loggerFactory.newLogger(HttpLoggingInterceptor::class) + getHttpLoggingInterceptorForLogger(httpLogger)?.let { + addNetworkInterceptor(it) + } + addNetworkInterceptor(HttpBearerAuth("bearer", config.apiToken())) +} + +private fun getHttpLoggingInterceptorForLogger(logger: Logger): Interceptor? = when { + logger.isDebugEnabled -> HttpLoggingInterceptor(logger = logger::debug).apply { level = BASIC } + logger.isTraceEnabled -> HttpLoggingInterceptor(logger = logger::debug).apply { level = BODY } + else -> null +} + +internal fun buildCache( + config: Config, + loggerFactory: LoggerFactory, +): Cache { + val cacheDir = config.cacheConfig.cacheDir + val maxSize = config.cacheConfig.maxCacheSize + val logger = loggerFactory.newLogger(Cache::class) + logger.debug("HTTP cache dir: {} (max {}B)", cacheDir, maxSize) + return Cache(cacheDir, maxSize) +} + +private fun buildCacheEnforcingInterceptor( + config: Config, +) = CacheEnforcingInterceptor( + longTermCacheUrlPattern = config.cacheConfig.longTermCacheUrlPattern, + longTermCacheMaxAge = config.cacheConfig.longTermCacheMaxAge, + shortTermCacheUrlPattern = config.cacheConfig.shortTermCacheUrlPattern, + shortTermCacheMaxAge = config.cacheConfig.shortTermCacheMaxAge, +) diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Retrofit.kt similarity index 86% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Retrofit.kt index 359f9f2a..2e3b6843 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Retrofit.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/Retrofit.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal -import com.gabrielfeo.gradle.enterprise.api.Config +import com.gabrielfeo.develocity.api.Config import com.squareup.moshi.Moshi import okhttp3.OkHttpClient import retrofit2.Retrofit diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/SystemProperties.kt similarity index 84% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/SystemProperties.kt index 302007d1..4305f1db 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/SystemProperties.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/SystemProperties.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal internal var systemProperties: SystemProperties = RealSystemProperties diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptor.kt similarity index 94% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptor.kt index 847e89d0..68f6be7d 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptor.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptor.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal.caching +package com.gabrielfeo.develocity.api.internal.caching import okhttp3.Interceptor import okhttp3.Request diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheHitLoggingInterceptor.kt similarity index 65% rename from library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt rename to library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheHitLoggingInterceptor.kt index 07f23289..f3149390 100644 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheHitLoggingInterceptor.kt +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheHitLoggingInterceptor.kt @@ -1,12 +1,12 @@ -package com.gabrielfeo.gradle.enterprise.api.internal.caching +package com.gabrielfeo.develocity.api.internal.caching import okhttp3.Interceptor import okhttp3.Response -import java.util.logging.Level -import java.util.logging.Logger +import org.slf4j.Logger + internal class CacheHitLoggingInterceptor( - private val logger: Logger = Logger.getGlobal(), + private val logger: Logger, ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { @@ -14,7 +14,7 @@ internal class CacheHitLoggingInterceptor( val response = chain.proceed(chain.request()) val wasHit = with(response) { cacheResponse != null && networkResponse == null } val hitOrMiss = if (wasHit) "hit" else "miss" - logger.log(Level.INFO, "Cache $hitOrMiss: $url") + logger.debug("Cache {}: {}", hitOrMiss, url) return response } } diff --git a/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt new file mode 100644 index 00000000..59aeeb4b --- /dev/null +++ b/library/src/main/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegration.kt @@ -0,0 +1,13 @@ +package com.gabrielfeo.develocity.api.internal.jupyter + +import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration + +@Suppress("unused") +class DevelocityApiJupyterIntegration : JupyterIntegration() { + + override fun Builder.onLoaded() { + import("com.gabrielfeo.develocity.api.*") + import("com.gabrielfeo.develocity.api.model.*") + import("com.gabrielfeo.develocity.api.extension.*") + } +} diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt deleted file mode 100644 index 1fec3fc9..00000000 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/Keychain.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - -internal var keychain: Keychain = RealKeychain(systemProperties) - -internal interface Keychain { - fun get(entry: String): KeychainResult -} - -internal sealed interface KeychainResult { - data class Success(val token: String) : KeychainResult - data class Error(val description: String) : KeychainResult -} - -internal class RealKeychain( - private val systemProperties: SystemProperties, -) : Keychain { - override fun get( - entry: String, - ): KeychainResult { - val login = systemProperties["user.name"] ?: - return KeychainResult.Error("null user.name") - val process = ProcessBuilder( - "security", "find-generic-password", "-w", "-a", login, "-s", entry - ).start() - val status = process.waitFor() - if (status != 0) { - return KeychainResult.Error("exit $status") - } - val token = process.inputStream.bufferedReader().use { - it.readText().trim() - } - return KeychainResult.Success(token) - } -} diff --git a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt b/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt deleted file mode 100644 index 84dfc855..00000000 --- a/library/src/main/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/OkHttpClient.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - -import com.gabrielfeo.gradle.enterprise.api.Config -import com.gabrielfeo.gradle.enterprise.api.internal.auth.HttpBearerAuth -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheEnforcingInterceptor -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheHitLoggingInterceptor -import okhttp3.Cache -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import okhttp3.logging.HttpLoggingInterceptor.Level.BODY -import java.time.Duration -import java.util.logging.Level -import java.util.logging.Logger - -/** - * Base instance just so that multiple created [Config]s will share resources by default. - */ -internal val basicOkHttpClient by lazy { - OkHttpClient.Builder().build() -} - -/** - * Builds the final `OkHttpClient` with a `Config`. - */ -internal fun buildOkHttpClient( - config: Config, -) = with(config.clientBuilder) { - readTimeout(Duration.ofMillis(config.readTimeoutMillis)) - if (config.cacheConfig.cacheEnabled) { - cache(buildCache(config)) - } - addInterceptors(config) - addNetworkInterceptors(config) - build().apply { - config.maxConcurrentRequests?.let { - dispatcher.maxRequests = it - dispatcher.maxRequestsPerHost = it - } - } -} - -private fun OkHttpClient.Builder.addInterceptors(config: Config) { - if (config.debugLoggingEnabled && config.cacheConfig.cacheEnabled) { - addInterceptor(CacheHitLoggingInterceptor()) - } -} - -private fun OkHttpClient.Builder.addNetworkInterceptors(config: Config) { - if (config.cacheConfig.cacheEnabled) { - addNetworkInterceptor(buildCacheEnforcingInterceptor(config)) - } - if (config.debugLoggingEnabled) { - addNetworkInterceptor(HttpLoggingInterceptor().apply { level = BODY }) - } - addNetworkInterceptor(HttpBearerAuth("bearer", config.apiToken())) -} - -internal fun buildCache( - config: Config -): Cache { - val cacheDir = config.cacheConfig.cacheDir - val maxSize = config.cacheConfig.maxCacheSize - if (config.debugLoggingEnabled) { - val logger = Logger.getGlobal() - logger.log(Level.INFO, "HTTP cache dir: $cacheDir (max ${maxSize}B)") - } - return Cache(cacheDir, maxSize) -} - -private fun buildCacheEnforcingInterceptor( - config: Config, -) = CacheEnforcingInterceptor( - longTermCacheUrlPattern = config.cacheConfig.longTermCacheUrlPattern, - longTermCacheMaxAge = config.cacheConfig.longTermCacheMaxAge, - shortTermCacheUrlPattern = config.cacheConfig.shortTermCacheUrlPattern, - shortTermCacheMaxAge = config.cacheConfig.shortTermCacheMaxAge, -) diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt similarity index 82% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt index c5f1bcef..9289734e 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/CacheConfigTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/CacheConfigTest.kt @@ -1,15 +1,13 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import kotlin.test.* class CacheConfigTest { @BeforeTest fun before() { - env = FakeEnv("GRADLE_ENTERPRISE_API_URL" to "https://example.com/api/") - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() + env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/") } @Test @@ -41,4 +39,4 @@ class CacheConfigTest { assertTrue(matches(it), "/$pattern/ doesn't match '$it'") } } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt new file mode 100644 index 00000000..99fb8977 --- /dev/null +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/ConfigTest.kt @@ -0,0 +1,54 @@ +package com.gabrielfeo.develocity.api + +import com.gabrielfeo.develocity.api.internal.* +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.test.* + +class ConfigTest { + + @BeforeTest + fun before() { + env = FakeEnv("DEVELOCITY_API_URL" to "https://example.com/api/") + } + + @Test + fun `Given no URL set in env, error`() { + env = FakeEnv() + assertFails { + Config() + } + } + + @Test + fun `Given URL set in env, apiUrl is env URL`() { + (env as FakeEnv)["DEVELOCITY_API_URL"] = "https://example.com/api/" + assertEquals("https://example.com/api/", Config().apiUrl) + } + + @Test + fun `Given no token, error`() { + assertFails { + Config().apiToken() + } + } + + @Test + fun `Given token set in env, apiToken is env token`() { + (env as FakeEnv)["DEVELOCITY_API_TOKEN"] = "bar" + assertEquals("bar", Config().apiToken()) + } + + @Test + fun `maxConcurrentRequests accepts int`() { + (env as FakeEnv)["DEVELOCITY_API_MAX_CONCURRENT_REQUESTS"] = "1" + assertDoesNotThrow { + Config().maxConcurrentRequests + } + } + + @Test + fun `Given timeout set in env, readTimeoutMillis returns env value`() { + (env as FakeEnv)["DEVELOCITY_API_READ_TIMEOUT_MILLIS"] = "100000" + assertEquals(100_000L, Config().readTimeoutMillis) + } +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiExtensionsTest.kt similarity index 87% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiExtensionsTest.kt index 82c9fc25..5cca4128 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiExtensionsTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiExtensionsTest.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.extension.getBuildsFlow -import com.gabrielfeo.gradle.enterprise.api.extension.getGradleAttributesFlow -import com.gabrielfeo.gradle.enterprise.api.model.FakeBuild +import com.gabrielfeo.develocity.api.extension.getBuildsFlow +import com.gabrielfeo.develocity.api.extension.getGradleAttributesFlow +import com.gabrielfeo.develocity.api.model.FakeBuild import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.runTest @@ -11,7 +11,7 @@ import kotlin.test.assertEquals import kotlin.time.Duration.Companion.seconds @OptIn(ExperimentalCoroutinesApi::class) -class GradleEnterpriseApiExtensionsTest { +class DevelocityApiExtensionsTest { private val api = FakeBuildsApi( builds = listOf( diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt similarity index 51% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt index 31b23514..4c19dbf4 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/GradleEnterpriseApiTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/DevelocityApiTest.kt @@ -1,36 +1,32 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import kotlin.test.Test import kotlin.test.assertContains -class GradleEnterpriseApiTest { +class DevelocityApiTest { @Test fun `Fails eagerly if no API URL`() { env = FakeEnv() - keychain = FakeKeychain() - systemProperties = FakeSystemProperties.linux val error = assertThrows { - GradleEnterpriseApi.newInstance(Config()) + DevelocityApi.newInstance(Config()) } - error.assertRootMessageContains("GRADLE_ENTERPRISE_API_URL") + error.assertRootMessageContains("DEVELOCITY_API_URL") } @Test fun `Fails lazily if no API token`() { - env = FakeEnv("GRADLE_ENTERPRISE_API_URL" to "example-url") - keychain = FakeKeychain() - systemProperties = FakeSystemProperties.linux + env = FakeEnv("DEVELOCITY_API_URL" to "example-url") val api = assertDoesNotThrow { - GradleEnterpriseApi.newInstance(Config()) + DevelocityApi.newInstance(Config()) } val error = assertThrows { api.buildsApi.toString() } - error.assertRootMessageContains("GRADLE_ENTERPRISE_API_TOKEN") + error.assertRootMessageContains("DEVELOCITY_API_TOKEN") } private fun Throwable.assertRootMessageContains(text: String) { diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/FakeBuildsApi.kt similarity index 93% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/FakeBuildsApi.kt index 87af1da0..9f1f1313 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeBuildsApi.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/FakeBuildsApi.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.model.* import kotlinx.coroutines.flow.MutableStateFlow import retrofit2.http.Query @@ -21,6 +21,7 @@ class FakeBuildsApi( maxWaitSecs: Int?, query: String?, models: List?, + allModels: Boolean?, ): List { getBuildsCallCount.value++ check((reverse ?: maxWaitSecs ?: query ?: models) == null) { "Not supported" } @@ -50,4 +51,4 @@ class FakeBuildsApi( val attrs = readFromJsonResource("gradle-attributes-response.json") return attrs.copy(id = id) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt similarity index 55% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt index 41f68205..800abd9b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/OkHttpClientTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/OkHttpClientTest.kt @@ -1,10 +1,9 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* -import com.gabrielfeo.gradle.enterprise.api.internal.FakeKeychain -import com.gabrielfeo.gradle.enterprise.api.internal.auth.HttpBearerAuth -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheEnforcingInterceptor -import com.gabrielfeo.gradle.enterprise.api.internal.caching.CacheHitLoggingInterceptor +import com.gabrielfeo.develocity.api.internal.* +import com.gabrielfeo.develocity.api.internal.auth.HttpBearerAuth +import com.gabrielfeo.develocity.api.internal.caching.CacheEnforcingInterceptor +import com.gabrielfeo.develocity.api.internal.caching.CacheHitLoggingInterceptor import okhttp3.Dispatcher import okhttp3.OkHttpClient import kotlin.test.* @@ -20,7 +19,7 @@ class OkHttpClientTest { @Test fun `Given maxConcurrentRequests, sets values in Dispatcher`() { val client = buildClient( - "GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS" to "123" + "DEVELOCITY_API_MAX_CONCURRENT_REQUESTS" to "123" ) assertEquals(123, client.dispatcher.maxRequests) assertEquals(123, client.dispatcher.maxRequestsPerHost) @@ -40,34 +39,16 @@ class OkHttpClientTest { assertEquals(1, client.dispatcher.maxRequestsPerHost) } - @Test - fun `Given debug logging and cache enabled, adds logging interceptors`() { - val client = buildClient( - "GRADLE_ENTERPRISE_API_DEBUG_LOGGING" to "true", - "GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true", - ) - assertTrue(client.interceptors.any { it is CacheHitLoggingInterceptor }) - } - - @Test - fun `Given debug logging disabled, doesn't add logging interceptors`() { - val client = buildClient( - "GRADLE_ENTERPRISE_API_DEBUG_LOGGING" to "false", - "GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true", - ) - assertTrue(client.interceptors.none { it is CacheHitLoggingInterceptor }) - } - @Test fun `Given cache enabled, configures caching`() { - val client = buildClient("GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "true") + val client = buildClient("DEVELOCITY_API_CACHE_ENABLED" to "true") assertTrue(client.networkInterceptors.any { it is CacheEnforcingInterceptor }) assertNotNull(client.cache) } @Test fun `Given cache disabled, no caching or cache logging`() { - val client = buildClient("GRADLE_ENTERPRISE_API_CACHE_ENABLED" to "false") + val client = buildClient("DEVELOCITY_API_CACHE_ENABLED" to "false") assertTrue(client.networkInterceptors.none { it is CacheEnforcingInterceptor }) assertTrue(client.interceptors.none { it is CacheHitLoggingInterceptor }) assertNull(client.cache) @@ -85,17 +66,15 @@ class OkHttpClientTest { clientBuilder: OkHttpClient.Builder? = null, ): OkHttpClient { val fakeEnv = FakeEnv(*envVars) - if ("GRADLE_ENTERPRISE_API_TOKEN" !in fakeEnv) - fakeEnv["GRADLE_ENTERPRISE_API_TOKEN"] = "example-token" - if ("GRADLE_ENTERPRISE_API_URL" !in fakeEnv) - fakeEnv["GRADLE_ENTERPRISE_API_URL"] = "example-url" + if ("DEVELOCITY_API_TOKEN" !in fakeEnv) + fakeEnv["DEVELOCITY_API_TOKEN"] = "example-token" + if ("DEVELOCITY_API_URL" !in fakeEnv) + fakeEnv["DEVELOCITY_API_URL"] = "example-url" env = fakeEnv - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() val config = when (clientBuilder) { null -> Config() else -> Config(clientBuilder = clientBuilder) } - return buildOkHttpClient(config) + return buildOkHttpClient(config, RealLoggerFactory(config)) } } diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt similarity index 61% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt index b3f23c84..3a79fad2 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/RetrofitTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/RetrofitTest.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.* +import com.gabrielfeo.develocity.api.internal.* import com.squareup.moshi.Moshi import retrofit2.Retrofit import kotlin.test.* @@ -10,7 +10,7 @@ class RetrofitTest { @Test fun `Sets instance URL from options, stripping api segment`() { val retrofit = buildRetrofit( - "GRADLE_ENTERPRISE_API_URL" to "https://example.com/api/", + "DEVELOCITY_API_URL" to "https://example.com/api/", ) // That's what generated classes expect assertEquals("https://example.com/", retrofit.baseUrl().toString()) @@ -20,7 +20,7 @@ class RetrofitTest { fun `Rejects invalid URL`() { assertFails { buildRetrofit( - "GRADLE_ENTERPRISE_API_URL" to "https://example.com/", + "DEVELOCITY_API_URL" to "https://example.com/", ) } } @@ -29,16 +29,14 @@ class RetrofitTest { vararg envVars: Pair, ): Retrofit { val fakeEnv = FakeEnv(*envVars) - if ("GRADLE_ENTERPRISE_API_TOKEN" !in fakeEnv) - fakeEnv["GRADLE_ENTERPRISE_API_TOKEN"] = "example-token" + if ("DEVELOCITY_API_TOKEN" !in fakeEnv) + fakeEnv["DEVELOCITY_API_TOKEN"] = "example-token" env = fakeEnv - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() val config = Config() return buildRetrofit( config = config, - client = buildOkHttpClient(config), + client = buildOkHttpClient(config, RealLoggerFactory(config)), moshi = Moshi.Builder().build() ) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/TestResourceUtils.kt similarity index 79% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/TestResourceUtils.kt index 80b64cc7..862b131b 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/TestResourceUtils.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/TestResourceUtils.kt @@ -1,6 +1,6 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.internal.infrastructure.Serializer +import com.gabrielfeo.develocity.api.internal.infrastructure.Serializer import com.squareup.moshi.adapter import okio.buffer import okio.source diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsTest.kt similarity index 91% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsTest.kt index fa2aa5e0..767c067a 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/BuildsApiExtensionsTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/BuildsApiExtensionsTest.kt @@ -1,8 +1,8 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.FakeBuildsApi -import com.gabrielfeo.gradle.enterprise.api.model.Build -import com.gabrielfeo.gradle.enterprise.api.model.FakeBuild +import com.gabrielfeo.develocity.api.FakeBuildsApi +import com.gabrielfeo.develocity.api.model.Build +import com.gabrielfeo.develocity.api.model.FakeBuild import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -63,4 +63,4 @@ class BuildsApiExtensionsTest { assertEquals(2, api.getBuildsCallCount.value) channel.close() } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/MappingTest.kt similarity index 91% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/MappingTest.kt index cced9987..caa580b9 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/extension/MappingTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/extension/MappingTest.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.extension +package com.gabrielfeo.develocity.api.extension -import com.gabrielfeo.gradle.enterprise.api.FakeBuildsApi -import com.gabrielfeo.gradle.enterprise.api.model.FakeBuild +import com.gabrielfeo.develocity.api.FakeBuildsApi +import com.gabrielfeo.develocity.api.model.FakeBuild import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.asFlow @@ -51,4 +51,4 @@ class MappingTest { } assertEquals(5, api.getGradleAttributesCallCount.value) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt new file mode 100644 index 00000000..6ee2553f --- /dev/null +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt @@ -0,0 +1,2 @@ +package com.gabrielfeo.develocity.api.internal + diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt new file mode 100644 index 00000000..6ee2553f --- /dev/null +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeKeychain.kt @@ -0,0 +1,2 @@ +package com.gabrielfeo.develocity.api.internal + diff --git a/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt new file mode 100644 index 00000000..6ee2553f --- /dev/null +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/FakeSystemProperties.kt @@ -0,0 +1,2 @@ +package com.gabrielfeo.develocity.api.internal + diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptorTest.kt similarity index 97% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptorTest.kt index 10e4a140..2120ce96 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/caching/CacheEnforcingInterceptorTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/internal/caching/CacheEnforcingInterceptorTest.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal.caching +package com.gabrielfeo.develocity.api.internal.caching import okhttp3.OkHttpClient import okhttp3.Request diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/model/BuildAttributesValueExtensionsTest.kt similarity index 76% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/model/BuildAttributesValueExtensionsTest.kt index 8ac01051..dea7df32 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/BuildAttributesValueExtensionsTest.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/model/BuildAttributesValueExtensionsTest.kt @@ -1,7 +1,7 @@ -package com.gabrielfeo.gradle.enterprise.api.model +package com.gabrielfeo.develocity.api.model -import com.gabrielfeo.gradle.enterprise.api.extension.contains -import com.gabrielfeo.gradle.enterprise.api.extension.get +import com.gabrielfeo.develocity.api.extension.contains +import com.gabrielfeo.develocity.api.extension.get import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -25,4 +25,4 @@ class BuildAttributesValueExtensionsTest { assertTrue("foo" in list) assertTrue("bar" !in list) } -} \ No newline at end of file +} diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt b/library/src/test/kotlin/com/gabrielfeo/develocity/api/model/FakeBuild.kt similarity index 80% rename from library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt rename to library/src/test/kotlin/com/gabrielfeo/develocity/api/model/FakeBuild.kt index cff233dc..c956c003 100644 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/model/FakeBuild.kt +++ b/library/src/test/kotlin/com/gabrielfeo/develocity/api/model/FakeBuild.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.model +package com.gabrielfeo.develocity.api.model @Suppress("TestFunctionName") fun FakeBuild(id: String, availableAt: Long) = Build( diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt deleted file mode 100644 index 769a54e1..00000000 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/ConfigTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api - -import com.gabrielfeo.gradle.enterprise.api.internal.* -import org.junit.jupiter.api.assertDoesNotThrow -import kotlin.test.* - -class ConfigTest { - - @BeforeTest - fun before() { - env = FakeEnv("GRADLE_ENTERPRISE_API_URL" to "https://example.com/api/") - systemProperties = FakeSystemProperties.macOs - keychain = FakeKeychain() - } - - @Test - fun `Given no URL set in env, error`() { - env = FakeEnv() - assertFails { - Config() - } - } - - @Test - fun `Given URL set in env, apiUrl is env URL`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_URL"] = "https://example.com/api/" - assertEquals("https://example.com/api/", Config().apiUrl) - } - - @Test - fun `Given macOS and keychain token, keychain token used`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_TOKEN"] = "bar" - keychain = FakeKeychain("gradle-enterprise-api-token" to "foo") - assertEquals("foo", Config().apiToken()) - } - - @Test - fun `Given macOS but no keychain token, env token used`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_TOKEN"] = "bar" - assertEquals("bar", Config().apiToken()) - } - - @Test - fun `Given Linux, keychain never tried and env token used`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_TOKEN"] = "bar" - keychain = object : Keychain { - override fun get(entry: String) = - error("Error: Tried to access macOS keychain in Linux") - } - systemProperties = FakeSystemProperties.linux - assertEquals("bar", Config().apiToken()) - } - - @Test - fun `Given macOS and no token anywhere, error`() { - assertFails { - Config().apiToken() - } - } - - @Test - fun `Given Linux and no env token, fails`() { - systemProperties = FakeSystemProperties.linux - assertFails { - Config().apiToken() - } - } - - @Test - fun `maxConcurrentRequests accepts int`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_MAX_CONCURRENT_REQUESTS"] = "1" - assertDoesNotThrow { - Config().maxConcurrentRequests - } - } - - @Test - fun `Given timeout set in env, readTimeoutMillis returns env value`() { - (env as FakeEnv)["GRADLE_ENTERPRISE_API_READ_TIMEOUT_MILLIS"] = "100000" - assertEquals(100_000L, Config().readTimeoutMillis) - } -} \ No newline at end of file diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt deleted file mode 100644 index b4de697f..00000000 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt +++ /dev/null @@ -1,2 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt deleted file mode 100644 index b4de697f..00000000 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt +++ /dev/null @@ -1,2 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - diff --git a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt b/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt deleted file mode 100644 index b4de697f..00000000 --- a/library/src/test/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt +++ /dev/null @@ -1,2 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/FakeDevelocityApiScaffold.kt similarity index 63% rename from library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt rename to library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/FakeDevelocityApiScaffold.kt index a6903f38..a1469de1 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/FakeGradleEnterpriseApiScaffold.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/FakeDevelocityApiScaffold.kt @@ -1,10 +1,10 @@ -package com.gabrielfeo.gradle.enterprise.api +package com.gabrielfeo.develocity.api -import com.gabrielfeo.gradle.enterprise.api.model.* +import com.gabrielfeo.develocity.api.model.* import retrofit2.http.Query /** - * Scaffold for a fake `GradleEnterpriseApi` implementation with default methods throwing a + * Scaffold for a fake `DevelocityApi` implementation with default methods throwing a * [NotImplementedError]. Extend this interface and override methods to fake behavior as needed. */ interface FakeBuildsApiScaffold : BuildsApi { @@ -12,6 +12,7 @@ interface FakeBuildsApiScaffold : BuildsApi { override suspend fun getBuild( id: String, models: List?, + allModels: Boolean?, availabilityWaitTimeoutSecs: Int?, ): Build { TODO("Not yet implemented") @@ -27,49 +28,45 @@ interface FakeBuildsApiScaffold : BuildsApi { maxWaitSecs: Int?, query: String?, models: List?, + allModels: Boolean?, ): List { TODO("Not yet implemented") } - override suspend fun getGradleNetworkActivity( + override suspend fun getGradleArtifactTransformExecutions( id: String, availabilityWaitTimeoutSecs: Int?, - ): GradleNetworkActivity { + ): GradleArtifactTransformExecutions { TODO("Not yet implemented") } - override suspend fun getMavenDependencyResolution( - id: String, - availabilityWaitTimeoutSecs: Int?, - ): MavenDependencyResolution { + override suspend fun getGradleAttributes(id: String, availabilityWaitTimeoutSecs: Int?): GradleAttributes { TODO("Not yet implemented") } - override suspend fun getGradleAttributes( + override suspend fun getGradleBuildCachePerformance( id: String, availabilityWaitTimeoutSecs: Int?, - ): GradleAttributes { + ): GradleBuildCachePerformance { TODO("Not yet implemented") } - override suspend fun getGradleBuildCachePerformance( - id: String, - availabilityWaitTimeoutSecs: Int?, - ): GradleBuildCachePerformance { + override suspend fun getGradleDeprecations(id: String, availabilityWaitTimeoutSecs: Int?): GradleDeprecations { TODO("Not yet implemented") } - override suspend fun getGradleProjects( + override suspend fun getGradleNetworkActivity( id: String, availabilityWaitTimeoutSecs: Int?, - ): List { + ): GradleNetworkActivity { TODO("Not yet implemented") } - override suspend fun getMavenAttributes( - id: String, - availabilityWaitTimeoutSecs: Int?, - ): MavenAttributes { + override suspend fun getGradleProjects(id: String, availabilityWaitTimeoutSecs: Int?): List { + TODO("Not yet implemented") + } + + override suspend fun getMavenAttributes(id: String, availabilityWaitTimeoutSecs: Int?): MavenAttributes { TODO("Not yet implemented") } @@ -80,10 +77,14 @@ interface FakeBuildsApiScaffold : BuildsApi { TODO("Not yet implemented") } - override suspend fun getMavenModules( + override suspend fun getMavenDependencyResolution( id: String, availabilityWaitTimeoutSecs: Int?, - ): List { + ): MavenDependencyResolution { + TODO("Not yet implemented") + } + + override suspend fun getMavenModules(id: String, availabilityWaitTimeoutSecs: Int?): List { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt similarity index 84% rename from library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt rename to library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt index c79dd768..291648d8 100644 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeEnv.kt +++ b/library/src/testFixtures/kotlin/com/gabrielfeo/develocity/api/internal/FakeEnv.kt @@ -1,4 +1,4 @@ -package com.gabrielfeo.gradle.enterprise.api.internal +package com.gabrielfeo.develocity.api.internal class FakeEnv( vararg vars: Pair, @@ -10,4 +10,4 @@ class FakeEnv( operator fun set(name: String, value: String?) = vars.put(name, value) operator fun contains(name: String) = name in vars -} \ No newline at end of file +} diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt deleted file mode 100644 index 37fa4315..00000000 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeKeychain.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - -internal class FakeKeychain( - vararg entries: Pair, -) : Keychain { - - private val entries = entries.toMap() - - override fun get(entry: String) = - entries[entry]?.let { KeychainResult.Success(it) } - ?: KeychainResult.Error("entry $entry not mocked") -} \ No newline at end of file diff --git a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt b/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt deleted file mode 100644 index 3694b67f..00000000 --- a/library/src/testFixtures/kotlin/com/gabrielfeo/gradle/enterprise/api/internal/FakeSystemProperties.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.gabrielfeo.gradle.enterprise.api.internal - -class FakeSystemProperties( - vararg vars: Pair, -) : SystemProperties { - - companion object { - val macOs = FakeSystemProperties("os.name" to "Mac OS X") - val linux = FakeSystemProperties("os.name" to "Linux") - } - - private val vars = vars.toMap(HashMap()) - - override fun get(name: String) = vars[name] - - operator fun set(name: String, value: String?) = vars.put(name, value) - operator fun contains(name: String) = name in vars -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index fd448217..33402b7d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,7 @@ +pluginManagement { + includeBuild("./build-logic") +} + plugins { id("com.gradle.enterprise") version("3.13.2") id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0") @@ -5,7 +9,8 @@ plugins { include( ":library", - ":examples:example-project:app", + ":examples", + ":examples:example-project", ) dependencyResolutionManagement {