Skip to content

Add common languages SDKs#13978

Merged
sebastienros merged 62 commits intomainfrom
sebros/poly
Jan 22, 2026
Merged

Add common languages SDKs#13978
sebastienros merged 62 commits intomainfrom
sebros/poly

Conversation

@sebastienros
Copy link
Member

@sebastienros sebastienros commented Jan 16, 2026

Description

Adds Python, Go, Java and Rust polyglot apphost support.

Currently in Draft to test the CI.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

This commit adds code generation support for Go and Java AppHosts:

Go Code Generator (Aspire.Hosting.CodeGeneration.Go):
- AtsGoCodeGenerator.cs: Generates Go SDK wrapper code from ATS capabilities
- GoLanguageSupport.cs: Provides scaffolding, detection, and runtime config
- Resources/transport.go: JSON-RPC client with Windows named pipe support
- Resources/base.go: Base types (HandleWrapperBase, ReferenceExpression, etc.)

Java Code Generator (Aspire.Hosting.CodeGeneration.Java):
- AtsJavaCodeGenerator.cs: Generates Java SDK wrapper code with camelCase naming
- JavaLanguageSupport.cs: Provides scaffolding, detection, and runtime config
- Resources/Transport.java: JSON-RPC client with Windows named pipe support
- Resources/Base.java: Base types for handle wrappers

Both generators:
- Generate wrapper classes that proxy capabilities via JSON-RPC
- Support enums, DTOs, and handle types from ATS context
- Include registration helpers for handle wrapper factories
- Generate connection helpers (Aspire.Connect(), Aspire.CreateBuilder())

CLI Integration:
- Register Go and Java in DefaultLanguageDiscovery.cs
- Add language constants to KnownLanguageId.cs
- Add code generator assemblies to AppHostServerProject.cs
- Add InternalsVisibleTo entries in Aspire.Hosting.csproj
- Add projects to Aspire.slnx
This commit adds initial Rust code generation support for AppHosts:

Rust Code Generator (Aspire.Hosting.CodeGeneration.Rust):
- AtsRustCodeGenerator.cs: Generates Rust SDK wrapper code with snake_case naming
- RustLanguageSupport.cs: Provides scaffolding, detection, and runtime config
- Resources/transport.rs: JSON-RPC client with Windows named pipe support
- Resources/base.rs: Base types for handle wrappers

The generator produces:
- Enum types with serde Serialize/Deserialize derives
- DTO structs with optional field support
- Handle wrapper structs that proxy capabilities via JSON-RPC
- Connection helpers (connect(), create_builder())

CLI Integration:
- Register Rust in DefaultLanguageDiscovery.cs
- Add language constants to KnownLanguageId.cs
- Add code generator assembly to AppHostServerProject.cs
- Add InternalsVisibleTo entry in Aspire.Hosting.csproj
- Add project to Aspire.slnx

Note: This is a work-in-progress. The generated Rust code compiles the
infrastructure but needs further refinement for full AppHost functionality.
The handle wrapping pattern needs adjustment for Rust's ownership model.
- Add serde Serialize/Deserialize derives to ReferenceExpression in base.rs
- Fix DTO property serde attributes to only use skip_serializing_if for optional fields
- Exclude ReferenceExpression from IsHandleType check
- Add special handling for CancellationToken, AspireDict, AspireList return types
- Update CancellationToken to support both local and handle-backed modes

The generated Rust code now compiles successfully with cargo build.
…pendencies runs before code generation, so it cannot compile generated code. Moved compilation to Execute step for both Java and Rust. - Make Rust parameters idiomatic: String parameters now use &str instead of String, which is more idiomatic Rust.
- Remove InstallDependencies (go run handles dependencies automatically)
- Add 'require' directive to go.mod (needed alongside 'replace' directive)
@sebastienros sebastienros changed the title Add common language SDKs Add common languages SDKs Jan 16, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 16, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 13978

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 13978"

- Added 19 tests covering:
  - Language property returns Python
  - Embedded resources (transport.py, base.py) match snapshots
  - Generated code includes expected capabilities
  - Method names derive correctly
  - Parameters are captured correctly
  - ReturnsBuilder is set for fluent chaining
  - Scanner metadata verification via Verify snapshots
  - Two-pass scanning merges types from all assemblies
  - Generated code uses snake_case method names
  - Generated code has create_builder function
  - Generated code uses Python type hints

- Shares TestTypes with TypeScript tests via Compile Include
- Added Verify.XunitV3 for snapshot testing
- All tests pass
- Go: 19 tests with snapshot verification for generated code
- Java: 19 tests with snapshot verification for generated code
- Rust: 19 tests with snapshot verification for generated code
- Tests follow the TypeScript test project pattern with shared TestTypes
- Uses Verify.XunitV3 for snapshot testing
… Rust

Following the TypeScript improvements in main for List properties on context
classes, this commit applies the same pattern to all other language generators:

- Add lazy handle resolution to AspireList and AspireDict classes
- Generate cached property getters for List/Dict type properties
- Context classes now create AspireList/AspireDict with getter capability ID
- The handle is resolved lazily on first access

This ensures consistent behavior across all polyglot code generators when
accessing List/Dict properties on [AspireExport(ExposeProperties=true)] types.
@davidfowl
Copy link
Member

davidfowl commented Jan 17, 2026

🐛 Go SDK Code Generation Error

Tested with CLI version 13.2.0-pr.13978.gb4f81399

Command

aspire sdk generate path/to/Aspire.Hosting.csproj --language go --output ./go-sample

