Question

I am trying to transform a T4 template from command line using TextTransform.exe with the following command line:

"%ProgramFiles(x86)%\Common Files\Microsoft Shared\TextTemplating\10.0\TextTransform.exe" -out .\MyProj\MyT4.cs -I "%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\Templates\Includes" -a !NamespaceHint!MyNameSpace -dp T4VSHost!Microsoft.Data.Entity.Design.VisualStudio.Directives.FallbackT4VSHostProcessor!"%ProgramFiles(x86)%\Microsoft Visual Studio 10.0\Common7\IDE\Microsoft.Data.Entity.Design.dll" .\MyProj\MyT4.tt

Results:

  1. No error messages
  2. The %ERRORLEVEL% is 0 on completion.
  3. The files are generated
  4. The .csproj does not change

The problem is point 4. This may be expected, since the .csproj isn't a part of the above command line, however, I can't find any parameters which can accept it.

What am I doing wrong or what should I be doing instead?

P.S. When I use the button in Visual Studio the process works as excepted (new files are added to project).

Was it helpful?

Solution

Solved using the following method:

  1. Added these parameters to command line:

    -a !!ProjPath!.\MyProj\MyProj.csproj -a !!T4Path!.\MyProj\MyT4.tt

  2. Changed the include directory parameter to a local path:

    -I ".\Dependencies"

  3. Copied EF.Utility.CS.ttinclude to that path and made the following changes:

3.1. Replaced:

    public static EntityFrameworkTemplateFileManager Create(object textTransformation)
    {
        DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
        IDynamicHost host = transformation.Host;

#if !PREPROCESSED_TEMPLATE
        if (host.AsIServiceProvider() != null)
        {
            return new VsEntityFrameworkTemplateFileManager(transformation);
        }
#endif
        return new EntityFrameworkTemplateFileManager(transformation);
    }

with

    public static EntityFrameworkTemplateFileManager Create(object textTransformation)
    {
        DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
        IDynamicHost host = transformation.Host;

#if !PREPROCESSED_TEMPLATE
        if (host.AsIServiceProvider() != null)
        {
            return new VsEntityFrameworkTemplateFileManager(transformation);
        }
#endif
        return new EFTemplateFileManagerPlus(transformation);
    }

(Last return has the change)

Add added this class to the file:

private sealed class EFTemplateFileManagerPlus : EntityFrameworkTemplateFileManager
{
        private Action<IEnumerable<string>> projectSyncAction;
        private readonly string _projPath;
        private readonly string _t4Name;

        public EFTemplateFileManagerPlus(object textTemplating)
            : base(textTemplating)
        {
            var projPath = _textTransformation.Host.ResolveParameterValue("", "", "ProjPath");
            var t4Path = _textTransformation.Host.ResolveParameterValue("", "", "T4Path");
            _projPath = System.IO.Path.GetFullPath(projPath);
            _t4Name = System.IO.Path.GetFileName(t4Path);

            projectSyncAction = files => SyncCsProjFile(_projPath, _t4Name, files);
        }

        public static void SyncCsProjFile(string csProjFilePath, string t4FileName, IEnumerable<string> files)
        {
            files = files.Select(f => System.IO.Path.GetFileName(f)).Distinct().ToList();

            var csProjDocument = new XmlDocument();
            csProjDocument.Load(csProjFilePath);

            var root = csProjDocument.DocumentElement;

            XmlElement itemGroup = root.ChildNodes.OfType<XmlElement>()
                .Where(n => n.Name == "ItemGroup")
                .SelectMany(n => n.ChildNodes.OfType<XmlNode>()
                    .Where(c => c.Name == "Compile")
                    )
                .Select(c => c.ParentNode)
                .FirstOrDefault() as XmlElement;

            if (itemGroup == null)
            {
                itemGroup = csProjDocument.CreateNode(XmlNodeType.Element, "ItemGroup", null) as XmlElement;
                root.AppendChild(itemGroup);
            }

            var codeFiles = itemGroup.ChildNodes.OfType<XmlElement>()
                .Where(c =>
                    c.Name == "Compile"
                    && c.HasAttribute("Include") && !String.IsNullOrEmpty(c.GetAttribute("Include")))
                .ToList();

            var dependantFiles = codeFiles
                .Where(f =>
                    f.ChildNodes.OfType<XmlElement>().Any(c =>
                        c.Name == "DependentUpon"
                        && c.InnerText == t4FileName)
                ).ToList();

            // Remove redundant files
            foreach (var node in dependantFiles)
            {
                if (!files.Contains(node.GetAttribute("Include")))
                    itemGroup.RemoveChild(node);
            }

            // Add missing files
            foreach (var name in files)
            {
                if (!dependantFiles.Any(node => node.GetAttribute("Include") == name))
                {
                    var node = csProjDocument.CreateNode(XmlNodeType.Element, "Compile", null) as XmlElement;
                    node.SetAttribute("Include", name);
                    itemGroup.AppendChild(node);

                    var node2 = csProjDocument.CreateNode(XmlNodeType.Element, "DependentUpon", null) as XmlElement;
                    node2.InnerText = t4FileName;
                    node.AppendChild(node2);
                }
            }

            SaveClean(csProjDocument, csProjFilePath);
        }

        static private void SaveClean(XmlDocument doc, string path)
        {
            StringBuilder sb = new StringBuilder();
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = Encoding.UTF8;
            settings.Indent = true;
            settings.IndentChars = "  ";
            settings.NewLineChars = "\r\n";
            settings.NewLineHandling = NewLineHandling.Replace;
            settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
            using (XmlWriter writer = XmlWriter.Create(sb, settings))
            {
                doc.Save(writer);
            }

            var newXml = sb.ToString().Replace("encoding=\"utf-16\"", "encoding=\"utf-8\"").Replace(" xmlns=\"\"", string.Empty);
            System.IO.File.WriteAllText(path, newXml, Encoding.UTF8);
        }

        public override IEnumerable<string> Process(bool split)
        {
            var generatedFileNames = base.Process(split);

            projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));

            return generatedFileNames;
        }
    }

Now the project file sync works using TextTransform.exe too.

OTHER TIPS

I believe the command line host cannot change the .csproj. Only the VS host can do it, through the access to the DTE object.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top