Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,6 @@
<Folder Name="/src/">
<Project Path="src/Aspire.Hosting.CodeGeneration.TypeScript/Aspire.Hosting.CodeGeneration.TypeScript.csproj" />
<Project Path="src/Aspire.Hosting.CodeGeneration.Python/Aspire.Hosting.CodeGeneration.Python.csproj" />
<Project Path="src/Aspire.Cli.NuGetHelper/Aspire.Cli.NuGetHelper.csproj" />
<Project Path="src/Aspire.Hosting.CodeGeneration.Go/Aspire.Hosting.CodeGeneration.Go.csproj" />
<Project Path="src/Aspire.Hosting.CodeGeneration.Java/Aspire.Hosting.CodeGeneration.Java.csproj" />
<Project Path="src/Aspire.Hosting.CodeGeneration.Rust/Aspire.Hosting.CodeGeneration.Rust.csproj" />
Expand Down
291 changes: 109 additions & 182 deletions docs/specs/bundle.md

Large diffs are not rendered by default.

56 changes: 21 additions & 35 deletions eng/Bundle.proj
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
<Project>
<!--
Bundle.proj - Standalone project for creating the Aspire CLI bundle.

Usage: dotnet msbuild eng/Bundle.proj /p:TargetRid=linux-x64 /p:Configuration=Release

This project:
1. Publishes managed components (NuGetHelper, AppHostServer, Dashboard)
1. Publishes the unified managed binary (Aspire.Managed) as self-contained
2. Publishes native CLI (unless SkipNativeBuild=true)
3. Restores DCP package
4. Runs CreateLayout to assemble the bundle

Options:
- SkipNativeBuild=true: Skip building native CLI (use pre-built from artifacts)
- SkipManagedBuild=true: Skip building managed projects (use pre-built)
- BundleVersion: Version string for the bundle (auto-computed in CI if not set)
- BundleRuntimeVersion: .NET runtime version to include (required)
-->

<!-- Import Versions.props for version info -->
Expand All @@ -23,32 +22,33 @@
<PropertyGroup>
<!-- Configuration -->
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>

<!-- Runtime ID for the bundle -->
<TargetRid Condition="'$(TargetRid)' == '' and '$(TargetRids)' != ''">$(TargetRids)</TargetRid>
<TargetRid Condition="'$(TargetRid)' == '' and $([MSBuild]::IsOSPlatform('Windows'))">win-x64</TargetRid>
<TargetRid Condition="'$(TargetRid)' == '' and $([MSBuild]::IsOSPlatform('OSX'))">osx-arm64</TargetRid>
<TargetRid Condition="'$(TargetRid)' == ''">linux-x64</TargetRid>

<!-- Paths -->
<RepoRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..'))</RepoRoot>
<ArtifactsDir>$(RepoRoot)artifacts\</ArtifactsDir>
<ArtifactsLogDir>$(ArtifactsDir)log\$(Configuration)\</ArtifactsLogDir>
<BundleOutputDir>$(ArtifactsDir)bundle\$(TargetRid)\</BundleOutputDir>

<!-- Project paths -->
<CliProjectPath>$(RepoRoot)src\Aspire.Cli\Aspire.Cli.csproj</CliProjectPath>
<NuGetHelperProjectPath>$(RepoRoot)src\Aspire.Cli.NuGetHelper\Aspire.Cli.NuGetHelper.csproj</NuGetHelperProjectPath>
<AppHostServerProjectPath>$(RepoRoot)src\Aspire.Hosting.RemoteHost\Aspire.Hosting.RemoteHost.csproj</AppHostServerProjectPath>
<DashboardProjectPath>$(RepoRoot)src\Aspire.Dashboard\Aspire.Dashboard.csproj</DashboardProjectPath>
<ManagedProjectPath>$(RepoRoot)src\Aspire.Managed\Aspire.Managed.csproj</ManagedProjectPath>
<AppHostProjectPath>$(RepoRoot)src\Aspire.Hosting.AppHost\Aspire.Hosting.AppHost.csproj</AppHostProjectPath>
<CreateLayoutProjectPath>$(RepoRoot)tools\CreateLayout\CreateLayout.csproj</CreateLayoutProjectPath>

<!-- Bundle version: use VersionSuffix from CI if provided, else dev version for local builds -->
<!-- In GitHub Actions CI: VersionSuffix is set via /p:VersionSuffix=pr.XXXX.gSHA -->
<BundleVersion Condition="'$(BundleVersion)' == '' and '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix)</BundleVersion>
<BundleVersion Condition="'$(BundleVersion)' == ''">$(VersionPrefix)-dev</BundleVersion>


