Skip to content

✅ add support for Windows#48

Merged
ryandens merged 37 commits into
mainfrom
ryandens/windows-build
Jul 6, 2026
Merged

✅ add support for Windows#48
ryandens merged 37 commits into
mainfrom
ryandens/windows-build

Conversation

@ryandens

@ryandens ryandens commented Nov 1, 2023

Copy link
Copy Markdown
Owner

Add Windows support to javaagent application distribution plugin

  • Introduces a new Platform enum with per-platform constants (line separator, path separator, app home variable, JVM opts format) for Unix and Windows.
  • Updates JavaagentAwareStartScriptGenerator to accept a Platform parameter, generating correct -javaagent paths and DEFAULT_JVM_OPTS format for each OS.
  • Updates JavaagentApplicationDistributionPlugin to use JavaagentAwareStartScriptGenerator for both Unix and Windows, replacing the previous no-op WindowsStartScriptGenerator.
  • CI matrix in gradle.yml now runs builds on both ubuntu-latest and windows-latest.
  • Functional tests are updated to handle OS-specific paths, script extensions, and expected script content; on Windows, TestKit uses a short root path to avoid MAX_PATH issues.
  • Behavioral Change: Windows start scripts now include -javaagent options using %APP_HOME% paths and quoted JVM opt tokens; previously Windows scripts had no javaagent support.

Macroscope summarized 54dca34.

@pixee-ryandens

pixee-ryandens Bot commented Nov 1, 2023

Copy link
Copy Markdown

Nice work! ✅ I analyzed your pull request and don't see any hardening opportunities.

@ryandens ryandens changed the title ✅ add windows to CI ✅ add support for Windows Nov 1, 2023
@ryandens ryandens changed the title ✅ add support for Windows ✅ add support for Windows Nov 1, 2023
@neboskreb

Copy link
Copy Markdown
Contributor

@ryandens this PR seems a bit stuck. Would you like some assistance? I'd gladly help to move it forward.

@ryandens ryandens force-pushed the ryandens/windows-build branch 2 times, most recently from 31ef87d to b6917f3 Compare December 18, 2025 03:41
@ryandens

Copy link
Copy Markdown
Owner Author

@neboskreb feel free! i just rebased it with main. I don't have a windows machine accessible, so getting the integration tests to pass on windows is a bit painful.

@neboskreb

Copy link
Copy Markdown
Contributor

I fixed a couple of very small bugs and it seems to work for me.

@ryandens I'd push my changes if you add me to the collaborators

@neboskreb

Copy link
Copy Markdown
Contributor

@ryandens I found a way to push my commits without pushing :) Created PR #298 to "push" those 2 commits into this branch - please see and merge.

@neboskreb

Copy link
Copy Markdown
Contributor

My investigation finds that the test failures in #298 come from IllegalArgumentException thrown from the internals of Java Instrumentation, probably triggered by an error condition inside or around the OpenTelemetryAgent:

Hello from my simple agent!
ERROR io.opentelemetry.javaagent.OpenTelemetryAgent
java.lang.IllegalArgumentException
	at java.instrument/sun.instrument.InstrumentationImpl.appendToClassLoaderSearch0(Native Method)
	at java.instrument/sun.instrument.InstrumentationImpl.appendToBootstrapClassLoaderSearch(InstrumentationImpl.java:276)
	at io.opentelemetry.javaagent.OpenTelemetryAgent.installBootstrapJar(OpenTelemetryAgent.java:98)
	at io.opentelemetry.javaagent.OpenTelemetryAgent.startAgent(OpenTelemetryAgent.java:56)
	at io.opentelemetry.javaagent.OpenTelemetryAgent.premain(OpenTelemetryAgent.java:46)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:544)
	at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:556)

Unfortunately, the exact cause of the exception is not know because OpenTelemetryAgent does not do a best job logging it, showing only the topmost layer and not printing the "caused by" part.

This error occurs only in test configuration, whereas the run configuration completes successfully.

Bumping up the version from 1.11.1 to the newest 1.23.0 brought no relief.

