Change assembly name for build settings in .csproj - c#

I have an application which I want to publish with ClickOnce via command line. I have a test and a live version. It should be allowed to have both installed at the same time, which means that I need to change the assemble name (and preferably also the product name) for one of the builds. I would like to do this in the build settings.
I have managed to make some build settings, which works fine, but I cannot figure out how to change the assembly and product name, for just one of them.
I have added the following code to my .csproj file, which I call with the command msbuild /target:Test or msbuild /target:Live. But where do I implement the assembly and product name change?
<PropertyGroup>
<ProjLocation>$(ProjectDir)</ProjLocation>
<ProjLocationReleaseDir>$(ProjLocation)\bin\Debug</ProjLocationReleaseDir>
<ProjPublishLocation>$(ProjLocationReleaseDir)\app.publish</ProjPublishLocation>
<DeploymentFolder>C:\MyProjects\Software\Publish\</DeploymentFolder>
</PropertyGroup>
<!-- Build settings for live version -->
<Target Name="Live" DependsOnTargets="Clean">
<MSBuild Projects="$(ProjLocation)\$(ProjectName).csproj"
Properties="$(DefaultBuildProperties)"
Targets="Publish"/>
<ItemGroup>
<SetupFiles Include="$(ProjPublishLocation)\*.*"/>
<UpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(SetupFiles)" DestinationFolder="$(DeploymentFolder)\Live\" />
<Copy SourceFiles="#(UpdateFiles)" DestinationFolder="$(DeploymentFolder)\Live\Application Files\%(RecursiveDir)"/>
</Target>
<!-- Build settings for test version -->
<Target Name="Test" DependsOnTargets="Clean">
<MSBuild Projects="$(ProjLocation)\$(ProjectName).csproj"
Properties="$(DefaultBuildProperties)"
Targets="Publish"/>
<ItemGroup>
<SetupFiles Include="$(ProjPublishLocation)\*.*"/>
<UpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="#(SetupFiles)" DestinationFolder="$(DeploymentFolder)\Public Test\" />
<Copy SourceFiles="#(UpdateFiles)" DestinationFolder="$(DeploymentFolder)\Public Test\Application Files\%(RecursiveDir)"/>
</Target>

You can add "AssemblyName" property to the PropertyGroup. like this:
<PropertyGroup>
<AssemblyName>YourAppName</AssemblyName>
</PropertyGroup>
or you can use the MSBuild command line switch. like this :
msbuild /property:AssemblyName=YourAppName

I had almost the same task (need to distinguish between Staging and Production) and I solved it with the following MSBuild-Target:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- call "nuget restore Deployment\packages.config" before executing this script -->
<Import Project="..\packages\MSBuildTasks.1.5.0.235\build\MSBuildTasks.targets" />
<PropertyGroup>
<Configuration>Release</Configuration>
<ClientProject>..\MyProject\MyProject.vbproj</ClientProject>
<ClientPublishDir Condition="$(Environment) == 'Staging'">\\deployment-staging\MyProject\</ClientPublishDir>
<ClientPublishDir Condition="$(Environment) == 'Production'">\\deployment\MyProject\</ClientPublishDir>
</PropertyGroup>
<ItemGroup>
<ClientModifiedAppConfig Include="$(ClientProject)\..\App.$(Environment).config" />
</ItemGroup>
<Target Name="DeployClient">
<Error Condition="$(Environment) == ''" Text="The Property 'Environment' has not been set." />
<Error Condition="$(Environment) != 'Staging' AND $(Environment) != 'Production'" Text="The Property 'Environment' has not been set properly. Valid values are 'Staging' and 'Production'." />
<!-- Sets different assembly names for INT and PRD applications. Due to this, both INT and PRD applications with the
same version number can be installed on the same system. -->
<XmlUpdate Condition="$(Environment) == 'Staging'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:AssemblyName" Value="MyProjectStaging" />
<XmlUpdate Condition="$(Environment) == 'Staging'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:ProductName" Value="MyProject Staging" />
<XmlUpdate Condition="$(Environment) == 'Production'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:AssemblyName" Value="MyProject" />
<XmlUpdate Condition="$(Environment) == 'Production'" Prefix="n" Namespace="http://schemas.microsoft.com/developer/msbuild/2003" XmlFileName="$(ClientProject)" Xpath="/n:Project/n:PropertyGroup/n:ProductName" Value="MyProject" />
<!-- Overwrites the original App.config with the environment-dependent App.config.
Reason: ClickOnce only uses App.config and does not apply transformations, as it is done in Web Projects. -->
<Copy
SourceFiles="#(ClientModifiedAppConfig)"
DestinationFiles="$(ClientProject)\..\App.config"
OverwriteReadOnlyFiles="true"
/>
<!-- Publish -->
<MSBuild
Projects="$(ClientProject)"
Targets="Publish"
Properties="
PublishDir=$(ClientPublishDir);
Configuration=$(Configuration);
Platform=x86" />
</Target>
</Project>
To make this work, you have to restore the NuGet package MSBuildTasks.
Finally, all I have to call is msbuild Deployment.targets /t:DeployClient /p:Environment=Staging
Hope that helps!

