/// <summary> /// Creates a log file if any files were generated in a different folder or project. /// </summary> /// <param name="outputFiles"> /// A collection of <see cref="OutputFile"/> objects. /// </param> /// <param name="template"> /// A <see cref="ProjectItem"/> that represents T4 template being transformed. /// </param> /// <remarks> /// A log file is created as an <see cref="OutputFile"/> object and added to /// the collection. The <see cref="UpdateOutputFiles"/> method takes care of /// saving it. /// </remarks> private static void CreateLogIfNecessary(ICollection <OutputFile> outputFiles, ProjectItem template) { string templateProject = GetPath(template.ContainingProject); string templateDirectory = Environment.CurrentDirectory; //Only create a log file when the directories or the projects are different. if (outputFiles.Any(output => !OutputInfo.SamePath(Path.GetDirectoryName(output.File), templateDirectory) || (!string.IsNullOrEmpty(output.Project) && !OutputInfo.SamePath(output.Project, templateProject)))) { OutputFile log = new OutputFile(); log.File = GetLogFileName(); log.Project = string.Empty; outputFiles.Add(log); log.Content.AppendLine("// <autogenerated>"); log.Content.AppendLine("// This file contains the list of files generated by " + Path.GetFileName(Host.TemplateFile) + "."); log.Content.AppendLine("// It is used by T4 Toolbox (http://www.codeplex.com/t4toolbox) to automatically delete"); log.Content.AppendLine("// generated files that are no longer necessary. Do not modify this file manually. Manual"); log.Content.AppendLine("// changes may cause orphaned files and will be lost next time the code is regenerated."); log.Content.AppendLine("// </autogenerated>"); foreach (OutputFile output in outputFiles) { // Don't log the generated files that will be preserved if (!output.PreserveExistingFile) { log.Content.AppendLine(GetRelativePath(Host.TemplateFile, output.File)); } } } }
/// <summary> /// Appends generated <paramref name="content"/> to the specified <see cref="OutputInfo.File"/>. /// </summary> /// <param name="output"> /// An <see cref="OutputInfo"/> object that describes content generated by a template. /// </param> /// <param name="content"> /// A <see cref="String"/> that contains content generated by a template. /// </param> /// <param name="host"> /// An <see cref="ITextTemplatingEngineHost"/> object hosting the transformation. /// </param> private void AppendToOutputFile(OutputInfo output, string content, ITextTemplatingEngineHost host) { // If some content was already generated for this file string filePath = GetFullFilePath(output, host); OutputFile outputFile = this.outputFiles.FirstOrDefault(o => OutputInfo.SamePath(o.File, filePath)); if (outputFile != null) { // Verify that output properties match Validate(output, outputFile); } else { // Otherwise, create a new output container outputFile = new OutputFile(); outputFile.File = filePath; outputFile.Project = GetFullProjectPath(output); outputFile.Encoding = output.Encoding; outputFile.BuildAction = output.BuildAction; outputFile.CustomTool = output.CustomTool; outputFile.CustomToolNamespace = output.CustomToolNamespace; outputFile.CopyToOutputDirectory = output.CopyToOutputDirectory; outputFile.PreserveExistingFile = output.PreserveExistingFile; this.outputFiles.Add(outputFile); } outputFile.Content.Append(content); outputFile.AppendBuildProperties(output.BuildProperties); outputFile.AppendReferences(output.References); }
/// <summary> /// Finds project item collection for the output file in Visual Studio solution. /// </summary> /// <param name="output"> /// An <see cref="OutputFile"/> that needs to be added to the solution. /// </param> /// <param name="projects"> /// All <see cref="Project"/>s in the current <see cref="Solution"/>. /// </param> /// <param name="template"> /// A <see cref="ProjectItem"/> that represents T4 template being transformed. /// </param> /// <returns>A <see cref="ProjectItems"/> collection where the generated file should be added.</returns> private static ProjectItems FindProjectItemCollection(OutputFile output, IEnumerable <Project> projects, ProjectItem template) { ProjectItems collection; // collection to which output file needs to be added string relativePath; // path from the collection to the file string basePath; // absolute path to the directory to which an item is being added if (!string.IsNullOrEmpty(output.Project)) { // If output file needs to be added to another project Project project = projects.First(p => OutputInfo.SamePath(GetPath(p), output.Project)); collection = project.ProjectItems; relativePath = GetRelativePath(GetPath(project), output.File); basePath = Path.GetDirectoryName(GetPath(project)); } else if (output.PreserveExistingFile || !OutputInfo.SamePath(Path.GetDirectoryName(output.File), Environment.CurrentDirectory)) { // If output file needs to be added to another folder of the current project collection = template.ContainingProject.ProjectItems; relativePath = GetRelativePath(GetPath(template.ContainingProject), output.File); basePath = Path.GetDirectoryName(GetPath(template.ContainingProject)); } else { // Add the output file to the list of children of the template file collection = template.ProjectItems; relativePath = GetRelativePath(template.get_FileNames(1), output.File); basePath = Path.GetDirectoryName(template.get_FileNames(1)); } // make sure that all folders in the file path exist in the project. if (relativePath.StartsWith("." + Path.DirectorySeparatorChar, StringComparison.Ordinal)) { // Remove leading .\ from the path relativePath = relativePath.Substring(relativePath.IndexOf(Path.DirectorySeparatorChar) + 1); while (relativePath.Contains(Path.DirectorySeparatorChar)) { string folderName = relativePath.Substring(0, relativePath.IndexOf(Path.DirectorySeparatorChar)); ProjectItem folder = AddFolder(collection, folderName, basePath); collection = folder.ProjectItems; relativePath = relativePath.Substring(folderName.Length + 1); basePath = Path.Combine(basePath, folderName); } } return(collection); }
/// <summary> /// Validates that <paramref name="output"/> properties are consistent /// with properties of the <paramref name="previousOutput"/>. /// </summary> /// <param name="output"> /// An <see cref="OutputInfo"/> object that represents the generated output content. /// </param> /// <param name="previousOutput"> /// An <see cref="OutputInfo"/> object that represents previously generated output content. /// </param> private static void Validate(OutputInfo output, OutputInfo previousOutput) { if (!OutputInfo.SamePath(previousOutput.Project, GetFullProjectPath(output))) { throw new TransformationException("Project name doesn't match previously generated value"); } if (previousOutput.Encoding != output.Encoding) { throw new TransformationException("Encoding doesn't match previously generated value"); } if (previousOutput.BuildAction != output.BuildAction) { throw new TransformationException("Build action doesn't match previously generated value"); } if (previousOutput.CopyToOutputDirectory != output.CopyToOutputDirectory) { throw new TransformationException("CopyToOutputDirectory doesn't match previously generated value"); } if (previousOutput.CustomTool != output.CustomTool) { throw new TransformationException("CustomTool doesn't match previously generated value"); } if (previousOutput.CustomToolNamespace != output.CustomToolNamespace) { throw new TransformationException("CustomToolNamespace doesn't match previously generated value"); } foreach (KeyValuePair <string, string> buildProperty in previousOutput.BuildProperties) { string previousValue; bool propertyExists = output.BuildProperties.TryGetValue(buildProperty.Key, out previousValue); if (propertyExists && buildProperty.Value != previousValue) { throw new TransformationException("Build property doesn't match previously generated value"); } } if (previousOutput.PreserveExistingFile != output.PreserveExistingFile) { throw new TransformationException("PreserveExistingFile doesn't match previously generated value"); } }
/// <summary> /// Performs validation tasks that require accessing Visual Studio automation model. /// </summary> /// <param name="outputFiles"> /// <see cref="OutputFile"/>s that need to be added to the solution. /// </param> /// <param name="projects"> /// All <see cref="Project"/>s in the current solution. /// </param> /// <remarks> /// Most of the output validation is done on the fly, by the <see cref="OutputManager.Append"/> /// method. This method performs the remaining validations that access Visual /// Studio automation model and cannot cross <see cref="bAppDomain"/> boundries. /// </remarks> private static void Validate(IEnumerable <OutputFile> outputFiles, IEnumerable <Project> projects) { foreach (OutputFile outputFile in outputFiles) { if (string.IsNullOrEmpty(outputFile.Project)) { continue; } // Make sure that project is included in the solution bool projectInSolution = projects.Any(p => OutputInfo.SamePath(GetPath(p), outputFile.Project)); if (!projectInSolution) { throw new TransformationException( string.Format(CultureInfo.CurrentCulture, "Target project {0} does not belong to the solution", outputFile.Project)); } } }
/// <summary> /// Deletes output files that were not generated by the current session. /// </summary> /// <param name="outputFiles"> /// A read-only collection of <see cref="OutputFile"/> objects. /// </param> /// <param name="solution"> /// Current Visual Studio <see cref="Solution"/>. /// </param> /// <param name="template"> /// A <see cref="ProjectItem"/> that represents T4 template being transformed. /// </param> private static void DeleteOldOutputs(IEnumerable <OutputFile> outputFiles, Solution solution, ProjectItem template) { // If previous transformation produced a log of generated ouptut files string logFile = GetLogFileName(); if (File.Exists(logFile)) { // Delete all files recorded in the log that were not regenerated foreach (string line in File.ReadAllLines(logFile)) { string relativePath = line.Trim(); // Skip blank lines if (relativePath.Length == 0) { continue; } // Skip comments if (relativePath.StartsWith("//", StringComparison.OrdinalIgnoreCase)) { continue; } string absolutePath = Path.GetFullPath(relativePath); // Skip the file if it was regenerated during current transformation if (outputFiles.Any(output => OutputInfo.SamePath(output.File, absolutePath))) { continue; } // The file wasn't regenerated, delete it from the solution, source control and file storage ProjectItem projectItem = solution.FindProjectItem(absolutePath); if (projectItem != null) { DeleteProjectItem(projectItem); } } } // Also delete all project items nested under template if they weren't regenerated string templateFileName = Path.GetFileNameWithoutExtension(Host.TemplateFile); foreach (ProjectItem childProjectItem in template.ProjectItems) { // Skip the file if it has the same name as the template file. This will prevent constant // deletion and adding of the main output file to the project, which is slow and may require // the user to check the file out unnecessarily. if (templateFileName == Path.GetFileNameWithoutExtension(childProjectItem.Name)) { continue; } // If the file wan't regenerated, delete it from the the solution, source control and file storage if (!outputFiles.Any(o => OutputInfo.SamePath(o.File, childProjectItem.get_FileNames(1)))) { childProjectItem.Delete(); } } }