<!-- Forward VersionSuffix to Exec-invoked dotnet commands (MSBuild properties don't flow to child processes) -->
<_VersionSuffixArg Condition="'$(VersionSuffix)' != ''">/p:VersionSuffix=$(VersionSuffix)</_VersionSuffixArg>

<!-- Binlog arguments for dotnet commands -->
<_BinlogArg Condition="'$(ContinuousIntegrationBuild)' == 'true'">-bl:$(ArtifactsLogDir)</_BinlogArg>
</PropertyGroup>
Expand All @@ -67,7 +67,7 @@

<Target Name="_ShowBanner">
<MakeDir Directories="$(ArtifactsLogDir)" Condition="'$(ContinuousIntegrationBuild)' == 'true'" />

<Message Importance="high" Text="" />
<Message Importance="high" Text="========================================" />
<Message Importance="high" Text="Building Aspire CLI Bundle" />
Expand All @@ -89,26 +89,18 @@
<_CliBinlog Condition="'$(ContinuousIntegrationBuild)' == 'true'">-bl:$(ArtifactsLogDir)PublishCli.binlog</_CliBinlog>
<!-- The tar.gz archive produced by CreateLayout is embedded as a resource in the CLI binary -->
<_BundleArchivePath>$(ArtifactsDir)bundle\aspire-$(BundleVersion)-$(TargetRid).tar.gz</_BundleArchivePath>
<!-- VersionSuffix must be forwarded explicitly because this is an Exec (outer MSBuild process),
so inner MSBuild properties like VersionSuffix don't flow automatically. Without this,
the CLI binary gets a version like "13.2.0-dev" instead of the CI-provided suffix. -->
<_VersionSuffixArg Condition="'$(VersionSuffix)' != ''">/p:VersionSuffix=$(VersionSuffix)</_VersionSuffixArg>
</PropertyGroup>
<Message Importance="high" Text="Publishing native AOT CLI with embedded bundle: $(_BundleArchivePath)" />
<Exec Command="dotnet publish &quot;$(CliProjectPath)&quot; -c $(Configuration) -r $(TargetRid) /p:BundlePayloadPath=&quot;$(_BundleArchivePath)&quot; $(_VersionSuffixArg) $(_CliBinlog)" />
</Target>

<Target Name="_PublishManagedProjects" Condition="'$(SkipManagedBuild)' != 'true'">
<PropertyGroup>
<_NuGetHelperBinlog Condition="'$(ContinuousIntegrationBuild)' == 'true'">-bl:$(ArtifactsLogDir)PublishNuGetHelper.binlog</_NuGetHelperBinlog>
<_AppHostServerBinlog Condition="'$(ContinuousIntegrationBuild)' == 'true'">-bl:$(ArtifactsLogDir)PublishAppHostServer.binlog</_AppHostServerBinlog>
<_DashboardBinlog Condition="'$(ContinuousIntegrationBuild)' == 'true'">-bl:$(ArtifactsLogDir)PublishDashboard.binlog</_DashboardBinlog>
<_ManagedBinlog Condition="'$(ContinuousIntegrationBuild)' == 'true'">-bl:$(ArtifactsLogDir)PublishManaged.binlog</_ManagedBinlog>
</PropertyGroup>
<Message Importance="high" Text="Publishing managed projects..." />
<!-- Publish as single-file, framework-dependent. Disable XML docs and IDE0005 analyzer for bundle optimization -->
<Exec Command="dotnet publish &quot;$(NuGetHelperProjectPath)&quot; -c $(Configuration) /p:PublishSingleFile=true /p:SelfContained=false /p:GenerateDocumentationFile=false /p:EnforceCodeStyleInBuild=false $(_NuGetHelperBinlog)" />
<Exec Command="dotnet publish &quot;$(AppHostServerProjectPath)&quot; -c $(Configuration) /p:PublishSingleFile=true /p:SelfContained=false /p:GenerateDocumentationFile=false /p:EnforceCodeStyleInBuild=false $(_AppHostServerBinlog)" />
<Exec Command="dotnet publish &quot;$(DashboardProjectPath)&quot; -c $(Configuration) /p:PublishSingleFile=true /p:SelfContained=false /p:GenerateDocumentationFile=false /p:EnforceCodeStyleInBuild=false /p:IsTransformWebConfigDisabled=true $(_DashboardBinlog)" />
<Message Importance="high" Text="Publishing aspire-managed (self-contained)..." />
<!-- Publish as self-contained single-file for the target RID -->
<Exec Command="dotnet publish &quot;$(ManagedProjectPath)&quot; -c $(Configuration) -r $(TargetRid) --self-contained /p:GenerateDocumentationFile=false /p:EnforceCodeStyleInBuild=false $(_VersionSuffixArg) $(_ManagedBinlog)" />
</Target>

<Target Name="_RestoreDcpPackage">
Expand All @@ -124,16 +116,10 @@
<!-- Remove trailing slashes for command line arguments -->
<_BundleOutputDirArg>$(BundleOutputDir.TrimEnd('\').TrimEnd('/'))</_BundleOutputDirArg>
<_ArtifactsDirArg>$(ArtifactsDir.TrimEnd('\').TrimEnd('/'))</_ArtifactsDirArg>

<!-- BundleRuntimePath is provided for local development builds when the .NET runtime
is already available on disk (e.g., from a previous download). When not set,
CreateLayout downloads the runtime automatically. -->
<_RuntimeArgs Condition="'$(BundleRuntimePath)' != ''">--runtime "$(BundleRuntimePath)"</_RuntimeArgs>
<_RuntimeArgs Condition="'$(BundleRuntimePath)' == ''">--download-runtime</_RuntimeArgs>

<_CreateLayoutArgs>--output "$(_BundleOutputDirArg)" --artifacts "$(_ArtifactsDirArg)" --rid $(TargetRid) --bundle-version $(BundleVersion) --runtime-version $(BundleRuntimeVersion) --verbose $(_RuntimeArgs) --archive</_CreateLayoutArgs>

<_CreateLayoutArgs>--output "$(_BundleOutputDirArg)" --artifacts "$(_ArtifactsDirArg)" --rid $(TargetRid) --bundle-version $(BundleVersion) --verbose --archive</_CreateLayoutArgs>
</PropertyGroup>

<Message Importance="high" Text="Creating bundle layout..." />
<Exec Command="dotnet run --project &quot;$(CreateLayoutProjectPath)&quot; -c $(Configuration) -- $(_CreateLayoutArgs)" />
</Target>
Expand Down
2 changes: 0 additions & 2 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
<DotNetSdkPreviousVersionForTesting>8.0.415</DotNetSdkPreviousVersionForTesting>
<!-- dotnet 9.0 versions for running tests - used for templates tests -->
<DotNetSdkCurrentVersionForTesting>9.0.306</DotNetSdkCurrentVersionForTesting>
<!-- .NET SDK version to include in the bundle for running managed components -->
<BundleRuntimeVersion>10.0.102</BundleRuntimeVersion>
<XunitV3Version>3.1.0</XunitV3Version>
<XUnitAnalyzersVersion>1.21.0</XUnitAnalyzersVersion>
<XunitRunnerVisualStudioVersion>3.1.0</XunitRunnerVisualStudioVersion>
Expand Down
5 changes: 0 additions & 5 deletions eng/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,6 @@ if ($bundle) {
$bundleArgs += "/p:SkipNativeBuild=true"
}

# Pass through runtime version if set
if ($runtimeVersion) {
$bundleArgs += "/p:BundleRuntimeVersion=$runtimeVersion"
}

# CI flag is passed to Bundle.proj which handles version computation via Versions.props
if ($ci) {
$bundleArgs += "/p:ContinuousIntegrationBuild=true"
Expand Down
5 changes: 0 additions & 5 deletions eng/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,6 @@ if [ "$build_bundle" = true ]; then
fi
done

# Pass through runtime version if set
if [ -n "$runtime_version" ]; then
bundle_args+=("/p:BundleRuntimeVersion=$runtime_version")
fi

# CI flag is passed to Bundle.proj which handles version computation via Versions.props
if [ "${CI:-}" = "true" ]; then
bundle_args+=("/p:ContinuousIntegrationBuild=true")
Expand Down
106 changes: 93 additions & 13 deletions localhive.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

<#!
.SYNOPSIS
Build local NuGet packages and Aspire CLI, then create/update a hive and install the CLI (Windows/PowerShell).
Build local NuGet packages, Aspire CLI, and bundle, then create/update a hive and install everything (Windows/PowerShell).

.DESCRIPTION
Mirrors localhive.sh behavior on Windows. Packs the repo, creates a symlink from
$HOME/.aspire/hives/<HiveName> to artifacts/packages/<Config>/Shipping (or copies .nupkg files),
and installs the locally-built Aspire CLI to $HOME/.aspire/bin.
installs the locally-built Aspire CLI to $HOME/.aspire/bin, and builds/installs the bundle
(aspire-managed + DCP) to $HOME/.aspire so the CLI can auto-discover it.

.PARAMETER Configuration
Build configuration: Release or Debug (positional parameter 0). If omitted, the script tries Release then falls back to Debug.
Expand All @@ -24,6 +25,12 @@
.PARAMETER SkipCli
Skip installing the locally-built CLI to $HOME/.aspire/bin.

.PARAMETER SkipBundle
Skip building and installing the bundle (aspire-managed + DCP) to $HOME/.aspire.

.PARAMETER NativeAot
Build and install the native AOT CLI (self-extracting binary with embedded bundle) instead of the dotnet tool version.

.PARAMETER Help
Show help and exit.

Expand Down Expand Up @@ -58,6 +65,10 @@ param(

[switch] $SkipCli,

[switch] $SkipBundle,

[switch] $NativeAot,

[Alias('h')]
[switch] $Help
)
Expand All @@ -80,6 +91,8 @@ Options:
-VersionSuffix (-v) Prerelease version suffix (default: auto-generates local.YYYYMMDD.tHHmmss)
-Copy Copy .nupkg files instead of creating a symlink
-SkipCli Skip installing the locally-built CLI to $HOME\.aspire\bin
-SkipBundle Skip building and installing the bundle (aspire-managed + DCP)
-NativeAot Build native AOT CLI (self-extracting with embedded bundle)
-Help (-h) Show this help and exit

Examples:
Expand Down Expand Up @@ -155,9 +168,12 @@ function Get-PackagesPath {

$effectiveConfig = if ($Configuration) { $Configuration } else { 'Release' }

# Skip native AOT during pack unless user will build it separately via -NativeAot + Bundle.proj
$aotArg = if (-not $NativeAot) { "/p:PublishAot=false" } else { "" }

if ($Configuration) {
Write-Log "Building and packing NuGet packages [-c $Configuration] with versionsuffix '$VersionSuffix'"
& $buildScript -restore -build -pack -c $Configuration "/p:VersionSuffix=$VersionSuffix" "/p:SkipTestProjects=true" "/p:SkipPlaygroundProjects=true"
& $buildScript -restore -build -pack -c $Configuration "/p:VersionSuffix=$VersionSuffix" "/p:SkipTestProjects=true" "/p:SkipPlaygroundProjects=true" $aotArg
if ($LASTEXITCODE -ne 0) {
Write-Err "Build failed for configuration $Configuration."
exit 1
Expand All @@ -170,7 +186,7 @@ if ($Configuration) {
}
else {
Write-Log "Building and packing NuGet packages [-c Release] with versionsuffix '$VersionSuffix'"
& $buildScript -restore -build -pack -c Release "/p:VersionSuffix=$VersionSuffix" "/p:SkipTestProjects=true" "/p:SkipPlaygroundProjects=true"
& $buildScript -restore -build -pack -c Release "/p:VersionSuffix=$VersionSuffix" "/p:SkipTestProjects=true" "/p:SkipPlaygroundProjects=true" $aotArg
if ($LASTEXITCODE -ne 0) {
Write-Err "Build failed for configuration Release."
exit 1
Expand Down Expand Up @@ -236,22 +252,81 @@ else {
}
}

# Install the locally-built CLI to $HOME/.aspire/bin
if (-not $SkipCli) {
$cliBinDir = Join-Path (Join-Path $HOME '.aspire') 'bin'
# The CLI is built as part of the pack target in artifacts/bin/Aspire.Cli.Tool/<Config>/net10.0/publish
$cliPublishDir = Join-Path $RepoRoot "artifacts" "bin" "Aspire.Cli.Tool" $effectiveConfig "net10.0" "publish"
# Determine the RID for the current platform
if ($IsWindows) {
$bundleRid = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { 'win-arm64' } else { 'win-x64' }
} elseif ($IsMacOS) {
$bundleRid = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { 'osx-arm64' } else { 'osx-x64' }
} else {
$bundleRid = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { 'linux-arm64' } else { 'linux-x64' }
}

$aspireRoot = Join-Path $HOME '.aspire'
$cliBinDir = Join-Path $aspireRoot 'bin'

# Build the bundle (aspire-managed + DCP, and optionally native AOT CLI)
if (-not $SkipBundle) {
$bundleProjPath = Join-Path $RepoRoot "eng" "Bundle.proj"
$skipNativeArg = if ($NativeAot) { '' } else { '/p:SkipNativeBuild=true' }

Write-Log "Building bundle (aspire-managed + DCP$(if ($NativeAot) { ' + native AOT CLI' }))..."
$buildArgs = @($bundleProjPath, '-c', $effectiveConfig, "/p:VersionSuffix=$VersionSuffix")
if (-not $NativeAot) {
$buildArgs += '/p:SkipNativeBuild=true'
}
& dotnet build @buildArgs
if ($LASTEXITCODE -ne 0) {
Write-Err "Bundle build failed."
exit 1
}

if (-not (Test-Path -LiteralPath $cliPublishDir)) {
# Fallback: try the non-publish directory
$cliPublishDir = Join-Path $RepoRoot "artifacts" "bin" "Aspire.Cli.Tool" $effectiveConfig "net10.0"
$bundleLayoutDir = Join-Path $RepoRoot "artifacts" "bundle" $bundleRid

if (-not (Test-Path -LiteralPath $bundleLayoutDir)) {
Write-Err "Bundle layout not found at $bundleLayoutDir"
exit 1
}

# Copy managed/ and dcp/ to $HOME/.aspire so the CLI auto-discovers them
foreach ($component in @('managed', 'dcp')) {
$sourceDir = Join-Path $bundleLayoutDir $component
$destDir = Join-Path $aspireRoot $component
if (Test-Path -LiteralPath $sourceDir) {
if (Test-Path -LiteralPath $destDir) {
Remove-Item -LiteralPath $destDir -Force -Recurse
}
Write-Log "Copying $component/ to $destDir"
Copy-Item -LiteralPath $sourceDir -Destination $destDir -Recurse -Force
} else {
Write-Warn "$component/ not found in bundle layout at $sourceDir"
}
}

Write-Log "Bundle installed to $aspireRoot (managed/ + dcp/)"
}

# Install the CLI to $HOME/.aspire/bin
if (-not $SkipCli) {
$cliExeName = if ($IsWindows) { 'aspire.exe' } else { 'aspire' }

if ($NativeAot) {
# Native AOT CLI is produced by Bundle.proj's _PublishNativeCli target
$cliPublishDir = Join-Path $RepoRoot "artifacts" "bin" "Aspire.Cli" $effectiveConfig "net10.0" $bundleRid "native"
if (-not (Test-Path -LiteralPath $cliPublishDir)) {
$cliPublishDir = Join-Path $RepoRoot "artifacts" "bin" "Aspire.Cli" $effectiveConfig "net10.0" $bundleRid "publish"
}
} else {
# Framework-dependent CLI from dotnet tool build
$cliPublishDir = Join-Path $RepoRoot "artifacts" "bin" "Aspire.Cli.Tool" $effectiveConfig "net10.0" "publish"
if (-not (Test-Path -LiteralPath $cliPublishDir)) {
$cliPublishDir = Join-Path $RepoRoot "artifacts" "bin" "Aspire.Cli.Tool" $effectiveConfig "net10.0"
}
}

$cliSourcePath = Join-Path $cliPublishDir $cliExeName

if (Test-Path -LiteralPath $cliSourcePath) {
Write-Log "Installing Aspire CLI to $cliBinDir"
Write-Log "Installing Aspire CLI$(if ($NativeAot) { ' (native AOT)' }) to $cliBinDir"
New-Item -ItemType Directory -Path $cliBinDir -Force | Out-Null

# Copy all files from the publish directory (CLI and its dependencies)
Expand Down Expand Up @@ -286,4 +361,9 @@ if (-not $SkipCli) {
Write-Log "The locally-built CLI was installed to: $(Join-Path (Join-Path $HOME '.aspire') 'bin')"
Write-Host
}
if (-not $SkipBundle) {
Write-Log "Bundle (aspire-managed + DCP) installed to: $(Join-Path $HOME '.aspire')"
Write-Log " The CLI at ~/.aspire/bin/ will auto-discover managed/ and dcp/ in the parent directory."
Write-Host
}
Write-Log 'The Aspire CLI discovers channels automatically from the hives directory; no extra flags are required.'
Loading