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

Improve usage of ProjectReferences in the AppHost #1074

Closed
davidfowl opened this issue Nov 28, 2023 · 18 comments · Fixed by #1726
Closed

Improve usage of ProjectReferences in the AppHost #1074

davidfowl opened this issue Nov 28, 2023 · 18 comments · Fixed by #1726
Assignees
Labels
area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication area-app-model-offsite area-tooling

Comments

@davidfowl
Copy link
Member

davidfowl commented Nov 28, 2023

We use project references from the app host to service projects for 2 reasons:

  1. dotnet run just works. We get an orchestrated build and run experience for free from the command line.
  2. We use these P2Ps to create a class for each referenced project for a type-safe experience for adding project references.

There are 2 major things that have come up recently with respect to the app host and orchestration:

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 28, 2023
@davidfowl davidfowl added area-tooling and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 28, 2023
@davidfowl davidfowl added this to the preview 3 (Jan) milestone Nov 28, 2023
@danegsta
Copy link
Member

We should be able to control these transitive dependency behaviors via ProjectReference metadata. If I remember correctly, <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> and <ReferenceOutputAssembly>false</ReferenceOutputAssembly> will give us the behavior we want, but we should definitely test to be sure (see here for the list of ProjectReference metadata).

In order to guard against an unforeseen future scenario where a user does want a ProjectReference with full transitive dependency behavior in their AppHost project, we could update our scaffolding to append a custom metadata value to the service ProjectReference items. Something along the lines of <IsAspireService>true</IsAspireService> that we can key off of to automatically apply the above dependency metadata values.

@DamianEdwards
Copy link
Member

@baronfel thoughts?

@DamianEdwards
Copy link
Member

DamianEdwards commented Nov 28, 2023

and <ReferenceOutputAssembly>false</ReferenceOutputAssembly>

When I tried that it broke our current code-generation logic. Likely it can be fixed though by altering our approach.

@baronfel
Copy link
Member

baronfel commented Nov 28, 2023

@baronfel thoughts?

Oh boy, do I!

I did a lot of this in this commit of a branch with the goal of keeping ProjectReferences for describing the relationship between components. It works quite well! You can go even further by removing the requirement to even build.

The ProjectReference metadata changes that @danegsta mentions could be handled by the Aspire.Hosting.Targets themselves if you have the custom metadata hook he's describing - in this case you could have an <ItemGroup> that does something like this in your targets:

<ItemGroup>
  <ProjectReference Update="@(ProjectReference)" Condition="'%(ProjectReference.IsAspireService)' == 'true'">
    <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
    <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
  <ProjectReference>
</ItemGroup>

That's an established and fine pattern - the only potential concern could be if a referenced project exposes multiple TFMs. The branch I have above kind of works like that, but can't really be used in the aspire samples themselves because they take a project reference on aspire itself, instead of a PackageReference. That gap would be solved if you were ok with needing to add a piece of metadata.

If you wanted to go further then you could have the Aspire Host not even build the projects by setting BuildReference=false - this would speed up operations like building the Aspire AppHost and generating the Aspire manifest massively, but at the cost of making the AppHost need to orchestrate building the applications after the AppHost was launched (e.g. docker-compose up building containers on-demand).

@baronfel
Copy link
Member

When I tried that it broke our current code-generation logic. Likely it can be fixed though by altering our approach.

Yes, you get around this by changing the code-generation logic to operate on the ProjectReferences themselves, which you can see in this diff. You ask each project reference to compute its output path (a very fast operation) and then go from there.

@rainersigwald
Copy link
Member

I'm surprised to see an explicit <MSBuild task call -- could you instead use OutputItemType to hook on the "normal" infrastructure? I guess that'd require TF negotiation if the service was multitargeted but that doesn't seem horrible . . .

@baronfel
Copy link
Member

Do you mean on my branch @rainersigwald? That could work if the other projects were built normally first, which currently is the case, but that's not strictly required just to generate the codegen'd structures.

@eerhardt
Copy link
Member

eerhardt commented Nov 28, 2023

A problem with blindly setting ReferenceOutputAssembly=false on all the AppHost's ProjectReferences is the case where a user has a set of AppHost extensions in a class library, and references that class library from the AppHost (i.e. the "normal" case when you want to use ProjectReference). This would break that scenario.

A thought I've been throwing around is to create a new type of Reference item, maybe AspireProjectReference, and then behind the scenes we could turn those into <ProjectReference ReferenceOutputAssembly=false SkipGetTargetFrameworkProperties=true /> items. (Note this is just a minor tweak on @danegsta's thoughts above on <IsAspireService>true</IsAspireService>.)

@DamianEdwards
Copy link
Member

I don't mind that suggestion @eerhardt but I think it's worth pointing it means we lose a built-in CLI story for adding references to the AppHost as one can't just use dotnet add reference anymore.

@danegsta
Copy link
Member

@eerhardt yeah, that would really just come down to whether we use <ProjectReference Include="..."> or <ProjectReference Update="..."> to generate/update the needed ProjectReference items. It's mostly down to the preferred UX for the references. @DamianEdwards if there's not a way to add custom metadata with dotnet add reference, the CLI story may be broken either way.

@rainersigwald
Copy link
Member

If that's a high priority the inverse of IsAspireService may be what you want -- IsAppHostExtension. Then dotnet add reference works for services and you're an edit away for extensions.

@DamianEdwards
Copy link
Member

Wasn't trying to convey a priority, just wanted to ensure it was entered into the conversation as we figure this out.

@danegsta
Copy link
Member

@rainersigwald only downside with the inverse property is that we could break existing projects (the Aspire tests, for example, would probably be broken without adding IsAppHostExtension to some project references). But it does say "Preview" on the box.

@DamianEdwards
Copy link
Member

The inverse property idea seems like a bad precedent to set, i.e. making regular looking ProjectReference items behave totally differently. If anything I think we'd want a new item type or metadata on an existing item type.

@eerhardt
Copy link
Member

I don't mind that suggestion @eerhardt but I think it's worth pointing it means we lose a built-in CLI story for adding references to the AppHost as one can't just use dotnet add reference anymore.

dotnet add aspire-reference 😄

@DamianEdwards
Copy link
Member

I don't mind that suggestion @eerhardt but I think it's worth pointing it means we lose a built-in CLI story for adding references to the AppHost as one can't just use dotnet add reference anymore.

dotnet add aspire-reference 😄

😬

@danegsta
Copy link
Member

Regardless of the approach, we should make sure to add a comment to the AppHost project template(s) to explain the behavior for anyone looking to manually add a service project reference.

@DamianEdwards DamianEdwards added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Dec 11, 2023
@mitchdenny
Copy link
Member

Extending dotnet add [something] is probably something that would need to wait until .NET 9.0? We probably need an interim solution.

eerhardt added a commit to eerhardt/aspire that referenced this issue Jan 19, 2024
At a high-level there are 2 problems with how we are using ProjectReference to reference the individual "service application" projects in the AppHost.

1. The AppHost may not have a compatible TFM with the referenced application project. For example if the referenced application project was targeting `net8-windows`.
2. When we are generating IServiceMetadata code, we are using ReferencePathWithRefAssemblies items, which means if the ProjectReference has ReferenceOutputAssembly="false" on it, no IServiceMetadata code will be generated.

To resolve these issues, we are taking the following approach:

- By default ProjectReferences in the AppHost will mean a reference to a "service application", and not a regular project reference. If an AppHost project truly wants a ProjectReference to another project, it can be marked as `<ProjectReference Include="..." AppHostLibrary="true" />`.
- ProjectReferences to service applications will be marked `ReferenceOutputAssembly=false`, `SkipGetTargetFrameworkProperties=true`, `ExcludeAssets=all`. This will cause the AppHost to not reference the service app projects.
- Refactor the IServiceMetadata code gen to act directly on ProjectReference items.

Fix dotnet#1074
Fix dotnet#1563
eerhardt added a commit to eerhardt/aspire that referenced this issue Jan 19, 2024
At a high-level there are 2 problems with how we are using ProjectReference to reference the individual "service application" projects in the AppHost.

1. The AppHost may not have a compatible TFM with the referenced application project. For example if the referenced application project was targeting `net8-windows`.
2. When we are generating IServiceMetadata code, we are using ReferencePathWithRefAssemblies items, which means if the ProjectReference has ReferenceOutputAssembly="false" on it, no IServiceMetadata code will be generated.

To resolve these issues, we are taking the following approach:

- By default ProjectReferences in the AppHost will mean a reference to a "service application", and not a regular project reference. If an AppHost project truly wants a ProjectReference to another project, it can be marked as `<ProjectReference Include="..." AppHostLibrary="true" />`.
- ProjectReferences to service applications will be marked `ReferenceOutputAssembly=false`, `SkipGetTargetFrameworkProperties=true`, `ExcludeAssets=all`. This will cause the AppHost to not reference the service app projects.
- Refactor the IServiceMetadata code gen to act directly on ProjectReference items.

Fix dotnet#1074
Fix dotnet#1563
eerhardt added a commit to eerhardt/aspire that referenced this issue Jan 22, 2024
At a high-level there are 2 problems with how we are using ProjectReference to reference the individual "service application" projects in the AppHost.

1. The AppHost may not have a compatible TFM with the referenced application project. For example if the referenced application project was targeting `net8-windows`.
2. When we are generating IServiceMetadata code, we are using ReferencePathWithRefAssemblies items, which means if the ProjectReference has ReferenceOutputAssembly="false" on it, no IServiceMetadata code will be generated.

To resolve these issues, we are taking the following approach:

- By default ProjectReferences in the AppHost will mean a reference to a "service application", and not a regular project reference. If an AppHost project truly wants a ProjectReference to another project, it can be marked as `<ProjectReference Include="..." AppHostLibrary="true" />`.
- ProjectReferences to service applications will be marked `ReferenceOutputAssembly=false`, `SkipGetTargetFrameworkProperties=true`, `ExcludeAssets=all`. This will cause the AppHost to not reference the service app projects.
- Refactor the IServiceMetadata code gen to act directly on ProjectReference items.

Fix dotnet#1074
Fix dotnet#1563
@eerhardt eerhardt self-assigned this Jan 22, 2024
eerhardt added a commit that referenced this issue Jan 24, 2024
* Refactor ProjectReferences in AppHost

At a high-level there are 2 problems with how we are using ProjectReference to reference the individual "service application" projects in the AppHost.

1. The AppHost may not have a compatible TFM with the referenced application project. For example if the referenced application project was targeting `net8-windows`.
2. When we are generating IServiceMetadata code, we are using ReferencePathWithRefAssemblies items, which means if the ProjectReference has ReferenceOutputAssembly="false" on it, no IServiceMetadata code will be generated.

To resolve these issues, we are taking the following approach:

- By default ProjectReferences in the AppHost will mean a reference to a "project resource", and not a regular project reference. If an AppHost project truly wants a ProjectReference to another project, it can be marked as `<ProjectReference Include="..." IsAspireProjectResource="false" />`.
- ProjectReferences to project resources will be marked `ReferenceOutputAssembly=false`, `SkipGetTargetFrameworkProperties=true`, `ExcludeAssets=all`. This will cause the AppHost to not reference the app projects.
- Refactor the IServiceMetadata code gen to act directly on ProjectReference items.

Fix #1074
Fix #1563
Fix #1278

* Move ProjectReference defaulting logic into the SDK targets so it is respected by NuGet Restore

* Add a build warning informing developers when they ProjectReference a library in the AppHost, but didn't set IsAspireProjectResource=false.

* Add test to ensure warning is emitted.

* Rename ProjectReference properties

- 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
- ServiceNameOverride => AspireProjectMetadataTypeName

This also allows us to rename IServiceMetadata to IProjectMetadata.
@github-actions github-actions bot locked and limited conversation to collaborators Apr 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication area-app-model-offsite area-tooling
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants