It's not clear to me what the purpose of the WriteCodeFragment task is (https://msdn.microsoft.com/en-us/library/microsoft.build.tasks.writecodefragment.aspx). Google just seems to turn-up documentation and the one example I've seen isn't definitive.
Can someone clarify? Thanks.
It looks like it's only good for populating a sourcecode file (of your choice of language) with attributes (read: to embed build information into a project).
This is the sourcecode for the task:
https://github.com/Microsoft/msbuild/blob/master/src/XMakeTasks/WriteCodeFragment.cs
The specific GenerateCode() method for reference. The type that it's principally enumerating looks to be attribute-specific (read: declarative and non-functional):
/// <summary>
/// Generates the code into a string.
/// If it fails, logs an error and returns null.
/// If no meaningful code is generated, returns empty string.
/// Returns the default language extension as an out parameter.
/// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.StringWriter.#ctor(System.Text.StringBuilder)", Justification = "Reads fine to me")]
private string GenerateCode(out string extension)
{
extension = null;
bool haveGeneratedContent = false;
CodeDomProvider provider;
try
{
provider = CodeDomProvider.CreateProvider(Language);
}
catch (ConfigurationException ex)
{
Log.LogErrorWithCodeFromResources("WriteCodeFragment.CouldNotCreateProvider", Language, ex.Message);
return null;
}
catch (SecurityException ex)
{
Log.LogErrorWithCodeFromResources("WriteCodeFragment.CouldNotCreateProvider", Language, ex.Message);
return null;
}
extension = provider.FileExtension;
CodeCompileUnit unit = new CodeCompileUnit();
CodeNamespace globalNamespace = new CodeNamespace();
unit.Namespaces.Add(globalNamespace);
// Declare authorship. Unfortunately CodeDOM puts this comment after the attributes.
string comment = ResourceUtilities.FormatResourceString("WriteCodeFragment.Comment");
globalNamespace.Comments.Add(new CodeCommentStatement(comment));
if (AssemblyAttributes == null)
{
return String.Empty;
}
// For convenience, bring in the namespaces, where many assembly attributes lie
globalNamespace.Imports.Add(new CodeNamespaceImport("System"));
globalNamespace.Imports.Add(new CodeNamespaceImport("System.Reflection"));
foreach (ITaskItem attributeItem in AssemblyAttributes)
{
CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(new CodeTypeReference(attributeItem.ItemSpec));
// Some attributes only allow positional constructor arguments, or the user may just prefer them.
// To set those, use metadata names like "_Parameter1", "_Parameter2" etc.
// If a parameter index is skipped, it's an error.
IDictionary customMetadata = attributeItem.CloneCustomMetadata();
List<CodeAttributeArgument> orderedParameters = new List<CodeAttributeArgument>(new CodeAttributeArgument[customMetadata.Count + 1] /* max possible slots needed */);
List<CodeAttributeArgument> namedParameters = new List<CodeAttributeArgument>();
foreach (DictionaryEntry entry in customMetadata)
{
string name = (string)entry.Key;
string value = (string)entry.Value;
if (name.StartsWith("_Parameter", StringComparison.OrdinalIgnoreCase))
{
int index;
if (!Int32.TryParse(name.Substring("_Parameter".Length), out index))
{
Log.LogErrorWithCodeFromResources("General.InvalidValue", name, "WriteCodeFragment");
return null;
}
if (index > orderedParameters.Count || index < 1)
{
Log.LogErrorWithCodeFromResources("WriteCodeFragment.SkippedNumberedParameter", index);
return null;
}
// "_Parameter01" and "_Parameter1" would overwrite each other
orderedParameters[index - 1] = new CodeAttributeArgument(String.Empty, new CodePrimitiveExpression(value));
}
else
{
namedParameters.Add(new CodeAttributeArgument(name, new CodePrimitiveExpression(value)));
}
}
bool encounteredNull = false;
for (int i = 0; i < orderedParameters.Count; i++)
{
if (orderedParameters[i] == null)
{
// All subsequent args should be null, else a slot was missed
encounteredNull = true;
continue;
}
if (encounteredNull)
{
Log.LogErrorWithCodeFromResources("WriteCodeFragment.SkippedNumberedParameter", i + 1 /* back to 1 based */);
return null;
}
attribute.Arguments.Add(orderedParameters[i]);
}
foreach (CodeAttributeArgument namedParameter in namedParameters)
{
attribute.Arguments.Add(namedParameter);
}
unit.AssemblyCustomAttributes.Add(attribute);
haveGeneratedContent = true;
}
StringBuilder generatedCode = new StringBuilder();
using (StringWriter writer = new StringWriter(generatedCode, CultureInfo.CurrentCulture))
{
provider.GenerateCodeFromCompileUnit(unit, writer, new CodeGeneratorOptions());
}
string code = generatedCode.ToString();
// If we just generated infrastructure, don't bother returning anything
// as there's no point writing the file
return haveGeneratedContent ? code : String.Empty;
}
That only other example that I found was this:
(Using WriteCodeFragment MSBuild Task)
<Target Name="BeforeBuild">
<ItemGroup>
<AssemblyAttributes Include="AssemblyVersion">
<_Parameter1>123.132.123.123</_Parameter1>
</AssemblyAttributes>
</ItemGroup>
<WriteCodeFragment Language="C#" OutputFile="BuildVersion.cs" AssemblyAttributes="@(AssemblyAttributes)" />
</Target>
Plugging this into an actual build renders:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: AssemblyVersion("123.132.123.123")]
// Generated by the MSBuild WriteCodeFragment class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With