Search code examples
c#visual-studiomsbuildvsixvs-extensibility

Obtaining the vcxproj AdditionalOptions field from within a VSIX extension


I've got a Visual Studio extension that requires read access to the Additional Options field that's found within a Visual C++ Project's Configuration Properties, under the C/C++ compiler. What is the modern way of obtaining this field from within a Visual Studio 2015-2019 extension?


I can't seem to use EnvDTE because enumerated VS2019 C++ projects can't be cast as VCProject which in turn prevents me from retrieving the VCCLCompilerTool.AdditionalOptions.

I could extract some other properties using the IVsBuildPropertyStorage interface:

IVsBuildPropertyStorage buildPropertyStorage = hierarchyProject as IVsBuildPropertyStorage;
buildPropertyStorage.GetPropertyValue("IntermediateOutputPath", configName, (uint)_PersistStorageType.PST_USER_FILE, out additionalOptions);

But that nets an empty string for Additional Options. I suspect that the said property is not a property at all as it's not within a PropertyGroup container but is instead under the ClCompile node in the vcxproj so it might have to be queried differently.

I also tried browsing through the project file using the functionality that’s exposed as part of the Microsoft.Build.Construction namespace but that’s as good as opening the project in an Xml parser. There doesn't seem to be an easy way to evaluate conditions within the document which makes this approach unreliable.

There’s potentially some functionality I could use in the Microsoft.VisualStudio.Workspace.Extensions namespace but it’s not available in VS2015.


Ideally I'd like to avoid deprecated APIs. At the moment it’s looking like there’s no proper support for ClCompiler properties.


Solution

  • I went through the Microsoft .NET API docs and came across the Microsoft.Build.Evaluation.Project class which facilitates access to evaluated props and items. So one way to achieve what I want would be traversing that object in the following fashion:

    Project project = ProjectCollection.GlobalProjectCollection.LoadProject(projectPath);
    var evaluatedMetadataNode = project.AllEvaluatedItemDefinitionMetadata.FirstOrDefault
        (item => item.ItemType == "ClCompile" && item.Name == "AdditionalOptions");
    string additionalOptions = evaluatedMetadataNode.EvaluatedValue;
    

    However, I'm curious if there's a better solution out there because there doesn't seem to be an easy way to specify a configuration that should be used for evaluation, making this solution inappropriate for some situations.

    Update: I found that using the Project's constructor that allows the specification of a global properties dictionary permits configuration-specific evaluations. For that to happen you need to put in the Configuration and Platform properties with their values in said dictionary:

    IDictionary<string, string> props = new Dictionary<string, string>();
    props["Configuration"] = "Release";
    props["Platform"] = "x64";
    
    Project project = new Project(pathToProject, props, null, new ProjectCollection());