Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ProjectReferences in AppHost #1726

Merged
merged 14 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" AspireHostLibrary="true" />
Copy link
Member

Choose a reason for hiding this comment

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

NIT: Not really a suggestion, but I'm not sure I love the name of the metadata. From a user's POV it is not obvious what this means until you understand the context. I wonder if we should instead use something more explicit, like AddReferenceToAssembly or something that is more obvious.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree that I'm not super happy about this name, but I haven't found one I like better yet.

The metadata does more than just take away the reference to the assembly, it is really the switch between "is this an app that you want to orchestrate?" or "is this a library that has AppHost extension logic you want to use?".

Another idea is to flip it to be AspireServiceApp=false. We already have ServiceNameOverride if you want to specify the name of the Projects class. Setting AspireServiceApp=false would mean "this isn't an application, so reference it like a normal ProjectReference".

Copy link
Member

Choose a reason for hiding this comment

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

super nit: Prefixing the name with Is* would make it more readable.

Copy link
Member Author

Choose a reason for hiding this comment

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

super nit: Prefixing the name with Is* would make it more readable.

Agreed. I think initially I didn't add Is* because the other ProjectReference metadata didn't follow this pattern:

  • ReferenceOutputAssembly
  • SkipGetTargetFrameworkProperties
  • ExcludeAssets

But these are more "should" and not "is", so I think it is a different scenario.

So should we have:

  • IsAspireHostLibrary=true
  • -or-
  • IsAspireServiceApp=false
    ?

I think I'm leaning towards IsAspireHostLibrary=true because the naming aligns with IsAspireHost=true for the whole project.

Copy link
Member

Choose a reason for hiding this comment

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

I don't really know yet all the special stuff done for these projects, so not sure what a ServiceApp vs Library would mean.

Copy link
Member

Choose a reason for hiding this comment

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

ServiceApp is literally any type of project which represents a microservice that you want to launch via the AppHost. This means the AppHost will orchestrate those types of projects.

The AppHost, while it is in reality an orchestrator, in the end is just a C# project, so you could think of scenarios where people want to add project references to libraries too (which aren't really microservices). For example, imagine a company that has several Aspire apps, and they want to share some of the logic they have in their AppHost projects. For this, they would build a shared library, which AppHost projects would all reference. This library, is not really a service, but instead just a library used by the AppHost.

Copy link
Member

Choose a reason for hiding this comment

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

So:

  • ServiceApp - special project that the AppHost project will use in some way, maybe post-process etc.
  • Other case - simple library projects, and AppHost will reference the assemblies from these, and use them
  • Wasn't there another case of referencing projects that are simply meant as a build dependency, and should not affect AppHost project's build itself?

Copy link
Member Author

Choose a reason for hiding this comment

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

ServiceApp - special project that the AppHost project will use in some way, maybe post-process etc.

It isn't a "special" project. But ServiceApps are just regular ASP.NET apps - like a web api, or blazor server app. They can also be "Worker" apps, if they don't need to expose an HTTP endpoint.

The AppHost's Program.cs files uses these apps by calling builder.AppProject<ServiceApp>("serviceName").

Wasn't there another case of referencing projects that are simply meant as a build dependency, and should not affect AppHost project's build itself?

I don't know of this case. In my head there are mainly 2 cases: ServiceApps and "normal" ProjectReferences to libraries.

Copy link
Member

Choose a reason for hiding this comment

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

I don't know of this case. In my head there are mainly 2 cases: ServiceApps and "normal" ProjectReferences to libraries.

My misunderstanding then.

I would vote for IsAspireServiceApp=false because we are changing the default ProjectReference to mean a ServiceApp reference which once you know it, makes sense. IOW, AppHost project references ServiceApps by default. In that case for any other project references an opt-out of that default would make sense.

Copy link
Member Author

Choose a reason for hiding this comment

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

Discussing this more with @DamianEdwards, we decided to go with the name:

  • IsAspireProjectResource=false
    • Setting this to false opts you into being an "AppHost library", you don't get "Projects" code generated, and you start referencing the project

to align with the "Resource" terminology in the app model.

We can then do some more renames:

  • ServiceNameOverride => AspireProjectMetadataTypeName
  • IServiceMetadata => IProjectMetadata

<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" AspireHostLibrary="true" />

<ProjectReference Include="..\CosmosEndToEnd.ApiService\CosmosEndToEnd.ApiService.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />

