TS-47380 Improve DX - #956
Merged
Merged
Conversation
Shadow deprecated `enableKotlinModuleRemapping` in 9.5.0 and removes it in Shadow 10 (GradleUp/shadow#2073). Applying the transformer explicitly keeps the contents of the `.kotlin_module` files relocated. The shaded agent jar is byte-identical to before. Also drop the four explicit relocate(...) calls. They are leftovers from the kotlin-shadow-relocator plugin removed in 3fd564f, where they took care of the Kotlin metadata that the transformer now handles. In regular builds they only duplicated what enableAutoRelocation already does, save for two unwanted string rewrites: the "retrofitBuilderAction" parameter name in HttpUtils and a string in retrofit's own @metadata, both caused by "retrofit" matching beyond package boundaries. In -Pdebug=true builds they relocated kotlin, okhttp3, okio and retrofit even though that build exists precisely to keep the package names in the jar matching the ones IntelliJ knows from the source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… time The logback configurations hardcoded the shadow relocation prefix, so they were only valid for the production build. A -Pdebug=true build disables auto relocation, but the bundled configs still asked for shadow.ch.qos.logback.*; logback then failed to create the appenders and LoggingUtils swallowed the resulting JoranException, so the agent silently logged nowhere. The configurations are now checked in without the prefix, which makes them usable from the IDE, from unit tests and in debug builds. ShadowLoggingPackages adds the prefix while packaging them into the shaded jar, into the shadow distribution and into the maven plugin jar, whose config is handed to the shaded agent. VerifyShadowedLoggingConfigs scans the produced archives and asserts the expected form for both build modes, so a newly added configuration that is not covered by the patterns fails the build. Also in this commit: - The <logger> entries for org.apache.spark and org.eclipse.jetty are gone. Neither can exist in the shipped agent since Jetty was removed in 37.0.0 and spark-core is only a test dependency. - sample-debugging-app no longer keeps its own copy of logback.console.xml and uses the now unprefixed dist template instead. - The shadow convention applies KotlinModuleMetadataTransformer explicitly and no longer declares the relocations that auto relocation already covers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Gradle's --debug-jvm only suspends the test JVM. System tests that spawn their own JVM via ProcessUtils (e.g. teamscale-profiler-configuration-test, sut-uses-logback-test) run the agent in that spawned process, which could not be debugged at all. Running the build with -PdebugSut[=<port>] now makes every `java` process started by a system test wait for a debugger on the given port (5005 by default). Other commands, such as Maven or chcp.com, are left untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… control To profile sample-debugging-app against a real Teamscale instance, the committed jacocoagent.properties had to be edited with a real access token, which is easy to commit by accident. The run task now prefers jacocoagent.local.properties if it exists, and that file is git-ignored. jacocoagent.properties stays in the repository as the template to copy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every other system test packages its system under test alongside itself in src/main/.../systemundertest/. http-server-shutdown instead ran the top-level sample-app module, and teamscale-profiler-configuration-test declared a dependency on it that it never used, since it runs its own jar. http-server-shutdown now runs its own SystemUnderTest, which it already contained but never executed, and the unused dependency is gone. Nothing in the build references sample-app any more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The agent logs into a temporary directory that users cannot realistically find, and never fails the profiled application. A misconfiguration therefore shows up as an application that runs fine and silently collects no coverage, unless the agent reports the problem on the console. That behaviour was only checked by hand via sample-app/run-log-test.sh. It is now a system test covering both cases the script exercised: options that fail to parse, and a log directory that cannot be written to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing in the build depends on it any more: http-server-shutdown runs its own system under test, and run-log-test.sh has been replaced by the invalid-options-logging-test system test. The remaining run-with-profiler.sh was a manual smoke test of the packaged distribution against a hardcoded localhost Teamscale, which no longer justifies a module of its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The README explained how to attach a debugger to the agent, but not how to get the profiler talking to a real Teamscale instance, how its configuration is assembled from five different sources, where it writes its logs, or why a misconfigured profiler starts up without complaining. Adds docs/DEBUGGING.md covering those topics, and reduces the README's debugging sections to a pointer so each topic is documented in one place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two JavaExec tasks that attach the profiler each declared the dependency on :agent:shadowJar themselves. Declaring it in teamscaleAgent() instead keeps it next to the -javaagent argument that needs the jar.
The configuration cache could not be stored at all. Two script lambdas
captured the build script itself, the installer's --patch-module argument
provider captured a SourceSetOutput that the cache cannot restore into
that field type, and the jlink opt-outs pointed at a bug that
badass-jlink fixed in 4.0.0.
Project isolation additionally failed on our own cross-project access.
group and version now come from a beforeProject hook in the settings file
instead of allprojects {}, the publishToMavenLocal aggregator depends on
explicit task paths instead of inspecting evaluated subprojects, and both
the shaded agent jar and the installer's jlink image are shared through
configurations rather than by reaching into another project's tasks. The
latter also removes evaluationDependsOn(":installer"). The packaged
distribution is unchanged.
The configuration cache is stored and reused now, but stays disabled by
default: jdkDownload puts a Groovy closure into the jlink targetPlatforms
input, which the cache replaces with a non-serializable BrokenObject, so
every build that runs jlink still has to fall back. The opt-outs are kept
with that as their reason.
Explicit daemon heap is needed because the Kotlin daemon runs out of
memory compiling the larger modules, and configuring all projects in
parallel needs more headroom in the Gradle daemon itself.
The one remaining project isolation problem comes from
io.github.gradle-nexus.publish-plugin, which cross-configures subprojects
and has no newer release to upgrade to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The com.teamscale.agent-jar convention plugin already wires up the dependency: createAgentCopy() resolves the teamscaleAgentJar configuration, which points at :agent's shaded jar, and declares it as a task dependency. Depending on :agent:shadowJar by path on top of that adds nothing and hides where the dependency actually comes from. The same redundancy existed twice, in teamscaleAgent() and in the test task of the system test convention. Verified that :agent:shadowJar is still in the task graph of both a JavaExec and a test task without it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The jlink tasks expose their target platforms as an @input. Configuring a platform with `jdkDownload` stores the JDK home in there as a lazily evaluated Groovy closure, and the configuration cache replaces that closure's owner with a non-serializable BrokenObject. Fingerprinting the input then fails once the task graph is restored from the cache: java.io.NotSerializableException: ...ClosureCodec$BrokenObject Since the whole build degrades to running without the configuration cache as soon as an incompatible task is in the graph, this affected `build`, `assemble`, `dist` and `publish` alike, not just the installer. We now provision the JDKs ourselves and hand jlink a plain path, which keeps the input serializable. The archives are declared as dependencies of an Adoptium Ivy repository, so Gradle caches them across builds instead of re-downloading them into the build directory, and unpacking them is an ordinary Sync task. The archive names and the release the repository points at both derive from the JDK version in gradle.properties, so an upgrade only has to touch one place. The opt-out for CreateMergedModuleTask was redundant either way, as it extends BaseTask like every other task that reads a target platform. Verified that this does not change what we ship: of the 227 files in the two runtime images, 226 are bit-identical to the ones the previous setup produced. The remaining one, lib/modules, differs between two runs of unchanged code as well, so jlink does not write it deterministically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every task in the build is compatible with it now, so developers no
longer have to remember --configuration-cache. Also turns on parallel
storing and loading of the cache entry.
Project isolation stays off, and gradle.properties records why: the
nexus publish plugin cross-configures every project via allprojects {},
which fails any build that enables it, down to ./gradlew help.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
io.github.gradle-nexus.publish-plugin applies plugins to every project
via allprojects {}, which project isolation forbids. It is also the last
release of a plugin built for OSSRH, which Sonatype has replaced with
the Central Portal; we were already routing through the portal's OSSRH
compatibility API.
com.gradleup.nmcp publishes to the portal directly. Each publishing
project contributes its publications to an outgoing variant, and the
root project aggregates those into a single deployment by declaring
ordinary project dependencies, so nothing cross-configures anything.
The Gradle plugin keeps going to the Gradle Plugin Portal only, as
before, so it stays out of the aggregation on both sides.
Credentials still come from the sonatypeUsername and sonatypePassword
properties, which the nexus plugin read by convention and we now pass
explicitly, so the CI secrets stay as they are. They have to be a
Central Portal user token rather than a portal login.
Verified that the deployment is equivalent to what we published before:
the same seven artifacts, each with a jar, sources, javadoc, POM, module
file and checksums, and release rather than snapshot file names when
GITHUB_REF points at a tag. The signing tasks stay wired in front of the
staging step, though signatures themselves could only be checked where
the keys are available, i.e. in CI.
Project isolation still cannot be enabled: with the publish plugin gone,
org.beryx.jlink is now the first thing to fail it. gradle.properties
records the details.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Project isolation configures the projects in parallel and caches their
configuration per project, so that editing one build script no longer
invalidates the whole build. Enabling it required getting rid of
org.beryx.jlink.
That plugin collects the projects :installer depends on and reads their
configurations (Util.getAllDependentProjectsExt), which project isolation
forbids:
Plugin 'org.beryx.jlink': Project ':installer' cannot access
'Project.configurations' functionality on another project ':agent'
That fails every build, down to ./gradlew help, and 4.1.1 is still the
latest release.
com.github.iherasymenko.jlink does not reach into other projects, and it
builds cross-platform images the same way we did by hand: it links
against the jmods folder of a downloaded JDK rather than the one it runs
on, so one machine still produces both the Linux and the Windows image.
Its artifact transform unpacks that JDK, which replaces the
unpack<Os>RuntimeJdk tasks and the configuration cache workaround around
them.
The new plugin links real modules only, so the jars that ship without a
descriptor get one from org.gradlex.extra-java-module-info instead of
being merged into beryx's synthetic module: okio, jna, jna-platform and
the annotations that kotlin-stdlib pulls in. failOnAutomaticModules keeps
that list complete. kotlin-stdlib carries a real descriptor in
META-INF/versions/9, so forceMerge("kotlin") is gone as well.
The image is equivalent to the previous one: the same set of JDK modules,
with com.teamscale.merged.module replaced by com.sun.jna, the same size,
the same directory names, and both launchers that agent/src/dist starts.
-XX:-OmitStackTraceInFastThrow moves from beryx's hand-written launcher
scripts into the image itself via --add-options.
The JDK is declared as an ordinary single-string dependency rather than
through the plugin's group and jdkArchive properties, because the plugin
turns those into the map notation that Gradle 9 deprecates and Gradle 10
removes.
:installer no longer applies com.teamscale.system-test-convention. Its
tests are plain unit tests that only need a conflict-free port for their
mock Teamscale server; they never used the agent jar that the convention
pulls in, which is also what kept the build green while beryx was still
in place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stef2k16
reviewed
Aug 18, 2026
| **`-Punshaded=true` turns off relocation.** The agent is loaded by the same class loader as the application it | ||
| profiles, so anything it ships can interfere with that application. The jar therefore carries everything under a | ||
| `shadow.` package prefix which keeps the two apart. Those class names do not match what the IDE knows from the source | ||
| tree, so breakpoints would not bind and stack traces would be unreadable. `-Punshaded=true` disables relocation. |
Contributor
There was a problem hiding this comment.
We could remove the "-Punshaded=true disables relocation" since it is already mentioned above.
| - [Running the profiler locally](#running-the-profiler-locally) | ||
| - [How the configuration is resolved](#how-the-configuration-is-resolved) | ||
| - [Configuring the profiler from Teamscale (`config-id`)](#configuring-the-profiler-from-teamscale-config-id) | ||
| - [When nothing happens at all](#when-nothing-happens-at-all) |
Contributor
There was a problem hiding this comment.
The table of contents seems to be outdated. At least the "When nothing happens at all" section isn't there.
Adds the missing interface comments and lifts the jdwp flag conversion out of the `whenReady` lambda to stay within the nesting depth threshold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stef2k16
approved these changes
Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses issue TS-47380
Please respect the vote of the Teamscale bot or flag irrelevant findings as tolerated or false positives. If you feel that the Teamscale config needs adjustment, please state so in a comment and discuss this with your reviewer.