Related

MSBuild target before build

I have project A that need to insert a TextId.cs before build. And, there is project B. TextId.cs would generated after project B is compiled and executed.
Now I'd like to integrate the compile and execute in Directory.Build.targets in project A. It is not worked as I expect. TextId.cs will generate but the build would still failed as no TextId.cs if I set BeforeTargets="BeforeBuild" as below.
Anyone knows that which target is OK? or, any other solution?
<Project>
<ItemGroup>
<ProjectReferences Include="c:\code\textidfilegenerator\*.*proj" />
</ItemGroup>
<Target Name="BuildOtherProjects">
<Message Importance="High" Text="-----------------------" />
<MSBuild
Projects="#(ProjectReferences)"
Targets="Build">
</MSBuild>
</Target>
<Target Name="CopyText" DependsOnTargets="BuildOtherProjects" BeforeTargets="BeforeBuild">
<Message Importance="High" Text="**********************" />
<Exec Command="C:\Code\TextIdFileGenerator\bin\Debug\net6.0\TextIdFileGenerator.exe C:\Code\Sys1500TestDriver\TextProvider\TextIds.cs" IgnoreExitCode="true"/>
</Target>
</Project>
Before you will dive into the comments below, go through these docs:
MSBuild reserved and well-known properties
Common MSBuild project properties
<Project>
<ItemGroup>
<ProjectReferences Include="c:\code\textidfilegenerator\*.*proj" /> <!--you should use relative path like <ProjectReference Include="../**/*.csproj or absolute with MSBuild well-known properties" />-->
</ItemGroup>
<Target Name="BuildOtherProjects"> <!--you don't need this target if you have project reference-->
<Message Importance="High" Text="-----------------------" />
<MSBuild
Projects="#(ProjectReferences)"
Targets="Build">
</MSBuild>
</Target>
<Target Name="CopyText" DependsOnTargets="BuildOtherProjects" BeforeTargets="BeforeBuild"><!-- 'BeforeBuild' will not work because TextIdFileGenerator.exe needs to be created before you will do anything with it so you should use 'AfterTargets="Build"'-->
<Message Importance="High" Text="**********************" />
<Exec Command="C:\Code\TextIdFileGenerator\bin\Debug\net6.0\TextIdFileGenerator.exe C:\Code\Sys1500TestDriver\TextProvider\TextIds.cs" IgnoreExitCode="true"/><!-- again, you should use relative paths or absolute ones with combination of MSBuildProjectDirectory and OutDir -->
</Target>
</Project>

Python as a post-build step to C#