<!-- NOTE: This line is only required because we are using P2P references, not NuGet. It will not exist in real apps. -->
<Import Project="../../../src/Aspire.Hosting/build/Aspire.Hosting.targets" />
<!-- NOTE: These lines are only required because we are using P2P references, not NuGet. They will not exist in real apps. -->
<PropertyGroup>
<_SuppressImportAspireHostingOrchestrationTargets>true</_SuppressImportAspireHostingOrchestrationTargets>
</PropertyGroup>
<Import Project="..\..\..\src\Aspire.Hosting\build\Aspire.Hosting.targets" />
<Import Project="..\..\..\src\Aspire.Hosting.Sdk\SDK\Sdk.targets" />

</Project>
5 changes: 3 additions & 2 deletions playground/dapr/AppHost/DaprAppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Dapr\Aspire.Hosting.Dapr.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" AspireHostLibrary="true" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Dapr\Aspire.Hosting.Dapr.csproj" AspireHostLibrary="true" />

<ProjectReference Include="..\ServiceA\DaprServiceA.csproj" />
<ProjectReference Include="..\ServiceB\DaprServiceB.csproj" />
</ItemGroup>
Expand Down
8 changes: 6 additions & 2 deletions playground/dapr/AppHost/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../../'))" />

<!-- NOTE: This line is only required because we are using P2P references, not NuGet. It will not exist in real apps. -->
<!-- NOTE: These lines are only required because we are using P2P references, not NuGet. They will not exist in real apps. -->
<PropertyGroup>
<_SuppressImportAspireHostingOrchestrationTargets>true</_SuppressImportAspireHostingOrchestrationTargets>
</PropertyGroup>
<Import Project="..\..\..\src\Aspire.Hosting\build\Aspire.Hosting.targets" />

<Import Project="..\..\..\src\Aspire.Hosting.Sdk\SDK\Sdk.targets" />

</Project>
7 changes: 4 additions & 3 deletions playground/eShopLite/AppHost/AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" AspireHostLibrary="true" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" AspireHostLibrary="true" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj" AspireHostLibrary="true" />

<ProjectReference Include="..\ApiGateway\ApiGateway.csproj" />
<ProjectReference Include="..\BasketService\BasketService.csproj" />
<ProjectReference Include="..\CatalogDb\CatalogDb.csproj" />
Expand Down
10 changes: 7 additions & 3 deletions playground/eShopLite/AppHost/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />

<!-- NOTE: This line is only required because we are using P2P references, not NuGet. It will not exist in real apps. -->
<Import Project="../../../src/Aspire.Hosting/build/Aspire.Hosting.targets" />

<!-- NOTE: These lines are only required because we are using P2P references, not NuGet. They will not exist in real apps. -->
<PropertyGroup>
<_SuppressImportAspireHostingOrchestrationTargets>true</_SuppressImportAspireHostingOrchestrationTargets>
</PropertyGroup>
<Import Project="..\..\..\src\Aspire.Hosting\build\Aspire.Hosting.targets" />
<Import Project="..\..\..\src\Aspire.Hosting.Sdk\SDK\Sdk.targets" />

</Project>
10 changes: 7 additions & 3 deletions playground/orleans/OrleansAppHost/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />

<!-- NOTE: This line is only required because we are using P2P references, not NuGet. It will not exist in real apps. -->
<Import Project="../../../src/Aspire.Hosting/build/Aspire.Hosting.targets" />

<!-- NOTE: These lines are only required because we are using P2P references, not NuGet. They will not exist in real apps. -->
<PropertyGroup>
<_SuppressImportAspireHostingOrchestrationTargets>true</_SuppressImportAspireHostingOrchestrationTargets>
</PropertyGroup>
<Import Project="..\..\..\src\Aspire.Hosting\build\Aspire.Hosting.targets" />
<Import Project="..\..\..\src\Aspire.Hosting.Sdk\SDK\Sdk.targets" />

</Project>
7 changes: 4 additions & 3 deletions playground/orleans/OrleansAppHost/OrleansAppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Orleans\Aspire.Hosting.Orleans.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" AspireHostLibrary="true" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj" AspireHostLibrary="true" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Orleans\Aspire.Hosting.Orleans.csproj" AspireHostLibrary="true" />

<ProjectReference Include="..\OrleansServer\OrleansServer.csproj" />
</ItemGroup>

