Pre-Processing C++ file using MSBuild and Visual Studio 2012+ - c++

I have a series of standard cpp files and each of these files contain a file-specific #include statement. However, the content of those included files must be populated by a pre-processing tool prior to invoking the standard C++ compiler.
The tricky part is that I want this to be fully integrated into Visual Studio using MSBuild. Therefore, when I bring up Visual Studio's Property Window on a cpp file, I want to see all the standard C++ compiler options and, ideally, some Custom Properties controlling the pre-processor tool. As a OOP analogy, I want my build tool to inherit everything from the standard CL MSBuild Rule, and add some Custom Properties & Build Steps to it.
I have successfully done this through an extremely laborious process of basically creating a Custom MSBuild Rule and Copy/Paste most of the C++ Options into my Custom Rule. Finally, I then pass along the million C++ Options to the standard C++ compiler through the CommandLineTemplate entry in my MSBuild .props file. It's ridiculously complicated and the C++ options don't automatically get updated as I update Visual Studio.
I can find plenty of examples of Custom MSBuild Rules, but I haven't been able to find one where it piggybacks onto an existing one.

Not a lot of love for MSBuild, I take it...
Anyway, after years of going back and forth on that one, I finally found something, soon after I posted my question. The key was to search for "extending" existing Rule which, apparently, I hadn't tried before.
Usually, when you create a Build Customization in VS, you end up with 3 files:
MyCustomBuild.xml:
Contains the Properties & Switches, as shown on the VS's Property Sheet.
MyCustomBuild.props:
Contains default values for those Properties. They can be made conditional through the use of the Condition attribute.
MyCustomBuild.targers:
Contains a line to load up your xml and the Target/Task entries.
So the first part was to extend the existing C/C++ Properties as shown in Visual Studio. I found this link, which finally gave me something to work with:
https://github.com/Microsoft/VSProjectSystem/blob/master/doc/extensibility/extending_rules.md
Here's the xml bit.
<Rule
Name="RuleToExend"
DisplayName="File Properties"
PageTemplate="generic"
Description="File Properties"
OverrideMode="Extend"
xmlns="http://schemas.microsoft.com/build/2009/properties">
<!-- Add new properties, data source, categories, etc -->
</Rule>
Name attribute:
The Name attribute must match the rule being extended. In this case, I wanted to extend the CL rule, so I set that attribute = "CL".
DisplayName attribute:
This is optional. When provided, it will overwrite the tool's name seen on the Property Sheet. In this case, the tool name shown is "C/C++". I can change it to show "My C/C++" by setting this attribute.
PageTemplate attribute:
If this attribute is provided, it must match the overwritten rule's value. In this case, it would be "tool". Just leaving it out seems to work fine. I suspect this could be needed if 2 rules had the same name, but a different template. You could use this to clarify which one you wanted to extend.
Description attribute:
Optional. I don't know where that even shows up within the VS GUI. Maybe it's just for documenting the xml file.
OverrideMode attribute:
This is an important one! It can be set to either "Extend" or "Replace". In my case, I chose "Extend".
xmlns attribute:
Required. Doesn't work properly if not present.
As the link suggest, you can then provide the properties, data source and categories. Keep in mind that categories are usually displayed in the order they appear in the xml file. Since I was extending an existing Rule, my custom categories would all show up after the standard C/C++ categories. Given that my tool is for pre-processing the files, I would have preferred having my custom options at the top of the Property Sheet. But I couldn't find way around that.
Note that you do NOT need the ItemType/FileExtension or ContenType Properties, typically found for custom Rules.
So once I entered all of that, my custom pre-processing options showed up alongside the standard C/C++ properties on the Property Sheet. Note that all these new properties would be attached to the "ClCompile" Item list, with all the other C/C++ properties.
The next step was to update the .props file. I'm not going to get into it since it's pretty much standard when create these custom build Rules. Just know that you need to set them using the "ClCompile" Item, as mentioned above.
The final step was to get the .targets file to do what I wanted.
The first part was to "import" (not really an import entry) the custom Rule through the typical entry:
<ItemGroup>
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)MyCustomBuild.xml" />
</ItemGroup>
Then I needed to pre-process every source file. Ideally, it would have been nicer to pre-process a file and then compile it - one file a time. I could have done this by overwriting the "ClCompile" target within my own .targets file. This Target is defined under the "Microsoft.CppCommon.targets" file (location under "C:\Program Files (x86)" varies, depending on the VS version). I basically could have Cut & Pasted the whole Target into my file, then add my pre-processing task code before the "CL" Task. I also would have needed to convert the Target into a Target Batch, by adding an "Outputs=%(ClCompile.Identity)" attribute to the "ClCompile" Target. Without this, my pre-processing task would have ran on all files before moving on to the "CL" task, bringing me back to square one. Finally, I would have needed to deal with Pre-Compiled Header files, since they need to be compiled first.
All of this was just too much of a pain. So I selected the simpler option of defining a Target which looks like this:
<Target Name="MyPreProcessingTarget"
Condition="'#(ClCompile)' != ''"
Outputs ="%(ClCompile.Identity)"
DependsOnTargets="_SelectedFiles"
BeforeTargets="ClCompile">
There are a number of attributes defined but the most important one is the BeforeTargets="ClCompile" attribute. This is what forces this target to execute before the cpp files are compiled.
I also chose to do a Target Batch processing here [Outputs ="%(ClCompile.Identity)"] because it was just easier to do what I wanted to do, if I assumed to have 1 file being processed at a time, in my Target.
The attribute DependsOnTargets="_SelectedFiles" is used to know if a GUI user has some selected file within VS Solution Explorer. If so, the files will be stored in the #(SelectedFiles) Item List (generated by the "_SelectedFiles" Target). Typically, when selecting specific files within the Solution Explorer and choosing to compile them, VS will forcefully compile them even if they are up-to-date. I wanted to preserve that functionality for the automatically-generated pre-processed include files, and forcefully regenerate them as well, for those selected files. So I added this block:
<ItemGroup Condition="'#(SelectedFiles)' != ''">
<IncFilesToDelete Include="%(ClCompile.Filename)_pp.h"/>
</ItemGroup>
<Delete
Condition="'#(IncFilesToDelete)' != ''"
Files="%(IncFilesToDelete.FullPath)" />
Note that the automatically-generated include files are named SourceFileName_pp.h. By deleting those files, my pre-processing Task will forcefully re-generate them.
Next, I build a new Item list from the "ClCompile" Item list, but with the "_pp.h" versions of the files. I do so with the following code:
<ItemGroup>
<PPIncFiles
Condition="'#(ClCompile)' != '' and '%(ClCompile.ExcludedFromBuild)' != 'true'"
Include="%(ClCompile.Filename)_pp.h" />
</ItemGroup>
The final part is a little uglier.
In order to run my pre-processing exe, I use the standard "Exec" Task. But I obviously only want to run it if the source file is newer than the generated file. I do so by storing the well-known metadata "ModifiedTime" of the source file, and the generated file into a couple of dynamic Properties. But I can't use the ModifiedTime metadata directly, as it's not an comparable value. So I used the following code, which I have found on StackOverflow here:
Comparing DateTime stamps in Msbuild
<PropertyGroup>
<SourceFileDate>$([System.DateTime]::Parse('%(ClCompile.ModifiedTime)').Ticks)</SourceFileDate>
<PPIncFileDate Condition="!Exists(%(PPIncFiles.Identity))">0</PPIncFileDate>
<PPIncFileDate Condition="Exists(%PPIncFiles.Identity))">$([System.DateTime]::Parse('%(PPIncFiles.ModifiedTime)').Ticks)</PPIncFileDate>
</PropertyGroup>
Note that I can store the timestamps in Properties, given that the Item Lists only contain one Item per Target Pass, because of Target Batching.
Finally, I can invoke my pre-processor using the "Exec" Task, as follows:
<Exec
Condition="'#(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"
Command="pptool.exe [options] %(ClCompile.Identity)" />
Supplying the options was yet, another headache.
Typically, the switches as defined under the xml file are just passed to a "CommandLineTemplate" metadata under the .props file using [OptionName]. This will pass the "Switch" attribute of the Property defined under the xml file. But that implies defining your own TaskName item, made from a TaskFactory, under the .targets file. But in my case, I was just using the existing "Exec" Task, which doesn't know anything about my custom Properties. I didn't know how to retrieve the "Switch" attribute in this case, and what seems to be available is just whatever the "Name" attribute contains. Luckily, a Property has both a Name and a DisplayName. The DisplayName is what the GUI user sees. So I just copied the "Switch" value into the "Name" value, when defining the Properties under the xml file. I could then pass the option to the Exec Task using something like:
<Exec
Condition="'#(PPIncFiles)' != '' and $(SourceFileDate) > $(PPIncFileDate)"
Command="pptool.exe %(ClCompile.Option1) %(ClCompile.Option2)... %(ClCompile.Identity)" />
Where I defined all my Properties as "EnumProperty", with an "EnumValue" having Name="" for disabled options, and other EnumValue having Name="switch" for the others. Not very elegant, but I didn't know a way around this.
Finally, it is recommended that when automatically generating files, the .targets file should also include a way to clean them up when the user Cleans up the Project. That's pretty standard but I'll include it here for convenience.
<PropertyGroup>
<CleanDependsOn>$(CleanDependsOn);PPIncCleanTarget</CleanDependsOn>
</PropertyGroup>
<Target Name="PPIncCleanTarget" Condition ="'#(ClCompile)' != ''">
<ItemGroup>
<PPIncFilesToDelete Include="%(ClCompile.Filename)_pp.h" />
</ItemGroup>
<Delete Files="%(PPIncFilesToDelete.FullPath)" Condition="'#(PPIncFilesToDelete)' != ''"/>
</Target>

