summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.BuildTasks/DowngradeProjects.cs
blob: 0b2109ae055da29ad00fbf2f7a2d40f51cc5920f (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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//-----------------------------------------------------------------------
// <copyright file="DowngradeProjects.cs" company="Outercurve Foundation">
//     Copyright (c) Outercurve Foundation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

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

	/// <summary>
	/// Downgrades Visual Studio 2010 solutions and projects so that they load in Visual Studio 2008.
	/// </summary>
	public class DowngradeProjects : Task {
		/// <summary>
		/// Gets or sets the projects and solutions to downgrade.
		/// </summary>
		[Required]
		public ITaskItem[] Projects { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether project files are downgraded and re-saved to the same paths.
		/// </summary>
		public bool InPlaceDowngrade { get; set; }

		/// <summary>
		/// Gets or sets the set of newly created project files.  Empty if <see cref="InPlaceDowngrade"/> is <c>true</c>.
		/// </summary>
		[Output]
		public ITaskItem[] DowngradedProjects { get; set; }

		/// <summary>
		/// Gets or sets a value indicating whether ASP.NET MVC 2 projects are downgraded to MVC 1.0.
		/// </summary>
		public bool DowngradeMvc2ToMvc1 { get; set; }

		/// <summary>
		/// Executes this instance.
		/// </summary>
		public override bool Execute() {
			var newProjectToOldProjectMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			var createdProjectFiles = new List<TaskItem>();

			foreach (ITaskItem taskItem in this.Projects) {
				switch (GetClassification(taskItem)) {
					case ProjectClassification.VS2010Project:
					case ProjectClassification.VS2010Solution:
						string projectNameForVS2008 = InPlaceDowngrade
														? taskItem.ItemSpec
														: Path.Combine(
															Path.GetDirectoryName(taskItem.ItemSpec),
															Path.GetFileNameWithoutExtension(taskItem.ItemSpec) + "-vs2008" +
															Path.GetExtension(taskItem.ItemSpec));
						newProjectToOldProjectMapping[taskItem.ItemSpec] = projectNameForVS2008;
						break;
				}
			}

			foreach (ITaskItem taskItem in this.Projects) {
				switch (GetClassification(taskItem)) {
					case ProjectClassification.VS2010Project:
						this.Log.LogMessage(MessageImportance.Low, "Downgrading project \"{0}\".", taskItem.ItemSpec);
						var project = new Project();
						project.Load(taskItem.ItemSpec, ProjectLoadSettings.IgnoreMissingImports);
						project.DefaultToolsVersion = "3.5";

						if (this.DowngradeMvc2ToMvc1) {
							string projectTypeGuids = project.GetEvaluatedProperty("ProjectTypeGuids");
							if (!string.IsNullOrEmpty(projectTypeGuids)) {
								projectTypeGuids = projectTypeGuids.Replace("{F85E285D-A4E0-4152-9332-AB1D724D3325}", "{603c0e0b-db56-11dc-be95-000d561079b0}");
								project.SetProperty("ProjectTypeGuids", projectTypeGuids);
							}
						}

						// MSBuild v3.5 doesn't support the GetDirectoryNameOfFileAbove function
						var enlistmentInfoImports = project.Imports.Cast<Import>().Where(i => i.ProjectPath.IndexOf("[MSBuild]::GetDirectoryNameOfFileAbove", StringComparison.OrdinalIgnoreCase) >= 0);
						enlistmentInfoImports.ToList().ForEach(i => project.Imports.RemoveImport(i));

						// Web projects usually have an import that includes these substrings));)
						foreach (Import import in project.Imports) {
							import.ProjectPath = import.ProjectPath
								.Replace("$(MSBuildExtensionsPath32)", "$(MSBuildExtensionsPath)")
								.Replace("VisualStudio\\v10.0", "VisualStudio\\v9.0");
						}

						// VS2010 won't let you have a System.Core reference, but VS2008 requires it.
						BuildItemGroup references = project.GetEvaluatedItemsByName("Reference");
						if (!references.Cast<BuildItem>().Any(item => item.FinalItemSpec.StartsWith("System.Core", StringComparison.OrdinalIgnoreCase))) {
							project.AddNewItem("Reference", "System.Core");
						}

						// Rewrite ProjectReferences to other renamed projects.
						BuildItemGroup projectReferences = project.GetEvaluatedItemsByName("ProjectReference");
						foreach (var mapping in newProjectToOldProjectMapping) {
							string oldName = Path.GetFileName(mapping.Key);
							string newName = Path.GetFileName(mapping.Value);
							foreach (BuildItem projectReference in projectReferences) {
								projectReference.Include = Regex.Replace(projectReference.Include, oldName, newName, RegexOptions.IgnoreCase);
							}
						}

						project.Save(newProjectToOldProjectMapping[taskItem.ItemSpec]);
						createdProjectFiles.Add(new TaskItem(taskItem) { ItemSpec = newProjectToOldProjectMapping[taskItem.ItemSpec] });
						break;
					case ProjectClassification.VS2010Solution:
						this.Log.LogMessage(MessageImportance.Low, "Downgrading solution \"{0}\".", taskItem.ItemSpec);
						string[] contents = File.ReadAllLines(taskItem.ItemSpec);
						if (contents[1] != "Microsoft Visual Studio Solution File, Format Version 11.00" ||
							contents[2] != "# Visual Studio 2010") {
							this.Log.LogError("Unrecognized solution file header in \"{0}\".", taskItem.ItemSpec);
							break;
						}

						contents[1] = "Microsoft Visual Studio Solution File, Format Version 10.00";
						contents[2] = "# Visual Studio 2008";

						for (int i = 3; i < contents.Length; i++) {
							contents[i] = contents[i].Replace("TargetFrameworkMoniker = \".NETFramework,Version%3Dv", "TargetFramework = \"");
						}

						foreach (var mapping in newProjectToOldProjectMapping) {
							string oldName = Path.GetFileName(mapping.Key);
							string newName = Path.GetFileName(mapping.Value);
							for (int i = 0; i < contents.Length; i++) {
								contents[i] = Regex.Replace(contents[i], oldName, newName, RegexOptions.IgnoreCase);
							}
						}

						File.WriteAllLines(newProjectToOldProjectMapping[taskItem.ItemSpec], contents);
						createdProjectFiles.Add(new TaskItem(taskItem) { ItemSpec = newProjectToOldProjectMapping[taskItem.ItemSpec] });
						break;
					default:
						this.Log.LogWarning("Unrecognized project type for \"{0}\".", taskItem.ItemSpec);
						break;
				}
			}

			if (InPlaceDowngrade) {
				this.DowngradedProjects = new ITaskItem[0];
			} else {
				this.DowngradedProjects = createdProjectFiles.ToArray();
			}

			return !this.Log.HasLoggedErrors;
		}

		private static ProjectClassification GetClassification(ITaskItem taskItem) {
			if (Path.GetExtension(taskItem.ItemSpec).EndsWith("proj", StringComparison.OrdinalIgnoreCase)) {
				return ProjectClassification.VS2010Project;
			} else if (Path.GetExtension(taskItem.ItemSpec).Equals(".sln", StringComparison.OrdinalIgnoreCase)) {
				return ProjectClassification.VS2010Solution;
			} else {
				return ProjectClassification.Unrecognized;
			}
		}

		private enum ProjectClassification {
			VS2010Project,
			VS2010Solution,
			Unrecognized,
		}
	}
}