Search Unity

Persist File Properties in VS Solution Between Builds?

Discussion in 'Editor & General Support' started by Piefayth, Apr 18, 2019.

  1. Piefayth

    Piefayth

    Joined:
    Feb 7, 2017
    Posts:
    61
    I have some T4 templates (.tt) that I have in my project. In order for these templates to work, they need to have the "Custom Tool" property under their "File Properties" in Visual Studio be "TextTemplatingFilePreprocessor". This configuration is stored in the .csproj file. Every time Unity regenerates the .csproj file, it blows out this value and I need to reset it manually, which really hampers the code generation workflow. Is there a way to persist changes to the .csproj file or otherwise keep my file properties between reloads?
     
    Last edited: Apr 18, 2019
  2. Piefayth

    Piefayth

    Joined:
    Feb 7, 2017
    Posts:
    61
    Well, I was hoping there was a better way to do this, because the approach I went with seems rather brittle. It turns out there is a hook that gets called after the .csproj files are generated, and you can modify them from this point.

    Got the idea from here and I'll leave my full implementation below for anyone googling with a similar issue. I imagine someone has written a plugin to do this sort of thing at some point, but, this works for me for now...

    Code (CSharp):
    1. using System.IO;
    2. using UnityEditor;
    3. using UnityEngine;
    4. using System.Xml;
    5.  
    6. [InitializeOnLoad]
    7. class CsprojCorrector : AssetPostprocessor {
    8.     public static void OnGeneratedCSProjectFiles() {
    9.         Debug.Log("OnGeneratedCSProjectFiles");
    10.         string dir = Directory.GetCurrentDirectory();
    11.         string[] files = Directory.GetFiles(dir, "*.csproj");
    12.         foreach (string file in files) {
    13.             PatchCsproj(file);
    14.         }
    15.     }
    16.  
    17.     static void PatchCsproj(string file) {
    18.         XmlDocument doc = new XmlDocument();
    19.         doc.PreserveWhitespace = true;
    20.  
    21.         try {
    22.             doc.Load(file);
    23.         } catch (System.Exception e) {
    24.             Debug.Log($"Could not patch {file} due to {e}");
    25.             return;
    26.         }
    27.  
    28.         XmlNamespaceManager xmlnsManager = new XmlNamespaceManager(doc.NameTable);
    29.         xmlnsManager.AddNamespace("def", "http://schemas.microsoft.com/developer/msbuild/2003");
    30.  
    31.         XmlNode root = doc.DocumentElement;
    32.  
    33.         RestoreTemplateProperties(doc, root, xmlnsManager);
    34.         doc.Save(file);
    35.     }
    36.  
    37.     static void RestoreTemplateProperties(XmlDocument doc, XmlNode root, XmlNamespaceManager xmlnsManager) {
    38.         XmlNodeList templateNodes = root.SelectNodes("//def:None[contains(@Include, '.tt')]", xmlnsManager);
    39.  
    40.         foreach (XmlNode templateNode in templateNodes) {
    41.             string[] splitFilePath = templateNode.Attributes["Include"].Value.Split('\\');
    42.             string filename = splitFilePath[splitFilePath.Length - 1];
    43.             filename = filename.Remove(filename.Length - 3, 3);
    44.  
    45.             XmlNode generatorNode = doc.CreateElement("Generator", doc.DocumentElement.NamespaceURI);
    46.             generatorNode.InnerText = "TextTemplatingFilePreprocessor";
    47.             templateNode.AppendChild(generatorNode);
    48.  
    49.             XmlNode lastGeneratedNode = doc.CreateElement("LastGenOutput", doc.DocumentElement.NamespaceURI);
    50.             lastGeneratedNode.InnerText = filename + ".cs";
    51.             templateNode.AppendChild(lastGeneratedNode);
    52.            
    53.             XmlNode compileNode = root.SelectSingleNode($"//def:Compile[contains(@Include, '{filename}.cs')]", xmlnsManager);
    54.  
    55.             if (compileNode != null) {
    56.                 XmlNode autoGenNode = doc.CreateElement("AutoGen", doc.DocumentElement.NamespaceURI);
    57.                 autoGenNode.InnerText = "True";
    58.                 compileNode.AppendChild(autoGenNode);
    59.  
    60.                 XmlNode designTimeNode = doc.CreateElement("DesignTime", doc.DocumentElement.NamespaceURI);
    61.                 designTimeNode.InnerText = "True";
    62.                 compileNode.AppendChild(designTimeNode);
    63.  
    64.                 XmlNode dependentUponNode = doc.CreateElement("DependentUpon", doc.DocumentElement.NamespaceURI);
    65.                 dependentUponNode.InnerText = $"{filename}.tt";
    66.                 compileNode.AppendChild(dependentUponNode);
    67.             }
    68.         }
    69.     }
    70. }