Add Java hosting integration - #18033
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 18033Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 18033" |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
# Conflicts: # Aspire.slnx # extension/src/capabilities.ts # extension/src/dcp/types.ts # extension/src/debugger/debuggerExtensions.ts # src/Aspire.Hosting/Aspire.Hosting.csproj
- ExecutableLaunchConfiguration moved from Aspire.Hosting.Dcp.Model to Aspire.Hosting.ApplicationModel and is now public + experimental. - TestTempDirectory was replaced in main (microsoft#18683); add a project-level TempJavaAppDirectory mirroring Aspire.Hosting.Rust.Tests.TempCrateDirectory. - Drop the InternalsVisibleTo on Aspire.Hosting for Aspire.Hosting.Java: the launch configuration base type is public now, so the package builds against the public surface only, matching Aspire.Hosting.Rust. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
…ect command, safe JAVA_TOOL_OPTIONS - Replace the public mutable JarPath property with a JavaJarPathAnnotation so run, publish and debug read one source of truth. - Set the build tool wrapper as the command in every execution context. Setting it only in run mode left publish emitting the uninvokable 'java spring-boot:run'. - Apply the JAR/build-tool mutual exclusion guard to WithMavenGoal as well as WithGradleTask, and reject configuring two build tools. - Make WithWrapperPath order-independent by re-pointing the command and any build step that already resolved the default wrapper. - Constrain WithJvmArgs/WithOtelAgent to JavaAppResource so they no longer appear on every resource builder, and require an agent path for WithOtelAgent. - Quote JAVA_TOOL_OPTIONS values containing whitespace and merge non-string values through ReferenceExpression instead of discarding them. - Seal the PKCS#12 trust store with an empty password so no secret reaches JAVA_TOOL_OPTIONS, and load/dispose certificates with X509CertificateLoader. - Add WithMainClass and WithJarArtifact, and emit main_class/build_tool in the launch configuration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
Publishing: - Add JavaDockerfileGenerator: multi-stage Maven/Gradle container build driven by the project's own wrapper, BuildKit dependency cache mounts, JRE runtime stage, non-root user, and exec-form ENTRYPOINT so the JVM receives SIGTERM. - Add JavaVersionDetector: reads the target release from pom.xml or a Gradle build script so the build and runtime images match what the project compiles against. - Deterministic JAR selection that filters -plain/-sources/-javadoc and fails the container build on ambiguity rather than producing an image that cannot start. - Wire PublishAsDockerFile into AddJavaApp, leaving an authored Dockerfile or .dockerignore alone. - JavaBuildStepAnnotation now records the tool and arguments in every execution context so the container build reuses what WithMavenBuild/WithGradleBuild configured, and repeated calls no longer collide on the build resource name. VS Code extension: - java.ts rewritten: workspace-relative session names, validated launch config, honours the request kind, sets mainClass, forwards application arguments, keeps the merged environment, and treats the Java language server as optional so a failed classpath refresh cannot abort the launch. - isJavaInstalled now requires both redhat.java and vscjava.vscode-java-debug. - Localize javaDisplayName/javaLabel and add javaDebugger.test.ts (26 tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The agent path reached JAVA_TOOL_OPTIONS exactly as authored, which is wrong in both execution modes. In run mode JAVA_TOOL_OPTIONS is inherited by every JVM started beneath the resource, and build tools start JVMs from directories other than the application directory. The Gradle daemon starts from its own distribution directory, so a relative -javaagent: path failed to resolve and the daemon died during VM initialization rather than reporting a build failure. Relative paths are now resolved against the application directory. In publish mode a relative path names a file the build produces, so it only existed in the Dockerfile's build stage. The published container started a JVM pointing at an agent JAR that was not in the image. The runtime stage now copies the agent forward and the environment variable points at it. Absolute paths are left alone in publish, since they cannot have come from the build context. Reworks playground/JavaSpringBoot into three services covering Maven with spring-boot:run, Gradle with bootRun, and a plain JAR, and removes 23 MB of committed build output. Registers it in the solution and gives it a Docker Compose environment so `aspire publish` has somewhere to publish to. Verified locally: all six resources reach Running from a clean checkout, the orders to catalog call produces a distributed trace spanning both services, and all three published images build and run with the agent loaded and the process running as uid 999. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
Replace the 13-line placeholder README with full documentation covering prerequisites, the three launch modes, build steps, telemetry, VS Code debugging, and container publishing. The C# and TypeScript samples were both checked against the real API surface -- the TypeScript one type-checks against the generated bindings, where `params string[]` projects as a required array (matching Rust). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
Both branches of QuoteIfNeeded were verified against a real JVM: an unquoted -javaagent: path containing a space aborts startup with "Unrecognized option", and quoting only the value after '=' is what lets the JVM actually load a trust store from a path with spaces. Neither behavior was covered, so add tests for both and verify they fail when the quoting is removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The scaffolded AppHost is a compact source file with an instance main method. That form was previewed in Java 21-24 (JEP 445, 463, 477, 495) and finalized in Java 25 by JEP 512, so on the Java 25 floor the runner already targets, --enable-preview compiles no preview feature at all. Verified against the real polyglot fixture: the AppHost plus all 224 generated SDK sources compile with --release 25 and no preview flag, the class files carry minor version 0 rather than the preview marker 65535, and the compiled AppHost still resolves its instance main at run time without --enable-preview on the java command. Passing the flag is not merely redundant. Any future preview usage would silently stamp the class files with minor version 65535, which binds them to one exact JDK release and makes the runtime flag mandatory. Also switch --source to --release. --source only selects the language level, leaving the compiler linked against the class library of the running JDK, so an AppHost could bind to APIs missing from a conforming Java 25 runtime and fail there. --release constrains the API surface too. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
There was a problem hiding this comment.
Pull request overview
Adds first-party Java hosting support, including Maven/Gradle/JAR execution, container publishing, certificate trust, polyglot APIs, and VS Code debugging.
Changes:
- Adds the
Aspire.Hosting.Javaresource model and publishing support. - Adds unit, snapshot, polyglot, and playground coverage.
- Extends the VS Code extension with Java debugging.
Show a summary per file
| File | Description |
|---|---|
Aspire-Core.slnf |
Includes Java projects. |
Aspire.slnx |
Registers integration, tests, and playground. |
eng/github-ci/test-trigger-map.yml |
Maps Java integration tests. |
src/Aspire.Hosting/Aspire.Hosting.csproj |
Grants test internals access. |
src/Aspire.Hosting.Java/Aspire.Hosting.Java.csproj |
Defines the package. |
src/Aspire.Hosting.Java/JavaAppResource.cs |
Defines Java resources. |
src/Aspire.Hosting.Java/JavaBuildToolAnnotation.cs |
Tracks build configuration. |
src/Aspire.Hosting.Java/JavaDockerfileGenerator.cs |
Generates deployment Dockerfiles. |
src/Aspire.Hosting.Java/JavaHostingExtensions.cs |
Implements public hosting APIs. |
src/Aspire.Hosting.Java/JavaJarPathAnnotation.cs |
Tracks JAR and main-class settings. |
src/Aspire.Hosting.Java/JavaLaunchConfiguration.cs |
Defines IDE launch metadata. |
src/Aspire.Hosting.Java/JavaVersionDetector.cs |
Detects target Java versions. |
src/Aspire.Hosting.Java/GradleBuildResource.cs |
Models Gradle builds. |
src/Aspire.Hosting.Java/MavenBuildResource.cs |
Models Maven builds. |
src/Aspire.Hosting.Java/WrapperAnnotation.cs |
Tracks wrapper overrides. |
src/Aspire.Hosting.Java/README.md |
Documents the integration. |
extension/src/capabilities.ts |
Advertises Java capability. |
extension/src/dcp/types.ts |
Adds Java launch types. |
extension/src/debugger/debuggerExtensions.ts |
Registers Java debugging. |
extension/src/debugger/languages/java.ts |
Implements Java debugger configuration. |
extension/src/loc/strings.ts |
Adds localized Java labels. |
extension/src/test/javaDebugger.test.ts |
Tests Java debugging. |
extension/src/test/strings.test.ts |
Validates Java localization. |
extension/package.nls.json |
Defines Java localization strings. |
extension/loc/xlf/aspire-vscode.xlf |
Updates generated localization catalog. |
playground/JavaSpringBoot/.gitattributes |
Normalizes wrapper line endings. |
playground/JavaSpringBoot/JavaSpringBoot.AppHost/AppHost.cs |
Orchestrates sample Java apps. |
playground/JavaSpringBoot/JavaSpringBoot.AppHost/JavaSpringBoot.AppHost.csproj |
Defines sample AppHost. |
playground/JavaSpringBoot/JavaSpringBoot.AppHost/aspire.config.json |
Configures sample AppHost. |
playground/JavaSpringBoot/JavaSpringBoot.AppHost/Properties/launchSettings.json |
Configures sample launch profiles. |
playground/JavaSpringBoot/catalog/pom.xml |
Defines Maven catalog app. |
playground/JavaSpringBoot/catalog/mvnw |
Adds POSIX Maven wrapper. |
playground/JavaSpringBoot/catalog/mvnw.cmd |
Adds Windows Maven wrapper. |
playground/JavaSpringBoot/catalog/.mvn/wrapper/maven-wrapper.properties |
Pins Maven distribution. |
playground/JavaSpringBoot/catalog/src/main/java/com/example/catalog/CatalogApplication.java |
Starts catalog service. |
playground/JavaSpringBoot/catalog/src/main/java/com/example/catalog/CatalogController.java |
Implements catalog endpoints. |
playground/JavaSpringBoot/orders/build.gradle |
Defines Gradle orders app. |
playground/JavaSpringBoot/orders/settings.gradle |
Configures Gradle toolchains. |
playground/JavaSpringBoot/orders/gradlew |
Adds POSIX Gradle wrapper. |
playground/JavaSpringBoot/orders/gradlew.bat |
Adds Windows Gradle wrapper. |
playground/JavaSpringBoot/orders/gradle/wrapper/gradle-wrapper.jar |
Supplies Gradle wrapper runtime. |
playground/JavaSpringBoot/orders/gradle/wrapper/gradle-wrapper.properties |
Pins Gradle distribution. |
playground/JavaSpringBoot/orders/src/main/java/com/example/orders/OrdersApplication.java |
Starts orders service. |
playground/JavaSpringBoot/orders/src/main/java/com/example/orders/OrdersController.java |
Implements orders endpoints. |
playground/JavaSpringBoot/worker/pom.xml |
Defines plain-JAR worker. |
playground/JavaSpringBoot/worker/src/main/java/com/example/worker/Worker.java |
Implements background worker. |
tests/Aspire.Hosting.Java.Tests/Aspire.Hosting.Java.Tests.csproj |
Defines integration tests. |
tests/Aspire.Hosting.Java.Tests/AddJavaAppTests.cs |
Tests run-mode behavior. |
tests/Aspire.Hosting.Java.Tests/AddJavaAppPublishTests.cs |
Tests generated publishing artifacts. |
tests/Aspire.Hosting.Java.Tests/JavaPublicApiTests.cs |
Tests resource validation. |
tests/Aspire.Hosting.Java.Tests/TempJavaAppDirectory.cs |
Provides isolated test directories. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_CopiesABuildProducedOtelAgentIntoTheRuntimeImage.verified.txt |
Verifies agent copying. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_DetectsTheBuildToolFromDiskWhenOnlyAJarPathWasGiven.verified.txt |
Verifies build-tool detection. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_EmitsABuildContextIgnoreThatExcludesBuildOutputDirectories.verified.txt |
Verifies ignore rules. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_GeneratesAGradleBuild.verified.txt |
Verifies Gradle Dockerfile. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_GeneratesAMavenBuildAndJreRuntimePair.verified.txt |
Verifies Maven Dockerfile. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_HonoursWithDockerfileBaseImage.verified.txt |
Verifies image overrides. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_ProducesAContainerManifestEntry.verified.json |
Verifies container manifest. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_ReusesTheArgumentsConfiguredForTheHostBuildStep.verified.txt |
Verifies build arguments. |
tests/Aspire.Hosting.Java.Tests/Snapshots/AddJavaAppPublishTests.VerifyPublish_UsesTheWrapperWhenTheProjectShipsOne.verified.txt |
Verifies wrapper usage. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Go/apphost.go |
Exercises Go bindings. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Go/aspire.config.json |
Configures Go validation. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Go/go.mod |
Defines Go module. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Java/AppHost.java |
Exercises Java bindings. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Java/aspire.config.json |
Configures Java validation. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Python/apphost.py |
Exercises Python bindings. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Python/apphost_requirements.txt |
Defines Python fallback requirements. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Python/aspire.config.json |
Configures Python validation. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/Python/pylock.apphost.toml |
Locks Python AppHost module. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/apphost.mts |
Exercises TypeScript bindings. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/aspire.config.json |
Configures TypeScript validation. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/package.json |
Defines TypeScript tooling. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/package-lock.json |
Locks TypeScript dependencies. |
tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/tsconfig.json |
Configures TypeScript compilation. |
Review details
Files not reviewed (1)
- tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/package-lock.json: Generated file
- Files reviewed: 72/74 changed files
- Comments generated: 9
- Review effort level: Balanced
Resource debugging: stop sending a JAR path as the debug adapter's main
class. The adapter documents mainClass as "the fully qualified class name
(e.g. [java module name/]com.xyz.MainApp) or the java file path of the
program entry", so it never opens an archive, and a JAR path left it
unable to resolve an entry point at all. The app host now reads Main-Class
from the JAR manifest itself, including values the manifest wrapped across
lines, and sends the archive on a new class_paths field that maps to the
adapter's classPaths. That is what java -jar does, and it keeps working
for Spring Boot fat JARs whose manifest names JarLauncher. Reading the
manifest is best effort: a missing or entry-point-less archive still gets
a classpath so the IDE can resolve the entry point from the project.
AppHost project model: add JavaAppHostToolchainResolver, mirroring the
TypeScript package manager resolver, because the choice depends on files
in the AppHost directory and GetRuntimeSpec() cannot see them. A pom.xml
selects Maven and a build.gradle selects Gradle; anything else keeps the
existing javac path byte for byte, so adopting a build tool is opt-in and
a bare JDK remains enough. A wrapper checked into the project wins over
the tool on PATH, and is referenced by absolute path because the process
starts without a shell.
Under a build tool the AppHost gains a real restore phase
(dependency:copy-dependencies, or a Gradle init script that adds the
equivalent task so no user build file edit is needed) and is compiled
before launch instead of inline on first run. It is then launched with a
plain java command rather than mvn exec:java or gradle run: those run the
app inside, or as a child of, the build tool's JVM, so console signals
never reach the AppHost. Launching directly also avoids the {args}
placeholder, so CLI arguments arrive as real argv entries instead of being
spliced into a shell string.
AppHost debugging: advertise ExtensionLaunchCapability so the CLI routes
the AppHost launch through the extension's Java debug adapter, the same
mechanism TypeScript and Rust already use.
Detection now also finds src/main/java/AppHost.java, which is where a
Maven or Gradle project puts it, and the javac command quotes the
substituted AppHost path so a directory containing spaces still builds.
Scaffolding emits .vscode/settings.json declaring both source roots.
Without a build file the Java language server treats the folder as an
invisible project rooted at the workspace, so every reference to the
generated SDK under .aspire/modules failed to resolve: no completion, no
navigation, and no breakpoint binding.
Fix the java-starter template, which could never run. It declared
"package aspire;", so it compiled to .java-build/aspire/AppHost.class
while the runner loads AppHost from the default package, failing with
ClassNotFoundException even though compilation succeeded. Verified
against the real generated SDK, and covered by a regression test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
There was a problem hiding this comment.
Review details
Files not reviewed (1)
- tests/PolyglotAppHosts/Aspire.Hosting.Java/TypeScript/package-lock.json: Generated file
Suppressed comments (4)
Previously missed (1) — in code that hasn't changed since the last review.
src/Aspire.Hosting.Java/JavaVersionDetector.cs:16
- This XML documentation names
WithDockerfileBaseImages, but the public API and README use the singularWithDockerfileBaseImage. The current IntelliSense reference sends users to a method that does not exist.
src/Aspire.Hosting.Java/JavaDockerfileGenerator.cs:254
- Joining build arguments verbatim loses the argument boundaries preserved by
WithMavenBuild/WithGradleBuildand lets shell metacharacters change the generatedRUNcommand. For example, one argument such as-Dmessage=hello worldbecomes two arguments during publish even though it is one argument in run mode. Shell-quote every configured argument before composing the command.
src/Aspire.Hosting.Java/JavaDockerfileGenerator.cs:149 - Published containers drop all Java application arguments.
PublishAsDockerFileexplicitly clears executable arguments, launch-tool arguments are skipped for containers, and this Dockerfile sets only anENTRYPOINTwith no correspondingCMD. Thus arguments passed to the JAR overload or viaWithArgs(for example the playground worker's--interval-seconds 10) work locally but disappear after publish. Preserve the evaluated application arguments as the container command/CMD.
src/Aspire.Hosting.Java/JavaDockerfileGenerator.cs:86 - A genuinely prebuilt JAR cannot be published:
Writealways resolves a Maven or Gradle build before generating the image, andResolveToolAndArgsthrows when the directory contains only the JAR. This contradicts theAddJavaApp(..., jarPath)prebuilt-JAR mode and the README statement that an app that runs should publish without extra configuration. HandleJavaJarPathAnnotationby copying the existing JAR from the build context instead of requiring a build tool.
- Files reviewed: 78/80 changed files
- Comments generated: 4
- Review effort level: Balanced
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
…ws wrappers Requires a checked-in Maven or Gradle wrapper everywhere a build tool is invoked, in run mode, in publish mode, and for Java AppHosts. A globally installed tool is now rejected with an actionable message naming the command that generates the wrapper. The wrapper pins the tool version in the repository so the AppHost, CI, and the container image all build with the same one. Because the wrapper carries its own distribution, the container build stage is always a plain JDK image. Resolves a Java AppHost's project root instead of assuming it is the AppHost's own directory. The conventional src/main/java layout keeps its build file three levels up, and commands run from the AppHost directory, so build tool paths are rewritten relative to it and the tool is pointed back at the project. Runs Windows wrappers through the command interpreter. They are batch files, which produce no output when launched directly with redirected stdout. The wrapper is passed as a relative path so cmd.exe never sees a quoted first token, which is the case where its quote stripping mangles the rest of the command line. Other review fixes: prebuilt JARs now publish as a single-stage JRE image, publish honors WithWrapperPath, build arguments are shell quoted, container file sources get pipeline dependencies, the language server readiness wait is bounded, repeated WithOtelAgent calls collapse to one -javaagent entry, and the README uses the hosting convention for link bullets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
Follows the precedent set by the JavaScript integration's pnpm image tests. The JAR is produced inside a JDK container, so the test needs Docker and nothing else, and only the runtime image's own JDK can emit class files that image is guaranteed to load. The test asserts on the published pair rather than the image alone. Application arguments are deliberately not baked into the image, exactly like every other container resource: they belong to the deployment spec, so the manifest carries them and the runtime appends them to the entrypoint. Reading them back out of the manifest and passing them to docker run is what proves the two halves agree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Three problems, all confirmed from the Windows CI logs rather than inferred.
`File.SetLastWriteTimeUtc` was called on a directory. That overload opens the
path without FILE_FLAG_BACKUP_SEMANTICS, which Windows refuses for a directory
handle, so the churn test died with
System.UnauthorizedAccessException : Access to the path
'...\classes\config' is denied.
at System.IO.File.SetLastWriteTimeUtc(String path, DateTime lastWriteTimeUtc)
`Directory.SetLastWriteTimeUtc` is the directory overload, and is already what
BackdateWorkspace uses.
The staged-dependency test stamped at DateTime.UtcNow and then relied on the
real timestamp of an add/remove being newer. On Windows UtcNow advances in
~15ms steps, so the two landed in the same tick, the check saw no change, and
the assertion failed with the AppHost launched but never compiled. Both tests
now age the workspace first and stamp behind it, which is the pattern the
deletion tests already use.
Also restores three method braces that had been joined onto their signature
lines, against the .editorconfig rule.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
VS Code renders one merged CodeLens set per document, so a `java` file shows no lenses at all until every registered provider has answered - including redhat.java's. On a cold CI runner that server is still importing the workspace, which is how this spec failed with Timed out waiting for a CodeLens containing 'bypass Aspire' in 'AppHost.java' CodeLenses: (none) while the Aspire lens itself was ready. `(none)` rather than a list missing our lens is what points at the merge rather than at the provider. The other two Java specs already wait on `serverReady()` here, and carry the 30 minute suite budget that wait needs; this spec was the only one that did not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The probe suite opens editors on a dozen files in a temp directory, which left two Windows-only failures behind. Removing the temp directory raced the handle Windows releases after an editor closes and threw EPERM, so the teardown failed the suite. Node retries recursive removals for exactly that code, and a directory stranded in the OS temp folder is not worth failing a suite whose assertion is about editor tabs, so the removal now retries and then only reports. Showing those documents also queues background extension work, and on Windows a process it starts can still be in flight when a later test installs its own child_process stubs. That made `execFileStub.firstCall` describe the leaked process instead of the dotnet command under test, and the assertion reported the probe's temp directory as the working directory. The dotnet debugger tests now select the call by the project it was started for, which is what they meant to assert and is immune to unrelated activity in the shared extension host. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
These shards had not run since the Windows unit test failure started skipping them, so all three regressions had been accumulating unseen. java-apphost: the endpoint wait silently clamped the caller's budget to 300s via Math.min, so a spec asking for 900s got 300s and an error message that reported a timeout the caller never chose. The resource is a Spring Boot app being compiled and started under a debugger, which does not fit in 300s on a cold CI runner. Honor the caller's budget, and pre-compile the Maven projects in the cache warm step - dependency:go-offline does not fetch plugin artifacts, so the compiler and Spring Boot plugins were still being downloaded during the timed run. The Gradle project already ran compileJava; this removes the asymmetry. java-apphost-debug-startup-timeout: pwsh does not stop on a native command's exit code, and this step never checked $LASTEXITCODE after `aspire setup` even though the rest of the workflow does. A failed setup let the shard run on without the orchestration component and fail four minutes later with a debug startup timeout instead of the actual provisioning error. workspace-target-proof: the add-folder retry loop could not retry. A single failed attempt spends up to 30s in InputBox.create and another 30s confirming the folder never arrived, so the 90s deadline bought one attempt and part of a second before reporting the confirmation poll as the cause. Shorten the confirmation poll, which costs a successful add nothing, and size the deadline and suite timeout so the retries the comment promises can actually happen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The DTO's setter is generated from the keyword-escaped field, so a property named Default declares setDefault_. The exported-value initializer derived its call from the raw property name and emitted setDefault(...) instead, which javac rejects with "cannot find symbol". The CLI compiles the whole generated SDK in a single javac invocation, so one keyword-named property on any exported value would break `aspire run` for the entire Java AppHost - and the user cannot fix generated code. This was latent only because no current exported value uses a keyword-named property. Route both the accessor declarations and the initializer call through a single DtoAccessorSuffix helper so the two cannot drift apart again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
Windows releases the handle behind a closed editor or an exited child process asynchronously, so a teardown running right after the test body can still see the directory as in use and throw "EPERM, Permission denied". Mocha fails the hook, which fails the whole run and skips every E2E shard that depends on it. Fixing this per-file was whack-a-mole: e2eDiagnosticsProbe was fixed last week and aspireEditorCommandProvider failed the very next run with the identical error. The cleanup call sites had drifted into three different conventions - some passed maxRetries/retryDelay, most passed only force: true, which suppresses ENOENT and nothing else. Route all 74 recursive cleanup removals through one shared removeDirectorySafely helper that retries and then swallows. Leaving a temp directory behind is a non-event; losing a whole run's signal is not. Forcing the underlying removal to throw EPERM shows the exposure: without the guard 68 tests fail with the CI error shape, with it all 2244 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
package.nls.json is the only input to the XLF catalog (see gulpfile.js), so a string that exists only in strings.ts never reaches translators and ships English-only. The two configuredCliPath* strings added by this PR were missed, so the warning shown when a configured aspire.aspireCliExecutablePath cannot be used would have been English for every non-English user. The existing guards only scan the rust* and java* prefixes, which is why this passed CI. Replace that with a scan of every vscode.l10n.t export, allowlisting the 51 strings that predate the guard so the next omission fails here instead of in a localization drop. Removing the two new entries again fails the guard by name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The Java E2E shards ran for the first time on this PR because they are gated behind the Windows extension unit tests, which were red until the temp-directory cleanup fix. Three of them failed, and in each case the reported error described a symptom rather than the cause. The CodeLens spec waited on redhat.java's serverReady(), which resolves in LightWeight mode. VS Code renders one merged CodeLens set per document, so while that server was still importing the file showed `CodeLenses: (none)` - including the Aspire lens, which had been ready the whole time. The wait now requires Standard mode and names the mode it saw when it gives up. The workspace target spec drove `Workspaces: Add Folder to Workspace...` and its quick-open input. Adding the first folder converts the window into an untitled multi-root workspace, which reloads the extension host, and after that reload the second add never took - four retries failed identically, so the fragility was in the UI flow rather than the budget. What the spec proves is that CLI commands target the right workspace folder, so the folder now goes through the same API VS Code's own command calls and the bridge resolves once the extension host has observed it. The debug startup shard failed because PrebuiltAppHostServer set ASPIRE_DCP_PATH only when the layout supplied a DCP path and said nothing when it did not. The AppHost then fell back to the DcpCliPath assembly metadata, which points into ~/.nuget/packages - a package a guest-language AppHost never restores - and reported "The Aspire orchestration component is not installed at <nuget path>", describing the fallback instead of the missing layout. That case is now logged, and the workflow asserts the bundle really contains dcp right after `aspire setup` so a missing component fails on that line instead of minutes later. The endpoint wait reported "Last error: <none>" because a poll that returns undefined never records an error, which cannot distinguish a resource that failed to start from one still building from one that was never in the model. Waits can now attach the state they were looking at, and the resource endpoint wait reports every resource with its state, health, exit code and URLs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
# Conflicts: # extension/src/types/extensionApi.ts
Retry the integration build with a restore after a package-cache eviction -------------------------------------------------------------------------- The restore-skip fast path fingerprints this app's own inputs, but it cannot see the shared global package cache. After `dotnet nuget locals all --clear` (or ordinary cache eviction) the fingerprint is unchanged while the packages it assumes are gone, so the `--no-restore` build fails with NETSDK1064 / NU1101 / NU1102 rather than the NETSDK1004 the retry heuristic looked for. That made the failure permanent, not transient: the restore stamp is written only after a successful restore and is never cleared, so the next `aspire run` re-evaluates the same unchanged fingerprint, skips restore again, and fails identically until the user manually deletes obj/. Before the fast path existed the build always restored, so it self-healed. Broaden ShouldRetryWithRestore to the package-resolution error codes and record in the remarks why the fingerprint cannot cover this. Ordinary compile errors are still not retried, so the cost optimization is preserved -- the existing ShouldRetryWithRestore_DoesNotRetryAnOrdinaryCompileFailure guard still passes. Affects every bundle/guest-mode language (Python, JavaScript, Go, Rust, Java), not just Java. Verified red/green: the three new NETSDK1064/NU1101/NU1102 cases fail against the old heuristic and pass against the new one; PrebuiltAppHostServerTests is 120/120. Register JavaSwing.AppHost in Aspire.slnx ---------------------------------------- The JavaFX-to-Swing swap added playground/JavaSwing/JavaSwing.AppHost but never registered it, unlike its JavaSpringBoot and JavaQuarkus siblings. It still compiles (it matches the playground glob in eng/Build.props, so this was not a CI gap) but it was invisible in the IDE solution and to solution-scoped tooling. Verified with `dotnet sln Aspire.slnx list`, which now lists all three. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
This PR started forwarding AspireCliPath to the MCP server (upstream never set env at all here), but it was the only AspireCliPath producer that forwarded the raw resolved path without the forwardability guard. Every other site -- the process environments, the terminal collection, the environment synchronizer, and the dotnet debug env -- routes the value through getForwardableAspireCliPath or getForwardableResolvedAspireCliPath first. `aspire agent mcp` can build an AppHost, and that build inherits this environment. With an unbundled framework-dependent CLI (an inner-loop `dotnet build` output, where the apphost sits next to aspire.dll with no install sidecar and no adjacent bundle layout), MSBuild's ResolveAspireCliBundle binds bundle assets to a CLI that has no bundle layout and fails with ASPIRE009 -- while a plain terminal build in the same workspace succeeds, because the terminal env collection omits the unbundled path. Use getForwardableResolvedAspireCliPath, which is the right variant here: the caller has already selected and launched this exact executable, so rejection of a different configured value must not suppress it. Omitting the variable when it is not forwardable lets the build fall back to PATH probing, matching the other sites. Also drops the duplicate local 'AspireCliPath' literal in favour of the exported ASPIRE_CLI_PATH_ENV_VAR. Verified red/green: with the guard removed the new test fails, showing AspireCliPath='/repo/artifacts/bin/Aspire.Cli/Debug/aspire' forwarded where the env must be empty; with the guard it passes. Sibling tests for getForwardableResolvedAspireCliPath and AspireTerminalProvider already assert this same invariant -- the MCP provider was the only site missing it. Full extension unit suite 2251 passing, exit 0; tsc and eslint clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
|
Did a final pass over this after merging Fixed1. Permanent build failure after a NuGet cache eviction — and it hits every guest language, not just Java. The part that makes it more than a papercut: the restore stamp is only written on success and never cleared, so the next 2. MCP server was forwarding 3. CleanJava hosting/codegen and the playgrounds came back clean — keyword escaping, invariant number formatting, no-shell argument arrays, path-traversal rejection all check out. |
The new forwardability test hardcoded POSIX separators while the production guard builds the adjacent assembly with path.join(path.dirname(cliPath), 'aspire.dll'). On Windows that emits backslashes, so the stub never matched, the path looked forwardable, and the assertion failed. On macOS the separators coincide, which is why it passed locally and only broke on the Windows job. Build the expectation with the same expression production uses so the two agree on any platform. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The DCP guard searched the directory the CLI archive was unpacked into, but `aspire setup` extracts to SetupCommand.GetDefaultInstallPath, which is the parent of the parent of the CLI binary. With the CLI at artifacts/bundle/<rid>/aspire that install path is artifacts/bundle, so DCP lands beside the search root rather than inside it and the guard threw on every shard even though setup had succeeded. Derive the same path the CLI derives instead of assuming the layout. The guard had never executed before now: the shards were skipped in the two prior runs because the Windows unit job gates them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
Description
This pull request adds
Aspire.Hosting.Java, ported fromCommunityToolkit.Aspire.Hosting.Javaand then reworked for parity with the other resource languages (Rust, Go, Node, Python).The following features have been implemented:
java -jar app.jar)java -jar ...)mvnw spring-boot:run)gradlew bootRun)Added beyond the original scope:
AddSpringBootAppandAddQuarkusApp, which detect the build tool from the build file and wire the framework's own port and OpenTelemetry conventionsAddJavaContainer, for running an image built elsewherepom.xml/Gradle, unprivilegedUSER 999:999, JVM as PID 1, BuildKit dependency cacheAppHost.java, or Maven/Gradle atsrc/main/java/AppHost.java, with AppHost debuggingplayground/JavaSpringBoot) and Quarkus (playground/JavaQuarkus)Closes #17993
Checklist
<remarks />and<code />elements on your triple slash comments?The security-relevant choices are confined to the generated container image and the build tool it runs, and are documented in the README: the image runs as
USER 999:999with no passwd entry rather than root; a Maven or Gradle wrapper checked into the repository is required and a globally installedmvn/gradleis deliberately never used as a fallback, so the tool version cannot vary with the machine; and paths that would escape the build context are rejected rather than silently copied.