diff options
Diffstat (limited to 'src/tools/heat/VSProjectHarvester.cs')
-rw-r--r-- | src/tools/heat/VSProjectHarvester.cs | 1457 |
1 files changed, 0 insertions, 1457 deletions
diff --git a/src/tools/heat/VSProjectHarvester.cs b/src/tools/heat/VSProjectHarvester.cs deleted file mode 100644 index 92eac6ff..00000000 --- a/src/tools/heat/VSProjectHarvester.cs +++ /dev/null | |||
@@ -1,1457 +0,0 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Harvesters | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Reflection; | ||
8 | using System.Collections; | ||
9 | using System.Collections.Generic; | ||
10 | using System.Globalization; | ||
11 | using System.Text.RegularExpressions; | ||
12 | using System.Xml; | ||
13 | using WixToolset.Data; | ||
14 | using WixToolset.Extensibility.Services; | ||
15 | using WixToolset.Harvesters.Data; | ||
16 | using WixToolset.Harvesters.Extensibility; | ||
17 | using Wix = WixToolset.Harvesters.Serialize; | ||
18 | |||
19 | /// <summary> | ||
20 | /// Harvest WiX authoring for the outputs of a VS project. | ||
21 | /// </summary> | ||
22 | public sealed class VSProjectHarvester : BaseHarvesterExtension | ||
23 | { | ||
24 | // These format strings are used for generated element identifiers. | ||
25 | // {0} = project name | ||
26 | // {1} = POG name | ||
27 | // {2} = file name | ||
28 | private const string DirectoryIdFormat = "{0}.{1}"; | ||
29 | private const string ComponentIdFormat = "{0}.{1}.{2}"; | ||
30 | private const string FileIdFormat = "{0}.{1}.{2}"; | ||
31 | private const string VariableFormat = "$(var.{0}.{1})"; | ||
32 | private const string WixVariableFormat = "!(wix.{0}.{1})"; | ||
33 | |||
34 | private const string ComponentPrefix = "cmp"; | ||
35 | private const string DirectoryPrefix = "dir"; | ||
36 | private const string FilePrefix = "fil"; | ||
37 | |||
38 | private string projectGUID; | ||
39 | private string directoryIds; | ||
40 | private string directoryRefSeed; | ||
41 | private string projectName; | ||
42 | private string configuration; | ||
43 | private string platform; | ||
44 | private bool setUniqueIdentifiers; | ||
45 | private GenerateType generateType; | ||
46 | private bool generateWixVars; | ||
47 | |||
48 | |||
49 | private static readonly ProjectOutputGroup[] allOutputGroups = new ProjectOutputGroup[] | ||
50 | { | ||
51 | new ProjectOutputGroup("Binaries", "BuiltProjectOutputGroup", "TargetDir"), | ||
52 | new ProjectOutputGroup("Symbols", "DebugSymbolsProjectOutputGroup", "TargetDir"), | ||
53 | new ProjectOutputGroup("Documents", "DocumentationProjectOutputGroup", "ProjectDir"), | ||
54 | new ProjectOutputGroup("Satellites", "SatelliteDllsProjectOutputGroup", "TargetDir"), | ||
55 | new ProjectOutputGroup("Sources", "SourceFilesProjectOutputGroup", "ProjectDir"), | ||
56 | new ProjectOutputGroup("Content", "ContentFilesProjectOutputGroup", "ProjectDir"), | ||
57 | }; | ||
58 | |||
59 | private string[] outputGroups; | ||
60 | |||
61 | /// <summary> | ||
62 | /// Instantiate a new VSProjectHarvester. | ||
63 | /// </summary> | ||
64 | /// <param name="outputGroups">List of project output groups to harvest.</param> | ||
65 | public VSProjectHarvester(string[] outputGroups) | ||
66 | { | ||
67 | if (outputGroups == null) | ||
68 | { | ||
69 | throw new ArgumentNullException("outputGroups"); | ||
70 | } | ||
71 | |||
72 | this.outputGroups = outputGroups; | ||
73 | } | ||
74 | |||
75 | /// <summary> | ||
76 | /// Gets or sets the configuration to set when harvesting. | ||
77 | /// </summary> | ||
78 | /// <value>The configuration to set when harvesting.</value> | ||
79 | public string Configuration | ||
80 | { | ||
81 | get { return this.configuration; } | ||
82 | set { this.configuration = value; } | ||
83 | } | ||
84 | |||
85 | public string DirectoryIds | ||
86 | { | ||
87 | get { return this.directoryIds; } | ||
88 | set { this.directoryIds = value; } | ||
89 | } | ||
90 | |||
91 | /// <summary> | ||
92 | /// Gets or sets what type of elements are to be generated. | ||
93 | /// </summary> | ||
94 | /// <value>The type of elements being generated.</value> | ||
95 | public GenerateType GenerateType | ||
96 | { | ||
97 | get { return this.generateType; } | ||
98 | set { this.generateType = value; } | ||
99 | } | ||
100 | |||
101 | /// <summary> | ||
102 | /// Gets or sets whether or not to use wix variables. | ||
103 | /// </summary> | ||
104 | /// <value>Whether or not to use wix variables.</value> | ||
105 | public bool GenerateWixVars | ||
106 | { | ||
107 | get { return this.generateWixVars; } | ||
108 | set { this.generateWixVars = value; } | ||
109 | } | ||
110 | |||
111 | /// <summary> | ||
112 | /// Gets or sets the location to load MSBuild from. | ||
113 | /// </summary> | ||
114 | public string MsbuildBinPath { get; set; } | ||
115 | |||
116 | /// <summary> | ||
117 | /// Gets or sets the platform to set when harvesting. | ||
118 | /// </summary> | ||
119 | /// <value>The platform to set when harvesting.</value> | ||
120 | public string Platform | ||
121 | { | ||
122 | get { return this.platform; } | ||
123 | set { this.platform = value; } | ||
124 | } | ||
125 | |||
126 | /// <summary> | ||
127 | /// Gets or sets the project name to use in wix variables. | ||
128 | /// </summary> | ||
129 | /// <value>The project name to use in wix variables.</value> | ||
130 | public string ProjectName | ||
131 | { | ||
132 | get { return this.projectName; } | ||
133 | set { this.projectName = value; } | ||
134 | } | ||
135 | |||
136 | /// <summary> | ||
137 | /// Gets or sets the option to set unique identifiers. | ||
138 | /// </summary> | ||
139 | /// <value>The option to set unique identifiers.</value> | ||
140 | public bool SetUniqueIdentifiers | ||
141 | { | ||
142 | get { return this.setUniqueIdentifiers; } | ||
143 | set { this.setUniqueIdentifiers = value; } | ||
144 | } | ||
145 | |||
146 | /// <summary> | ||
147 | /// Gets or sets whether to ignore MsbuildBinPath when the project file specifies a known MSBuild version. | ||
148 | /// </summary> | ||
149 | public bool UseToolsVersion { get; set; } | ||
150 | |||
151 | /// <summary> | ||
152 | /// Gets a list of friendly output group names that will be recognized on the command-line. | ||
153 | /// </summary> | ||
154 | /// <returns>Array of output group names.</returns> | ||
155 | public static string[] GetOutputGroupNames() | ||
156 | { | ||
157 | string[] names = new string[VSProjectHarvester.allOutputGroups.Length]; | ||
158 | for (int i = 0; i < names.Length; i++) | ||
159 | { | ||
160 | names[i] = VSProjectHarvester.allOutputGroups[i].Name; | ||
161 | } | ||
162 | return names; | ||
163 | } | ||
164 | |||
165 | /// <summary> | ||
166 | /// Harvest a VS project. | ||
167 | /// </summary> | ||
168 | /// <param name="argument">The path of the VS project file.</param> | ||
169 | /// <returns>The harvested directory.</returns> | ||
170 | public override Wix.Fragment[] Harvest(string argument) | ||
171 | { | ||
172 | if (null == argument) | ||
173 | { | ||
174 | throw new ArgumentNullException("argument"); | ||
175 | } | ||
176 | |||
177 | if (!System.IO.File.Exists(argument)) | ||
178 | { | ||
179 | throw new FileNotFoundException(argument); | ||
180 | } | ||
181 | |||
182 | // Match specified output group names to available POG structures | ||
183 | // and collect list of build output groups to pass to MSBuild. | ||
184 | ProjectOutputGroup[] pogs = new ProjectOutputGroup[this.outputGroups.Length]; | ||
185 | string[] buildOutputGroups = new string[this.outputGroups.Length]; | ||
186 | for (int i = 0; i < this.outputGroups.Length; i++) | ||
187 | { | ||
188 | foreach (ProjectOutputGroup pog in VSProjectHarvester.allOutputGroups) | ||
189 | { | ||
190 | if (pog.Name == this.outputGroups[i]) | ||
191 | { | ||
192 | pogs[i] = pog; | ||
193 | buildOutputGroups[i] = pog.BuildOutputGroup; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | if (buildOutputGroups[i] == null) | ||
198 | { | ||
199 | throw new WixException(HarvesterErrors.InvalidOutputGroup(this.outputGroups[i])); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | string projectFile = Path.GetFullPath(argument); | ||
204 | |||
205 | IDictionary buildOutputs = this.GetProjectBuildOutputs(projectFile, buildOutputGroups); | ||
206 | |||
207 | ArrayList fragmentList = new ArrayList(); | ||
208 | |||
209 | for (int i = 0; i < pogs.Length; i++) | ||
210 | { | ||
211 | this.HarvestProjectOutputGroup(projectFile, buildOutputs, pogs[i], fragmentList); | ||
212 | } | ||
213 | |||
214 | return (Wix.Fragment[]) fragmentList.ToArray(typeof(Wix.Fragment)); | ||
215 | } | ||
216 | |||
217 | /// <summary> | ||
218 | /// Runs MSBuild on a project file to get the list of filenames for the specified output groups. | ||
219 | /// </summary> | ||
220 | /// <param name="projectFile">VS MSBuild project file to load.</param> | ||
221 | /// <param name="buildOutputGroups">List of MSBuild output group names.</param> | ||
222 | /// <returns>Dictionary mapping output group names to lists of filenames in the group.</returns> | ||
223 | private IDictionary GetProjectBuildOutputs(string projectFile, string[] buildOutputGroups) | ||
224 | { | ||
225 | MSBuildProject project = this.GetMsbuildProject(projectFile); | ||
226 | |||
227 | project.Load(projectFile); | ||
228 | |||
229 | IDictionary buildOutputs = new Hashtable(); | ||
230 | |||
231 | string originalDirectory = System.IO.Directory.GetCurrentDirectory(); | ||
232 | System.IO.Directory.SetCurrentDirectory(Path.GetDirectoryName(projectFile)); | ||
233 | bool buildSuccess = false; | ||
234 | try | ||
235 | { | ||
236 | buildSuccess = project.Build(projectFile, buildOutputGroups, buildOutputs); | ||
237 | } | ||
238 | finally | ||
239 | { | ||
240 | System.IO.Directory.SetCurrentDirectory(originalDirectory); | ||
241 | } | ||
242 | |||
243 | if (!buildSuccess) | ||
244 | { | ||
245 | throw new WixException(HarvesterErrors.BuildFailed()); | ||
246 | } | ||
247 | |||
248 | this.projectGUID = project.GetEvaluatedProperty("ProjectGuid"); | ||
249 | |||
250 | if (null == this.projectGUID) | ||
251 | { | ||
252 | throw new WixException(HarvesterErrors.BuildFailed()); | ||
253 | } | ||
254 | |||
255 | IDictionary newDictionary = new Dictionary<object, object>(); | ||
256 | foreach (string buildOutput in buildOutputs.Keys) | ||
257 | { | ||
258 | IEnumerable buildOutputFiles = buildOutputs[buildOutput] as IEnumerable; | ||
259 | |||
260 | bool hasFiles = false; | ||
261 | |||
262 | foreach (object file in buildOutputFiles) | ||
263 | { | ||
264 | hasFiles = true; | ||
265 | break; | ||
266 | } | ||
267 | |||
268 | // Try the item group if no outputs | ||
269 | if (!hasFiles) | ||
270 | { | ||
271 | IEnumerable itemFiles = project.GetEvaluatedItemsByName(String.Concat(buildOutput, "Output")); | ||
272 | List<object> itemFileList = new List<object>(); | ||
273 | |||
274 | // Get each BuildItem and add the file path to our list | ||
275 | foreach (object itemFile in itemFiles) | ||
276 | { | ||
277 | itemFileList.Add(project.GetBuildItem(itemFile)); | ||
278 | } | ||
279 | |||
280 | // Use our list for this build output | ||
281 | newDictionary.Add(buildOutput, itemFileList); | ||
282 | } | ||
283 | else | ||
284 | { | ||
285 | newDictionary.Add(buildOutput, buildOutputFiles); | ||
286 | } | ||
287 | } | ||
288 | |||
289 | return newDictionary; | ||
290 | } | ||
291 | |||
292 | /// <summary> | ||
293 | /// Creates WiX fragments for files in one output group. | ||
294 | /// </summary> | ||
295 | /// <param name="projectFile">VS MSBuild project file.</param> | ||
296 | /// <param name="buildOutputs">Dictionary of build outputs retrieved from an MSBuild run on the project file.</param> | ||
297 | /// <param name="pog">Project output group parameters.</param> | ||
298 | /// <param name="fragmentList">List to which generated fragments will be added.</param> | ||
299 | /// <returns>Count of harvested files.</returns> | ||
300 | private int HarvestProjectOutputGroup(string projectFile, IDictionary buildOutputs, ProjectOutputGroup pog, IList fragmentList) | ||
301 | { | ||
302 | string projectName = Path.GetFileNameWithoutExtension(projectFile); | ||
303 | string projectBaseDir = null; | ||
304 | |||
305 | if (this.ProjectName != null) | ||
306 | { | ||
307 | projectName = this.ProjectName; | ||
308 | } | ||
309 | |||
310 | string sanitizedProjectName = this.Core.CreateIdentifierFromFilename(projectName); | ||
311 | |||
312 | Wix.IParentElement harvestParent; | ||
313 | |||
314 | if (this.GenerateType == GenerateType.Container) | ||
315 | { | ||
316 | Wix.Container container = new Wix.Container(); | ||
317 | harvestParent = container; | ||
318 | |||
319 | container.Name = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
320 | } | ||
321 | else if (this.GenerateType == GenerateType.PayloadGroup) | ||
322 | { | ||
323 | Wix.PayloadGroup payloadGroup = new Wix.PayloadGroup(); | ||
324 | harvestParent = payloadGroup; | ||
325 | |||
326 | payloadGroup.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
327 | } | ||
328 | else if (this.GenerateType == GenerateType.PackageGroup) | ||
329 | { | ||
330 | Wix.PackageGroup packageGroup = new Wix.PackageGroup(); | ||
331 | harvestParent = packageGroup; | ||
332 | |||
333 | packageGroup.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
334 | } | ||
335 | else | ||
336 | { | ||
337 | string directoryRefId; | ||
338 | |||
339 | if (!String.IsNullOrEmpty(this.directoryIds)) | ||
340 | { | ||
341 | directoryRefId = this.directoryIds; | ||
342 | } | ||
343 | else if (this.setUniqueIdentifiers) | ||
344 | { | ||
345 | directoryRefId = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
346 | } | ||
347 | else | ||
348 | { | ||
349 | directoryRefId = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name)); | ||
350 | } | ||
351 | |||
352 | var directoryRef = DirectoryHelper.CreateDirectoryReference(directoryRefId); | ||
353 | harvestParent = directoryRef; | ||
354 | |||
355 | this.directoryRefSeed = this.Core.GenerateIdentifier(DirectoryPrefix, this.projectGUID, pog.Name); | ||
356 | } | ||
357 | |||
358 | IEnumerable pogFiles = buildOutputs[pog.BuildOutputGroup] as IEnumerable; | ||
359 | if (pogFiles == null) | ||
360 | { | ||
361 | throw new WixException(HarvesterErrors.MissingProjectOutputGroup( | ||
362 | projectFile, pog.BuildOutputGroup)); | ||
363 | } | ||
364 | |||
365 | if (pog.FileSource == "ProjectDir") | ||
366 | { | ||
367 | projectBaseDir = Path.GetDirectoryName(projectFile) + "\\"; | ||
368 | } | ||
369 | |||
370 | int harvestCount = this.HarvestProjectOutputGroupFiles(projectBaseDir, sanitizedProjectName, pog.Name, pog.FileSource, pogFiles, harvestParent); | ||
371 | |||
372 | if (this.GenerateType == GenerateType.Container) | ||
373 | { | ||
374 | // harvestParent must be a Container at this point | ||
375 | Wix.Container container = harvestParent as Wix.Container; | ||
376 | |||
377 | Wix.Fragment fragment = new Wix.Fragment(); | ||
378 | fragment.AddChild(container); | ||
379 | fragmentList.Add(fragment); | ||
380 | } | ||
381 | else if (this.GenerateType == GenerateType.PackageGroup) | ||
382 | { | ||
383 | // harvestParent must be a PackageGroup at this point | ||
384 | Wix.PackageGroup packageGroup = harvestParent as Wix.PackageGroup; | ||
385 | |||
386 | Wix.Fragment fragment = new Wix.Fragment(); | ||
387 | fragment.AddChild(packageGroup); | ||
388 | fragmentList.Add(fragment); | ||
389 | } | ||
390 | else if (this.GenerateType == GenerateType.PayloadGroup) | ||
391 | { | ||
392 | // harvestParent must be a Container at this point | ||
393 | Wix.PayloadGroup payloadGroup = harvestParent as Wix.PayloadGroup; | ||
394 | |||
395 | Wix.Fragment fragment = new Wix.Fragment(); | ||
396 | fragment.AddChild(payloadGroup); | ||
397 | fragmentList.Add(fragment); | ||
398 | } | ||
399 | else | ||
400 | { | ||
401 | // harvestParent must be a DirectoryRef at this point | ||
402 | Wix.DirectoryRef directoryRef = harvestParent as Wix.DirectoryRef; | ||
403 | |||
404 | if (harvestCount > 0) | ||
405 | { | ||
406 | Wix.Fragment drf = new Wix.Fragment(); | ||
407 | drf.AddChild(directoryRef); | ||
408 | fragmentList.Add(drf); | ||
409 | } | ||
410 | |||
411 | Wix.ComponentGroup cg = new Wix.ComponentGroup(); | ||
412 | |||
413 | if (this.setUniqueIdentifiers || !String.IsNullOrEmpty(this.directoryIds)) | ||
414 | { | ||
415 | cg.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
416 | } | ||
417 | else | ||
418 | { | ||
419 | cg.Id = directoryRef.Id; | ||
420 | } | ||
421 | |||
422 | if (harvestCount > 0) | ||
423 | { | ||
424 | this.AddComponentsToComponentGroup(directoryRef, cg); | ||
425 | } | ||
426 | |||
427 | Wix.Fragment cgf = new Wix.Fragment(); | ||
428 | cgf.AddChild(cg); | ||
429 | fragmentList.Add(cgf); | ||
430 | } | ||
431 | |||
432 | return harvestCount; | ||
433 | } | ||
434 | |||
435 | /// <summary> | ||
436 | /// Add all Components in an element tree to a ComponentGroup. | ||
437 | /// </summary> | ||
438 | /// <param name="parent">Parent of an element tree that will be searched for Components.</param> | ||
439 | /// <param name="cg">The ComponentGroup the Components will be added to.</param> | ||
440 | private void AddComponentsToComponentGroup(Wix.IParentElement parent, Wix.ComponentGroup cg) | ||
441 | { | ||
442 | foreach (Wix.ISchemaElement childElement in parent.Children) | ||
443 | { | ||
444 | Wix.Component c = childElement as Wix.Component; | ||
445 | if (c != null) | ||
446 | { | ||
447 | Wix.ComponentRef cr = new Wix.ComponentRef(); | ||
448 | cr.Id = c.Id; | ||
449 | cg.AddChild(cr); | ||
450 | } | ||
451 | else | ||
452 | { | ||
453 | Wix.IParentElement p = childElement as Wix.IParentElement; | ||
454 | if (p != null) | ||
455 | { | ||
456 | this.AddComponentsToComponentGroup(p, cg); | ||
457 | } | ||
458 | } | ||
459 | } | ||
460 | } | ||
461 | |||
462 | /// <summary> | ||
463 | /// Harvest files from one output group of a VS project. | ||
464 | /// </summary> | ||
465 | /// <param name="baseDir">The base directory of the files.</param> | ||
466 | /// <param name="projectName">Name of the project, to be used as a prefix for generated identifiers.</param> | ||
467 | /// <param name="pogName">Name of the project output group, used for generating identifiers for WiX elements.</param> | ||
468 | /// <param name="pogFileSource">The ProjectOutputGroup file source.</param> | ||
469 | /// <param name="outputGroupFiles">The files from one output group to harvest.</param> | ||
470 | /// <param name="parent">The parent element that will contain the components of the harvested files.</param> | ||
471 | /// <returns>The number of files harvested.</returns> | ||
472 | private int HarvestProjectOutputGroupFiles(string baseDir, string projectName, string pogName, string pogFileSource, IEnumerable outputGroupFiles, Wix.IParentElement parent) | ||
473 | { | ||
474 | int fileCount = 0; | ||
475 | |||
476 | Wix.ISchemaElement exeFile = null; | ||
477 | Wix.ISchemaElement dllFile = null; | ||
478 | Wix.ISchemaElement appConfigFile = null; | ||
479 | |||
480 | // Keep track of files inserted | ||
481 | // Files can have different absolute paths but get mapped to the same SourceFile | ||
482 | // after the project variables have been used. For example, a WiX project that | ||
483 | // is building multiple cultures will have many output MSIs/MSMs, but will all get | ||
484 | // mapped to $(var.ProjName.TargetDir)\ProjName.msm. These duplicates would | ||
485 | // prevent generated code from compiling. | ||
486 | Dictionary<string, bool> seenList = new Dictionary<string,bool>(); | ||
487 | |||
488 | foreach (object output in outputGroupFiles) | ||
489 | { | ||
490 | string filePath = output.ToString(); | ||
491 | string fileName = Path.GetFileName(filePath); | ||
492 | string fileDir = Path.GetDirectoryName(filePath); | ||
493 | string link = null; | ||
494 | |||
495 | MethodInfo getMetadataMethod = output.GetType().GetMethod("GetMetadata"); | ||
496 | if (getMetadataMethod != null) | ||
497 | { | ||
498 | link = (string)getMetadataMethod.Invoke(output, new object[] { "Link" }); | ||
499 | if (!String.IsNullOrEmpty(link)) | ||
500 | { | ||
501 | fileDir = Path.GetDirectoryName(Path.Combine(baseDir, link)); | ||
502 | } | ||
503 | } | ||
504 | |||
505 | Wix.IParentElement parentDir = parent; | ||
506 | // Ignore Containers and PayloadGroups because they do not have a nested structure. | ||
507 | if (baseDir != null && !String.Equals(Path.GetDirectoryName(baseDir), fileDir, StringComparison.OrdinalIgnoreCase) | ||
508 | && this.GenerateType != GenerateType.Container && this.GenerateType != GenerateType.PackageGroup && this.GenerateType != GenerateType.PayloadGroup) | ||
509 | { | ||
510 | Uri baseUri = new Uri(baseDir); | ||
511 | Uri relativeUri = baseUri.MakeRelativeUri(new Uri(fileDir)); | ||
512 | parentDir = this.GetSubDirElement(parentDir, relativeUri); | ||
513 | } | ||
514 | |||
515 | string parentDirId = null; | ||
516 | |||
517 | if (parentDir is Wix.DirectoryRef) | ||
518 | { | ||
519 | parentDirId = this.directoryRefSeed; | ||
520 | } | ||
521 | else if (parentDir is Wix.Directory) | ||
522 | { | ||
523 | parentDirId = ((Wix.Directory)parentDir).Id; | ||
524 | } | ||
525 | |||
526 | if (this.GenerateType == GenerateType.Container || this.GenerateType == GenerateType.PayloadGroup) | ||
527 | { | ||
528 | Wix.Payload payload = new Wix.Payload(); | ||
529 | |||
530 | this.HarvestProjectOutputGroupPayloadFile(baseDir, projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, payload, seenList); | ||
531 | } | ||
532 | else if (this.GenerateType == GenerateType.PackageGroup) | ||
533 | { | ||
534 | this.HarvestProjectOutputGroupPackage(projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, seenList); | ||
535 | } | ||
536 | else | ||
537 | { | ||
538 | Wix.Component component = new Wix.Component(); | ||
539 | Wix.File file = new Wix.File(); | ||
540 | |||
541 | this.HarvestProjectOutputGroupFile(baseDir, projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, parentDirId, component, file, seenList); | ||
542 | |||
543 | if (String.Equals(Path.GetExtension(file.Source), ".exe", StringComparison.OrdinalIgnoreCase)) | ||
544 | { | ||
545 | exeFile = file; | ||
546 | } | ||
547 | else if (String.Equals(Path.GetExtension(file.Source), ".dll", StringComparison.OrdinalIgnoreCase)) | ||
548 | { | ||
549 | dllFile = file; | ||
550 | } | ||
551 | else if (file.Source.EndsWith("app.config", StringComparison.OrdinalIgnoreCase)) | ||
552 | { | ||
553 | appConfigFile = file; | ||
554 | } | ||
555 | } | ||
556 | |||
557 | fileCount++; | ||
558 | } | ||
559 | |||
560 | // if there was no exe file found fallback on the dll file found | ||
561 | if (exeFile == null && dllFile != null) | ||
562 | { | ||
563 | exeFile = dllFile; | ||
564 | } | ||
565 | |||
566 | // Special case for the app.config file in the Binaries POG... | ||
567 | // The POG refers to the files in the OBJ directory, while the | ||
568 | // generated WiX code references them in the bin directory. | ||
569 | // The app.config file gets renamed to match the exe name. | ||
570 | if ("Binaries" == pogName && null != exeFile && null != appConfigFile) | ||
571 | { | ||
572 | if (appConfigFile is Wix.File) | ||
573 | { | ||
574 | Wix.File appConfigFileAsWixFile = appConfigFile as Wix.File; | ||
575 | Wix.File exeFileAsWixFile = exeFile as Wix.File; | ||
576 | // Case insensitive replace | ||
577 | appConfigFileAsWixFile.Source = Regex.Replace(appConfigFileAsWixFile.Source, @"app\.config", Path.GetFileName(exeFileAsWixFile.Source) + ".config", RegexOptions.IgnoreCase); | ||
578 | } | ||
579 | } | ||
580 | |||
581 | return fileCount; | ||
582 | } | ||
583 | |||
584 | private void HarvestProjectOutputGroupFile(string baseDir, string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, string parentDirId, Wix.Component component, Wix.File file, Dictionary<string, bool> seenList) | ||
585 | { | ||
586 | string varFormat = VariableFormat; | ||
587 | if (this.generateWixVars) | ||
588 | { | ||
589 | varFormat = WixVariableFormat; | ||
590 | } | ||
591 | |||
592 | if (pogName.Equals("Satellites", StringComparison.OrdinalIgnoreCase)) | ||
593 | { | ||
594 | Wix.Directory locDirectory = new Wix.Directory(); | ||
595 | |||
596 | locDirectory.Name = Path.GetFileName(Path.GetDirectoryName(Path.GetFullPath(filePath))); | ||
597 | file.Source = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", locDirectory.Name, "\\", Path.GetFileName(filePath)); | ||
598 | |||
599 | if (!seenList.ContainsKey(file.Source)) | ||
600 | { | ||
601 | parentDir.AddChild(locDirectory); | ||
602 | locDirectory.AddChild(component); | ||
603 | component.AddChild(file); | ||
604 | seenList.Add(file.Source, true); | ||
605 | |||
606 | if (this.setUniqueIdentifiers) | ||
607 | { | ||
608 | locDirectory.Id = this.Core.GenerateIdentifier(DirectoryPrefix, parentDirId, locDirectory.Name); | ||
609 | file.Id = this.Core.GenerateIdentifier(FilePrefix, locDirectory.Id, fileName); | ||
610 | component.Id = this.Core.GenerateIdentifier(ComponentPrefix, locDirectory.Id, file.Id); | ||
611 | } | ||
612 | else | ||
613 | { | ||
614 | locDirectory.Id = this.Core.CreateIdentifierFromFilename(String.Format(DirectoryIdFormat, (parentDir is Wix.DirectoryRef) ? ((Wix.DirectoryRef)parentDir).Id : parentDirId, locDirectory.Name)); | ||
615 | file.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.FileIdFormat, projectName, pogName, String.Concat(locDirectory.Name, ".", fileName))); | ||
616 | component.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.ComponentIdFormat, projectName, pogName, String.Concat(locDirectory.Name, ".", fileName))); | ||
617 | } | ||
618 | } | ||
619 | } | ||
620 | else | ||
621 | { | ||
622 | file.Source = GenerateSourceFilePath(baseDir, projectName, pogFileSource, filePath, link, varFormat); | ||
623 | |||
624 | if (!seenList.ContainsKey(file.Source)) | ||
625 | { | ||
626 | component.AddChild(file); | ||
627 | parentDir.AddChild(component); | ||
628 | seenList.Add(file.Source, true); | ||
629 | |||
630 | if (this.setUniqueIdentifiers) | ||
631 | { | ||
632 | file.Id = this.Core.GenerateIdentifier(FilePrefix, parentDirId, fileName); | ||
633 | component.Id = this.Core.GenerateIdentifier(ComponentPrefix, parentDirId, file.Id); | ||
634 | } | ||
635 | else | ||
636 | { | ||
637 | file.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.FileIdFormat, projectName, pogName, fileName)); | ||
638 | component.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.ComponentIdFormat, projectName, pogName, fileName)); | ||
639 | } | ||
640 | } | ||
641 | } | ||
642 | } | ||
643 | |||
644 | private void HarvestProjectOutputGroupPackage(string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, Dictionary<string, bool> seenList) | ||
645 | { | ||
646 | string varFormat = VariableFormat; | ||
647 | if (this.generateWixVars) | ||
648 | { | ||
649 | varFormat = WixVariableFormat; | ||
650 | } | ||
651 | |||
652 | if (pogName.Equals("Binaries", StringComparison.OrdinalIgnoreCase)) | ||
653 | { | ||
654 | if (String.Equals(Path.GetExtension(filePath), ".exe", StringComparison.OrdinalIgnoreCase)) | ||
655 | { | ||
656 | Wix.ExePackage exePackage = new Wix.ExePackage(); | ||
657 | exePackage.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath)); | ||
658 | if (!seenList.ContainsKey(exePackage.SourceFile)) | ||
659 | { | ||
660 | parentDir.AddChild(exePackage); | ||
661 | seenList.Add(exePackage.SourceFile, true); | ||
662 | } | ||
663 | } | ||
664 | else if (String.Equals(Path.GetExtension(filePath), ".msi", StringComparison.OrdinalIgnoreCase)) | ||
665 | { | ||
666 | Wix.MsiPackage msiPackage = new Wix.MsiPackage(); | ||
667 | msiPackage.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath)); | ||
668 | if (!seenList.ContainsKey(msiPackage.SourceFile)) | ||
669 | { | ||
670 | parentDir.AddChild(msiPackage); | ||
671 | seenList.Add(msiPackage.SourceFile, true); | ||
672 | } | ||
673 | } | ||
674 | } | ||
675 | } | ||
676 | |||
677 | private void HarvestProjectOutputGroupPayloadFile(string baseDir, string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, Wix.Payload file, Dictionary<string, bool> seenList) | ||
678 | { | ||
679 | string varFormat = VariableFormat; | ||
680 | if (this.generateWixVars) | ||
681 | { | ||
682 | varFormat = WixVariableFormat; | ||
683 | } | ||
684 | |||
685 | if (pogName.Equals("Satellites", StringComparison.OrdinalIgnoreCase)) | ||
686 | { | ||
687 | string locDirectoryName = Path.GetFileName(Path.GetDirectoryName(Path.GetFullPath(filePath))); | ||
688 | file.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", locDirectoryName, "\\", Path.GetFileName(filePath)); | ||
689 | |||
690 | if (!seenList.ContainsKey(file.SourceFile)) | ||
691 | { | ||
692 | parentDir.AddChild(file); | ||
693 | seenList.Add(file.SourceFile, true); | ||
694 | } | ||
695 | } | ||
696 | else | ||
697 | { | ||
698 | file.SourceFile = GenerateSourceFilePath(baseDir, projectName, pogFileSource, filePath, link, varFormat); | ||
699 | |||
700 | if (!seenList.ContainsKey(file.SourceFile)) | ||
701 | { | ||
702 | parentDir.AddChild(file); | ||
703 | seenList.Add(file.SourceFile, true); | ||
704 | } | ||
705 | } | ||
706 | } | ||
707 | |||
708 | /// <summary> | ||
709 | /// Helper function to generates a source file path when harvesting files. | ||
710 | /// </summary> | ||
711 | /// <param name="baseDir"></param> | ||
712 | /// <param name="projectName"></param> | ||
713 | /// <param name="pogFileSource"></param> | ||
714 | /// <param name="filePath"></param> | ||
715 | /// <param name="link"></param> | ||
716 | /// <param name="varFormat"></param> | ||
717 | /// <returns></returns> | ||
718 | private static string GenerateSourceFilePath(string baseDir, string projectName, string pogFileSource, string filePath, string link, string varFormat) | ||
719 | { | ||
720 | string ret; | ||
721 | |||
722 | if (null == baseDir && !String.IsNullOrEmpty(link)) | ||
723 | { | ||
724 | // This needs to be the absolute path as a link can be located anywhere. | ||
725 | ret = filePath; | ||
726 | } | ||
727 | else if (null == baseDir) | ||
728 | { | ||
729 | ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath)); | ||
730 | } | ||
731 | else if (filePath.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase)) | ||
732 | { | ||
733 | ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", filePath.Substring(baseDir.Length)); | ||
734 | } | ||
735 | else | ||
736 | { | ||
737 | // come up with a relative path to the file | ||
738 | Uri sourcePathUri = new Uri(filePath); | ||
739 | Uri baseDirUri = new Uri(baseDir); | ||
740 | Uri sourceRelativeUri = baseDirUri.MakeRelativeUri(sourcePathUri); | ||
741 | string relativePath = sourceRelativeUri.ToString().Replace('/', Path.DirectorySeparatorChar); | ||
742 | if (!sourceRelativeUri.UserEscaped) | ||
743 | { | ||
744 | relativePath = Uri.UnescapeDataString(relativePath); | ||
745 | } | ||
746 | |||
747 | ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", relativePath); | ||
748 | } | ||
749 | |||
750 | return ret; | ||
751 | } | ||
752 | |||
753 | /// <summary> | ||
754 | /// Gets a Directory element corresponding to a relative subdirectory within the project, | ||
755 | /// either by locating a suitable existing Directory or creating a new one. | ||
756 | /// </summary> | ||
757 | /// <param name="parentDir">The parent element which the subdirectory is relative to.</param> | ||
758 | /// <param name="relativeUri">Relative path of the subdirectory.</param> | ||
759 | /// <returns>Directory element for the relative path.</returns> | ||
760 | private Wix.IParentElement GetSubDirElement(Wix.IParentElement parentDir, Uri relativeUri) | ||
761 | { | ||
762 | string[] segments = relativeUri.ToString().Split('\\', '/'); | ||
763 | string firstSubDirName = Uri.UnescapeDataString(segments[0]); | ||
764 | DirectoryAttributeAccessor subDir = null; | ||
765 | |||
766 | if (String.Equals(firstSubDirName, "..", StringComparison.Ordinal)) | ||
767 | { | ||
768 | return parentDir; | ||
769 | } | ||
770 | |||
771 | Type directoryType; | ||
772 | Type directoryRefType; | ||
773 | if (parentDir is Wix.Directory || parentDir is Wix.DirectoryRef) | ||
774 | { | ||
775 | directoryType = typeof(Wix.Directory); | ||
776 | directoryRefType = typeof(Wix.DirectoryRef); | ||
777 | } | ||
778 | else | ||
779 | { | ||
780 | throw new ArgumentException("GetSubDirElement parentDir"); | ||
781 | } | ||
782 | |||
783 | // Search for an existing directory element. | ||
784 | foreach (Wix.ISchemaElement childElement in parentDir.Children) | ||
785 | { | ||
786 | if(VSProjectHarvester.AreTypesEquivalent(childElement.GetType(), directoryType)) | ||
787 | { | ||
788 | DirectoryAttributeAccessor childDir = new DirectoryAttributeAccessor(childElement); | ||
789 | if (String.Equals(childDir.Name, firstSubDirName, StringComparison.OrdinalIgnoreCase)) | ||
790 | { | ||
791 | subDir = childDir; | ||
792 | break; | ||
793 | } | ||
794 | } | ||
795 | } | ||
796 | |||
797 | if (subDir == null) | ||
798 | { | ||
799 | string parentId = null; | ||
800 | DirectoryAttributeAccessor parentDirectory = null; | ||
801 | DirectoryAttributeAccessor parentDirectoryRef = null; | ||
802 | |||
803 | if (VSProjectHarvester.AreTypesEquivalent(parentDir.GetType(), directoryType)) | ||
804 | { | ||
805 | parentDirectory = new DirectoryAttributeAccessor((Wix.ISchemaElement)parentDir); | ||
806 | } | ||
807 | else if (VSProjectHarvester.AreTypesEquivalent(parentDir.GetType(), directoryRefType)) | ||
808 | { | ||
809 | parentDirectoryRef = new DirectoryAttributeAccessor((Wix.ISchemaElement)parentDir); | ||
810 | } | ||
811 | |||
812 | if (parentDirectory != null) | ||
813 | { | ||
814 | parentId = parentDirectory.Id; | ||
815 | } | ||
816 | else if (parentDirectoryRef != null) | ||
817 | { | ||
818 | if (this.setUniqueIdentifiers) | ||
819 | { | ||
820 | //Use the GUID of the project instead of the project name to help keep things stable. | ||
821 | parentId = this.directoryRefSeed; | ||
822 | } | ||
823 | else | ||
824 | { | ||
825 | parentId = parentDirectoryRef.Id; | ||
826 | } | ||
827 | } | ||
828 | |||
829 | Wix.ISchemaElement newDirectory = (Wix.ISchemaElement)directoryType.GetConstructor(new Type[] { }).Invoke(null); | ||
830 | subDir = new DirectoryAttributeAccessor(newDirectory); | ||
831 | |||
832 | if (this.setUniqueIdentifiers) | ||
833 | { | ||
834 | subDir.Id = this.Core.GenerateIdentifier(DirectoryPrefix, parentId, firstSubDirName); | ||
835 | } | ||
836 | else | ||
837 | { | ||
838 | subDir.Id = String.Format(DirectoryIdFormat, parentId, firstSubDirName); | ||
839 | } | ||
840 | |||
841 | subDir.Name = firstSubDirName; | ||
842 | |||
843 | parentDir.AddChild(subDir.Element); | ||
844 | } | ||
845 | |||
846 | if (segments.Length == 1) | ||
847 | { | ||
848 | return subDir.ElementAsParent; | ||
849 | } | ||
850 | else | ||
851 | { | ||
852 | Uri nextRelativeUri = new Uri(Uri.UnescapeDataString(relativeUri.ToString()).Substring(firstSubDirName.Length + 1), UriKind.Relative); | ||
853 | return this.GetSubDirElement(subDir.ElementAsParent, nextRelativeUri); | ||
854 | } | ||
855 | } | ||
856 | |||
857 | private MSBuildProject GetMsbuildProject(string projectFile) | ||
858 | { | ||
859 | XmlDocument document = new XmlDocument(); | ||
860 | try | ||
861 | { | ||
862 | document.Load(projectFile); | ||
863 | } | ||
864 | catch (Exception e) | ||
865 | { | ||
866 | throw new WixException(HarvesterErrors.CannotLoadProject(projectFile, e.Message)); | ||
867 | } | ||
868 | |||
869 | string version = null; | ||
870 | |||
871 | if (this.UseToolsVersion) | ||
872 | { | ||
873 | foreach (XmlNode child in document.ChildNodes) | ||
874 | { | ||
875 | if (String.Equals(child.Name, "Project", StringComparison.Ordinal) && child.Attributes != null) | ||
876 | { | ||
877 | XmlNode toolsVersionAttribute = child.Attributes["ToolsVersion"]; | ||
878 | if (toolsVersionAttribute != null) | ||
879 | { | ||
880 | version = toolsVersionAttribute.Value; | ||
881 | this.Core.Messaging.Write(HarvesterVerboses.FoundToolsVersion(version)); | ||
882 | |||
883 | break; | ||
884 | } | ||
885 | } | ||
886 | } | ||
887 | |||
888 | switch (version) | ||
889 | { | ||
890 | case "4.0": | ||
891 | version = "4.0.0.0"; | ||
892 | break; | ||
893 | case "12.0": | ||
894 | version = "12.0.0.0"; | ||
895 | break; | ||
896 | case "14.0": | ||
897 | version = "14.0.0.0"; | ||
898 | break; | ||
899 | default: | ||
900 | if (String.IsNullOrEmpty(this.MsbuildBinPath)) | ||
901 | { | ||
902 | throw new WixException(HarvesterErrors.MsbuildBinPathRequired(version ?? "(none)")); | ||
903 | } | ||
904 | |||
905 | version = null; | ||
906 | break; | ||
907 | } | ||
908 | } | ||
909 | |||
910 | var project = this.ConstructMsbuild40Project(version); | ||
911 | return project; | ||
912 | } | ||
913 | |||
914 | private Assembly ResolveFromMsbuildBinPath(object sender, ResolveEventArgs args) | ||
915 | { | ||
916 | var assemblyName = new AssemblyName(args.Name); | ||
917 | |||
918 | var assemblyPath = Path.Combine(this.MsbuildBinPath, $"{assemblyName.Name}.dll"); | ||
919 | if (!File.Exists(assemblyPath)) | ||
920 | { | ||
921 | return null; | ||
922 | } | ||
923 | |||
924 | return Assembly.LoadFrom(assemblyPath); | ||
925 | } | ||
926 | |||
927 | private MSBuildProject ConstructMsbuild40Project(string loadVersion) | ||
928 | { | ||
929 | const string MSBuildEngineAssemblyName = "Microsoft.Build, Version={0}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; | ||
930 | const string MSBuildFrameworkAssemblyName = "Microsoft.Build.Framework, Version={0}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; | ||
931 | Assembly msbuildAssembly; | ||
932 | Assembly msbuildFrameworkAssembly; | ||
933 | |||
934 | if (loadVersion == null) | ||
935 | { | ||
936 | this.Core.Messaging.Write(HarvesterVerboses.LoadingProjectWithBinPath(this.MsbuildBinPath)); | ||
937 | AppDomain.CurrentDomain.AssemblyResolve += this.ResolveFromMsbuildBinPath; | ||
938 | |||
939 | try | ||
940 | { | ||
941 | msbuildAssembly = Assembly.Load("Microsoft.Build"); | ||
942 | } | ||
943 | catch (Exception e) | ||
944 | { | ||
945 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
946 | } | ||
947 | |||
948 | try | ||
949 | { | ||
950 | msbuildFrameworkAssembly = Assembly.Load("Microsoft.Build.Framework"); | ||
951 | } | ||
952 | catch (Exception e) | ||
953 | { | ||
954 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
955 | } | ||
956 | } | ||
957 | else | ||
958 | { | ||
959 | this.Core.Messaging.Write(HarvesterVerboses.LoadingProjectWithVersion(loadVersion)); | ||
960 | |||
961 | try | ||
962 | { | ||
963 | msbuildAssembly = Assembly.Load(String.Format(MSBuildEngineAssemblyName, loadVersion)); | ||
964 | } | ||
965 | catch (Exception e) | ||
966 | { | ||
967 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
968 | } | ||
969 | |||
970 | try | ||
971 | { | ||
972 | msbuildFrameworkAssembly = Assembly.Load(String.Format(MSBuildFrameworkAssemblyName, loadVersion)); | ||
973 | } | ||
974 | catch (Exception e) | ||
975 | { | ||
976 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
977 | } | ||
978 | } | ||
979 | |||
980 | Type projectType; | ||
981 | Type buildItemType; | ||
982 | |||
983 | Type buildManagerType; | ||
984 | Type buildParametersType; | ||
985 | Type buildRequestDataFlagsType; | ||
986 | Type buildRequestDataType; | ||
987 | Type hostServicesType; | ||
988 | Type projectCollectionType; | ||
989 | Type projectInstanceType; | ||
990 | |||
991 | Type writeHandlerType; | ||
992 | Type colorSetterType; | ||
993 | Type colorResetterType; | ||
994 | Type loggerVerbosityType; | ||
995 | Type consoleLoggerType; | ||
996 | Type iLoggerType; | ||
997 | |||
998 | try | ||
999 | { | ||
1000 | buildItemType = msbuildAssembly.GetType("Microsoft.Build.Execution.ProjectItemInstance", true); | ||
1001 | projectType = msbuildAssembly.GetType("Microsoft.Build.Evaluation.Project", true); | ||
1002 | |||
1003 | buildManagerType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildManager", true); | ||
1004 | buildParametersType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildParameters", true); | ||
1005 | buildRequestDataFlagsType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildRequestDataFlags", true); | ||
1006 | buildRequestDataType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildRequestData", true); | ||
1007 | hostServicesType = msbuildAssembly.GetType("Microsoft.Build.Execution.HostServices", true); | ||
1008 | projectCollectionType = msbuildAssembly.GetType("Microsoft.Build.Evaluation.ProjectCollection", true); | ||
1009 | projectInstanceType = msbuildAssembly.GetType("Microsoft.Build.Execution.ProjectInstance", true); | ||
1010 | |||
1011 | writeHandlerType = msbuildAssembly.GetType("Microsoft.Build.Logging.WriteHandler", true); | ||
1012 | colorSetterType = msbuildAssembly.GetType("Microsoft.Build.Logging.ColorSetter", true); | ||
1013 | colorResetterType = msbuildAssembly.GetType("Microsoft.Build.Logging.ColorResetter", true); | ||
1014 | loggerVerbosityType = msbuildFrameworkAssembly.GetType("Microsoft.Build.Framework.LoggerVerbosity", true); | ||
1015 | consoleLoggerType = msbuildAssembly.GetType("Microsoft.Build.Logging.ConsoleLogger", true); | ||
1016 | iLoggerType = msbuildFrameworkAssembly.GetType("Microsoft.Build.Framework.ILogger", true); | ||
1017 | } | ||
1018 | catch (TargetInvocationException tie) | ||
1019 | { | ||
1020 | throw new WixException(HarvesterErrors.CannotLoadMSBuildEngine(tie.InnerException.Message)); | ||
1021 | } | ||
1022 | catch (Exception e) | ||
1023 | { | ||
1024 | throw new WixException(HarvesterErrors.CannotLoadMSBuildEngine(e.Message)); | ||
1025 | } | ||
1026 | |||
1027 | MSBuild40Types types = new MSBuild40Types(); | ||
1028 | types.buildManagerType = buildManagerType; | ||
1029 | types.buildParametersType = buildParametersType; | ||
1030 | types.buildRequestDataFlagsType = buildRequestDataFlagsType; | ||
1031 | types.buildRequestDataType = buildRequestDataType; | ||
1032 | types.hostServicesType = hostServicesType; | ||
1033 | types.projectCollectionType = projectCollectionType; | ||
1034 | types.projectInstanceType = projectInstanceType; | ||
1035 | types.writeHandlerType = writeHandlerType; | ||
1036 | types.colorSetterType = colorSetterType; | ||
1037 | types.colorResetterType = colorResetterType; | ||
1038 | types.loggerVerbosityType = loggerVerbosityType; | ||
1039 | types.consoleLoggerType = consoleLoggerType; | ||
1040 | types.iLoggerType = iLoggerType; | ||
1041 | return new MSBuild40Project(null, projectType, buildItemType, loadVersion, types, this.Core, this.configuration, this.platform); | ||
1042 | } | ||
1043 | |||
1044 | private static bool AreTypesEquivalent(Type a, Type b) | ||
1045 | { | ||
1046 | return (a == b) || (a.IsAssignableFrom(b) && b.IsAssignableFrom(a)); | ||
1047 | } | ||
1048 | |||
1049 | private abstract class MSBuildProject | ||
1050 | { | ||
1051 | protected Type projectType; | ||
1052 | protected Type buildItemType; | ||
1053 | protected object project; | ||
1054 | private string loadVersion; | ||
1055 | |||
1056 | public MSBuildProject(object project, Type projectType, Type buildItemType, string loadVersion) | ||
1057 | { | ||
1058 | this.project = project; | ||
1059 | this.projectType = projectType; | ||
1060 | this.buildItemType = buildItemType; | ||
1061 | this.loadVersion = loadVersion; | ||
1062 | } | ||
1063 | |||
1064 | public string LoadVersion | ||
1065 | { | ||
1066 | get { return this.loadVersion; } | ||
1067 | } | ||
1068 | |||
1069 | public abstract bool Build(string projectFileName, string[] targetNames, IDictionary targetOutputs); | ||
1070 | |||
1071 | public abstract MSBuildProjectItemType GetBuildItem(object buildItem); | ||
1072 | |||
1073 | public abstract IEnumerable GetEvaluatedItemsByName(string itemName); | ||
1074 | |||
1075 | public abstract string GetEvaluatedProperty(string propertyName); | ||
1076 | |||
1077 | public abstract void Load(string projectFileName); | ||
1078 | } | ||
1079 | |||
1080 | private abstract class MSBuildProjectItemType | ||
1081 | { | ||
1082 | public MSBuildProjectItemType(object buildItem) | ||
1083 | { | ||
1084 | this.buildItem = buildItem; | ||
1085 | } | ||
1086 | |||
1087 | public abstract override string ToString(); | ||
1088 | |||
1089 | public abstract string GetMetadata(string name); | ||
1090 | |||
1091 | protected object buildItem; | ||
1092 | } | ||
1093 | |||
1094 | |||
1095 | private struct MSBuild40Types | ||
1096 | { | ||
1097 | public Type buildManagerType; | ||
1098 | public Type buildParametersType; | ||
1099 | public Type buildRequestDataFlagsType; | ||
1100 | public Type buildRequestDataType; | ||
1101 | public Type hostServicesType; | ||
1102 | public Type projectCollectionType; | ||
1103 | public Type projectInstanceType; | ||
1104 | public Type writeHandlerType; | ||
1105 | public Type colorSetterType; | ||
1106 | public Type colorResetterType; | ||
1107 | public Type loggerVerbosityType; | ||
1108 | public Type consoleLoggerType; | ||
1109 | public Type iLoggerType; | ||
1110 | } | ||
1111 | |||
1112 | private class MSBuild40Project : MSBuildProject | ||
1113 | { | ||
1114 | private MSBuild40Types types; | ||
1115 | private object projectCollection; | ||
1116 | private object currentProjectInstance; | ||
1117 | private object buildManager; | ||
1118 | private object buildParameters; | ||
1119 | private IHarvesterCore harvesterCore; | ||
1120 | |||
1121 | public MSBuild40Project(object project, Type projectType, Type buildItemType, string loadVersion, MSBuild40Types types, IHarvesterCore harvesterCore, string configuration, string platform) | ||
1122 | : base(project, projectType, buildItemType, loadVersion) | ||
1123 | { | ||
1124 | this.types = types; | ||
1125 | this.harvesterCore = harvesterCore; | ||
1126 | |||
1127 | this.buildParameters = this.types.buildParametersType.GetConstructor(new Type[] { }).Invoke(null); | ||
1128 | |||
1129 | try | ||
1130 | { | ||
1131 | var loggers = this.CreateLoggers(); | ||
1132 | |||
1133 | // this.buildParameters.Loggers = loggers; | ||
1134 | this.types.buildParametersType.GetProperty("Loggers").SetValue(this.buildParameters, loggers, null); | ||
1135 | } | ||
1136 | catch (TargetInvocationException tie) | ||
1137 | { | ||
1138 | if (this.harvesterCore != null) | ||
1139 | { | ||
1140 | this.harvesterCore.Messaging.Write(HarvesterWarnings.NoLogger(tie.InnerException.Message)); | ||
1141 | } | ||
1142 | } | ||
1143 | catch (Exception e) | ||
1144 | { | ||
1145 | if (this.harvesterCore != null) | ||
1146 | { | ||
1147 | this.harvesterCore.Messaging.Write(HarvesterWarnings.NoLogger(e.Message)); | ||
1148 | } | ||
1149 | } | ||
1150 | |||
1151 | this.buildManager = this.types.buildManagerType.GetConstructor(new Type[] { }).Invoke(null); | ||
1152 | |||
1153 | if (configuration != null || platform != null) | ||
1154 | { | ||
1155 | Dictionary<string, string> globalVariables = new Dictionary<string, string>(); | ||
1156 | if (configuration != null) | ||
1157 | { | ||
1158 | globalVariables.Add("Configuration", configuration); | ||
1159 | } | ||
1160 | |||
1161 | if (platform != null) | ||
1162 | { | ||
1163 | globalVariables.Add("Platform", platform); | ||
1164 | } | ||
1165 | |||
1166 | this.projectCollection = this.types.projectCollectionType.GetConstructor(new Type[] { typeof(IDictionary<string, string>) }).Invoke(new object[] { globalVariables }); | ||
1167 | } | ||
1168 | else | ||
1169 | { | ||
1170 | this.projectCollection = this.types.projectCollectionType.GetConstructor(new Type[] {}).Invoke(null); | ||
1171 | } | ||
1172 | } | ||
1173 | |||
1174 | private object CreateLoggers() | ||
1175 | { | ||
1176 | var logger = new HarvestLogger(this.harvesterCore.Messaging); | ||
1177 | var loggerVerbosity = Enum.Parse(this.types.loggerVerbosityType, "Minimal"); | ||
1178 | var writeHandler = Delegate.CreateDelegate(this.types.writeHandlerType, logger, nameof(logger.LogMessage)); | ||
1179 | var colorSetter = Delegate.CreateDelegate(this.types.colorSetterType, logger, nameof(logger.SetColor)); | ||
1180 | var colorResetter = Delegate.CreateDelegate(this.types.colorResetterType, logger, nameof(logger.ResetColor)); | ||
1181 | |||
1182 | var consoleLoggerCtor = this.types.consoleLoggerType.GetConstructor(new Type[] { | ||
1183 | this.types.loggerVerbosityType, | ||
1184 | this.types.writeHandlerType, | ||
1185 | this.types.colorSetterType, | ||
1186 | this.types.colorResetterType, | ||
1187 | }); | ||
1188 | var consoleLogger = consoleLoggerCtor.Invoke(new object[] { loggerVerbosity, writeHandler, colorSetter, colorResetter }); | ||
1189 | |||
1190 | var loggers = Array.CreateInstance(this.types.iLoggerType, 1); | ||
1191 | loggers.SetValue(consoleLogger, 0); | ||
1192 | |||
1193 | return loggers; | ||
1194 | } | ||
1195 | |||
1196 | public override bool Build(string projectFileName, string[] targetNames, IDictionary targetOutputs) | ||
1197 | { | ||
1198 | try | ||
1199 | { | ||
1200 | // this.buildManager.BeginBuild(this.buildParameters); | ||
1201 | this.types.buildManagerType.GetMethod("BeginBuild", new Type[] { this.types.buildParametersType }).Invoke(this.buildManager, new object[] { this.buildParameters }); | ||
1202 | |||
1203 | // buildRequestData = new BuildRequestData(this.currentProjectInstance, targetNames, null, BuildRequestData.BuildRequestDataFlags.ReplaceExistingProjectInstance); | ||
1204 | ConstructorInfo buildRequestDataCtor = this.types.buildRequestDataType.GetConstructor( | ||
1205 | new Type[] | ||
1206 | { | ||
1207 | this.types.projectInstanceType, typeof(string[]), this.types.hostServicesType, this.types.buildRequestDataFlagsType | ||
1208 | }); | ||
1209 | object buildRequestDataFlags = this.types.buildRequestDataFlagsType.GetField("ReplaceExistingProjectInstance").GetRawConstantValue(); | ||
1210 | object buildRequestData = buildRequestDataCtor.Invoke(new object[] { this.currentProjectInstance, targetNames, null, buildRequestDataFlags }); | ||
1211 | |||
1212 | // BuildSubmission submission = this.buildManager.PendBuildRequest(buildRequestData); | ||
1213 | object submission = this.types.buildManagerType.GetMethod("PendBuildRequest", new Type[] { this.types.buildRequestDataType }) | ||
1214 | .Invoke(this.buildManager, new object[] { buildRequestData }); | ||
1215 | |||
1216 | // BuildResult buildResult = submission.Execute(); | ||
1217 | object buildResult = submission.GetType().GetMethod("Execute", new Type[] { }).Invoke(submission, null); | ||
1218 | |||
1219 | // bool buildSucceeded = buildResult.OverallResult == BuildResult.Success; | ||
1220 | object overallResult = buildResult.GetType().GetProperty("OverallResult").GetValue(buildResult, null); | ||
1221 | bool buildSucceeded = String.Equals(overallResult.ToString(), "Success", StringComparison.Ordinal); | ||
1222 | |||
1223 | // this.buildManager.EndBuild(); | ||
1224 | this.types.buildManagerType.GetMethod("EndBuild", new Type[] { }).Invoke(this.buildManager, null); | ||
1225 | |||
1226 | // fill in empty lists for each target so that heat will look at the item group later | ||
1227 | foreach (string target in targetNames) | ||
1228 | { | ||
1229 | targetOutputs.Add(target, new List<object>()); | ||
1230 | } | ||
1231 | |||
1232 | return buildSucceeded; | ||
1233 | } | ||
1234 | catch (TargetInvocationException tie) | ||
1235 | { | ||
1236 | throw new WixException(HarvesterErrors.CannotBuildProject(projectFileName, tie.InnerException.Message)); | ||
1237 | } | ||
1238 | catch (Exception e) | ||
1239 | { | ||
1240 | throw new WixException(HarvesterErrors.CannotBuildProject(projectFileName, e.Message)); | ||
1241 | } | ||
1242 | } | ||
1243 | |||
1244 | public override MSBuildProjectItemType GetBuildItem(object buildItem) | ||
1245 | { | ||
1246 | return new MSBuild40ProjectItemType(buildItem); | ||
1247 | } | ||
1248 | |||
1249 | public override IEnumerable GetEvaluatedItemsByName(string itemName) | ||
1250 | { | ||
1251 | MethodInfo getEvaluatedItem = this.types.projectInstanceType.GetMethod("GetItems", new Type[] { typeof(string) }); | ||
1252 | return (IEnumerable)getEvaluatedItem.Invoke(this.currentProjectInstance, new object[] { itemName }); | ||
1253 | } | ||
1254 | |||
1255 | public override string GetEvaluatedProperty(string propertyName) | ||
1256 | { | ||
1257 | MethodInfo getProperty = this.types.projectInstanceType.GetMethod("GetPropertyValue", new Type[] { typeof(string) }); | ||
1258 | return (string)getProperty.Invoke(this.currentProjectInstance, new object[] { propertyName }); | ||
1259 | } | ||
1260 | |||
1261 | public override void Load(string projectFileName) | ||
1262 | { | ||
1263 | try | ||
1264 | { | ||
1265 | //this.project = this.projectCollection.LoadProject(projectFileName); | ||
1266 | this.project = this.types.projectCollectionType.GetMethod("LoadProject", new Type[] { typeof(string) }).Invoke(this.projectCollection, new object[] { projectFileName }); | ||
1267 | |||
1268 | // this.currentProjectInstance = this.project.CreateProjectInstance(); | ||
1269 | MethodInfo createProjectInstanceMethod = this.projectType.GetMethod("CreateProjectInstance", new Type[] { }); | ||
1270 | this.currentProjectInstance = createProjectInstanceMethod.Invoke(this.project, null); | ||
1271 | } | ||
1272 | catch (TargetInvocationException tie) | ||
1273 | { | ||
1274 | throw new WixException(HarvesterErrors.CannotLoadProject(projectFileName, tie.InnerException.Message)); | ||
1275 | } | ||
1276 | catch (Exception e) | ||
1277 | { | ||
1278 | throw new WixException(HarvesterErrors.CannotLoadProject(projectFileName, e.Message)); | ||
1279 | } | ||
1280 | } | ||
1281 | } | ||
1282 | |||
1283 | private class MSBuild40ProjectItemType : MSBuildProjectItemType | ||
1284 | { | ||
1285 | public MSBuild40ProjectItemType(object buildItem) | ||
1286 | : base(buildItem) | ||
1287 | { | ||
1288 | } | ||
1289 | |||
1290 | public override string ToString() | ||
1291 | { | ||
1292 | PropertyInfo includeProperty = this.buildItem.GetType().GetProperty("EvaluatedInclude"); | ||
1293 | return (string)includeProperty.GetValue(this.buildItem, null); | ||
1294 | } | ||
1295 | |||
1296 | public override string GetMetadata(string name) | ||
1297 | { | ||
1298 | MethodInfo getMetadataMethod = this.buildItem.GetType().GetMethod("GetMetadataValue"); | ||
1299 | if (null != getMetadataMethod) | ||
1300 | { | ||
1301 | return (string)getMetadataMethod.Invoke(this.buildItem, new object[] { name }); | ||
1302 | } | ||
1303 | return string.Empty; | ||
1304 | } | ||
1305 | } | ||
1306 | |||
1307 | /// <summary> | ||
1308 | /// Used internally in the VSProjectHarvester class to encapsulate | ||
1309 | /// the settings for a particular MSBuild "project output group". | ||
1310 | /// </summary> | ||
1311 | private struct ProjectOutputGroup | ||
1312 | { | ||
1313 | public readonly string Name; | ||
1314 | public readonly string BuildOutputGroup; | ||
1315 | public readonly string FileSource; | ||
1316 | |||
1317 | /// <summary> | ||
1318 | /// Creates a new project output group. | ||
1319 | /// </summary> | ||
1320 | /// <param name="name">Friendly name used by heat.</param> | ||
1321 | /// <param name="buildOutputGroup">MSBuild's name of the project output group.</param> | ||
1322 | /// <param name="fileSource">VS directory token containing the files of the POG.</param> | ||
1323 | public ProjectOutputGroup(string name, string buildOutputGroup, string fileSource) | ||
1324 | { | ||
1325 | this.Name = name; | ||
1326 | this.BuildOutputGroup = buildOutputGroup; | ||
1327 | this.FileSource = fileSource; | ||
1328 | } | ||
1329 | } | ||
1330 | |||
1331 | /// <summary> | ||
1332 | /// Internal class for getting and setting common attrbiutes on | ||
1333 | /// directory elements. | ||
1334 | /// </summary> | ||
1335 | internal class DirectoryAttributeAccessor | ||
1336 | { | ||
1337 | public Wix.ISchemaElement directoryElement; | ||
1338 | |||
1339 | public DirectoryAttributeAccessor(Wix.ISchemaElement directoryElement) | ||
1340 | { | ||
1341 | this.directoryElement = directoryElement; | ||
1342 | } | ||
1343 | |||
1344 | /// <summary> | ||
1345 | /// Gets the element as a ISchemaElement. | ||
1346 | /// </summary> | ||
1347 | public Wix.ISchemaElement Element | ||
1348 | { | ||
1349 | get { return this.directoryElement; } | ||
1350 | } | ||
1351 | |||
1352 | /// <summary> | ||
1353 | /// Gets the element as a IParentElement. | ||
1354 | /// </summary> | ||
1355 | public Wix.IParentElement ElementAsParent | ||
1356 | { | ||
1357 | get { return (Wix.IParentElement)this.directoryElement; } | ||
1358 | } | ||
1359 | |||
1360 | /// <summary> | ||
1361 | /// Gets or sets the Id attrbiute. | ||
1362 | /// </summary> | ||
1363 | public string Id | ||
1364 | { | ||
1365 | get | ||
1366 | { | ||
1367 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1368 | { | ||
1369 | return wixDirectory.Id; | ||
1370 | } | ||
1371 | else if (this.directoryElement is Wix.DirectoryRef wixDirectoryRef) | ||
1372 | { | ||
1373 | return wixDirectoryRef.Id; | ||
1374 | } | ||
1375 | else | ||
1376 | { | ||
1377 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Id")); | ||
1378 | } | ||
1379 | } | ||
1380 | set | ||
1381 | { | ||
1382 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1383 | { | ||
1384 | wixDirectory.Id = value; | ||
1385 | } | ||
1386 | else if (this.directoryElement is Wix.DirectoryRef wixDirectoryRef) | ||
1387 | { | ||
1388 | wixDirectoryRef.Id = value; | ||
1389 | } | ||
1390 | else | ||
1391 | { | ||
1392 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Id")); | ||
1393 | } | ||
1394 | } | ||
1395 | } | ||
1396 | |||
1397 | /// <summary> | ||
1398 | /// Gets or sets the Name attribute. | ||
1399 | /// </summary> | ||
1400 | public string Name | ||
1401 | { | ||
1402 | get | ||
1403 | { | ||
1404 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1405 | { | ||
1406 | return wixDirectory.Name; | ||
1407 | } | ||
1408 | else | ||
1409 | { | ||
1410 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Name")); | ||
1411 | } | ||
1412 | } | ||
1413 | set | ||
1414 | { | ||
1415 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1416 | { | ||
1417 | wixDirectory.Name = value; | ||
1418 | } | ||
1419 | else | ||
1420 | { | ||
1421 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Name")); | ||
1422 | } | ||
1423 | } | ||
1424 | } | ||
1425 | } | ||
1426 | |||
1427 | internal class HarvestLogger | ||
1428 | { | ||
1429 | public HarvestLogger(IMessaging messaging) | ||
1430 | { | ||
1431 | this.Color = ConsoleColor.Black; | ||
1432 | this.Messaging = messaging; | ||
1433 | } | ||
1434 | |||
1435 | private ConsoleColor Color { get; set; } | ||
1436 | private IMessaging Messaging { get; } | ||
1437 | |||
1438 | public void LogMessage(string message) | ||
1439 | { | ||
1440 | if (this.Color == ConsoleColor.Red) | ||
1441 | { | ||
1442 | this.Messaging.Write(HarvesterErrors.BuildErrorDuringHarvesting(message)); | ||
1443 | } | ||
1444 | } | ||
1445 | |||
1446 | public void SetColor(ConsoleColor color) | ||
1447 | { | ||
1448 | this.Color = color; | ||
1449 | } | ||
1450 | |||
1451 | public void ResetColor() | ||
1452 | { | ||
1453 | this.Color = ConsoleColor.Black; | ||
1454 | } | ||
1455 | } | ||
1456 | } | ||
1457 | } | ||