In a Visual Studio C++ project I can define custom build actions and corresponding property rules (see cl.xml under the MSBuild folder). How can I create such a rule for a custom build action in a C# project? Under the MSBuild folder there's a the file Microsoft.CSharp.CurrentVersion.targets which references CSharp.ProjectItemsSchema.xaml, CSharp.xaml and CSharp.BrowseObject.xaml which looks exacltly like the definitions I need. Using procmon I can see that these files are not accessed at all. What's the correct way to define custom build actions?
To clarify what I want to accomplish here's an example:
I add an image file (.png) to the project
I have a special image resource 'compiler' the transform the image (this compiler is defined using a msbuild target)
I want to change properties of the image file (e.g. target resolution, format, etc.). In a C++ project I can open the file property dialog for this.
These properties are saved as MSBuild item metadata and forwarded to the MSBuild target.
The resulting project file would contain data like this:
<ItemGroup>
<MyImageContent Include="Image.png">
<OutputName>MyImage.png</OutputName>
<TargetResX>512</TargetResX>
<TargetResY>256</TargetResY>
</MyImageContent>
</ItemGroup>
This additional metadata can easily be used by a custom MSBuild target. So far I know how to do it.
But: In a VC++ project this metadata can be easily edited in a property window provided by Visual Studio. The definition Visual Studio uses comes from a rules xml file similar to the cl.xml I mentioned. How can I accomplish this in a C# project?
What's the correct way to define custom build actions?
You can use the MSBuild Targets or imported .targets file to define custom build actions in the project file.
1.I add an image file (.png) to the project
For this build action, you can use a copy task in the target to copy the image file to the project folder:
<Target Name="CopyFiles">
<Copy
SourceFiles="#(MySourceFiles)"
DestinationFolder="c:\MyProject\Destination"
/>
</Target>
2.I have a special image resource 'compiler' the transform the image (this compiler is defined using a msbuild target)
For this build action, you can import that msbuild target.
3.I want to change properties of the image file.
In the C# project, the properties of the image are also saved as MSBuild item metadata and forwarded to the MSBuild target.
Update:
In a VC++ project this metadata can be easily edited in a property window provided by Visual Studio. The definition Visual Studio uses comes from a rules xml file similar to the cl.xml I mentioned. How can I accomplish this in a C# project?
As far as I know we could not make custom items have additional metadata which be editable in the property window. This only can be applied to C++ and C++/CLI projects. .NET projects (C# and Visual Basic) don’t have that many options to be tweaked. You can refer to the Sharing Project Properties in Visual C++ for more detail.
Hope this can help you.
Related
I created bunch of targets in msbuild project. Lets call this project TopLevelProject. Lets say this has a target called CollectNZip. TopLevelProject depends on SubProjectA, SubProjectB and SubProjectC.
I have a solutions targets file Directory.Solution.targets that contains all the projects below its folder including TopLevelProject.
As part of a target in this file say BuildAll, I like to invoke CollectNZip target of TopLevelProject. So I added TopLevelProject:CollectNZip as dependency.
When I invoke BuildAll, I do see TopLevelProject is invoked with target CollectNZip. But this sucker as part of dependency started invoking SubProjectA:CollectNZip, SubProjectB:CollectNZip etc. As those sub projects don't have CollectNZip, the buildall target is failing.
What is the trick to invoke a target of a project, but don't invoke the sub projects as part of the invocation?
If I understand the scenario:
There is a solution file with a set of projects. Let's say the solution is named 'MySolution.sln'.
I assume the solution file was created by either Visual Studio or the dotnet tool.
The set of projects in the solution include: 'TopLevelProject.csproj', 'SubProjectA.csproj', 'SubProjectB.csproj', and 'SubProjectC.csproj'.
I assume the project files were created as C# projects by either Visual Studio or the dotnet tool.
The project 'TopLevelProject' has ProjectReferences to 'SubProjectA', 'SubProjectB', and 'SubProjectC'.
The project 'TopLevelProject' also has a target named 'CollectNZip'.
There is a 'Directory.Solution.targets' file that is a peer of 'MySolution.sln' or in a parent directory.
'Directory.Solution.targets' contains a 'BuildAll` target.
The 'Directory.Solution.targets' file is ignored by Visual Studio so the 'BuildAll' target is only available when running from the command line.
Projects can be added to a solution file (SLN) but can't be added to an MSBuild file. The 'Directory.Solution.targets' file is an MSBuild file. It can't be a container for projects. I don't know what the following statement means:
I have a solutions targets file Directory.Solution.targets that contains all the projects below its folder including TopLevelProject.
Note that the Import element is a textual include. It doesn't "add" a project; it adds the content of the file in the Project attribute into the content of the current project.
From the command line, you can invoke the 'CollectNZip' target of project 'TopLevelProject' via the solution file.
e.g.
msbuild MySolution.sln /t:TopLevelProject:CollectNZip
This will invoke only the 'CollectNZip' target on only the TopLevelProject project. It will not run other projects from the solution.
I don't know what the following statement means:
As part of a target in this file say BuildAll, I like to invoke CollectNZip target of TopLevelProject. So I added TopLevelProject:CollectNZip as dependency.
The <ProjectName>:<TargetName> syntax is supported for the command line /target switch. It is not supported within the code of an MSBuild file. TopLevelProject:CollectNZip can't be a dependency of a target.
MSBuild doesn't have any notion of "sub projects" although there are two mechanisms which can add dependencies between projects.
A project dependency can be added to the solution file. The solution level project dependency effects the build order -- and does nothing else. It does not share files.
A ProjectReference can be added to a project file. The ProjectReference is an ItemGroup and is part of the C# project build system that is built on the general MSBuild build engine. ProjectReference is specific to certain targets of the C# build system, most importantly the build and clean targets. build and clean will evaluate the ProjectReference ItemGroup, will run the referenced projects, and on a build will copy in the product of the referenced project.
If I add a target named 'Fred' to all the projects and I invoke 'Fred' on one project via the solution, 'Fred' will not be called on projects in the ProjectReference ItemGroup.
A project is an encapsulation. It doesn't know its 'caller' and, excepting ProjectReference, it doesn't know about other projects.
The described behavior is not how MSBuild works and I'm guessing that the description is imprecise and/or there is pertinent code not shown.
If 'CollectNZip' should only run within the 'TopLevelProject' project, then only add the target to that project. If you want to be able to build with and without 'CollectNZip', define a property that can be used as a flag, e.g. add an 'EnableCollectNZip' property and add a Condition on the target that tests the value of the 'EnableCollectNZip' property.
Why can't I use MSBuild macros in a C# Project's properties? These all work find in a CPP project.
For example:
Create an empty C# console application
Change the main method to take command line arguments
Right click on the project in solution explorer and click properties
Choose "Debug" on the left side
In Command Line Argument's, enter: "$(SolutionDir)"
Debug your program
Notice that the argument is quite literally $(SolutionDir) rather than what it translates to.
I have the same problem with "Xml documentation file path" and other fields.
Why can't I use MSBuild macros in a C# Project's properties? These all work find in a CPP project.
This is because the way C# and CPP project introduce macros is not the same.
For C# project, it introduced by the .props, .targets files, for example, the Microsoft.CSharp.targets file. In your project file .csproj you will find following Import:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
For CPP project, it introduced by property sheets, you can get it from View->Other Windows->Property Manager, which is not supported by C# project.
The different:
You can use property sheets to create project configurations that can
be applied to multiple projects since project settings that are
defined in .vsprops files are inheritable, unlike project settings
defined in Project Files (.vcproj files). Therefore, a project
configuration defined in a .vcproj file can inherit project settings
from one or more property sheets (.vsprops files). For more
information, see Property Inheritance.
That the reason why you can use MSBuild macros in a CPP Project's properties but not in C# project.
You can check following document for some more details:
Common macros for build commands and properties
Property Sheets (C++)
Hope this helps.
I am creating an extension that analyses which type of project you have in your solution. And based on the project type it finds the location of generated bytecode. I am not able to find the project type for .Net standard. Any ideas how should I find it?
Inside .csproj file there is a PropertyGroup element with TargetFramework/TargetFrameworks element containing targets.
Example:
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netcoreapp1.1;net46</TargetFrameworks>
As you can see, "netstandard1.3" is specified here. It is the same as the name of the folder to which MSBuild builds.
You can use the Property Manager window in Visual Studio for managing common properties of C++ projects, but there appears to be no equivalent to this for C# projects.
Is there a way to set common settings across multiple projects? Examples of things I'd like to do:
add TEST preprocessor directive for all projects that have a TEST build configuration
change warnings to errors for all C# projects
add a reference to some common assembly for N selected C# projects at once
etc.
A plugin, macro, or extension would all be acceptable.
You can customize the C# project template for Visual Studio, so that every time you create a new project it has all the settings you want.
http://msdn.microsoft.com/en-us/library/6db0hwky.aspx
http://www.a2zdotnet.com/View.aspx?Id=170
The closest equivalent is currently using regex from within VS on an unloaded project or writing something to process the msbuild xml backing the .csproj files.
There is no a real equivalent of Property Manager for C# projects.
However, if the goal is to share common settings across multiple projects, you can create a NuGet package with .props file defining the shared properties and reference it in your C# projects - VS will automatically import it at build time.
.props and .targets inclusion is supported as of NuGet 2.x.
Tip: make sure the .props file name matches the NuGet package id, otherwise it won't be included.
You can see an example here.
We're trying to use T4 with Visual Studio 2010 (SP1) to build scripts for another language that are based upon some of our existing C# classes. I'm hoping for the following:
The template needs to load our existing assembly and use objects from a namespace in that assembly.
The transformation needs to run on every build on every development machine and build server without any additional installations.
(1) and (2) need to work together.
(1) was fairly straightforward:
<## assembly name="$(TargetDir)RequiredProject.dll" #>
<## import namespace="RequiredProject.RequiredNamespace" #>
Using the $(TargetDir) macro allowed me to reference the dll with a fully qualified UNC path (per the instructions found here).
(2) is a bit roundabout, but I think I've got it solved: I installed the required text transformation SDKs on a different machine and copied the required .targets and .dlls into a folder in my solution and then updated my .csproj file to reference the local .targets file.
(3) is where I run into problems. It seems like the <TransformOnBuild>true</TransformOnBuild> property doesn't play nicely when a referenced assembly needs to be built prior to the transformation. Everytime I enable transform on build with referenced assemblies, I get the following error:
Compiling transformation: Metadata file '$(TargetDir)RequiredProject.dll' could not be found.
However, I'm using the same assembly instruction that I was using in (1) to reference the assembly. In fact, going to the .tt template directly and saving it still produces the expected output -- it just doesn't work during the "build" step. Am I doing something wrong, or is there a way to ensure that the template transformations occur after the assemblies they depend on are built? (Or, more simply, that template transformations occur last?)
Unfortunately, the msbuild T4 host doesn't yet support embedded macro or msbuild variables in assembly names.
However, it does support Windows environment variables "%foo%", so although it means some machine-level setup, you can get something that works across in-IDE and build time transforms.
My understanding is that Visual Studio 2013 will finally solve this problem, but that doesn't do me much good as I'm still on Visual Studio 2012. After a lot of effort I finally ran across a solution.
In the project that has the template you wish to run, add the following as a pre-build step on the Build Events tab of the project properties page.
set textTransformPath="%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe"
if %textTransformPath%=="\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe" set textTransformPath="%CommonProgramFiles%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe"
set ProjectDir=$(ProjectDir)
%textTransformPath% "%ProjectDir%StringGenerator.tt"
The first two lines take care of the differences between locating TextTransform.exe on 32-bit and 64-bit systems. The third line is the key. I need the path to the project location inside my template, so I set a local environment variable equal to the value of the build's $(ProjectDir) property. Inside my template, just use the following:
var projectDir = Environment.GetEnvironmentVariable("ProjectDir");
This has solved my issue.
I created a seperate solution that contained my needed referenced assemblies. The I had my buildscript build the reference solution first, then transform the templates, then build the solution containing the generated code.
If you want to reference dependency assemblies within a T4 script using macros and have text templating succeed during build-time, then you have to use project properties.
Within your project:
<Import Project="$(ProgramFiles)\Microsoft Visual Studio\2017\Enterprise\MSBuild\Microsoft\VisualStudio\v15.0\TextTemplating\Microsoft.TextTemplating.targets" />
<PropertyGroup>
<T4ProjectDir>$(ProjectDir)</T4ProjectDir>
</PropertyGroup>
<ItemGroup>
<T4ParameterValues Include="T4ProjectDir">
<Value>$(T4ProjectDir)</Value>
<Visible>false</Visible>
</T4ParameterValues>
</ItemGroup>
Where the path to your text templating environment may be different.
Then use $(T4ProjectDir) as you would use any other macro in your text template.
Or you could also simply refer to existing properties:
<ItemGroup>
<T4ParameterValues Include="ProjectDir">
<Value>$(ProjectDir)</Value>
<Visible>false</Visible>
</T4ParameterValues>
</ItemGroup>