Expand Down
20 changes: 16 additions & 4 deletions src/Aspire.Hosting.Sdk/SDK/Sdk.targets
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<Project>

<!-- *** BEGIN *** -->

<!--
A property-based workload cannot import props files (except AutoImport.props, which has very tight limitations.
Instead, we copy everything from Aspire.Hosting.props into these targets.
Expand All @@ -20,7 +18,21 @@
<IsPackable Condition="'$(IsPackable)' == ''">false</IsPackable>
</PropertyGroup>

<!-- *** END *** -->
<!--
Default AppHost ProjectReference items to not be referenced in the AppHost, unless it is AspireHostLibrary=true.
This defaulting needs to happen in the SDK targets so this metadata affects NuGet Restore.
-->
<ItemGroup Condition="'$(IsAspireHost)' == 'true'">

<ProjectReference Update="@(ProjectReference)">
Copy link
Member

Choose a reason for hiding this comment

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

Should we add a global opt-out property for this behavior? e.g. EnableAspireProjectReferenceProcessing

Copy link
Member

Choose a reason for hiding this comment

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

Can you think of a good reason why?

Copy link
Member

Choose a reason for hiding this comment

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

Still slightly uneasy about taking over ProjectReferences and it's common for MSBuild defaults to have an opt-out. But we can wait for feedback.

Copy link
Member Author

Choose a reason for hiding this comment

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

What's the consensus here? I can add a property if people think it is valuable. (I also think we can add it later if we get feedback as well.)

<_IsServiceApp Condition="'%(_IsServiceApp)' == '' and '%(AspireHostLibrary)' != 'true'">true</_IsServiceApp>

<ReferenceOutputAssembly Condition="'%(ReferenceOutputAssembly)' == '' and '%(_IsServiceApp)' == 'true'">false</ReferenceOutputAssembly>
<SkipGetTargetFrameworkProperties Condition="'%(SkipGetTargetFrameworkProperties)' == '' and '%(_IsServiceApp)' == 'true'">true</SkipGetTargetFrameworkProperties>
joperezr marked this conversation as resolved.
Show resolved Hide resolved
<ExcludeAssets Condition="'%(ExcludeAssets)' == '' and '%(_IsServiceApp)' == 'true'">all</ExcludeAssets>
</ProjectReference>

</ItemGroup>

<Target Name="__WarnOnAspireCapabilityMissing" BeforeTargets="PrepareForBuild" Condition="!@(ProjectCapability->AnyHaveMetadataValue('Identity', 'Aspire'))">
<Warning Code="ASPIRE002" Text="$(MSBuildProjectName) is an Aspire AppHost project but necessary dependencies aren't present. Are you missing an Aspire.Hosting PackageReference?" />
Expand All @@ -37,6 +49,6 @@
also contains Aspire.Hosting.Orchestration and vice versa. Once Aspire/DCP are public
we can move it to WorkloadManifest.targets to ensure future correctness.
-->
<Import Project="Sdk.targets" Sdk="Aspire.Hosting.Orchestration" />
<Import Project="Sdk.targets" Sdk="Aspire.Hosting.Orchestration" Condition="'$(_SuppressImportAspireHostingOrchestrationTargets)' != 'true'" />
joperezr marked this conversation as resolved.
Show resolved Hide resolved

</Project>
156 changes: 109 additions & 47 deletions src/Aspire.Hosting/build/Aspire.Hosting.targets
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@
<ProjectCapability Include="Aspire" Condition=" '$(IsAspireHost)' == 'true' " />
</ItemGroup>

<Target Name="_CreateServiceAppProjects">

<ItemGroup>
<_ServiceAppProject Include="@(ProjectReference->WithMetadataValue('_IsServiceApp', 'true'))" />
</ItemGroup>

</Target>

<!-- Generate the data structures for doing the codegen for service references -->
<Target Name="CreateServiceMetadataSources"
DependsOnTargets="FindReferenceAssembliesForReferences"
Inputs="@(ReferencePathWithRefAssemblies)"
Outputs="@(ReferencePathWithRefAssemblies)">
DependsOnTargets="_CreateServiceAppProjects">
eerhardt marked this conversation as resolved.
Show resolved Hide resolved
<ItemGroup>
<ServiceMetadataSource Include="%(ReferencePathWithRefAssemblies.Identity)" Condition="%(ReferencePathWithRefAssemblies.ReferenceSourceTarget) == 'ProjectReference'">
<ClassName Condition="%(ReferencePathWithRefAssemblies.ReferenceSourceTarget) == 'ProjectReference' And %(ReferencePathWithRefAssemblies.ServiceNameOverride) == ''">$([System.IO.Path]::GetFileNameWithoutExtension(%(ReferencePathWithRefAssemblies.Identity)).Replace(".", "_").Replace("-","_").Replace(" ","_"))</ClassName>
<ClassName Condition="%(ReferencePathWithRefAssemblies.ReferenceSourceTarget) == 'ProjectReference' And %(ReferencePathWithRefAssemblies.ServiceNameOverride) != ''">$([System.String]::Copy(%(ReferencePathWithRefAssemblies.ServiceNameOverride)).Replace(".", "_").Replace("-","_").Replace(" ","_"))</ClassName>
<AssemblyName>$([System.IO.Path]::GetFileNameWithoutExtension(%(ReferencePathWithRefAssemblies.Identity)))</AssemblyName>
<ProjectPath>$([System.IO.Path]::GetFullPath(%(ReferencePathWithRefAssemblies.ProjectReferenceOriginalItemSpec)))</ProjectPath>
<Namespace>Projects</Namespace>
<ServiceMetadataSource Include="@(_ServiceAppProject)" Condition="'@(_ServiceAppProject)' != ''">
<ClassName Condition="%(_ServiceAppProject.ServiceNameOverride) == ''">$([System.IO.Path]::GetFileNameWithoutExtension(%(_ServiceAppProject.Identity)).Replace(".", "_").Replace("-","_").Replace(" ","_"))</ClassName>
Copy link
Member

@DamianEdwards DamianEdwards Jan 19, 2024

Choose a reason for hiding this comment

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

Should we update this from ServiceName to ResourceName now, i.e. ResourceNameOverride?

Copy link
Member

Choose a reason for hiding this comment

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

Or maybe just AspireResourceName and that can be used as the opt-out too by setting it to empty?

Copy link
Member Author

Choose a reason for hiding this comment

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

What about ProjectsClassName? Or even ProjectsName?

I don't like calling it "ResourceName" because that is what we call the string parameter that pass in to AddProject, which could be a different name than this name.

and that can be used as the opt-out too by setting it to empty?

In MSBuild, I haven't seen setting something to empty and not setting it being different behavior. Usually checking for == '' means "it isn't set".

Copy link
Member

Choose a reason for hiding this comment

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

For Items we do have the 'HasMetadata' item function, but aside from this it can be hard to distinguish between missing and empty.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't like setting this metadata to empty to mean something completely different than what the metadata is intended for.

  • <ProjectReference Include=.. /> - means this is a service app and don't reference it, use the default name for the Projects class name
  • <ProjectReference Include=.. AspireResourceName="foo" /> - means this is a service app and don't reference it, use "foo" for the Projects class name
  • <ProjectReference Include=.. AspireResourceName="" /> - means this isn't a service app and should be referenced

I do think we should rename ServiceNameOverride, but I don't know of a good name. If people think AspireResourceName is OK, I can rename it. I just have the reservations I listed above.

Copy link
Member

Choose a reason for hiding this comment

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

Strong agree that "empty metadatum" and "metadatum not specified" should not mean different things--in particular because there's no way to remove metadata other than setting it to empty.

<ClassName Condition="%(_ServiceAppProject.ServiceNameOverride) != ''">$([System.String]::Copy(%(_ServiceAppProject.ServiceNameOverride)).Replace(".", "_").Replace("-","_").Replace(" ","_"))</ClassName>
<ProjectPath>$([System.IO.Path]::GetFullPath(%(_ServiceAppProject.Identity)))</ProjectPath>
</ServiceMetadataSource>
</ItemGroup>
</Target>
Expand All @@ -32,33 +36,27 @@
<ItemGroup>
<ServiceMetadataSource Update="@(ServiceMetadataSource)">
<Source>
<![CDATA[using System%3B
<![CDATA[namespace Projects%3B

namespace ]]>%(Namespace)<![CDATA[%3B

[System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")]
public class ]]>%(ClassName)<![CDATA[ : IServiceMetadata
[global::System.Diagnostics.DebuggerDisplay("Type = {GetType().Name,nq}, ProjectPath = {ProjectPath}")]
public class ]]>%(ClassName)<![CDATA[ : global::Aspire.Hosting.IServiceMetadata
eerhardt marked this conversation as resolved.
Show resolved Hide resolved
{
public string ProjectPath => """]]>%(ProjectPath)<![CDATA["""%3B
}]]>
</Source>
</ServiceMetadataSource>
</ItemGroup>

<WriteLinesToFile
File="$(_AspireIntermediatePath)references\%(ServiceMetadataSource.AssemblyName).ServiceMetadata.g.cs"
Overwrite="true"
Lines="%(ServiceMetadataSource.Source)"
Encoding="Unicode"
Condition="%(ServiceMetadataSource.AssemblyName) != ''"
WriteOnlyWhenDifferent="true"
/>
<WriteLinesToFile File="$(_AspireIntermediatePath)references\%(ServiceMetadataSource.ClassName).ServiceMetadata.g.cs"
Overwrite="true"
Lines="%(ServiceMetadataSource.Source)"
Encoding="Unicode"
Condition="%(ServiceMetadataSource.ClassName) != ''"
WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_AspireIntermediatePath)references\%(ServiceMetadataSource.AssemblyName).ServiceMetadata.g.cs" />
<Compile
Include="$(_AspireIntermediatePath)references\%(ServiceMetadataSource.AssemblyName).ServiceMetadata.g.cs"
Condition="%(ServiceMetadataSource.AssemblyName) != ''"
/>
<FileWrites Include="$(_AspireIntermediatePath)references\%(ServiceMetadataSource.ClassName).ServiceMetadata.g.cs" />
<Compile Include="$(_AspireIntermediatePath)references\%(ServiceMetadataSource.ClassName).ServiceMetadata.g.cs"
Condition="%(ServiceMetadataSource.ClassName) != ''" />
</ItemGroup>
</Target>

Expand All @@ -75,9 +73,7 @@ public class ]]>%(ClassName)<![CDATA[ : IServiceMetadata
<ItemGroup>
<ProjectMetadataSource Update="@(ProjectMetadataSource)">
<Source>
<![CDATA[using System%3B

namespace ]]>Projects<![CDATA[%3B
<![CDATA[namespace Projects%3B

internal static class ]]>%(ClassName)<![CDATA[
{
Expand All @@ -87,13 +83,11 @@ internal static class ]]>%(ClassName)<![CDATA[
</ProjectMetadataSource>
</ItemGroup>

<WriteLinesToFile
File="$(_AspireIntermediatePath)references\_AppHost.ProjectMetadata.g.cs"
Overwrite="true"
Lines="%(ProjectMetadataSource.Source)"
Encoding="Unicode"
WriteOnlyWhenDifferent="true"
/>
<WriteLinesToFile File="$(_AspireIntermediatePath)references\_AppHost.ProjectMetadata.g.cs"
Overwrite="true"
Lines="%(ProjectMetadataSource.Source)"
Encoding="Unicode"
WriteOnlyWhenDifferent="true" />
<ItemGroup>
<FileWrites Include="$(_AspireIntermediatePath)references\_AppHost.ProjectMetadata.g.cs" />
<Compile Include="$(_AspireIntermediatePath)references\_AppHost.ProjectMetadata.g.cs" />
Expand All @@ -104,14 +98,83 @@ internal static class ]]>%(ClassName)<![CDATA[
<Warning Code="ASPIRE001" Text="The '$(Language)' language isn't fully supported by Aspire - some code generation targets will not run, so will require manual authoring." />
</Target>

<!--
Gets the references in 'AppProjectTargetFramework' that aren't executable projects.
-->
<UsingTask TaskName="GetNonExecutableReferences"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<AppProjectTargetFramework ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<NonExecutableReferences ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System.Xml.Linq" />
<Code Type="Fragment" Language="cs">
<![CDATA[
eerhardt marked this conversation as resolved.
Show resolved Hide resolved
HashSet<ITaskItem> nonExecutableReferences = new HashSet<ITaskItem>();

foreach (var appProject in AppProjectTargetFramework)
{
var additionalProperties = appProject.GetMetadata("AdditionalPropertiesFromProject");
if (string.IsNullOrEmpty(additionalProperties))
{
// Skip any projects that don't contain the right metadata
continue;
}

var additionalPropertiesXml = XElement.Parse(additionalProperties);
foreach (var targetFrameworkElement in additionalPropertiesXml.Elements())
{
var isExe = targetFrameworkElement.Element("_IsExecutable");
if (isExe != null && !string.Equals(isExe.Value, "true", StringComparison.OrdinalIgnoreCase))
{
nonExecutableReferences.Add(appProject);
}
}
}

NonExecutableReferences = nonExecutableReferences.ToArray();
]]>
</Code>
</Task>
</UsingTask>

<!--
Validates that all the ProjectReferences of an Aspire AppHost project are executables and
informs the developer to set 'AspireHostLibrary=true' if they really intended on ProjectReferencing a library.
-->
<Target Name="_ValidateAspireHostServiceReferences"
joperezr marked this conversation as resolved.
Show resolved Hide resolved
DependsOnTargets="_CreateServiceAppProjects"
Condition="'$(IsAspireHost)' == 'true' and '$(SkipValidateAspireHostServiceReferences)' != 'true'">

<MSBuild Projects="@(_ServiceAppProject)"
Targets="GetTargetFrameworks"
BuildInParallel="$(BuildInParallel)"
Properties="%(_ServiceAppProject.SetConfiguration); %(_ServiceAppProject.SetPlatform)"
ContinueOnError="!$(BuildingProject)"
RemoveProperties="%(_ServiceAppProject.GlobalPropertiesToRemove);TargetFramework;RuntimeIdentifier;SelfContained;$(_GlobalPropertiesToRemoveFromProjectReferences)"
SkipNonexistentTargets="true">
<Output TaskParameter="TargetOutputs" ItemName="_ServiceAppProjectTargetFramework" />
</MSBuild>

<GetNonExecutableReferences AppProjectTargetFramework="@(_ServiceAppProjectTargetFramework)">
<Output TaskParameter="NonExecutableReferences" ItemName="_NonExecutableServiceApps" />
</GetNonExecutableReferences>

<Warning Code="ASPIRE004"
joperezr marked this conversation as resolved.
Show resolved Hide resolved
Condition="'@(_NonExecutableServiceApps)' != ''"
Text="'%(_NonExecutableServiceApps.OriginalItemSpec)' is referenced by an Aspire AppHost project, but it is not an executable. Did you mean to set AspireHostLibrary=&quot;true&quot;?" />
</Target>

<PropertyGroup>
<!-- Easy extension point for adding new languages' write support. -->
<_WriteServiceMetadataSourcesDependsOn>_CSharpWriteServiceMetadataSources;_CSharpAppHostProjectMetadataSources;_WarnOnUnsupportedLanguage</_WriteServiceMetadataSourcesDependsOn>
<WriteServiceMetadataSourcesDependsOn>_CSharpWriteServiceMetadataSources;_CSharpAppHostProjectMetadataSources;_WarnOnUnsupportedLanguage;_ValidateAspireHostServiceReferences</WriteServiceMetadataSourcesDependsOn>
</PropertyGroup>

<!-- The purpose of this target is to take all of the generated service meta data and write them to the intermediate build directory
and reference them for compilation. There will be a AssemblyName.ServiceMetadata.g.cs file for each referenced project. -->
<Target Name="WriteServiceMetadataSources" DependsOnTargets="$(_WriteServiceMetadataSourcesDependsOn)" BeforeTargets="CoreCompile" />
<!-- The purpose of this target is to take all of the generated service metadata and write them to the intermediate build directory
and reference them for compilation. There will be a ClassName.ServiceMetadata.g.cs file for each referenced project. -->
<Target Name="WriteServiceMetadataSources" DependsOnTargets="$(WriteServiceMetadataSourcesDependsOn)" BeforeTargets="CoreCompile" />

<!-- This target registers the location of the Aspire orchestration dependencies -->
<Target Name="SetOrchestrationDiscoveryAttributes" BeforeTargets="GetAssemblyAttributes">
Expand Down Expand Up @@ -157,11 +220,10 @@ internal static class ]]>%(ClassName)<![CDATA[
</ItemGroup>

<Exec Command="@(_AspireManifestPublishArg, ' ')"
ConsoleToMsBuild="true"
IgnoreStandardErrorWarningFormat="true"
EchoOff="true"
StandardOutputImportance="low"
StandardErrorImportance="low"
/>
ConsoleToMsBuild="true"
IgnoreStandardErrorWarningFormat="true"
EchoOff="true"
StandardOutputImportance="low"
StandardErrorImportance="low" />
</Target>
</Project>
Loading