Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSBuild WriteCodeFragment Task

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.

like image 881
Dustin Oprea Avatar asked May 12 '16 15:05

Dustin Oprea


1 Answers

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.
like image 63
Dustin Oprea Avatar answered Nov 08 '22 23:11

Dustin Oprea