Also, no errors occur in test configuration if a different agent is used, for instance org.mockito:mockito-core:5.21.0 - in this case the test shows no malfunction.

@ryandens This looks to me like an Open Telemetry setup issue on Windows (or my machine?). Unfortunately, I'm not familiar with the framework. If you are, could you give me some hints to what could be wrong?

@ryandens

Copy link
Copy Markdown
Owner Author

Unfortunately, I also don't know what the issue was here - I also saw test failures in the javaagent-application plugin project, so I don't think this is isolated to the OpenTelemetry instrumentation agent. I could of course be mistaken. Thanks for your work on this issue, it looks like it brought it closer to the finish line.

@ryandens ryandens force-pushed the ryandens/windows-build branch from 38951ec to 2586352 Compare July 6, 2026 01:01
@ryandens

ryandens commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Manual reviews triggered for commit 2586352:

All prior checks · these links stay valid even if you push more commits.

@macroscopeapp

macroscopeapp Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review started! I'm analyzing the pull request and will post feedback through check runs when complete.

Comment thread .github/workflows/gradle.yml
Comment thread plugin/src/main/java/com/ryandens/javaagent/JavaForkOptionsConfigurer.java Outdated
Comment thread plugin/src/main/java/com/ryandens/javaagent/JavaForkOptionsConfigurer.java Outdated
@ryandens ryandens marked this pull request as ready for review July 6, 2026 08:34
Comment thread plugin/build.gradle.kts Outdated
ryandens and others added 25 commits July 6, 2026 09:49
… test

On Windows a `.bat` file is not a directly executable image, so Gradle's
Exec task (which uses ProcessBuilder/CreateProcess) fails to launch it with
"A problem occurred starting process 'command '.\hello-world.bat''". Route
the generated distribution start script through the command interpreter
(`cmd /c hello-world.bat`) on Windows while keeping `./hello-world` on Unix.

Fixes the 3 execStartScript-based functional tests on windows-latest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Unix start script relies on shell word-splitting of the unquoted
$DEFAULT_JVM_OPTS, so FakeTransformer collapses the individually-quoted
JVM opts into one space-separated string. On Windows the batch script does
not word-split inside quotes, so collapsing them glued `-Xmx256m` onto the
`-javaagent:` path, producing an invalid argument and making `java` (and
thus execStartScript) exit non-zero for distributions with an agent.

Skip the collapse on Windows so each opt stays its own quoted token, e.g.
  set DEFAULT_JVM_OPTS="-javaagent:%APP_HOME%\agent-libs\x.jar" "-Xmx256m"

Fixes the two agent-bearing execStartScript functional tests on windows-latest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The no-agent removal relied on the Unix opt-collapsing to turn the
placeholder into an unquoted `-javaagent:...PLACEHOLDER.jar ` substring.
On Windows each opt stays its own quoted token, so removing only the
unquoted placeholder left `DEFAULT_JVM_OPTS="" "-Xmx256m"`, and the empty
first argument made `java` (execStartScript) fail.

Also strip the surrounding quotes and trailing space of the Windows-style
`"-javaagent:...PLACEHOLDER.jar"` token so an agent-free distribution
produces `set DEFAULT_JVM_OPTS="-Xmx256m"`. Unix output is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Windows-only quoting in JavaForkOptionsConfigurer wrapped the agent
path in literal double quotes. The JVM launches fine (the Windows C runtime
strips the outer quotes when parsing the command line), but agents that
re-derive their own jar location get the still-quoted path: the OpenTelemetry
agent's premain throws IllegalArgumentException from
appendToBootstrapClassLoaderSearch, so its VersionLogger line never prints
and the two run/test functional tests fail on windows-latest.

The quoting is also unnecessary: these arguments are passed straight to a
forked JVM via ProcessBuilder (no shell), which already quotes arguments
containing spaces. Drop the manual quoting for all platforms.

Also reverts the temporary showStandardStreams diagnostic logging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenTelemetry javaagent 1.11.1 fails to load on Windows: its premain throws
IllegalArgumentException from appendToBootstrapClassLoaderSearch (an old bug
in how it locates its own bootstrap jar), so it never logs its version
banner. The `can attach to test task` and `can attach two agents` tests
assert on that banner, so they failed on windows-latest.