Related

Understanding MSBuild: Printing a variable value

I have following Import in a .csproj file. How can I find the value of Variable at this point of props?
<Import Project="<path_to_abc>\$(Variable)\abc.props>"
Condition=" '$(Variable)' != '' "
/>
I get build error:
can not find props files : <path_to_abc>\\abc.props
How can I see the value of Variable here? If I put Message in the .csproj file above Import, what Target dependency should I give?
<Target Name="PrintInfo" BeforeTargets="BeforeBuild">
<Message Text="'$(Variable)' $(Variable.length) " />
</Target>
gives me '' 0.
But is that because of BeforeBuild?
How does MSBuild work?
Does it process all properties before and in the first come first order?
And then if it processes the Targets, does it print the value at the current time?
How can I see what values are put in while evaluating the props?
Sadly the imports aren't currently logged, but this is about to change with the upcoming MSBuild 15.3 release and its binary logging feature.
A call to MSBuild using /flp:Verbosity=diagnostic will emit property reassignment events to an msbuild.log file like this:
0>Property reassignment: $(Foo)="bar" (previous value: "foo") at /Users/martin.ullrich/tmp/test.proj (10,5)
The log will then contain an Initial Properties list with the project's fully evaluated properties (including imports).
It is essential to understand that the statements property definition and import statements are processed in order so when an <Import> uses a property - either in a condition or the project path - it will use the value of the property at that moment.
There are a few other important aspects:
Property groups are processed before item groups and item definition groups. Even across all imported projects! (so an <ItemGroup> with a condition will see the values of properties defined/imported afterwards)
See Property and Item evaluation order
Target conditions are evaluated at the time the target is considered for execution and may be affected by both all imported project files and modifications that happened in other targets that already ran.
See Target Build Order

