Search code examples
visual-studiovisual-studio-2015msbuildmsbuild-task

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.


Solution

  • 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.