Bump those tests to 1.30.0, which logs its version banner on Windows -- the
same version the `can handle upgrade` distribution test already exercises
successfully there.

Also removes the temporary showStandardStreams diagnostic logging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
JavaagentApplicationRunPlugin configured the run task's -javaagent args via a
CommandLineArgumentProvider but never registered the javaagent configuration
as a task input, so Gradle established no dependency on the tasks that build
the agent jars. Under parallel execution the run task could launch with
-javaagent:simple-agent.jar before :simple-agent:jar had run, failing with
"Error opening zip file or JAR manifest missing" (reproducible 100% with
--rerun-tasks, and intermittently on CI).

Register the configuration as an input (a Configuration is Buildable), the
same way JavaagentApplicationDistributionPlugin already does, so the agent
jars are built before the run task launches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te PR)

The inputs.files/dependsOn wiring added to JavaagentApplicationRunPlugin to
fix the run-task build-ordering race also surfaced a pre-existing missing
dependency in the otel example (:app:run consuming :app:extendedAgent output
without a declared dependency), failing :otel:functionalTest validation.

Revert to keep this PR focused on Windows support; the run-task ordering
race will be addressed separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OpenTelemetry agent locates its own jar from the -javaagent path the JVM
was given, then appends it to the bootstrap classloader search. On the Windows
CI runner the Gradle caches sit behind a symlink/junction, so getCanonicalPath()
resolved to a path whose round-trip through the agent's jar-location logic
produced a name that appendToBootstrapClassLoaderSearch rejected with
IllegalArgumentException -- the agent then bailed out before logging its
version banner, failing the run/test functional tests on windows-latest.

Switch to getAbsolutePath(), the same unresolved form the distribution start
scripts already use successfully on Windows. Also drops the now-unnecessary
IOException handling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `can attach to test task` and `can attach two agents` tests attach the
OpenTelemetry javaagent from the TestKit's Gradle cache. That cache lives under
the deeply nested build/tmp/functionalTest/work/.gradle-test-kit, so on the
Windows CI runner the agent jar path
  D:\a\...\.gradle-test-kit\caches\modules-2\files-2.1\io.opentelemetry.javaagent\
    opentelemetry-javaagent\<ver>\<sha1>\opentelemetry-javaagent-<ver>.jar
was 267 characters -- past Windows' 260-char MAX_PATH. The JVM's native
appendToBootstrapClassLoaderSearch rejects such paths with
IllegalArgumentException, so the agent bailed out before logging its version
banner. simple-agent (121 chars) and the distribution start scripts (which copy
the agent into a short agent-libs/ path) were unaffected, and Linux has no such
limit -- which is why the failure was Windows-only and independent of the agent
version or path canonicalization.

Root the TestKit Gradle home at the drive root on Windows so the cached agent
jar path stays well within MAX_PATH.

Also reverts the temporary diagnostics and the speculative JavaForkOptionsConfigurer
changes (restored to the main baseline), which were not the cause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Platform now carries a defaultJvmOptsMapper (noop on Windows, collapse on
  Unix) and an agentArgSeparator, so JavaagentAwareStartScriptGenerator composes
  platform behavior instead of branching on `platform == Platform.WINDOWS`.
- Document what Fake.write does (rewrites/removes the javaagent placeholder in
  the rendered start script).
- Drop the org.apache.commons:commons-text test dependency; its only remaining
  use was escaping JAVA_HOME for the generated Groovy build script, now done
  with a small inline backslash/quote escape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ryandens ryandens force-pushed the ryandens/windows-build branch from 5422d5e to 54dca34 Compare July 6, 2026 16:49
@ryandens ryandens merged commit 1e746ca into main Jul 6, 2026
4 checks passed
@ryandens ryandens deleted the ryandens/windows-build branch July 6, 2026 16:58
@ryandens ryandens mentioned this pull request Jul 6, 2026
@ryandens ryandens linked an issue Jul 6, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for windows

3 participants