How To: Publish Output Groups from Other Projects in a ClickOnce Project
In addition to output from a "compile", projects are capable of creating other outputs. Setup projects can consume the output from other projects, package them up, and install them as part of the generated Windows Installer package. These groups of different outputs are referred to as, well, "output groups", and are exposed through the DTE as the following macro demonstrates:
Sub ListAll()
For Each proj As Project In DTE.Solution.Projects
Dim groupInfo As New StringBuilder()
If proj.ConfigurationManager IsNot Nothing Then
For Each group As OutputGroup In proj.ConfigurationManager.ActiveConfiguration.OutputGroups
groupInfo.AppendLine(String.Format("{0}: {1} ({2})", group.CanonicalName, group.DisplayName, group.FileCount))
Next
MsgBox(groupInfo.ToString(), MsgBoxStyle.OkOnly, proj.Name)
End If
Next
End Sub
The results for a very basic Winform Application is shown below:
It is a design limitation decision that ClickOnce projects are capable of publishing all of these output groups from its own project, but if you want the content, source, symbols, or documentation files from a different project in the solution, you are just plain out of luck. However, if you are willing to modify your project file just a little bit, your luck has just changed. Let's build upon an entry from last month and have a ClickOnce project publish output from other projects.
Its not well advertised, but there are Targets within Microsoft.Common.targets which expose the output groups of a project. For example, here is the Target which exposes the Content Files output group:
<Target
Name="ContentFilesProjectOutputGroup"
Outputs="@(ContentFilesProjectOutputGroupOutput)"
DependsOnTargets="$(ContentFilesProjectOutputGroupDependsOn)">
<!-- Convert items into final items; this way we can get the full path for each item. -->
<ItemGroup>
<ContentFilesProjectOutputGroupOutput Include="@(ContentWithTargetPath->'%(FullPath)')"/>
</ItemGroup>
</Target>
It would be tempting to simply add this Target as a dependency to the BeforePublish target used in the project file. However, doing so would get the contents of this project, not some other project. What we really need is to run MSBuild on a different project, call this target, and consume that target's outputs. And MSBuild has a way to do exactly that: The MSBuild task.
By using the MSBuild task, it is possible to modify the BeforePublish target to consume the outputs of another project. Something like this should do the trick:
<ItemGroup>
<PublishPOG Include="..\\ClassLibrary1\\ClassLibrary1.vbproj">
<Visible>false</Visible>
<OutputGroup>DebugSymbolsProjectOutputGroup</OutputGroup>
</PublishPOG>
<PublishPOG Include="..\\ClassLibrary2\\ClassLibrary2.vbproj">
<Visible>false</Visible>
<OutputGroup>ContentFilesProjectOutputGroup</OutputGroup>
</PublishPOG>
</ItemGroup>
<Target Name="BeforePublish">
<Touch Files="@(IntermediateAssembly)" />
<MSBuild Projects="%(PublishPOG.Identity)" Targets="%(OutputGroup)">
<Output TaskParameter="TargetOutputs" ItemName="_DeploymentManifestFiles" />
</MSBuild>
</Target>
This change consumes the outputs of two different projects: the debug symbols from ClassLibrary1 and the content files from ClassLibrary2 (why anyone would want to publish the pdbs of another project is left as an exercise to the reader). This information is stored as an item with identity of the project to build and the output group to consume. BeforePublish then calls the MSBuild task to append the outputs onto the _DeploymentManifestFiles item, which is consumed by the GenerateApplicationManifest task (amongst others). So, which outputs are available for consumption? Here is a handy chart for reference:
Output Type | MSBuild Target Name |
Content Files | ContentFilesProjectOutputGroup |
Source Files | SourceFilesProjectOutputGroup |
Debug Symbols | DebugSymbolsProjectOutputGroup |
Documentation Files | DocumentationProjectOutputGroup |
Localized resources | SatelliteDllsProjectOutputGroup |
Serialization assemblies | SGenFilesOutputGroup |
Primary output | BuiltProjectOutputGroup |
Note that the satellite dlls, serialization assemblies, and primary output of another project are generally included simply by adding a reference to that other project. But if you do go down this route for these output types, you should append the output to the _DeploymentManifestDependencies
item instead of _DeploymentManifestFiles
.
All of this talk of "output groups" and "ClickOnce" naturally (I think) leads to another question. I don't want to ask it because I don't want to answer it (right now). But if someone wants to ask, feel free to leave a comment. Its a question I want to answer with a later post, but we'll see if it happens.
Comments
Anonymous
August 21, 2008
This is a great post ! Thanks. This allowed me to take a complexe application that I could only deploy with a MSI and bring it back to the world of ClickOnce. I have another post suggestion for you and ClickOnce : 1 - How could we manage a situation where we would have three variants of app.config (app.dev.config, app.tst.config and app.tst.config) and the BeforePublish Target would replace app.config by the appropriate file for the publish. (it could also apply to other files, such as logging.config.... Thanks, Erik.Anonymous
August 29, 2008
The comment has been removed