-
Notifications
You must be signed in to change notification settings - Fork 959
Add Java hosting integration #18033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Marshal Hayes (marshalhayes)
wants to merge
152
commits into
microsoft:main
Choose a base branch
from
marshalhayes:java-integration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+39,525
−3,081
Draft
Add Java hosting integration #18033
Changes from 13 commits
Commits
Show all changes
152 commits
Select commit
Hold shift + click to select a range
ae00afd
Start port of Community Toolkit Java integration
marshalhayes 52f6926
Add missed maven-wrapper.properties file
marshalhayes d756681
Add wrapper path override support and certificate trust
marshalhayes e5a85b7
Add VSCode debugger support
marshalhayes e8ec238
Add tests for Aspire.Hosting.Java
marshalhayes 154670d
Add gradle-wrapper.jar that was accidentally excluded
marshalhayes 9393f44
Merge remote-tracking branch 'upstream/main' into pr-18033-java
27aebcf
Fix build breaks from merging latest main
bccb8bf
Rework Java hosting API: annotation-backed launch modes, publish-corr…
8a10cf8
Add Java container publishing and complete VS Code debugger parity
742cdc7
Fix OpenTelemetry agent resolution and complete the Java sample
adamint 8df6660
Document the Java integration
7bedc37
Lock in JAVA_TOOL_OPTIONS quoting for paths containing spaces
d4fe94b
Drop the inert --enable-preview flag from the Java AppHost build
ff48b09
Give Java AppHosts a build-tool project model and fix JAR debugging
313c6e0
Address Copilot review: wrapper requirement, project roots, and Windo…
4a6abd3
Add an outerloop test that builds and runs the published Java image
3c61c81
Warn that the Spring Boot Dashboard bypasses Aspire
306a0c9
Add AddJavaContainerApp
c956dcb
Compile Maven/Gradle AppHosts with javac against the staged classpath
610532a
Keep the build tool out of the BuildKit cache mount
c701af9
Build Java images natively instead of under emulation
9258102
Auto-resolve Java debug entry points and fix duplicate deployment tar…
3404f05
Add AddQuarkusApp and a Quarkus sample, and fix Spring Boot endpoint …
29558b8
Fix review findings: shell quoting, build JDK floor, and stale build …
828ff84
Cover the wrapper-built image and the Java AppHost debugger gates
df7acd2
Validate WithJarArtifact paths and revert the dashboard launch profiles
0c83297
Merge remote-tracking branch 'upstream/main' into pr-18033-java
d56efd3
Point Quarkus telemetry at the Aspire dashboard
cb80103
Read the build JDK from the wrapper, and repair the Gradle wrapper
f0e0f0a
Make published Java images readable by the runtime user
d7a7e71
Generate the resource-builder bridge for no-options union overloads
611db68
Register the Java starter template with the CLI
3853607
Recommend the Java extension pack in the Java starter template
d137862
Address review feedback on version detection, image hardening and docs
e9c4a41
Reject authored container paths containing whitespace
ea83fa2
Declare generated Java numeric parameters as java.lang.Number
09218bc
Reject build-tool wrappers when parsing a Java AppHost launch
ae81f2d
Scope the Spring Boot lens to code and cross-build only portable JARs
ace62e3
Apply API review to the Java hosting surface
187a521
Warn about the Spring Boot Dashboard in parserless AppHost languages
1e2851e
Merge remote-tracking branch 'upstream/main' into pr-18033-java
0953089
Honour the named JAR when publishing, and fix Maven release precedence
b240441
Scope the Java README prerequisites to the modes that need them
869a5e5
Document how publish selects the JAR, and when it builds rather than …
7e45256
Ignore quoted version text in Gradle scripts, and cover Java AppHost …
23382cc
Only warn about the Spring Boot Dashboard in a conventional AppHost file
7c13526
Teach the extension that a Java AppHost is a first-class AppHost
a66c89f
Watch the Java AppHost with its real casing
6723df2
Reject a jarPath the container build cannot produce, and document the…
02138b9
Stop stale JARs accumulating in the AppHost dependency staging directory
eac530d
Describe what WithJarArtifact actually does when a jarPath is also set
9a3e35c
Clear staged Maven dependencies from the install path, not toolchain …
cc08ba7
Launch build tool wrappers through sh on Unix
7f1b51f
Quote the selected JAR before copying it in the container build
08a6332
Reject Windows-absolute authored paths wherever publishing runs
0e0cd26
Warn about the Spring Boot Dashboard for AddSpringBootApp too
ffea012
Stop opening launch profile URLs for orchestrated resources
f842aff
Fix polyglot crash, CRLF wrappers, and review findings; add a Java Ap…
022be11
Generate the Java SDK into its package directory so IDEs can resolve it
656525e
Reject Windows OTel agent paths and stop truncating the Gradle init s…
5c4f7db
Address review findings: wrapper exec bit, staging failures, duplicat…
fd79a52
Launch Windows build wrappers through the command interpreter
b1d6049
Make the project-style Java AppHost sample work in the IDE
bc5a5a2
Stop the generated Java SDK from filling the user's Problems panel
d4a94a9
Prove the Java AppHost experience in a real VS Code
40737dc
Stop Quarkus rejecting the endpoint URL Aspire shows in the dashboard
49264c3
Add the Java grammar used by AppHost lenses
761c048
Parse Java AppHosts with tree-sitter
53fdbfb
Warn about Java Run and Debug from the parser path
295ad5d
Cover Java resource and entry point lenses
a6e4d18
Prove Java CodeLenses in the extension test harness
cea91b5
Make the Java E2E runner find local Java extensions
6ef6307
Unify Java build-tool detection
8834818
Fix four Java debugger defects in the VS Code extension
d907cf8
Centralize Java wrapper resolution
c5e80ce
Remove the unused Java toolchain command list
bcfc42e
Give the single-file Java AppHost an actual Java service to orchestrate
07e77a4
Align Java detection error coverage
93cd433
Remove the JavaFX playground
6931b4d
Snapshot the scaffolded Java AppHost template
dc4e9e7
Replace JavaFX desktop proof with Swing
4225fec
Ignore .java-build anywhere in the repository
76feaa1
Lock down Java publish JVM argument behavior
d6913a3
Call addJavaAppWithJar the way the generator emits it
97b5c39
Preserve publish diagnostics in shared Java detection
c25e588
Expect the generated Java SDK where javac needs it
744f80e
Explain why QuoteIfNeeded leaves quoted options alone
fb4d2ce
Preserve authored Java JAR paths across hosts
a3b396e
Defer Java framework build-tool selection
6cba689
Resolve the Java AppHost classpath before the adapter sees it
32820f7
Make the Java E2E shards actually run the Java specs
bab34b1
Stop Eclipse copying the Gradle AppHost's wrapper into bin/main
9f989c1
Collapse the two Java build-step resources into one
8db575f
Restore the truncated Java AppHost template snapshot
21260f2
Fix two Java hosting tests that could only pass on POSIX
86d3c04
Drive the request that the Java resource breakpoint needs
1cae0b4
Fully qualify the classpath test fixtures so they hold on Windows
614c93e
Assert the half of the no-agent test that was never checked
b974c8b
Preserve AppHostServer restore artifacts when the scaffold is unchanged
684b493
Keep CLI-scaffolded polyglot projects out of the repo build
3db2942
Let the backchannel connect timeout follow the AppHost startup budget
d5067ec
Build the OpenTelemetry agent before the application that loads it
6b2095c
Scope CLI path resolution per target and split the Java E2E shard
51b5499
Read the Java release from the build tool that actually builds
49c1d06
Forward the CLI path the extension actually launched
23b9ab8
Keep workspace-folder fixtures POSIX on Windows agents
4a433a8
Build the Quarkus fast JAR before the IDE launches it
0c2745a
Scope CLI resolution to the folder each operation belongs to
0550a32
Give the tokenized CLI path fixtures host-shaped folders
73926b0
Disable the Gradle daemon on every container build path
3d8b25a
Find the wrapper at the build root for module applications
9e182f3
Fix three CLI path and lifetime bugs in the debug path
68f74c7
Merge into an existing .vscode/settings.json instead of refusing to init
6328f92
Probe describe capabilities against the CLI each AppHost resolves to
5dfb2f2
Reject a build-produced OTel agent when the Dockerfile is authored
375f400
Run Windows build tool wrappers through call
f7f8ce2
Track --nologo support per CLI executable rather than per window
1b86238
Stage Quarkus legacy-jar packaging with the dependencies it needs
02eb967
Recognize a fully qualified Java CreateBuilder call
60805d6
Resolve the CLI path against the folder a command targets
2169e06
Poll for the Java CodeLens instead of reading it once
a6a15fa
Prove per-folder CLI resolution end to end
b2ae2aa
Restore the Java SDK twice so the test can see a stale refresh
b412e98
Fail the do command before asking which step to run
5838758
Reuse the CLI the availability gate already resolved
16b3ce7
Resolve the CLI path setting per folder and follow workspace trust
ea9113c
Activate the extension on a Java AppHost and fix the Windows test breaks
516cea6
Keep workspace-scoped CLI paths synchronized
e419a8d
Skip javac when the Java AppHost has not changed
a0bf7c5
Fix CliPathEnvironmentSynchronizer tests on Windows
d82c606
Use fully qualified workspace folder roots in CLI path tests
be82d7e
Only skip unchanged generated files for Java
718be38
Fix the E2E failures the Java shards uncovered
cc52334
Skip integration restore when its inputs are unchanged
73abfa6
Stop gitignoring the Java E2E workspace
eec2ddd
Warn when a configured CLI path is rejected, and harden restore skip
d4f0de7
Restrict trust-bundle and build-tool discovery to trusted paths
064428b
Stop the Java compile cache from reusing stale output
69d9413
Stop code generation and scaffolding from leaving files behind
b30ccfe
Give the Java template a Java service
a6e4888
Fix multi-root and privacy issues in the extension's CLI handling
386f7ce
Make a Java AppHost see its own arguments, and always build what it l…
29a8080
Let the Java playground publish
92a58e5
Merge remote-tracking branch 'upstream/main' into pr-18033-java
f08cba5
Stop Azure Container Apps from claiming a resource twice
c0367e0
Fingerprint the whole project graph before skipping a restore
3a5ac0f
Write generated files through one implementation
0681510
Redact resolved Java environments from debug configuration logs
2a7b43b
Stop the E2E diagnostics probe from accumulating editor tabs
01e6b6f
Reject a world-writable Maven or Gradle wrapper script
e35a0d3
Detect edits to nested Java packages in the compile up-to-date check
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| import * as path from 'path'; | ||
| import * as vscode from 'vscode'; | ||
| import { javaDebugExtensionId, javaLanguageExtensionId } from '../../capabilities'; | ||
| import { AspireResourceExtendedDebugConfiguration, ExecutableLaunchConfiguration, isJavaLaunchConfiguration, JavaLaunchConfiguration } from "../../dcp/types"; | ||
| import { invalidLaunchConfiguration, javaDisplayName, javaLabel } from "../../loc/strings"; | ||
| import { extensionLogOutputChannel } from "../../utils/logging"; | ||
| import { ResourceDebuggerExtension } from "../debuggerExtensions"; | ||
|
|
||
| // Commands contributed by redhat.java. They only exist once the language server has activated, so | ||
| // every call site has to tolerate them being missing. | ||
| const JAVA_EXECUTE_WORKSPACE_COMMAND = 'java.execute.workspaceCommand'; | ||
| const JAVA_RESOLVE_BUILD_FILES_COMMAND = 'vscode.java.resolveBuildFiles'; | ||
| const JAVA_PROJECT_CONFIGURATION_UPDATE_COMMAND = 'java.projectConfiguration.update'; | ||
|
|
||
| // Subset of the redhat.java extension API surface we use. | ||
| // https://github.com/redhat-developer/vscode-java#extension-api | ||
| interface JavaExtensionApi { | ||
| serverMode: string; | ||
| serverReady: () => Promise<boolean>; | ||
| } | ||
|
|
||
| async function getJavaExtensionApi(): Promise<JavaExtensionApi | null> { | ||
| const extension = vscode.extensions.getExtension<JavaExtensionApi>(javaLanguageExtensionId); | ||
|
|
||
| if (!extension) { | ||
| return null; | ||
| } | ||
|
|
||
| if (!extension.isActive) { | ||
| // Activation can fail (no JDK, corrupt workspace metadata, ...). Treat that the same as the | ||
| // extension being absent so the launch can still proceed without classpath refresh. | ||
| await extension.activate(); | ||
| } | ||
|
|
||
| return extension.exports ?? null; | ||
| } | ||
|
|
||
| async function waitForJavaLanguageServerReady(): Promise<boolean> { | ||
| try { | ||
| const api = await getJavaExtensionApi(); | ||
|
|
||
| if (!api) { | ||
| extensionLogOutputChannel.warn(`The Java language server (${javaLanguageExtensionId}) is not installed or exposes no API.`); | ||
| return false; | ||
| } | ||
|
|
||
| extensionLogOutputChannel.info(`Java language server is in ${api.serverMode} mode, waiting for readiness...`); | ||
|
|
||
| return await api.serverReady(); | ||
| } catch (e) { | ||
| extensionLogOutputChannel.warn(`Error waiting for Java language server readiness: ${e}`); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| async function updateJavaProjectConfiguration(buildTool: string): Promise<void> { | ||
| const buildFiles = await vscode.commands.executeCommand<string[]>( | ||
| JAVA_EXECUTE_WORKSPACE_COMMAND, | ||
| JAVA_RESOLVE_BUILD_FILES_COMMAND | ||
| ); | ||
|
|
||
| if (!buildFiles?.length) { | ||
| extensionLogOutputChannel.info(`The Java language server reported no ${buildTool} build files to refresh.`); | ||
| return; | ||
| } | ||
|
|
||
| extensionLogOutputChannel.info(`Updating ${buildTool} project configuration for ${buildFiles.length} build file(s)...`); | ||
|
|
||
| for (const buildFile of buildFiles) { | ||
| await vscode.commands.executeCommand(JAVA_PROJECT_CONFIGURATION_UPDATE_COMMAND, vscode.Uri.parse(buildFile)); | ||
| } | ||
| } | ||
|
|
||
| // Refreshing the classpath is a convenience for fresh clones and for projects whose build files | ||
| // changed since the language server last imported them; nothing about launching depends on it. | ||
| // redhat.java is therefore treated as optional at runtime: when it is missing or still starting, its | ||
| // commands are not registered and executeCommand rejects with "command not found", which would | ||
| // otherwise surface as an opaque failure that aborts the whole resource launch. | ||
| async function tryRefreshJavaProjectConfiguration(launchConfig: JavaLaunchConfiguration): Promise<void> { | ||
| // A null build_tool means the resource runs a prebuilt JAR, so there are no build files to | ||
| // reimport and no reason to pay for language server startup. | ||
| if (!launchConfig.build_tool) { | ||
| extensionLogOutputChannel.info('Skipping Java project configuration refresh because the resource does not declare a build tool.'); | ||
| return; | ||
| } | ||
|
|
||
| if (!await waitForJavaLanguageServerReady()) { | ||
| extensionLogOutputChannel.warn(`Skipping the ${launchConfig.build_tool} project configuration refresh because the Java language server is unavailable. Launching anyway.`); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await updateJavaProjectConfiguration(launchConfig.build_tool); | ||
| } catch (e) { | ||
| extensionLogOutputChannel.warn(`Failed to refresh the ${launchConfig.build_tool} project configuration: ${e}. Launching anyway.`); | ||
| } | ||
| } | ||
|
|
||
| // path.isAbsolute resolves against the *host* platform, but the app host can hand us a Windows path | ||
| // while the extension runs on POSIX (remote/WSL/container scenarios), so check both flavours. | ||
| // path.win32.isAbsolute also accepts POSIX-rooted paths, but being explicit keeps the intent clear. | ||
| function isAbsolutePath(value: string): boolean { | ||
| return path.win32.isAbsolute(value) || path.posix.isAbsolute(value); | ||
| } | ||
|
|
||
| // main_class is either a fully qualified class name (com.example.Api) or the absolute path of an | ||
| // executable JAR whose manifest declares Main-Class (/workspace/api/target/api.jar). Only the class | ||
| // name is worth showing in the Call Stack view; the JAR path is less specific than the project | ||
| // directory the user recognises. | ||
| function isFullyQualifiedClassName(mainClass: string): boolean { | ||
| return mainClass.includes('.') | ||
| && !mainClass.toLowerCase().endsWith('.jar') | ||
| && !isAbsolutePath(mainClass); | ||
| } | ||
|
|
||
| function getProjectFile(launchConfig: ExecutableLaunchConfiguration): string { | ||
| if (isJavaLaunchConfiguration(launchConfig)) { | ||
| // The Java project directory is the only path the app host sends. It also feeds the central | ||
| // cwd derivation in prepareDebugSession, which the callback below then overrides explicitly. | ||
| return launchConfig.working_directory || ''; | ||
| } | ||
|
|
||
| throw new Error(invalidLaunchConfiguration(JSON.stringify(launchConfig))); | ||
| } | ||
|
|
||
| export const javaDebuggerExtension: ResourceDebuggerExtension = { | ||
| resourceType: 'java', | ||
| debugAdapter: 'java', | ||
| extensionId: javaDebugExtensionId, | ||
|
|
||
| getDisplayName: (launchConfig: ExecutableLaunchConfiguration) => { | ||
| if (!isJavaLaunchConfiguration(launchConfig)) { | ||
| return javaLabel; | ||
| } | ||
|
|
||
| const mainClass = launchConfig.main_class; | ||
| if (mainClass && isFullyQualifiedClassName(mainClass)) { | ||
| return javaDisplayName(mainClass); | ||
| } | ||
|
|
||
| // asRelativePath keeps the Call Stack view readable. Rendering the directory through | ||
| // vscode.Uri.file(...).toString() instead produces a percent-encoded URI such as | ||
| // "Java: file:///c%3A/repo/api". | ||
| const workingDirectory = launchConfig.working_directory; | ||
|
|
||
| return workingDirectory ? javaDisplayName(vscode.workspace.asRelativePath(workingDirectory)) : javaLabel; | ||
| }, | ||
|
|
||
| getSupportedFileTypes: () => ['.java'], | ||
|
|
||
| getProjectFile: (launchConfig) => getProjectFile(launchConfig), | ||
|
|
||
| createDebugSessionConfigurationCallback: async ( | ||
|
adamint marked this conversation as resolved.
|
||
| launchConfig: ExecutableLaunchConfiguration, | ||
| args: string[] | undefined, | ||
| _env: { name: string; value: string }[], | ||
| launchOptions: { debug: boolean;[key: string]: any }, | ||
| debugConfiguration: AspireResourceExtendedDebugConfiguration | ||
| ): Promise<void> => { | ||
| if (!isJavaLaunchConfiguration(launchConfig)) { | ||
| extensionLogOutputChannel.info(`The resource type was not java for ${JSON.stringify(launchConfig)}`); | ||
| throw new Error(invalidLaunchConfiguration(JSON.stringify(launchConfig))); | ||
| } | ||
|
|
||
| await tryRefreshJavaProjectConfiguration(launchConfig); | ||
|
|
||
| debugConfiguration.type = 'java'; | ||
| // The app host always sends "launch" today, but the wire schema allows "attach", so honour | ||
| // whatever it sends rather than hard-coding the current behaviour. | ||
| debugConfiguration.request = launchConfig.request ?? 'launch'; | ||
| debugConfiguration.noDebug = !launchOptions.debug; | ||
|
|
||
| if (launchConfig.working_directory) { | ||
| debugConfiguration.cwd = launchConfig.working_directory; | ||
| } | ||
|
|
||
| // vscjava.vscode-java-debug requires mainClass to start a launch session, and accepts either | ||
| // a fully qualified class name or the absolute path of an executable JAR. When the app host | ||
| // omits it, leaving the attribute unset lets the adapter resolve the entry point from the | ||
| // project instead of failing on an empty value. | ||
| // https://github.com/microsoft/vscode-java-debug/blob/main/Configuration.md#main | ||
| // | ||
| // projectName is intentionally not set: the adapter defines it as the Maven artifactId or | ||
| // the Gradle baseName, neither of which can be derived from working_directory, and guessing | ||
| // it would scope class resolution to a project that may not exist. | ||
| if (launchConfig.main_class) { | ||
| debugConfiguration.mainClass = launchConfig.main_class; | ||
| } | ||
|
|
||
| // These are the application's own arguments. The app host strips the mvnw/gradlew wrapper | ||
| // arguments for java launch configurations (the wrappers fork a second JVM that a debugger | ||
| // attached to the wrapper would never see), so everything left here belongs to main(String[]). | ||
| // https://github.com/microsoft/vscode-java-debug/blob/main/Configuration.md#arguments | ||
| debugConfiguration.args = args ?? []; | ||
|
|
||
| // `env` is deliberately left alone. prepareDebugSession already set it to | ||
| // mergeEnvs(getEnvironmentWithoutE2EBridgeVariables(), env), i.e. the full inherited | ||
| // environment with the resource's variables layered on top. Reassigning it from `env` alone | ||
| // would launch the JVM without PATH or JAVA_HOME, so the adapter could not find `java`. | ||
| } | ||
| }; | ||
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.