I'm looking into ways to execute python as part of a C# build.
Specifically, I want to create a Python package based on a C# project through python.net. My general idea was to build the C# project first. And then, as some sort of post-build step, invoke python to build a package based on the newly generated NET assemblies.
I can't presume python will installed on the build host, so ideally I want to include a "portable" - even more ideally, nuget-based - python distribution.
I have found a promising nuget package, but am not entirely sure of its usage. It incldues no C# code, but has all python binaries included, and has build props as copy/pasted below for reference.
Given on that package's props - can I somehow reference its binaries from my own project as a post-build step?
Say, for example, I want to add a post-build step to my own project, that simply just invokes "python.exe" after the build. How could I do that?
My own project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="python" Version="3.10.0-a6" />
</ItemGroup>
<Target Name="MyCustomStep" AfterTargets="Build">
<!-- .. now what? I can't seem to access. e.g. #(PythonHome) or $(PythonHome) from here --/>
<Target>
</Project>
Props of the python package from nuget:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(Platform) == 'X64'">
<PythonHome Condition="$(PythonHome) == ''">$([System.IO.Path]::GetFullPath("$(MSBuildThisFileDirectory)\..\..\tools"))</PythonHome>
<PythonInclude>$(PythonHome)\include</PythonInclude>
<PythonLibs>$(PythonHome)\libs</PythonLibs>
<PythonTag>3.10</PythonTag>
<PythonVersion>3.10.0-a6</PythonVersion>
<IncludePythonExe Condition="$(IncludePythonExe) == ''">true</IncludePythonExe>
<IncludeDistutils Condition="$(IncludeDistutils) == ''">false</IncludeDistutils>
<IncludeLib2To3 Condition="$(IncludeLib2To3) == ''">false</IncludeLib2To3>
<IncludeVEnv Condition="$(IncludeVEnv) == ''">false</IncludeVEnv>
<GetPythonRuntimeFilesDependsOn>_GetPythonRuntimeFilesDependsOn310_None;$(GetPythonRuntimeFilesDependsOn)</GetPythonRuntimeFilesDependsOn>
</PropertyGroup>
<ItemDefinitionGroup Condition="$(Platform) == 'X64'">
<ClCompile>
<AdditionalIncludeDirectories>$(PythonInclude);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(PythonLibs);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<Target Name="GetPythonRuntimeFiles" Returns="#(PythonRuntime)" DependsOnTargets="$(GetPythonRuntimeFilesDependsOn)" />
<Target Name="_GetPythonRuntimeFilesDependsOn310_None" Returns="#(PythonRuntime)">
<ItemGroup>
<_PythonRuntimeExe Include="$(PythonHome)\python*.dll" />
<_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" />
<_PythonRuntimeExe>
<Link>%(Filename)%(Extension)</Link>
</_PythonRuntimeExe>
<_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" />
<_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" />
<_PythonRuntimeDlls>
<Link>DLLs\%(Filename)%(Extension)</Link>
</_PythonRuntimeDlls>
<_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" />
<_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" />
<_PythonRuntimeLib>
<Link>Lib\%(RecursiveDir)%(Filename)%(Extension)</Link>
</_PythonRuntimeLib>
<PythonRuntime Include="#(_PythonRuntimeExe);#(_PythonRuntimeDlls);#(_PythonRuntimeLib)" />
</ItemGroup>
<Message Importance="low" Text="Collected Python runtime from $(PythonHome):%0D%0A#(PythonRuntime->' %(Link)','%0D%0A')" />
</Target>
</Project>
That is for the use of internal nuget rather than your main project. You cannot get that property under main project.
You have to use my function:
1) edit csproj file and set this for your PackageReference python
<GeneratePathProperty>true</GeneratePathProperty>
Like this:
<ItemGroup>
<PackageReference Include="python" Version="3.10.0-a6">
<GeneratePathProperty>true</GeneratePathProperty>
</PackageReference>
</ItemGroup>
2) Then, you can use $(Pkgpython) to get that path.
<Target Name="MyCustomStep" AfterTargets="Build">
<Exec Command="$(Pkgpython)\tools\python.exe" />
</Target>

MSBuild. Create EmbeddedResource before build

I want to embed local references in the assembly before compiling the main unit. But the written target does not work.
<Target Name="EmbedLocal" BeforeTargets="CoreCompile">
<Message Text="Run EmbedLocal for $(MSBuildProjectFullPath)..." Importance="high"/>
<ItemGroup>
<EmbeddedResource Include="#( ReferencePath->WithMetadataValue( 'CopyLocal', 'true' )->Metadata( 'FullPath' ) )"/>
</ItemGroup>
<Message Text="Embed local references complete for $(OutputPath)$(TargetFileName)." Importance="high" />
</Target>
#(EmbeddedResource) at this moment contains valid list of paths.
Update:
Now my import file contains:
<Project ToolsVersion="$(MSBuildToolsVersion)" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<EmbedLocalReferences Condition=" '$(EmbedLocalReferences)' == '' ">True</EmbedLocalReferences>
</PropertyGroup>
<Target Name="EmbedLocal" BeforeTargets="ResolveReferences" Condition=" '$(EmbedLocalReferences)' == 'True' ">
<Message Text="Run EmbedLocal for $(MSBuildProjectFullPath)..." Importance="high"/>
<ItemGroup>
<EmbeddedResource Include="#(ReferenceCopyLocalPaths->WithMetadataValue( 'Extension', '.dll' )->Metadata( 'FullPath' ))">
<LogicalName>%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Message Text="Embed local references complete for $(OutputPath)$(TargetFileName)." Importance="high" />
</Target>
</Project>
It works fine. Output assembly contains all .dll references as EmbeddedResource.
MSBuild. Create EmbeddedResource before build
You can try to use BeforeBuild action to the csproj file to include the embedded resources:
<Target Name="BeforeBuild">
...
<ItemGroup>
<EmbeddedResource Include="..."/>
</ItemGroup>
...
</Target>
Now MSBuild will add this file as embedded resource into your assembly.
Update:
Thanks #Martin Ullrich. He pointed out the correct direction, we could use <Target Name="EmbedLocal" BeforeTargets="PrepareForBuild"> in the Directory.Build.props to resolve this issue. You can check if it works for you.
<Target Name="EmbedLocal" BeforeTargets="PrepareForBuild">
...
<ItemGroup>
<EmbeddedResource Include="..."/>
</ItemGroup>
...
</Target>

