I would like the files generated by my custom tool to be hidden, but I cannot find any documentation on how this is done.
An example of what I\'m looking for is WPF
To hide items from Visual Studio add a Visible
metadata property to the item. The InProject
metadata apparently does this too.
Visible: http://msdn.microsoft.com/en-us/library/ms171468(VS.90).aspx
InProject: http://blogs.msdn.com/b/jomo_fisher/archive/2005/01/25/360302.aspx
<ItemGroup>
<Compile Include="$(AssemblyInfoPath)">
<!-- either: -->
<InProject>false</InProject>
<!-- or: -->
<Visible>false</Visible>
</Compile>
</ItemGroup>
I think you want to look here: http://msdn.microsoft.com/en-us/library/ms171453.aspx.
Specifically, the "Creating Items During Execution" section.
The solution is to create a Target that adds your files to the Compile ItemGroup rather than adding them explicitly in your .csproj file. That way Intellisense will see them and they will be compiled into your executable, but they will not show up in Visual Studio.
Simple example
You also need to make sure your target is added to the CoreCompileDependsOn
property so it will execute before the compiler runs.
Here is an extremely simple example:
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);AddToolOutput</CoreCompileDependsOn>
</PropertyGroup>
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="HiddenFile.cs" />
</ItemGroup>
</Target>
If you add this to the bottom of your .csproj file (just before </Project>
), your "HiddenFile.cs" will be included in your compilation even though it doesn't appear in Visual Studio.
Using a separate .targets file
Instead of placing this directly in your .csproj file, you would generally placed it in a separate .targets file surrounded by:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
</Project>
and import into your .csproj with <Import Project="MyTool.targets">
. A .targets file is recommended even for one-off cases because it separates your custom code from the stuff in .csproj that is maintained by Visual Studio.
Constructing the generated filename(s)
If you are creating a generalized tool and/or using a separate .targets file, you probably don't want to explicitly list each hidden file. Instead you want to generate the hidden file names from other settings in the project. For example if you want all Resource files to have corresponding tool-generated files in the "obj" directory, your Target would be:
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="@(Resource->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
</ItemGroup>
</Target>
The "IntermediateOutputPath" property is what we all know as the "obj" directory, but if the end-user of your .targets has customized this your intermediate files will stil be found in the same place. If you prefer your generated files to be in the main project directory and not in the "obj" directory, you can leave this off.
If you want only some of the files of an existing item type to be processed by your custom tool? For example, you may want to generate files for all Page and Resource files with a ".xyz" extension.
<Target Name="AddToolOutput">
<ItemGroup>
<MyToolFiles Include="@(Page);@(Resource)" Condition="'%(Extension)'=='.xyz' />
<Compile Include="@(MyToolFiles->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')"/>
</ItemGroup>
</Target>
Note that you can't use the metadata syntax like %(Extension) in a top-level ItemGroup but you can do so within a Target.
Using a custom item type (aka Build Action)
The above processes files that have an existing item type such as Page, Resource, or Compile (Visual Studio calls this the "Build Action"). If your items are a new kind of file you can use your own custom item type. For example if your input files are called "Xyz" files, your project file can define "Xyz" as a valid item type:
<ItemGroup>
<AvailableItemName Include="Xyz" />
</ItemGroup>
after which Visual Studio will allow you to select "Xyz" in the Build Action in the file's properties, resulting in this being added to your .csproj:
<ItemGroup>
<Xyz Include="Something.xyz" />
</ItemGroup>
Now you can use the "Xyz" item type to create the filenames for tool output, just as we did previously with the "Resource" item type:
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
</ItemGroup>
</Target>
When using a custom item type you can cause your items to also be handled by built-in mechanisms by mapping them to another item type (aka Build Action). This is useful if your "Xyz" files are really .cs files or .xaml or if they need to be made
EmbeddedResources. For example you can cause all files with "Build Action" of Xyz to also be compiled:
<ItemGroup>
<Compile Include="@(Xyz)" />
</ItemGroup>
Or if your "Xyz" source files should be stored as embedded resources, you can express it this way:
<ItemGroup>
<EmbeddedResource Include="@(Xyz)" />
</ItemGroup>
Note that the second example won't work if you put it inside the Target, since the target isn't evaluated until just before the core compile. To make this work inside a Target you have to list the target name in PrepareForBuildDependsOn property instead of CoreCompileDependsOn.
Invoking your custom code generator from MSBuild
Having gone as far as creating a .targets file, you might consider invoking your tool directly from MSBuild rather than using a separate pre-build event or Visual Studio's flawed "Custom Tool" mechanism.
To do this:
UsingTask
element to your .targets file, and in your target add a call to your new taskHere is all you need to implement ITask:
public class GenerateCodeFromXyzFiles : ITask
{
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
public ITaskItem[] InputFiles { get; set; }
public ITaskItem[] OutputFiles { get; set; }
public bool Execute()
{
for(int i=0; i<InputFiles.Length; i++)
File.WriteAllText(OutputFiles[i].ItemSpec,
ProcessXyzFile(
File.ReadAllText(InputFiles[i].ItemSpec)));
}
private string ProcessXyzFile(string xyzFileContents)
{
// Process file and return generated code
}
}
And here is the UsingTask element and a Target that calls it:
<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />
<Target Name="GenerateToolOutput">
<GenerateCodeFromXyzFiles
InputFiles="@(Xyz)"
OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<Output TaskParameter="OutputFiles" ItemGroup="Compile" />
</GenerateCodeFromXyzFiles>
</Target>
Note that this target's Output element places the list of output files directly into Compile, so there is no need to use a separate ItemGroup to do this.
How the old "Custom Tool" mechanism is flawed and why not to use it
A note on Visual Studio's "Custom Tool" mechanism: In NET Framework 1.x we didn't have MSBuild, so we had to rely on Visual Studio to build our projects. In order to get Intellisense on generated code, Visual Studio had a mechanism called "Custom Tool" that can be set in the Properties window on a file. The mechanism was fundamentally flawed in several ways, which is why it was replaced with MSBuild targets. Some of the problems with the "Custom Tool" feature were:
If you are using the old "Custom Tool" feature, I strongly recommend you switch to using a MSBuild task. It works well with Intellisense and allows you to build your project without even installing Visual Studio (all you need is NET Framework).
When will your custom build task run?
In general your custom build task will run:
To be more precise:
You may want to force your generator to run at other times, such as when some environment variable changes, or force it to run synchronously rather in the background.
To cause the generator to re-run even when no input files have changed, the best way is usually to add an additional Input to your Target which is a dummy input file stored in the "obj" directory. Then whenever an environment variable or some external setting changes that should force your generator tool to re-run, simply touch this file (ie. create it or update its modified date).
To force the generator to run synchronously rather than waiting for IntelliSense to run it in the background, just use MSBuild to build your particular target. This could be as simple as executing "MSBuild /t:GenerateToolOutput", or VSIP may provide a build-in way to call custom build targets. Alternatively you could simply invoke the Build command and wait for it to complete.
Note that "Input files" in this section refers to whatever is listed in the "Inputs" attribute of the Target element.
Final notes
You may get warnings from Visual Studio that it doesn't know whether to trust your custom tool .targets file. To fix this, add it to the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\MSBuild\SafeImports registry key.
Here is a summary of what an actual .targets file would look like with all the pieces in place:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
</PropertyGroup>
<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />
<Target Name="GenerateToolOutput" Inputs="@(Xyz)" Outputs="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<GenerateCodeFromXyzFiles
InputFiles="@(Xyz)"
OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<Output TaskParameter="OutputFiles" ItemGroup="Compile" />
</GenerateCodeFromXyzFiles>
</Target>
</Project>
Please let me know if you have any questions or there is anything here you didn't understand.
The only way I know to do it is to add the generated file to have a dependency on the file you want it hidden behind - in the proj file.
For example:
<ItemGroup>
<Compile Include="test.cs" />
<Compile Include="test.g.i.cs">
<DependentUpon>test.cs</DependentUpon>
</Compile>
</ItemGroup>
If you removed the DependentUpon element then the file shows up beside the other file instead of behind it ... how does your generator add the files? can you walk us through the use case and how you would like it to work?