How do I inform dependent projects that an output must be copied local when they use ProjectReference? - visual-studio

I have two projects, let's call them Project A and Project B.
Project A has a CopyToOutputDirectory content item, as follows:
<ItemGroup>
<Content Include="MyExampleDependency.txt">
<Link>MyFunOutputLocation\MyExampleDependency.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
Project B references Project A:
<ItemGroup>
<ProjectReference Include="..\..\Shared\Project A.csproj">
<Project>{My GUID here</Project>
<Name>Project A</Name>
</ProjectReference>
</ItemGroup>
When I build Project B, it is smart enough to copy the .dll produced by Project A, as well as this text file, to the output. That is, I end up with something like this when I build Project B:
bin\Project A\Project A.dll
bin\Project A\MyFunOutputLocation\MyExampleDependency.txt
bin\Project B\Project A.dll
bin\Project B\Project B.exe
bin\Project B\MyFunOutputLocation\MyExampleDependency.txt
I have a new dependency that I want to start generating in Project A; for example from a T4 transform. This could be a file I generate with an <Exec task, or a file generated by some custom target, or similar. For example:
<ItemGroup>
<Content Include="Foo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<OutputFilePath>$(OutDir)</OutputFilePath>
<LastGenOutput>Foo.xml</LastGenOutput>
</Content>
</ItemGroup>
Now when I build I end up with something like this:
bin\Project A\Project A.dll
bin\Project A\Foo.xml <-- Not copied to Project B!
bin\Project A\MyFunOutputLocation\MyExampleDependency.txt
bin\Project B\Project A.dll
bin\Project B\Project B.exe
bin\Project B\MyFunOutputLocation\MyExampleDependency.txt
Note that the generated file which is required for Project A.dll to work is not being copied alongside that dll when it gets copied into Project B. How do I inform Project B that it needs to copy this file from Project A (without editing Project B's .csproj file)?

If it's just a design-time Foo.tt output then it's as simple as expanding it in Solution Explorer and marking Foo.txt with appropriate Copy To Output Directory, just remember to mark it again if you ever change the extension as that'll delete and read it into the itemgroup losing the CopyToOutputDirectory metadata.
If it's an outside task, then you need to hook into a delayed event (so it doesn't pollute explorer on load or even error on clean) in Project A and add it to Content preferably with a wildcard or exists condition. Keep in mind that event you choose needs to run after your custom task but before _CopyFilesMarkedCopyLocal, e.g. if you create a file in PreBuildEvent of Project A and hook into BeforeBuild it won't work on clean build on Project B, you'd have to rebuild it again as BeforeBuild runs first and won't find anything at point of evaluation.
Project A.csproj
<Target Name="BeforeBuild">
<ItemGroup>
<Content Include="$(OutputPath)\Bar.txt" Condition="Exists('$(OutputPath)\Bar.txt')">
<Link>Bar.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Target>
Edit:
Try the following.
In Project A define a Foo target for delayed/ondemand inclusion of the T4 output.
<Target Name="Foo">
<ItemGroup>
<Content Include="Foo.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Target>
You said you're transforming on build, so somewhere in the project you have the TextTemplating import that defines Transform target and its dependencies.
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v12.0\TextTemplating\Microsoft.TextTemplating.targets" />
Hook into one of them, i.e. AfterTransform, after the .targets import.
<PropertyGroup>
<AfterTransform>$(AfterTransform);Foo</AfterTransform>
</PropertyGroup>
That'll cover 90% of cases, the 9% left is building Project B directly in Visual Studio individually of Project A. The Rebuild would work out right thanks to AfterTransform but we need to hook into the Import since as you saw BeforeBuild is not executed as there are no changes and Visual Studio caches everything. We do this with InitialTargets on Project A.
<Project InitialTargets="Foo"
The final 1% is when you delete the .xml manually and build again as oppose to rebuild, but it's identical behaviour to deleting the .pdb, cached VS build will copy it again only if the .dll itself is missing or different.

Related

build project without runtimes folder but included nuget packages in nopcommerce 4.40(.net 5 and VS 2019)

I'm developing plugin for nopcommerce,
I'm using VS 2019 and nopcommerce 4.40.4(.net 5)
I should use a nuget package in my plugin,
If I set CopyLocalLockFileAssemblies to true, when I build my project, it created runtimes folder, which is about 65 MB,
If I set CopyLocalLockFileAssemblies to false, it does not create runtimes folder, but, the dll of nuget package which I should use, not included in the build folder,
would you please help me about this?
Note: set copy local to no, make no difference when I change for Nop.Services which I use in the project
this is my csproj and my the package is > SmsIrRestful.NetCore :
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<OutputPath>..\..\Presentation\Nop.Web\Plugins\AttributeStockSMS</OutputPath>
<OutDir>$(OutputPath)</OutDir>
<!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project.
You need to set this parameter to true if your plugin has a nuget package
to ensure that the dlls copied from the NuGet cache to the output of your project-->
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<ClearPluginAssemblies Include="$(MSBuildProjectDirectory)\..\..\Build\ClearPluginAssemblies.proj" />
</ItemGroup>
<ItemGroup>
<None Remove="plugin.json" />
</ItemGroup>
<ItemGroup>
<Content Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SmsIrRestful.NetCore" Version="1.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\Nop.Services\Nop.Services.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<Target Name="NopTarget" AfterTargets="Build">
<!-- Delete unnecessary libraries from plugins path -->
<MSBuild Projects="#(ClearPluginAssemblies)" Properties="PluginPath=$(MSBuildProjectDirectory)\$(OutDir)" Targets="NopClear" />
</Target>
</Project>
Instead of using NuGet reference, include the dll file of that NuGet package. For example, You are going to use TaxJar library then follow these steps.
Added NuGet reference.
Right click on NuGet package and go to properties.
Copy path value from property values as below.
Go to that path in file explorer.
Find dll file(s) from there, copy-paste into your plugin folder and add refence.
Repeat same procedures for dependent packages also (if any).
Mark as Copy Local to Yes from properties.

Teamcity .Net project, conditional project reference when building in Visual Studio

I have build chains in TeamCity, where the dependent artifact is copied to /bin directory of the main project. The project file references the artifact. That all works.
What I want is to allow a project file include, instead of the binary reference, when building/debugging from Visual Studio. I have tried some approaches, such as using conditionals in the project file, but is there a nice clean way to approach this?
May be there is the part of solution.
May way of using several referencing types of projs.
<ItemGroup Condition=" '$(ReferencedDACPAC)' == '' ">
<ProjectReference Include="..\OmniUS\OmniUS.sqlproj">
<Name>OmniUS</Name>
<Project>{26075a62-f6b0-40c3-baa2-b9a9829da3c4}</Project>
<Private>False</Private>
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
</ProjectReference>
<ProjectReference Include="..\OmniUS_Finance_Jural\OmniUS_Finance_Jural.sqlproj">
<Name>OmniUS_Finance_Jural</Name>
<Project>{c8b0aee7-c2a4-4370-8451-13b455bb5363}</Project>
<Private>False</Private>
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
</ProjectReference>
</ItemGroup>
<ItemGroup Condition=" '$(ReferencedDACPAC)' == 'true' ">
<ArtifactReference Include="..\DacPacs\OmniUS.sqlproj.dacpac">
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
</ArtifactReference>
<ArtifactReference Include="..\DacPacs\OmniUS_Finance_Jural.sqlproj.dacpac">
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
</ArtifactReference>
</ItemGroup>
When I build in TeamCity, I send ReferencedDACPAC as the "System" variable in the build, and thus refer to "ArtifactReference". When i build in VisualStudio, there is no var and the referencing occurs as "ProjectReference".

MSBuild missing output files in AfterBuild when solution is cleaned

I'm sure there is something small that I'm missing. Here's the problem:
I have a solution that has multiple projects which after each build will be zipped. Here is an example of the zip creation in one project (they are pretty much identical in others):
<ItemGroup>
<CopySourceFiles Include="$(OutDir)\**\*.*" Exclude="$(OutDir)\**\*.pdb;$(OutDir)\*.mdf;$(OutDir)\*.ldf;$(OutDir)\*.vshost.*" />
</ItemGroup>
...
<Target Name="AfterBuild" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<MakeDir Directories="$(OutDir)\..\zip_working" />
<!-- first copy the source files specified in the CorySourceFiles ItemGroup above. -->
<Copy SourceFiles="#(CopySourceFiles)" DestinationFiles="#(CopySourceFiles->'$(OutDir)\..\zip_working\%(RecursiveDir)%(Filename)%(Extension)')" />
<!-- Perform the zip by calling the UsingTask. Make sure the DestinationFiles above and the SourceDirectory below are pointing to the same place -->
<Zip SourceDirectory="$(OutDir)\..\zip_working" OutputFilename="$(OutDir)\..\zip\$(ProjectName).zip" />
<!-- Clean up. -->
<RemoveDir Directories="$(OutDir)\..\zip_working" />
</Target>
There is a final project which has links to the zipped files that it combines into a package. All appears normal, but apparently only when the bin and zip_working folders already exist. I.e. if I clean the solution, delete the bin folders and then rebuild, the final zip that is created in the "zip" folder for each project is empty...
And then the zip files have content only after I build again.
So I'm guessing that during the build process, the AfterBuild target is running before the build output files exist. Does that sound right? I trigger the builds purely from within Visual Studio.
Regardless, how can I ensure that I can run a task on build output files only after they've been created?
Applies to Visual Studio 2013 Update 5 / MSBuild 12.0
If you delete everything in OutDir and then build the project, a top-level (as in, not inside a target) ItemGroup is evaluated before the build even starts. Some info can be found here for example. In other words, before a build and with an empty OutDir $(OutDir)\**\*.* evaluates to nothing and your CopySourceFiles item is empty.
The solution is simply to move the ItemGroup inside of the AfterBuild target. It will then be evaluated after the build and hence gets a proper view on the current files in outDir.

How to add a command line code generator to Visual Studio?

I'm working on a project that uses code generation to generate C# classes using a command line tool from a text-based description. We are going to start using these descriptions for javascript too.
Currently these classes are generated and then checked in, however, I would like to be able to make the code generate automatically so that any changes are propagated to both builds.
The step that is run manually is:
servicegen.exe -i:MyService.txt -o:MyService.cs
When I build I want MSBuild/VS to first generate the CS file then compile it. It is possible to do this using, by modifying the csproj, perhaps using a MSBuild Task with Exec, DependentUpon & AutoGen?
Normally I would recommend a pre-build command be placed in a pre-build event, but since your command line tool will be creating C# classes needed for compiling, this should be done in the BeforeBuild target in the .csproj file. The reason for this is because MSBuild looks for the files it needs to compile between the time BeforeBuild is called and the time when PreBuildEvent is called in the overall process (you can see this flow in the Microsoft.Common.targets file used by MSBuild).
Call the Exec task from within the BeforeBuild target to generate the files:
<Target Name="BeforeBuild">
<Exec Command="servicegen.exe -i:MyService.txt -o:MyService.cs" />
</Target>
See the Exec task MSDN documentation for more details about specifying different options for the Exec task.
Antlr has an example of a process that can be used to add generated code to a project. This has the advantage of showing the files that are generated nested under the source file, although it is more complex to add.
You need add an item group with the file to be generated from, for example:
<ItemGroup>
<ServiceDescription Include="MyService.txt"/>
</ItemGroup>
Then add the cs file to be generated to the ItemGroup containing the rest of the source code.
<ItemGroup>
...
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
...etc..
<Compile Include="MyService.txt.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>MyService.txt</DependentUpon> <!--note: this should be the file name of the source file, not the path-->
</Compile>
</ItemGroup>
And then finally add the build target to execute the code generation (using % to execute the command for each item in the ItemGroup). This could be put into a separate file, so that it can be included from many projects.
<Target Name="GenerateService">
<Exec Command="servicegen.exe -i:%(ServiceDescription.Identity) -o:%(ServiceDescription.Identity).cs" />
</Target>
<PropertyGroup>
<BuildDependsOn>GenerateService;$(BuildDependsOn)</BuildDependsOn>
</PropertyGroup>

Copy built assemblies (including PDB, .config and XML comment files) to folder post build

Is there a generic way I can get a post-build event to copy the built assembly, and any .config and any .xml comments files to a folder (usually solution relative) without having to write a post-build event on each project in a solution?
The goal is to have a folder that contains the last successful build of an entire solution.
It would be nice to use the same build solution over multiple solutions too, possibly enabling/ disabling certain projects (so don't copy unit tests etc).
Thanks,
Kieron
You can set common OutputPath to build all projects in Sln in one temp dir and copy required files to the latest build folder. In copy action you can set a filter to copy all dlls without "test" in its name.
msbuild.exe 1.sln /p:Configuration=Release;Platform=AnyCPU;OutputPath=..\latest-temp
There exists more complicated and more flexible solution. You can setup a hook for build process using CustomAfterMicrosoftCommonTargets. See this post for example.
Sample targets file can be like that:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
PublishToLatest
</BuildDependsOn>
</PropertyGroup>
<Target Name="PreparePublishingToLatest">
<PropertyGroup>
<TargetAssembly>$(TargetPath)</TargetAssembly>
<TargetAssemblyPdb>$(TargetDir)$(TargetName).pdb</TargetAssemblyPdb>
<TargetAssemblyXml>$(TargetDir)$(TargetName).xml</TargetAssemblyXml>
<TargetAssemblyConfig>$(TargetDir)$(TargetName).config</TargetAssemblyConfig>
<TargetAssemblyManifest>$(TargetDir)$(TargetName).manifest</TargetAssemblyManifest>
<IsTestAssembly>$(TargetName.ToUpper().Contains("TEST"))</IsTestAssembly>
</PropertyGroup>
<ItemGroup>
<PublishToLatestFiles Include="$(TargetAssembly)" Condition="Exists('$(TargetAssembly)')" />
<PublishToLatestFiles Include="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" />
<PublishToLatestFiles Include="$(TargetAssemblyXml)" Condition="Exists('$(TargetAssemblyXml)')" />
<PublishToLatestFiles Include="$(TargetAssemblyConfig)" Condition="Exists('$(TargetAssemblyConfig)')" />
<PublishToLatestFiles Include="$(TargetAssemblyManifest)" Condition="Exists('$(TargetAssemblyManifest)')" />
</ItemGroup>
</Target>
<Target Name="PublishToLatest"
Condition="Exists('$(LatestDir)') AND '$(IsTestAssembly)' == 'False' AND '#(PublishToLatestFiles)' != ''"
DependsOnTargets="PreparePublishingToLatest">
<Copy SourceFiles="#(PublishToLatestFiles)" DestinationFolder="$(LatestDir)" SkipUnchangedFiles="true" />
</Target>
</Project>
In that targets file you can specify any actions you want.
You can place it here "C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets" or here "C:\Program Files\MSBuild\4.0\Microsoft.Common.targets\ImportAfter\PublishToLatest.targets".
And third variant is to add to every project you want to publish import of custom targets. See How to: Use the Same Target in Multiple Project Files

Resources