summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs
blob: cbdb8497a4facacd86a32fa5c01d0e35f19be037 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//-----------------------------------------------------------------------
// <copyright file="MergeProjectWithVSTemplate.cs" company="Andrew Arnott">
//     Copyright (c) Andrew Arnott. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace DotNetOpenAuth.BuildTasks {
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Text;
	using Microsoft.Build.Framework;
	using Microsoft.Build.Utilities;
	using System.Xml.Linq;
	using System.IO;
	using Microsoft.Build.BuildEngine;
	using System.Diagnostics.Contracts;

	public class MergeProjectWithVSTemplate : Task {
		internal const string VSTemplateNamespace = "http://schemas.microsoft.com/developer/vstemplate/2005";

		public string[] ProjectItemTypes { get; set; }

		public string[] ReplaceParametersExtensions { get; set; }

		public ITaskItem[] Templates { get; set; }

		/// <summary>
		/// Executes this instance.
		/// </summary>
		public override bool Execute() {
			foreach(ITaskItem sourceTemplateTaskItem in this.Templates) {
				var template = XElement.Load(sourceTemplateTaskItem.ItemSpec);
				var templateContentElement = template.Element(XName.Get("TemplateContent", VSTemplateNamespace));
				var projectElement = templateContentElement.Element(XName.Get("Project", VSTemplateNamespace));
				if (projectElement == null) {
					Log.LogMessage("Skipping merge of \"{0}\" with a project because no project was referenced from the template.", sourceTemplateTaskItem.ItemSpec);
					continue;
				}

				var projectPath = Path.Combine(Path.GetDirectoryName(sourceTemplateTaskItem.ItemSpec), projectElement.Attribute("File").Value);
				Log.LogMessage("Merging project \"{0}\" with \"{1}\".", projectPath, sourceTemplateTaskItem.ItemSpec);
				var sourceProject = new Microsoft.Build.BuildEngine.Project();
				sourceProject.Load(projectPath);

				// Collect the project items from the project that are appropriate
				// to include in the .vstemplate file.
				var itemsByFolder = from item in sourceProject.EvaluatedItems.Cast<BuildItem>()
							where this.ProjectItemTypes.Contains(item.Name)
							orderby item.Include
							group item by Path.GetDirectoryName(item.Include);
				foreach (var folder in itemsByFolder) {
					XElement parentNode = FindOrCreateParent(folder.Key, projectElement);

					foreach (var item in folder) {
						bool replaceParameters = this.ReplaceParametersExtensions.Contains(Path.GetExtension(item.Include));
						var projectItem = new XElement(
							XName.Get("ProjectItem", VSTemplateNamespace),
							new XAttribute("ReplaceParameters", replaceParameters ? "true" : "false"),
							Path.GetFileName(item.Include));
						parentNode.Add(projectItem);
					}
				}

				template.Save(sourceTemplateTaskItem.ItemSpec);
			}

			return !Log.HasLoggedErrors;
		}

		private static XElement FindOrCreateParent(string directoryName, XElement projectElement) {
			Contract.Requires<ArgumentNullException>(projectElement != null);

			if (string.IsNullOrEmpty(directoryName)) {
				return projectElement;
			}

			string[] segments = directoryName.Split(Path.DirectorySeparatorChar);
			XElement parent = projectElement;
			for (int i = 0; i < segments.Length; i++) {
				var candidateName = XName.Get("Folder", VSTemplateNamespace);
				var candidate = parent.Elements(XName.Get("Folder", VSTemplateNamespace)).FirstOrDefault(n => n.Attribute("Name").Value == segments[i]);
				if (candidate == null) {
					candidate = new XElement(
						candidateName,
						new XAttribute("Name", segments[i]));
					parent.Add(candidate);
				}

				parent = candidate;
			}

			return parent;
		}
	}
}