Use the same project for EMF model and edit code?

Can I somehow use the same Eclipse plug-in project for both a generated EMF model and the corresponding generated EMF Edit code?
Normally these two components reside in two different projects, the EMF Edit one with the suffix .edit to its name. I find this superfluous, since there is so little code in the Edit project, and it is so closely related to the model code.
I have tried setting both the modelDirectory and the editDirectory Gen Model attributes to (different) directories in the same project, but that seems to lead to endless confusion and build problems. I think maybe the two generation steps overwrite each others project setting files.
After some more experimentation it seems like it works fine to have EMF and EMF Edit generated code in the same project.
The things I had to do to make it work are the following:
Setting the genmodel property modelDirectory and editDirectory to the same directory. Otherwise I got a build error saying "The type ... is already defined in ...".
Setting the genmodel property bundleManifest="false". Otherwise the plug-in ID is overwritten by the generation process.
Apart from this I also set updateClasspath="false" to avoid that the generation process messes around with that.
The automatic updates to the manifest and plugin.xml files seem to be the following:
Set plug-in ID
Add exported packages
Add EMF extensions to plugin.xml
2 and 3 needs to be performed manually if they are desired. That would involve adding entries to plugin.xml similar to these:
<extension point="org.eclipse.emf.ecore.generated_package">
<!-- #generated model -->
<package
uri="somePackage"
class="somePackage.SomePackage"
genModel="model/model.xcore"/>
</extension>
<extension point="org.eclipse.emf.edit.itemProviderAdapterFactories">
<!-- #generated model -->
<factory
uri="somePackage"
class="somePackage.someClass"
supportedTypes=
"org.eclipse.emf.edit.provider.IEditingDomainItemProvider
org.eclipse.emf.edit.provider.IStructuredItemContentProvider
org.eclipse.emf.edit.provider.ITreeItemContentProvider
org.eclipse.emf.edit.provider.IItemLabelProvider
org.eclipse.emf.edit.provider.IItemPropertySource"/>
</extension>

echo All Elements of an ItemGroup