Access PropertyGroup outside Target

Problem
I defined 4 custom Property at a top level PropertyGroup:
AndroidResourcesFolder
NugetFolder
BrandingFiles (uses NugetFolder)
AssemblyFile (uses BrandingFiles)
... and I want to access AndroidResourcesFolder from a Target.
When accesing AssemblyFile from a UsingTask, the Property is properly resolved, but when I access AndroidResourcesFolder from a Target, Visual Studio reports that the Property has no value:
error MSB4044: The "Mobile.Branding.Build.Task.CleanResources" task was not given a value for the required parameter "AndroidResourcesFolder".
Code
This is my .targets file:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AndroidResourcesFolder>$(MSBuildProjectDirectory)\Resources\</AndroidResourcesFolder>
<NugetFolder>$(MSBuildThisFileDirectory)..\..\</NugetFolder>
<BrandingFiles>$(NugetFolder)MonoAndroid81\</BrandingFiles>
<AssemblyFile>$(BrandingFiles)\Amaris.Mobile.Branding.Build.dll</AssemblyFile>
</PropertyGroup>
<UsingTask
TaskName="Mobile.Branding.Build.Task.CleanResources" AssemblyFile="$(AssemblyFile)" />
<UsingTask
TaskName="Mobile.Branding.Build.Task.ReadConfig" AssemblyFile="$(AssemblyFile)" />
<Target Name="ReadConfig" >
<PropertyGroup>
<ConfigFile>$(MSBuildProjectDirectory)\branding.xml</ConfigFile>
</PropertyGroup>
<Mobile.Branding.Build.Task.ReadConfig
ConfigFile="$(ConfigFile)" />
</Target>
<Target Name="BeforeClean" DependsOnTargets="ReadConfig" >
<Mobile.Branding.Build.Task.CleanResources
AndroidResourcesFolder="$(AndroidResourcesFolder)" />
</Target>
</Project>
Question
Why were only 3 Property resolved and the rest (AndroidResourcesFolder) left out ?
Thanks!

Why is MsBuild's ItemGroup->EndWith not working for '.exe'?

I'm trying to make an item group of dll's and exe's but filtering out any msi's and test.dll's.
The following snippet doesn't include the .exe in the UniqueAssemblies itemgroup. It does contain all the dll's and removes the msi as expected though. UniqueCompiledFiles does contain all the expected output files (.test.dll, .dll, .msi, .exe)
<Target Name="CustomCompile">
<MSBuild
BuildInParallel="true"
Projects="#(ProjectFiles)"
Properties="$(ProjectProperties)"
>
<Output TaskParameter="TargetOutputs" ItemName="CompiledFiles" />
</MSBuild>
<RemoveDuplicates Inputs="#(CompiledFiles)">
<Output TaskParameter="Filtered" ItemName="UniqueCompiledFiles" />
</RemoveDuplicates>
<ItemGroup>
<UniqueAssemblies
Include="%(UniqueCompiledFiles.Identity)"
Condition=" '#(UniqueCompiledFiles->EndsWith('.dll'))' == 'true' " />
<UniqueAssemblies
Include="%(UniqueCompiledFiles.Identity)"
Condition=" '#(UniqueCompiledFiles->EndsWith('.exe'))' == 'true' " />
</ItemGroup>
I also figured out this workaround that does properly filter the .exe.
<ItemGroup>
<UniqueAssemblies2
Include="%(UniqueCompiledFiles.Identity)"
Condition=" $([System.String]::new('%(UniqueCompiledFiles.Identity)').EndsWith('.exe')) " />
</ItemGroup>
Found out the culprit lines that could be removed to fix the problem but it doesn't actually answer the question.
<ItemGroup>
<!-- Workaround for MSBuild defect: https://github.com/Microsoft/msbuild/issues/69 -->
<UniqueCompiledFiles Include="Project\bin\release\Project.exe">
<MSBuildSourceProjectFile>Project\Project.csproj</MSBuildSourceProjectFile>
<Platform>x86</Platform>
</UniqueCompiledFiles>
</ItemGroup>
Why is the #(UniqueCompiledFiles->EndsWith('.exe')) syntax not working as expected?
You're trying to use a Property Function on an item (instead of a property).
You should be able to get the desired result by using something like this:
<ItemGroup>
<UniqueAssemblies2
Include="%(UniqueCompiledFiles.Identity)"
Condition=" '%(Extension)' == '.exe' " />
</ItemGroup>

Categories