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
Three CliPathEnvironmentSynchronizer tests drive the real CliPathResolver with a
${workspaceFolder} token and then assert on hardcoded POSIX paths. Production
expands those tokens through expandConfiguredCliPath, which finishes with
pathLib.normalize using path.win32 on Windows, so the expansion is
\repo\a\aspire and never matches '/repo/a/aspire'. Two tests saw the wrong
value and the third hung for its full 20s timeout, because its tryExecute stub
gates a promise on being called with the expanded path and never was.
Derive the expectations with path.normalize so they track the platform the same
way production does, and drop the file-private createWorkspaceFolder in favour
of the shared testHelpers one, which pins fsPath so the folder literals survive
Uri.file on Windows.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
The three ${workspaceFolder} tests still failed on Windows after normalizing
their expectations, because the fixture roots themselves are the problem.
'/repo/a' becomes '\repo\a' there, and isAbsoluteCliPath deliberately treats a
driveless root as drive-relative rather than absolute, so the resolver rejected
the expanded candidate and fell through to PATH instead of probing it.
Build the roots with path.resolve so they are drive qualified on Windows and
unchanged on POSIX, and derive the expected CLI paths from them with path.join.
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. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Skipping the rewrite of identical generated files broke the Python SDK validation: the run added Redis, and the AppHost then failed with 'DistributedApplicationBuilder' object has no attribute 'add_redis'. Python does not run .aspire/modules in place. uv builds it into the virtual environment and decides whether to reuse its cached build from the source timestamps, so the generated pyproject.toml staying untouched let uv keep the install it had built at 'aspire init', before Redis was ever added. Rewriting unchanged files was accidentally load-bearing there. Make it an opt-in on LanguageInfo instead, set only for Java, whose javac, Maven and Gradle commands read the generated sources directly and are the reason the optimization exists. The regression is covered by a test that a language which does not opt in still rewrites identical content, and by one pinning Java as the only language that opts in. 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. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Five separate problems, all found by letting a full CI run finish: The Java CodeLens shard reported no lenses even though the provider produced one. ExTester's TextEditor.getCodeLenses() enumerates only lenses rendered as anchors, and Aspire's entry-point warning is contributed with an empty command precisely so it is not a dead link, so it was structurally invisible to that API. Read the codelens widget text directly instead, which is also closer to what a user sees. The provider now logs the lenses it computed and catches its own failures, because a provider that silently returns nothing leaves no trace in any log and cost a long time to diagnose. deploy and publish were excluded from the shared CLI availability gate, so invoking them with no CLI installed silently did nothing instead of offering to install it. That exclusion was not needed and is reverted. The package surface test hardcodes the extension's activation events, so the three Java workspaceContains entries had to be mirrored there. Adding the first folder in the workspace target proof converts the window into an untitled multi-root workspace and reloads it, which can swallow the second add. Retry until the extension reports the folder. Its update --self assertion also required the literal 'aspire update --self', which only holds on a machine with no CLI installed; assert the property under test instead, that a window-scoped command resolves its CLI outside every workspace folder. Finally, the Java shards downloaded the entire Spring dependency tree inside each test's readiness budget. Cache the user-level Maven and Gradle stores and resolve dependencies before VS Code starts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5cff55fa-c3e2-4230-afa6-3831ae75b35d
|
Correction to an earlier note in this PR: the "skip rewriting generated files that haven't changed" optimization is no longer unconditional. It is now opt-in per language, and only Java opts in. Leaving it on for every language broke Python. The Python SDK is rebuilt by uv from file timestamps, so preserving an unchanged file's mtime made uv believe the SDK was already current and skip work it actually needed to do.
|
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
The synthesized IntegrationRestore project is rebuilt on every polyglot AppHost launch, and restore dominates it: 5.6s of a 6.7s warm build, measured on the real generated project. Restore only needs to run again when something restore reads has changed, and the generated project file is the single input that expresses all of it - package identities and versions, project references, channel sources and the synthesized NuGet.config path are all written into it. So an assets file no older than the project file means the last restore already saw this exact input. The generated files are now written only when their content differs, since an unconditional rewrite would touch the very timestamp the check reads. Two properties keep this safe: skipping restore never skips the build, so edits to referenced projects are still compiled; and a stale or partially cleaned obj/ - the one input the check cannot see - retries with restore instead of failing. Measured end to end on a bundle-mode Java AppHost: 8.2s with restore, 6.3s with it skipped. Also explain the failure that occurs when the AppHost server is older than the integration packages it loads. It surfaces as a bare type-load error naming an assembly version the user never chose, with nothing to indicate that the CLI and the packages are out of step. 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. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
`aspire ls` discovers AppHosts from `git ls-files` when it runs inside a work tree. The Java E2E workspace has to live inside the repository so a dev-build CLI can resolve its AppHost server, so ignoring it made `git ls-files` return nothing there: `aspire ls` reported zero candidates, and every Java spec failed in its `before all` hook waiting for an AppHost that discovery could no longer see. The C# AppHost still appeared through the extension's own filesystem scan, which made it look like Java discovery was broken rather than the workspace being invisible. The teardown path already deletes this workspace precisely so it can stay un-ignored, so the ignore rule was solving a problem that was already solved. Add a guard that names the cause, because a stray ignore rule surfaces only as an empty candidate list several minutes into a run. 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. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
The extension silently fell back to the installed CLI when aspire.aspireCliExecutablePath could not be resolved, so a pin at a build output directory looked like it worked while a stale CLI actually ran. That surfaced as an NU1605 package downgrade rather than anything pointing at the setting. - Resolve a configured path that names a build output directory by probing <path>/aspire, so pointing at the build output works. - Show a warning notification with an "Open Setting" action when a configured path is rejected, instead of only writing to the output channel. - Skip the integration restore only when every referenced project pins exact versions; a floating version can resolve differently without any input changing. - Fan out AggregateException children when looking for reflection load failures so assembly skew is still described when the failure is wrapped. 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. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
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.