I have an MSBuild ItemGroup and I would like to be able to echo it out in the "Post-Build Event".
However when I try commands like: echo My ItemGroup: #(Foo)
I get the error:
error MSB4164: The value "echo My ItemGroup: #(Foo)" of metadata "Command" contains an item list expression. Item list expressions are not allowed on default metadata values.
I'm not very good with ItemGroups as of yet. Is there a way I can just echo the list of files that Foo contains?
Try %(Foo.Identity) instead. That will print just one item from the list, but cause the Task containing it (the Exec I suppose) to loop over the items.
If that doesn't work, be sure to work with the XML file directly rather than the IDE, in case it escapes things or puts in other code we don't see.
(later)
It might be like this post, where they lament it is not simple and needs direct editing of the XML anyway. So just change it to a Exec task where the itemlist expression appears in an attribute, not a metadata definition.
It is written that the PostBuildEvent is more of a backward compatibility thing, and the good one to use is the AfterBuild target, that “is able to contain arbitrary MSBuild tasks, including one ore more Exec tasks … it doesn't have a custom UI in the IDE … edit it as XML …” Tip 43 in Brian Kretzler's book.
You'll want something like:
<ItemGroup>
<ForcedUsingFilesList Include="c:\path\to\files\*" />
</ItemGroup>
<Target Name="MyTarget">
<PropertyGroup>
<MyFiles>
#(ForcedUsingFilesList->'%(FullPath)')
</MyFiles>
</PropertyGroup>
<Exec>echo $(MyFiles)</Exec>
</Target>

List all available models in EMF application

I'm working on a project consisting of two Eclipse plugin projects. One is an EMF project and contains the metamodel for the application. The other one is the acctual plugin working on that metamodel.
I'm now looking for a way to list all types of models available in the metamodel project. Since I basically need all generated classes I could use reflections to iterate through the metamodel package but I'd prefer an easier way if there is one.
The models are already listed as extensions in the plugin.xml like this:
<plugin>
<extension point="org.eclipse.emf.ecore.generated_package">
<package
uri="MyModel"
class="org.myproject.metamodel.MyModel.MyModelPackage"
genModel="model/MetaModel.genmodel"/>
</extension>
</plugin>
where the class MyModelPackage extends EPackage and org.myproject.metamodel.MyModel also contains all the other generated classes I need to list. I'm guessing that I can use that information but I still don't know how.
Update
The project I'm working on is based on EMFStore. Running it offers the EMFStore perspective. If I have the Navigator view with a project I can right click on that project and select New Model Element. This opens a dialog where all the model elements from my metamodel are listed so it is possible. It must be done somewhere in EMFStore or one of it's dependencies. I looked through the source code but can't seem to find where it's done.
The plugin.xml of the project org.eclipse.emf.emfstore.perspective refers to the class org.eclipse.emf.emfstore.emfperspective.EMFStorePerspective which I can't find in the sources. I imported the project via the Eclipse Import Plug-Ins and Fragments functionality and it has no source folder. In the EMFStore git repositories I can't even find that project.
Update
I now got the registry that contains the generated packages using EPackage.Registry.INSTANCE. Unfortunately it contains more than the EPackages from the one project containing the metadata (org.myproject.metamodel). Now I'm just looking for a proper way to filter it, but still can't get the hang of it.
Update
As the filtering is not part of my original question I accepted the answer by #SpaceTrucker. For those who are curious, this is how I've done it now:
Registry registry = EPackage.Registry.INSTANCE;
for (String key : new HashSet<String>(registry.keySet())) {
EPackage ePackage = registry.getEPackage(key);
if (ePackage.getClass().getName().startsWith("org.myproject.metamodel")) {
//do stuf
}
}
I found no way to filter for the project but luckily all the packages start with the same prefix.
EPackages may be registered via an EPackage.Registry. There seems to be a globally used instance available via ECorePlugin.getDefaultRegistryImplementation(). However I'm not 100% sure on that.
MoDisco comes with a EMF Model Browser, where you are also able to select any registered EMF model. So you also could have a look at those sources.

how to use wicket XsltTransformerBehavior when I have multiple .xsl files

I'm working part of a project that needs to display a .xml file with .xsl files.
Here's part of the code:
FILE_PATH = "myxsl.xsl"
...
Label content = new Label("content",xmlContent);
content.setEscapeModelStrings(false);
content.add(new XsltTransformerBehavior(FILE_PATH));
add(content);
...
Currently the page works if I use only one .xsl file. However, because the .xsl files I need to deal with can be really long, they are separated into several components.
for example, I will have
mymain.xsl, head.xsl, tables.xsl
the mymain.xsl has inclusion of the other like this
<xsl:include href="head.xsl"/>
<xsl:include href="tables.xsl"/>
I tried to set FILE_PATH to mymain.xsl, but it didn't work. The program can find mymain.xsl but cannot compile the stylesheet because it cannot find head.xsl and tables.xsl
I've been searching for a long time but still have no clue how to do this. Really appreciate any help. Thanks in advance.
I looked at the documentation of XsltTransformerBehavior, and it looks like it doesn't expose the functionality to set a custom URI resolver (which is basically what you want to do).
However, in the case that no-one else has a better solution, I can propose a workaround: Write an XSLT that merges your XSLT files into one file. See here for an XSLT that does this. If you're using Maven as build system, you can do this automatically in the generate-resources phase using the transform goal of the Maven XML Plugin. You can then associate the XsltTransformerBehavior with the single-file XSLT which will be available at run-time.