/// <summary> /// Writes generated content to output files and deletes the old files that were not regenerated. /// </summary> /// <param name="inputFile"> /// Full path to the template file. /// </param> /// <param name="outputFiles"> /// A collection of <see cref="OutputFile"/> objects produced by the template. /// </param> void ITransformationContextProvider.UpdateOutputFiles(string inputFile, OutputFile[] outputFiles) { if (inputFile == null) { throw new ArgumentNullException("inputFile"); } if (!Path.IsPathRooted(inputFile)) { throw new ArgumentException(Resources.InputFilePathMustBeAbsoluteMessage, "inputFile"); } if (outputFiles == null) { throw new ArgumentNullException("outputFiles"); } foreach (OutputFile newOutput in outputFiles) { if (newOutput == null) { throw new ArgumentException(Resources.OutputFileIsNullMessage, "outputFiles"); } } // Validate the output files immediately. Exceptions will be reported by the templating service. var manager = new OutputFileManager(this.serviceProvider, inputFile, outputFiles); manager.Validate(); // Wait for the default output file to be generated var watcher = new FileSystemWatcher(); watcher.Path = Path.GetDirectoryName(inputFile); watcher.Filter = Path.GetFileNameWithoutExtension(inputFile) + "*." + this.GetTransformationOutputExtensionFromHost(); FileSystemEventHandler runManager = (sender, args) => { watcher.Dispose(); // Store the actual output file name OutputFile defaultOutput = outputFiles.FirstOrDefault(output => string.IsNullOrEmpty(output.File)); if (defaultOutput != null) { defaultOutput.File = Path.GetFileName(args.FullPath); } // Finish updating the output files on the UI thread ThreadHelper.Generic.BeginInvoke(manager.DoWork); }; watcher.Created += runManager; watcher.Changed += runManager; watcher.EnableRaisingEvents = true; }
/// <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> /// Writes text to the output of this transformation. /// </summary> /// <param name="output">An <see cref="OutputItem"/> object that specifies how the text must be saved.</param> /// <param name="text">A <see cref="String"/> that contains generated content.</param> public void Write(OutputItem output, string text) { if (output == null) { throw new ArgumentNullException("output"); } output.Validate(); OutputFile outputFile = this.outputFiles.FirstOrDefault(o => string.Equals(o.Path, output.Path, StringComparison.OrdinalIgnoreCase)); // If content was previously generated for this file if (outputFile != null) { // Validate new output attributes for consistency outputFile.Validate(output); } else { // create a new output file outputFile = new OutputFile(); this.outputFiles.Add(outputFile); } outputFile.CopyPropertiesFrom(output); if (string.IsNullOrEmpty(output.File)) { // Append text to transformation output this.WriteToTransformation(text); } else { // Append text to the output file outputFile.Content.Append(text); } }
/// <summary> /// Creates a new output file or updates it's contents if it already exists. /// </summary> /// <param name="output">An <see cref="OutputFile"/> object.</param> private static void UpdateOutputFile(OutputFile output) { if (File.Exists(output.File)) { // Don't do anything unless the output file has changed and needs to be overwritten if (output.Content.ToString() == File.ReadAllText(output.File, output.Encoding)) { return; } // Don't do anything if the file should be preserved. if (output.PreserveExistingFile) { return; } } // Check out the file if it is under source control SourceControl sourceControl = GetDte().SourceControl; if (sourceControl.IsItemUnderSCC(output.File) && !sourceControl.IsItemCheckedOut(output.File)) { sourceControl.CheckOutItem(output.File); } Directory.CreateDirectory(Path.GetDirectoryName(output.File)); File.WriteAllText(output.File, output.Content.ToString(), output.Encoding); }
/// <summary> /// Sets the known properties for the <see cref="ProjectItem"/> to be added to solution. /// </summary> /// <param name="projectItem"> /// A <see cref="ProjectItem"/> that represents the generated item in the solution. /// </param> /// <param name="output"> /// An <see cref="OutputFile"/> that holds metadata about the <see cref="ProjectItem"/> to be added to the solution. /// </param> private static void SetProjectItemProperties(ProjectItem projectItem, OutputFile output) { // Set "Build Action" property if (!string.IsNullOrEmpty(output.BuildAction)) { ICollection<string> buildActions = GetAvailableBuildActions(projectItem); if (!buildActions.Contains(output.BuildAction)) { throw new TransformationException( string.Format(CultureInfo.CurrentCulture, "Build Action {0} is not supported for {1}", output.BuildAction, projectItem.Name)); } SetPropertyValue(projectItem, "ItemType", output.BuildAction); } // Set "Copy to Output Directory" property if (output.CopyToOutputDirectory != default(CopyToOutputDirectory)) { SetPropertyValue(projectItem, "CopyToOutputDirectory", (int)output.CopyToOutputDirectory); } // Set "Custom Tool" property if (!string.IsNullOrEmpty(output.CustomTool)) { SetPropertyValue(projectItem, "CustomTool", output.CustomTool); } // Set "Custom Tool Namespace" property if (!string.IsNullOrEmpty(output.CustomToolNamespace)) { SetPropertyValue(projectItem, "CustomToolNamespace", output.CustomToolNamespace); } }
/// <summary> /// Sets the known properties for the <see cref="ProjectItem"/> added to solution. /// </summary> /// <param name="projectItem"> /// A <see cref="ProjectItem"/> that represents the generated item in the solution. /// </param> /// <param name="output"> /// An <see cref="OutputFile"/> that holds metadata about the <see cref="ProjectItem"/> to be added to the solution. /// </param> private static void SetProjectItemBuildProperties(ProjectItem projectItem, OutputFile output) { if (output.BuildProperties.Count > 0) { // Get access to the build property storage service IServiceProvider serviceProvider = (IServiceProvider)Host; IVsSolution solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)); IVsHierarchy hierarchy; ErrorHandler.ThrowOnFailure(solution.GetProjectOfUniqueName(projectItem.ContainingProject.UniqueName, out hierarchy)); IVsBuildPropertyStorage buildPropertyStorage = hierarchy as IVsBuildPropertyStorage; if (buildPropertyStorage == null) { throw new TransformationException( string.Format( CultureInfo.CurrentCulture, "Project {0} does not support build properties required by {1}", projectItem.ContainingProject.Name, projectItem.Name)); } // Find the target project item in the property storage uint projectItemId; ErrorHandler.ThrowOnFailure(hierarchy.ParseCanonicalName(projectItem.get_FileNames(1), out projectItemId)); // Set build projerties for the target project item foreach (KeyValuePair<string, string> buildProperty in output.BuildProperties) { ErrorHandler.ThrowOnFailure(buildPropertyStorage.SetItemAttribute(projectItemId, buildProperty.Key, buildProperty.Value)); } } }
/// <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> /// 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> /// Adds assembly references required by the project item to the target project. /// </summary> /// <param name="projectItem"> /// A <see cref="ProjectItem"/> that represents the generated item in the solution. /// </param> /// <param name="output"> /// An <see cref="OutputFile"/> that holds metadata about the <see cref="ProjectItem"/> to be added to the solution. /// </param> private static void AddProjectItemReferences(ProjectItem projectItem, OutputFile output) { if (output.References.Count > 0) { VSProject project = projectItem.ContainingProject.Object as VSProject; if (project == null) { throw new TransformationException( string.Format( CultureInfo.CurrentCulture, "Project {0} does not support assembly references required by {1}", projectItem.ContainingProject.Name, projectItem.Name)); } foreach (string reference in output.References) { project.References.Add(reference); } } }