Error

System.MethodAccessException: Attempt by method 
Aspire.Hosting.CodeGeneration.Go.AtsGoCodeGenerator.GenerateHandleWrapperRegistrations(...) 
to access method Aspire.Hosting.Ats.AtsConstants.IsDict(System.String) failed.

Stack Trace

at AtsGoCodeGenerator.GenerateHandleWrapperRegistrations(...) in AtsGoCodeGenerator.cs:line 372
at AtsGoCodeGenerator.GenerateAspireSdk(AtsContext context) in AtsGoCodeGenerator.cs:line 89
at AtsGoCodeGenerator.GenerateDistributedApplication(AtsContext context) in AtsGoCodeGenerator.cs:line 36
at CodeGenerationService.GenerateCode(String language) in CodeGenerationService.cs:line 162

Root Cause

The Go code generator is calling AtsConstants.IsDict() which appears to be internal in Aspire.Hosting.Ats. The Go codegen assembly does not have access.

Suggested Fix

Add [InternalsVisibleTo("Aspire.Hosting.CodeGeneration.Go")] to Aspire.Hosting.Ats, or make IsDict()/IsList() public.

@mitchdenny
Copy link
Member

@copilot in the Python polyglot E2E CLI test you need to enably polyglot support via the feature flag. Review the typescript E2E for an example.

Copy link
Contributor

Copilot AI commented Jan 17, 2026

@mitchdenny I've opened a new pull request, #13981, to work on those changes. Once the pull request is ready, I'll request review from you.

…generators

Replace calls to internal AtsConstants.IsDict()/IsList() methods with
AtsTypeCategory checks. The internal methods caused MethodAccessException
when assemblies were dynamically loaded at runtime.

- Go, Python, Java generators now use Dictionary<string, bool> to track
  whether each collected type ID is a Dict or List
- Category is determined at collection time using AtsTypeCategory enum
- Fixes runtime error: MethodAccessException when accessing internal methods
Remove the 'aspire run' step from the E2E test since Python runtime
may not be available on CI. The test now only verifies:
- Creating a Python apphost with 'aspire init -l python'
- Adding Redis integration with 'aspire add redis'
Verify that generated files contain expected code:
- apphost.py contains create_builder import and usage
- .modules/aspire.py contains add_redis method after adding Redis integration
# Conflicts:
#	.github/workflows/cli-e2e-recording-comment.yml
- Use 'aspire run --detach' instead of timeout with background process
- Use 'aspire stop' for cleanup instead of kill
- Add polling loop (12 attempts x 10s = 2 min, 18 for Rust = 3 min)
- Keep --non-interactive flag on aspire add command
- Use 'aspire run --detach' instead of timeout with background process
- Use 'aspire stop' for cleanup instead of kill
- Add polling loop (12 attempts x 10s = 2 min, 18 for Rust = 3 min)
- Keep --non-interactive flag on aspire add command

# Conflicts:
#	.github/workflows/polyglot-validation/test-go.sh
#	.github/workflows/polyglot-validation/test-java.sh
#	.github/workflows/polyglot-validation/test-python.sh
#	.github/workflows/polyglot-validation/test-rust.sh
The save_global_settings call for channel can fail in some container
environments. Since the workflow explicitly sets the channel config
afterward, this failure should not be fatal.
When --non-interactive flag is passed and there are multiple packages
or versions to choose from, auto-select the first one (latest version)
instead of trying to prompt and throwing an exception.
This ensures the workflow uses the script from the current branch/PR,
which includes fixes for non-fatal config set errors.
Run aspire run in background with &, poll for Redis container,
then kill the process on cleanup. Also capture aspire.log on failure
for debugging.
The native AOT Aspire CLI requires GLIBC 2.38 which is only available
in Debian 13 (trixie), not Debian 12 (bookworm).
…lotJavaTests

These are replaced by the polyglot-validation workflow which runs
SDK validation in Docker containers.
Start aspire run in its own process group with setsid, then kill the
entire group with kill -9 -$PID to ensure all child processes are
terminated.
@davidfowl
Copy link
Member

Would it be cleaner to build docker files for each scenario

- Create Dockerfile.python, Dockerfile.go, Dockerfile.java, Dockerfile.rust, Dockerfile.typescript
- Update polyglot-validation.yml to build and run Dockerfiles instead of inline scripts
- Improves maintainability and enables easier local testing
@radical
Copy link
Member

radical commented Jan 22, 2026

/azp run aspire-tests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@sebastienros
Copy link
Member Author

@radical why did you break my build?

@radical
Copy link
Member

radical commented Jan 22, 2026

/ba-g known failures

@radical
Copy link
Member

radical commented Jan 22, 2026

@radical why did you break my build?

Just making sure it didn't break on azdo. It's green to merge now.

}

// ... otherwise we had better prompt.
// In non-interactive mode, auto-select the latest version.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg thank you this was driving me nuts....

@sebastienros sebastienros merged commit d1c3d6e into main Jan 22, 2026
325 of 329 checks passed
@sebastienros sebastienros deleted the sebros/poly branch January 22, 2026 23:42
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Jan 22, 2026

# Add Redis integration
echo "Adding Redis integration..."
aspire add Aspire.Hosting.Redis --non-interactive 2>&1 || {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sebastienros would this use the default docker registry? it might get rate limited.

Copy link
Member Author

@sebastienros sebastienros Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be the case. To solve that we need to call .WithImageRegistry("netaspireci.azurecr.io") when we add AddRedis(...) in the apphosts. #14113

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants