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

On a ProjectReference, setting ReferenceOutputAssembly=false and Private=true does not copy the reference to the output without referencing it #1916

Open
agocke opened this issue Mar 27, 2017 · 8 comments
Labels

Comments

@agocke
Copy link
Member

agocke commented Mar 27, 2017

As far as I can see, the problem is that if ReferenceOutputAssembly is set to false, then the project will not be added to _ResolvedProjectReferencePaths, which is what gets passed to RAR, so RAR never even has the reference to consider.

I would expect ReferenceOutputAssembly to prevent the compiler from seeing the reference, but not prevent copying as a dependency (say, for reflection purposes).

@chm-tm
Copy link

chm-tm commented Aug 11, 2017

Refer to this article from Kirill Osenkov for a work around.

@chm-tm
Copy link

chm-tm commented Nov 6, 2017

On a related note, in Visual Studio, you'll need to set the DisableFastUpToDateCheck property to true in order to maintain correct incremental builds. I tried to comment about this on @KirillOsenkov's blog, but waiting forever for moderation. For this reason and because the whole ReferenceOutputAssembly stuff seems to be undocumented for now, I've put this information on SO.

@KirillOsenkov
Copy link
Member

Oops, I went ahead and approved your comments. Turns out I had 41 comments waiting for moderation :( Software didn't let me know or I turned all notifications off... sorry about that.

@cmclernon-fvs
Copy link

Found this and it's exactly my issue. The workaround is not a solution as it doesn't copy the referenced projects dependencies. I'm using VS2019 (Version 16.4.2) and am using another workaround but it bloats the output folder so it's not ideal.

So as I see it, we need the ability for Project A to reference Project B (where project B has nuget dependencies) in order to get it included in Project A's build output folder (including nuget dependencies) but for Project B to not be available to the compiler so Project A can't new up objects incorrectly. If implemented correctly, Project A's output folder should be exactly the same as a build with Project B referenced with ReferenceOutputAssembly=true.

Perhaps there's a way to accomplish this that I've been unable to find?

@jods4
Copy link

jods4 commented Mar 21, 2023

This issue is still open, so I assume it isn't fixed.
As it's pretty old, I wonder if people have found any new work-arounds, or if there are little-known MSBuild properties that can be used for the same effect, etc?

I'm trying to build a project where the core module loads all other modules by reflection. So basically I want them to be built and published (copied) together, but I don't want a real dependency.

@KirillOsenkov 's blog linked above is an interesting take on this problem, but it only copies the DLL (possibly PDB) from referenced project. If the referenced project itself has some unique dependencies, we need to copy them as well for the application to run properly --> that doesn't work with the technique described in the blog post.

As of 2023 has anything changed? What would be the best way to do this with .net 7?

@AR-May AR-May added the triaged label Feb 21, 2024
@parched
Copy link

parched commented Mar 31, 2024

I'm doing it like this. I'm not sure if there's a better way though.
B.csproj

...
 <PropertyGroup>
    <EnableDynamicLoading>true</EnableDynamicLoading>
  </PropertyGroup>
...

A.csproj

  <ItemGroup>
    <ProjectReference Include="..\B\B.csproj">
      <!-- This project reference is to ensure B is built as dependency so we can copy it to a sub directory -->
      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
    </ProjectReference>
  </ItemGroup>

  <Target Name="CopyB" AfterTargets="AfterResolveReferences">
    <ItemGroup Condition="$(TargetFramework.StartsWith('net7.0'))">
      <BFiles Include="..\B\bin\$(Configuration)\net7.0\**\*" />
      <Content Include="%(BFiles.Identity)">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        <Link>B\%(BFiles.RecursiveDir)%(BFiles.FileName)%(BFiles.Extension)</Link>
      </Content>
    </ItemGroup>
  </Target>

@jods4
Copy link

jods4 commented Apr 2, 2024

@parched Here's what I ended up doing, it's a little simpler and works with many dependencies:

<!-- 
  Start by adding "weak" references as normal ones.
  Unfortunate side-effect is that in IDE code from those can be referenced, 
  but then full build would fail because of next step below.
  
  You can add individual ProjectReference as usual, or even grab many projects with a glob as shown here.
  
  The key is to tag "weak" projects that must be compiled and deployed together, 
  but not be a direct reference, with a custom property "IsWeakReference".
-->
<ProjectReference Include="../Modules/**/*.csproj">
  <IsWeakReference>true</IsWeakReference>
</ProjectReference>

<!--
  The timing of this target is key to make this solution work. Goal is:
  1. Automatically compile the "weak" references with main project, **without introducing a real reference.**
     This preserves architecture layering and also avoids interference with things like Microsoft.NET.Sdk.Web SDK
     that has a target that automatically adds ApplicationPartAttribute for each reference that itself references MVC.
  2. Have the weak referenced compilation output copied in main project output (during build or publish).
     A challenge here is to also include transitive references to nuget packages.

  The solution has 3 parts:
  1. Use MSBuild flag ReferenceOutputAssembly=false. 
     With this flag, MSBuild builds the weak references first, 
     but doesn't add them as actual references to the main project.
  2. Use combination OutputItemType=Content and CopyToOutputDirectory to copy built projects output.
     Unfortunately this does NOT copy transitive dependencies :(
  3. To work-around that limitation, we first include the references as regular ones, 
     *without* ReferenceOutputAssembly=false.
     During target AssignProjectConfiguration, .net SDK flattens transitive references,
     so $(ProjectReferenceWithConfiguration) now contains both direct project references 
     and their transitive dependencies. This is key to getting all dlls copied in output.
     At this step, before C# compilation is invoked, the configuration of modules references is modified
     so that they're not referenced by compilation anymore (set ReferenceOutputAssembly=false)
     and still copied to output (OutputItemType + CopyToOutputDirectory).
-->
<Target Name="SetModuleReferencesAsPrivate" 
    AfterTargets="AssignProjectConfiguration"
    BeforeTargets="ResolveProjectReferences">
  <ItemGroup>
    <ProjectReferenceWithConfiguration 
        Condition="%(ProjectReferenceWithConfiguration.IsWeakReference) == 'true'">
      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
      <OutputItemType>Content</OutputItemType>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </ProjectReferenceWithConfiguration>
  </ItemGroup>
</Target>

That's some deep MS Build / .NET SDK trickery, but it has been working well for us.

@jeremy-visionaid
Copy link

jeremy-visionaid commented Jul 18, 2024

Might also be worth mentioning here that some extra tinkering is required for C/C++ dependencies:
#2823

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants