Pass parameter/property from publish profile to csproj file - asp.net-core

I exclude some folders while building for development in .csproj file.
<ItemGroup>
<Content Remove="Production\**" />
<Compile Remove="Production\**" />
</ItemGroup>
I want to include those folders back in a "publish profile" but it does not work.
<ItemGroup>
<Content Include="Production\**" />
<Compile Include="Production\**" />
</ItemGroup>
So, how could i pass parameters from "publish profile" to build(.csproj) and prevent exclusion of those folders or include them back.
<ItemGroup Condition="'$(SOMEPARAM)'!='Production'">
<Content Remove="Production\**" />
<Compile Remove="Production\**" />
</ItemGroup>
So, i need to determine when build runs with "publish profile" in .csproj file and take according action.
I know it is possible with command line parameters but i want to use Visual Studio, not command line.
dotnet build /p:DeployOnBuild=true /p:PublishProfile=FolderProfile;SOMEPARAM=Production
Update:(Solution)
Check my answer below, it works fine when CopyToPublishDirectory used.
Update:(Another Solution)
Alternatively, when targets specified with same names, a target from publish file will override the target from project file so we could define what to include/exclude separately in project and publish files.

You can define any property inside a <PropertyGroup> in the publish profile and use it in a condition in the csproj's <ItemGroup>s.
This works beause the publish profile is imported into the project and msbuild evaluates all static property groups before all item groups, which means that even a file that is imported at the end of it can affect item groups logically above it.

Sometimes my projects have "similar" code but hard-coded limitations like features supported by the operating system or license features.
I have solved it using 2 "configurations", let me walk you through it
Step 1. define your compiler directive in a central location where you need it in your code like so:
Console.WriteLine("Hello, World!");
#if IDPSH1
Console.WriteLine("IDPS H 1");
#elif IDPSH3
Console.WriteLine("IDPS H 2");
#elif IDPSH5
Console.WriteLine("IDPS H 3");
#endif
Console.ReadKey();
Step 2, in your build process, generate a build that would tell the compiler to generate what you need where you need it
dotnet publish ConsoleDefineConstants.csproj --output bin\release\IDPS-H-1 --configuration Release /p:DefineConstants=IDPSH1
dotnet publish ConsoleDefineConstants.csproj --output bin\release\IDPS-H-2 --configuration Release /p:DefineConstants=IDPSH2
dotnet publish ConsoleDefineConstants.csproj --output bin\release\IDPS-H-3 --configuration Release /p:DefineConstants=IDPSH3
In the sample, I instruct dotnet to build and publish
the relative project ConsoleDefineConstants.csproj
in release mode
using a compiler constant using the /p:DefineConstants=
in a location where my deployment packaging expects it
if I start the sample, I get
I added all the code you need in the sample, let me know if you like me to clarify something

So, i determined that those includes need CopyToPublishDirectory. So following works fine too.
In .csproj file;
<ItemGroup>
<Content Remove="Production\**" />
<Compile Remove="Production\**" />
</ItemGroup>
In publish profile;
<ItemGroup>
<Content Include="Production\**" CopyToPublishDirectory="PreserveNewest" />
<Compile Include="Production\**" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>

Related

.NET Core 2.1 : How to trigger Copy task defined in the project file during debugging in the Visual Studio 2017?

There are some files residing in other directories that, I would like to copy to project folder automatically before build and publishing.
After some research and experimentation, I have come up with the following .csproj file.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.4</RuntimeFrameworkVersion>
<TieredCompilation>true</TieredCompilation>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>
<ItemGroup>
<APIDefinition Include="D:\SomePlace\*.API.*.yaml" />
</ItemGroup>
<Target Name="CopyFiles" BeforeTargets="Compile;Build;Publish">
<Copy SourceFiles="#(APIDefinition)" DestinationFolder="wwwroot" />
<Copy SourceFiles="D:\SomePlaceElse\BaseAPISettings.json" DestinationFolder="$(MSBuildProjectDirectory)" />
</Target>
<ItemGroup>
<Compile Remove="wwwroot\**\*;node_modules;bower_components" />
<None Update="**.user;**.vspscc">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
</ItemGroup>
</Project>
Here I have defined CopyFiles target, which should be run before the targets I've placed there. This target uses Copy task to copy YAML format API definition files and base API settings to the project directory.
This works well during build, publish etc. Also, if I delete the local file in the IDE, it instantly recopies it from the source.
Sometimes I make changes to these files between debugging sessions. Then, when I start debugging from Visual Studio, since the project files aren't changed, obviously the already built project is run.
Since the project is not built, my copy tasks are not triggered, and I end up with stale files during debuging.
Is there anything I can do to have my Copy tasks triggered, when I do "Start Debugging F5" in the IDE, regardless of the project build state ?
P.S. : I'm using Visual Studio 2017 15.8.5 and targeting .NET Core 2.1.4 runtime, if it makes any difference.
To integrate fully into the up-to-date check of the project system inside Visual Studio, I susggest the following changes:
Make the items' source and target paths known before
Register them to the up-to-date check system. (Also needs a hack to make sure the project source code is recompiled so that the output will have a newer time stamp)
Make the MSBuild target itself incremental. This also helps for command-line builds when the files don't have to be copied.
The complete changes look like this:
<ItemGroup>
<CustomCopyFile Include="..\TestFiles\*.API.*.yaml"
TargetPath="wwwroot\%(Filename)%(Extension)" />
<CustomCopyFile Include="..\TestFiles\BaseAPISettings.json"
TargetPath="%(Filename)%(Extension)" />
<UpToDateCheckInput Include="#(CustomCopyFile)" />
<UpToDateCheckBuild Include="#(CustomCopyFile->'%(TargetPath)')"
Original="#(CustomCopyFile)" />
<CustomAdditionalCompileInputs Include="#(CustomCopyFile->'%(TargetPath)')" />
</ItemGroup>
<Target Name="CopyFiles"
BeforeTargets="BeforeBuild;BeforePublish"
Inputs="#(CustomCopyFile)"
Outputs="#(CustomCopyFile->'%(TargetPath)')">
<Copy SourceFiles="#(CustomCopyFile)"
DestinationFiles="#(CustomCopyFile->'%(TargetPath)')" />
</Target>
CustomCopyFile now collects all the source files and we put the expected destination file name into the TargetPath metadata.
UpToDateCheckInput items tell Visual Studio to rebuild the project if one of these items change.
UpToDateCheckBuild items instruct Visual Studio to only check these items against special source items. This is redundant for this example project but may be helpful if the target path wasn't inside the project directory but some intermediate output (obj..) folder and no re-evaluation would see these new files. It would also be helpful if the files were also modified as part of processing (e.g. replacing variables inside the files).
CustomAdditionalCompileInputs is a hack here since the items are copied to the project folder and are considered to be "inputs to the output" automatically.. So we force the project to recompile if our source files change. If we don't do so, it would never consider the project up-to-date after a change to the source yaml files since they would be newer than the compiled app.dll file.

MSbuild run specific targets for specific projects

I am using following code in 2 our of 3 projects from the solution default target is set to PublishMe
<Target Name="PublishMe" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
<Error Condition="'$(PublishDestination)'==''" Text="The PublishDestination property must be set to the intended publishing destination." />
<MakeDir Condition="!Exists($(PublishDestination))" Directories="$(PublishDestination)" />
<ItemGroup>
<PublishFiles Include="$(_PackageTempDir)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(PublishFiles)" DestinationFiles="#(PublishFiles->'$(PublishDestination)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="True" />
</Target>
If I run msbuild it builds and publishes just as expected, the problem is that I can't build it from VS anymore, I am having error of missing projectname.dll
Is there way I can change default build target if it is msbuild doing build?
I tried to /t:ProjectName:target;ProjectName2:target, but it isn't working with custom targets for some reason.
Is there way I can specify param from msbuild console to run PublishMe target and if not present run simple build?
You can usefollowing command
msbuild projectname /t:targetname.
Also please check the below link for more info
Building a solution file using msbuild

How to generate files during build using msbuild

Does anyone know how to modify a csproj file in a way to generate code files during build without actually referencing the files?
A process like :
create file,
dynamically reference temporary file during build
compiled assembly has additional members, depending on the files created during build
The purpose of this is to create a way of generating code files using roslyn instead of using t4 templates, which are very awkward to use once you're trying to do something depending on attributes.
Hence i am planning on providing a way to use a special csharp file (for full syntax support) to generate files programatically based on the contents of that special file.
I've spent a couple of weeks looking into resources on the internet (with the topic msbuild), but until now it seems i didn't use the right keywords.
This one has been the most insightful one to me yet:
https://www.simple-talk.com/dotnet/.net-tools/extending-msbuild/
My guess is, that the correct build target for my purpose should be "BeforeCompile" in order to somehow populate the build process with custom code files.
Does anyone have experience with my issue, or is aware of any particular resources which deal with the task?
Solution i got it working with:
<UsingTask TaskName="DynamicCodeGenerator.DynamicFileGeneratorTask" AssemblyFile="..\DynamicCodeGenerator\bin\Debug\DynamicCodeGenerator.dll" />
<Target Name="DynamicCodeGeneratorTarget" BeforeTargets="BeforeBuild;BeforeRebuild">
<DynamicFileGeneratorTask>
<Output ItemName="Generated" TaskParameter="GeneratedFilePaths" />
</DynamicFileGeneratorTask>
<ItemGroup>
<Compile Include="#(Generated)" />
<FileWrites Include="#(Generated)" />
<!-- For clean to work properly -->
</ItemGroup>
</Target>
Unfortunately i did not get it to work with a propertygroup override as suggested
Update: This link is interesting too: https://github.com/firstfloorsoftware/xcc/blob/master/FirstFloor.Xcc/Targets/Xcc.targets
Generating the code file can be achieved by msbuild task or msbuild inline task. It is up to you to generate the proper code. One thing that you must care of is creating output item parameter in order to append it to the #(Compile) item. You can use $(IntDir) location to locate your newly generated file, and add them to the #(FileWrites) item group in order for Clean target work properly.
When you finish writing your task, you must use it in your project like this:
<UsingTask TaskName="TaskTypeFullName" AssemblyFile="YourAssembly.dll"/>
<PropertyGroup>
<!-- Here you need to experiment with [Build/Compile/SomeOther]DependsOn property -->
<BuildDependsOn>
MyCodeGenerator;
$(BuildDependsOn)
</BuildDependsOn>
</PropertyGroup>
<Target Name="MyCodeGenerator">
<YourTaskName>
<Output ItemName="Generated" TaskParameter="GeneratedFiles" />
</YourTaskName>
<ItemGroup>
<Compile Include="#(Generated)" />
<FileWrites Include="#(Generated)" /> <!-- For clean to work properly -->
</ItemGroup>
</Target>
I wanted to use a bash script to generate code for a project using dotnet core on Linux. Here is what worked for me. And thanks #stukselbax, I built this off of your answer.
<Target Name="GenerateProtocolBuffers" BeforeTargets="BeforeBuild;BeforeRebuild">
<Exec Command="./generatecode.sh" Outputs="proto/*.cs">
<Output ItemName="Generated" TaskParameter="Outputs" />
</Exec>
<ItemGroup>
<Compile Include="#(Generated)" />
<FileWrites Include="#(Generated)" />
</ItemGroup>
</Target>
Note that the script I'm using to generate the code is called generatecode.sh. Replace this with your own.

How do I implicitly use a Target to generate an Included file?

I'm a make(1) guy by nature. What I want to do is the equivalent of this make fragment:
FILES = generated.cs
app.exe : $(FILES)
csc -out:$# $(FILES)
generated.cs :
echo "// generated file" > $#
That is, $(FILES) contains a list of files, some of which may be generated by other targets within the Makefile. This Just Works.
I would like to do the ~same thing with MSBuild. Unfortunately, my attempt has failed:
<Target Name="BuildGenerated"
Outputs="Generated.cs"
>
<WriteLinesToFile
File="Generated.cs"
Lines="// generated file"
Overwrite="True"
/>
</Target>
<ItemGroup>
<Compile Include="Generated.cs" />
</ItemGroup>
That is, use <Compile/> to include a generated file and have MSBuild deduce that since Generated.cs doesn't exist, it should find some <Target/> which will generate that file and then execute it.
It looks like the only way to do something like this is to add pre-build steps, but that seems like a hack. Are pre-build steps the only way to do this? Or is there some way to make MSBuild act like it has a brain?
Update 1: For reference, this would be the pre-build incantation needed to make it work, and (again) this is something I'd rather avoid if possible:
<PropertyGroup>
<CompileDependsOn>
BuildGenerated;$(CompileDependsOn)
</CompileDependsOn>
</PropertyGroup>
This would need to occur after the <Import Project="..." /> element(s) defining <CompileDependsOn/>.
The problem is that your file is a generated one and MsBuild parses the item and property group only once (at the beginning of your build). Thus, when MsBuild tries to include the generated.cs file, it is not created yet and MsBuild does include nothing at all.
A correct way of doing it would be to do the include part inside the BuildGenerated target which would cause it to be dynamically evaluated.
<Target Name="BuildGenerated"
Outputs="Generated.cs"
>
<WriteLinesToFile
File="Generated.cs"
Lines="// generated file"
Overwrite="True"
/>
<ItemGroup>
<Compile Include="Generated.cs" />
</ItemGroup>
</Target>
More information in my answer to this question : Bin folder not being copied with MSBuild, Teamcity
EDIT
Sorry, I did not read correctly your question. in your makefile, in the app.exe target (the default one) you explicitely call your generated.cs target. You can do the same with MsBuild.
By default, MsBuild search for the Build target (the 'all' in makefile), if your main target is "app.exe" you have to call BuildGenerated target within with one of the following option :
<Target Name="app.exe" DependsOnTargets="BuildGenerated>
<!-- Stuff here -->
</Target>
or
<Target Name="app.exe">
<CallTarget Targets="BuildGenerated"/>
<!-- Stuff here -->
</Target>
If you don't set a default target, you can do it either via commandline with the /t parameter or in the project declaration :
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="app.exe">

Custom common target to build a solution

I created a custom common target "RealClean" which remove every files in the output and "intermediate output" directory. I put it in the Microsoft.Common.targets file.
When I run MsBuild on my csproj everything is fine.
But when I run MsBuild on my sln (which just references a list of csproj) I have the following error
error MSB4057: The target "RealClean" does not exist in the project.
Here is the command line I enter to run MsBuild
C:\Windows\Microsoft .NET\Framework\v3.5\MsBuild.exe /p:Configuration="Release";OutputPath="..\..\MSBuild.Referentiel.net35";nowarn="1591,1573" /t:RealClean mySolution.sln
Any hint?
I had the same issue but didn't want to modify things outside of the source tree in order to get this to work. Adding files to C:\Program Files... means that you have to do this manually on every dev machine to get the same behavior.
I did three things:
1) Created a Custom targets file which I import into every C# and/or VB/F# project in my solution by adding the following to each proj file:
<!-- Rest of project file -->
<PropertyGroup Condition="'$(SolutionDir)' == '' or '$(SolutionDir)' == '*undefined*'">
<!-- Relative path to containing solution folder -->
<SolutionDir>..\</SolutionDir>
</PropertyGroup>
<Import Project="$(SolutionDir)CommonSettings.targets" />
2) Added a clean target which gets called after the real Clean (using the AfterTargets attribute from MSBuild 4.0):
<Target Name="CleanCs" AfterTargets="Clean">
<Message Text="Deep cleaning C# project..." />
<CreateItem Include="$(OutDir)**\*.*; $(ProjectDir)\obj\**\*.*; $(IntermediateOutputPath)**\*.*"
Exclude="**\bin\**\*.vshost.exe; $(IntermediateOutputPath)**\*.log">
<Output TaskParameter="Include" ItemName="AfterClean_FilesToDelete"/>
</CreateItem>
<Delete Files="#(AfterClean_FilesToDelete)" />
<CreateItem Include="$(ProjectDir)\obj\" >
<Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete" />
</CreateItem>
<CreateItem Include ="$(ProjectDir)\bin\" Condition="'$(TargetExt)' != '.exe'" >
<Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete"/>
</CreateItem>
<RemoveDir ContinueOnError="true" Directories="#(AfterClean_DirectoriesToDelete)" />
</Target>
3) In my continuous integration MSBuild project I check and make sure that all proj files have #1:
<ItemGroup>
<!-- Exclude viewer acceptance tests as they must compile as x86 -->
<CheckProjects_CsProjects Include="**\*.csproj" />
</ItemGroup>
<Target Name="CheckProjects">
<!--
Look for C# projects that don't import CommonSettingsCs.targets
-->
<XmlRead XPath="//n:Project[count(n:Import[#Project[contains(string(), 'CommonSettingsCs.targets')]]) = 0]/n:PropertyGroup/n:AssemblyName/text() "
XmlFileName="%(CheckProjects_CsProjects.Identity)"
Namespace="http://schemas.microsoft.com/developer/msbuild/2003"
Prefix="n" >
<Output TaskParameter="Value" ItemName="CheckProjects_CsMissingImports"/>
</XmlRead>
<Error Text="Project missing CommonSettingsCs.targets: %(CheckProjects_CsMissingImports.Identity)"
Condition="'%(CheckProjects_CsMissingImports.Identity)' != ''" />
</Target>
This prevents developers from forgetting to add #1. You could create your own project template to ensure that al new projects have this by default.
The advantage to this approach is setting up a new source tree enlistment doesn't involve anything more than getting the current source tree. The downside is that you have to edit the project files once when you create them.
To work on solution file, MSBuild creates a temporary MSBuild project file containing only some targets like Build and Clean. So you can't call your custom target on a solution file.
Madgnome is probably right. But I wanted to add that you should not be editing the Microsoft.common.targets files. If you do so you risk having a different build process on that machine versus what everybody else has. In your case you could have created a new MSBuild file with just the RealClean target and placed it at
C:\Program Files (x86)\MSBuild\v4.0\Custom.After.Microsoft.Common.targets
or for 32 bit
C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets
and essentially that would be the same as putting that file inside of Microsoft.Common.targets, except you don't have to modify that file.