diff options
author | Rob Mensching <rob@firegiant.com> | 2022-07-26 17:20:39 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2022-08-01 20:25:19 -0700 |
commit | a627ca9b720047e633a8fe72003ab9bee31006c5 (patch) | |
tree | 2bc8a924bb4141ab718e74d08f6459a0ffe8d573 /src/tools/heat/VSProjectHarvester.cs | |
parent | 521eb3c9cf38823a2c4019abb85dc0b3200b92cb (diff) | |
download | wix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.gz wix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.bz2 wix-a627ca9b720047e633a8fe72003ab9bee31006c5.zip |
Create WixToolset.Heat.nupkg to distribute heat.exe and Heat targets
Moves Heat functionality to the "tools" layer and packages it all
up in WixToolset.Heat.nupkg for distribution in WiX v4.
Completes 6838
Diffstat (limited to 'src/tools/heat/VSProjectHarvester.cs')
-rw-r--r-- | src/tools/heat/VSProjectHarvester.cs | 1455 |
1 files changed, 1455 insertions, 0 deletions
diff --git a/src/tools/heat/VSProjectHarvester.cs b/src/tools/heat/VSProjectHarvester.cs new file mode 100644 index 00000000..93b20cd8 --- /dev/null +++ b/src/tools/heat/VSProjectHarvester.cs | |||
@@ -0,0 +1,1455 @@ | |||
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 | Wix.DirectoryRef directoryRef = new Wix.DirectoryRef(); | ||
338 | harvestParent = directoryRef; | ||
339 | |||
340 | if (!String.IsNullOrEmpty(this.directoryIds)) | ||
341 | { | ||
342 | directoryRef.Id = this.directoryIds; | ||
343 | } | ||
344 | else if (this.setUniqueIdentifiers) | ||
345 | { | ||
346 | directoryRef.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
347 | } | ||
348 | else | ||
349 | { | ||
350 | directoryRef.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name)); | ||
351 | } | ||
352 | |||
353 | this.directoryRefSeed = this.Core.GenerateIdentifier(DirectoryPrefix, this.projectGUID, pog.Name); | ||
354 | } | ||
355 | |||
356 | IEnumerable pogFiles = buildOutputs[pog.BuildOutputGroup] as IEnumerable; | ||
357 | if (pogFiles == null) | ||
358 | { | ||
359 | throw new WixException(HarvesterErrors.MissingProjectOutputGroup( | ||
360 | projectFile, pog.BuildOutputGroup)); | ||
361 | } | ||
362 | |||
363 | if (pog.FileSource == "ProjectDir") | ||
364 | { | ||
365 | projectBaseDir = Path.GetDirectoryName(projectFile) + "\\"; | ||
366 | } | ||
367 | |||
368 | int harvestCount = this.HarvestProjectOutputGroupFiles(projectBaseDir, projectName, pog.Name, pog.FileSource, pogFiles, harvestParent); | ||
369 | |||
370 | if (this.GenerateType == GenerateType.Container) | ||
371 | { | ||
372 | // harvestParent must be a Container at this point | ||
373 | Wix.Container container = harvestParent as Wix.Container; | ||
374 | |||
375 | Wix.Fragment fragment = new Wix.Fragment(); | ||
376 | fragment.AddChild(container); | ||
377 | fragmentList.Add(fragment); | ||
378 | } | ||
379 | else if (this.GenerateType == GenerateType.PackageGroup) | ||
380 | { | ||
381 | // harvestParent must be a PackageGroup at this point | ||
382 | Wix.PackageGroup packageGroup = harvestParent as Wix.PackageGroup; | ||
383 | |||
384 | Wix.Fragment fragment = new Wix.Fragment(); | ||
385 | fragment.AddChild(packageGroup); | ||
386 | fragmentList.Add(fragment); | ||
387 | } | ||
388 | else if (this.GenerateType == GenerateType.PayloadGroup) | ||
389 | { | ||
390 | // harvestParent must be a Container at this point | ||
391 | Wix.PayloadGroup payloadGroup = harvestParent as Wix.PayloadGroup; | ||
392 | |||
393 | Wix.Fragment fragment = new Wix.Fragment(); | ||
394 | fragment.AddChild(payloadGroup); | ||
395 | fragmentList.Add(fragment); | ||
396 | } | ||
397 | else | ||
398 | { | ||
399 | // harvestParent must be a DirectoryRef at this point | ||
400 | Wix.DirectoryRef directoryRef = harvestParent as Wix.DirectoryRef; | ||
401 | |||
402 | if (harvestCount > 0) | ||
403 | { | ||
404 | Wix.Fragment drf = new Wix.Fragment(); | ||
405 | drf.AddChild(directoryRef); | ||
406 | fragmentList.Add(drf); | ||
407 | } | ||
408 | |||
409 | Wix.ComponentGroup cg = new Wix.ComponentGroup(); | ||
410 | |||
411 | if (this.setUniqueIdentifiers || !String.IsNullOrEmpty(this.directoryIds)) | ||
412 | { | ||
413 | cg.Id = String.Format(CultureInfo.InvariantCulture, DirectoryIdFormat, sanitizedProjectName, pog.Name); | ||
414 | } | ||
415 | else | ||
416 | { | ||
417 | cg.Id = directoryRef.Id; | ||
418 | } | ||
419 | |||
420 | if (harvestCount > 0) | ||
421 | { | ||
422 | this.AddComponentsToComponentGroup(directoryRef, cg); | ||
423 | } | ||
424 | |||
425 | Wix.Fragment cgf = new Wix.Fragment(); | ||
426 | cgf.AddChild(cg); | ||
427 | fragmentList.Add(cgf); | ||
428 | } | ||
429 | |||
430 | return harvestCount; | ||
431 | } | ||
432 | |||
433 | /// <summary> | ||
434 | /// Add all Components in an element tree to a ComponentGroup. | ||
435 | /// </summary> | ||
436 | /// <param name="parent">Parent of an element tree that will be searched for Components.</param> | ||
437 | /// <param name="cg">The ComponentGroup the Components will be added to.</param> | ||
438 | private void AddComponentsToComponentGroup(Wix.IParentElement parent, Wix.ComponentGroup cg) | ||
439 | { | ||
440 | foreach (Wix.ISchemaElement childElement in parent.Children) | ||
441 | { | ||
442 | Wix.Component c = childElement as Wix.Component; | ||
443 | if (c != null) | ||
444 | { | ||
445 | Wix.ComponentRef cr = new Wix.ComponentRef(); | ||
446 | cr.Id = c.Id; | ||
447 | cg.AddChild(cr); | ||
448 | } | ||
449 | else | ||
450 | { | ||
451 | Wix.IParentElement p = childElement as Wix.IParentElement; | ||
452 | if (p != null) | ||
453 | { | ||
454 | this.AddComponentsToComponentGroup(p, cg); | ||
455 | } | ||
456 | } | ||
457 | } | ||
458 | } | ||
459 | |||
460 | /// <summary> | ||
461 | /// Harvest files from one output group of a VS project. | ||
462 | /// </summary> | ||
463 | /// <param name="baseDir">The base directory of the files.</param> | ||
464 | /// <param name="projectName">Name of the project, to be used as a prefix for generated identifiers.</param> | ||
465 | /// <param name="pogName">Name of the project output group, used for generating identifiers for WiX elements.</param> | ||
466 | /// <param name="pogFileSource">The ProjectOutputGroup file source.</param> | ||
467 | /// <param name="outputGroupFiles">The files from one output group to harvest.</param> | ||
468 | /// <param name="parent">The parent element that will contain the components of the harvested files.</param> | ||
469 | /// <returns>The number of files harvested.</returns> | ||
470 | private int HarvestProjectOutputGroupFiles(string baseDir, string projectName, string pogName, string pogFileSource, IEnumerable outputGroupFiles, Wix.IParentElement parent) | ||
471 | { | ||
472 | int fileCount = 0; | ||
473 | |||
474 | Wix.ISchemaElement exeFile = null; | ||
475 | Wix.ISchemaElement dllFile = null; | ||
476 | Wix.ISchemaElement appConfigFile = null; | ||
477 | |||
478 | // Keep track of files inserted | ||
479 | // Files can have different absolute paths but get mapped to the same SourceFile | ||
480 | // after the project variables have been used. For example, a WiX project that | ||
481 | // is building multiple cultures will have many output MSIs/MSMs, but will all get | ||
482 | // mapped to $(var.ProjName.TargetDir)\ProjName.msm. These duplicates would | ||
483 | // prevent generated code from compiling. | ||
484 | Dictionary<string, bool> seenList = new Dictionary<string,bool>(); | ||
485 | |||
486 | foreach (object output in outputGroupFiles) | ||
487 | { | ||
488 | string filePath = output.ToString(); | ||
489 | string fileName = Path.GetFileName(filePath); | ||
490 | string fileDir = Path.GetDirectoryName(filePath); | ||
491 | string link = null; | ||
492 | |||
493 | MethodInfo getMetadataMethod = output.GetType().GetMethod("GetMetadata"); | ||
494 | if (getMetadataMethod != null) | ||
495 | { | ||
496 | link = (string)getMetadataMethod.Invoke(output, new object[] { "Link" }); | ||
497 | if (!String.IsNullOrEmpty(link)) | ||
498 | { | ||
499 | fileDir = Path.GetDirectoryName(Path.Combine(baseDir, link)); | ||
500 | } | ||
501 | } | ||
502 | |||
503 | Wix.IParentElement parentDir = parent; | ||
504 | // Ignore Containers and PayloadGroups because they do not have a nested structure. | ||
505 | if (baseDir != null && !String.Equals(Path.GetDirectoryName(baseDir), fileDir, StringComparison.OrdinalIgnoreCase) | ||
506 | && this.GenerateType != GenerateType.Container && this.GenerateType != GenerateType.PackageGroup && this.GenerateType != GenerateType.PayloadGroup) | ||
507 | { | ||
508 | Uri baseUri = new Uri(baseDir); | ||
509 | Uri relativeUri = baseUri.MakeRelativeUri(new Uri(fileDir)); | ||
510 | parentDir = this.GetSubDirElement(parentDir, relativeUri); | ||
511 | } | ||
512 | |||
513 | string parentDirId = null; | ||
514 | |||
515 | if (parentDir is Wix.DirectoryRef) | ||
516 | { | ||
517 | parentDirId = this.directoryRefSeed; | ||
518 | } | ||
519 | else if (parentDir is Wix.Directory) | ||
520 | { | ||
521 | parentDirId = ((Wix.Directory)parentDir).Id; | ||
522 | } | ||
523 | |||
524 | if (this.GenerateType == GenerateType.Container || this.GenerateType == GenerateType.PayloadGroup) | ||
525 | { | ||
526 | Wix.Payload payload = new Wix.Payload(); | ||
527 | |||
528 | this.HarvestProjectOutputGroupPayloadFile(baseDir, projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, payload, seenList); | ||
529 | } | ||
530 | else if (this.GenerateType == GenerateType.PackageGroup) | ||
531 | { | ||
532 | this.HarvestProjectOutputGroupPackage(projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, seenList); | ||
533 | } | ||
534 | else | ||
535 | { | ||
536 | Wix.Component component = new Wix.Component(); | ||
537 | Wix.File file = new Wix.File(); | ||
538 | |||
539 | this.HarvestProjectOutputGroupFile(baseDir, projectName, pogName, pogFileSource, filePath, fileName, link, parentDir, parentDirId, component, file, seenList); | ||
540 | |||
541 | if (String.Equals(Path.GetExtension(file.Source), ".exe", StringComparison.OrdinalIgnoreCase)) | ||
542 | { | ||
543 | exeFile = file; | ||
544 | } | ||
545 | else if (String.Equals(Path.GetExtension(file.Source), ".dll", StringComparison.OrdinalIgnoreCase)) | ||
546 | { | ||
547 | dllFile = file; | ||
548 | } | ||
549 | else if (file.Source.EndsWith("app.config", StringComparison.OrdinalIgnoreCase)) | ||
550 | { | ||
551 | appConfigFile = file; | ||
552 | } | ||
553 | } | ||
554 | |||
555 | fileCount++; | ||
556 | } | ||
557 | |||
558 | // if there was no exe file found fallback on the dll file found | ||
559 | if (exeFile == null && dllFile != null) | ||
560 | { | ||
561 | exeFile = dllFile; | ||
562 | } | ||
563 | |||
564 | // Special case for the app.config file in the Binaries POG... | ||
565 | // The POG refers to the files in the OBJ directory, while the | ||
566 | // generated WiX code references them in the bin directory. | ||
567 | // The app.config file gets renamed to match the exe name. | ||
568 | if ("Binaries" == pogName && null != exeFile && null != appConfigFile) | ||
569 | { | ||
570 | if (appConfigFile is Wix.File) | ||
571 | { | ||
572 | Wix.File appConfigFileAsWixFile = appConfigFile as Wix.File; | ||
573 | Wix.File exeFileAsWixFile = exeFile as Wix.File; | ||
574 | // Case insensitive replace | ||
575 | appConfigFileAsWixFile.Source = Regex.Replace(appConfigFileAsWixFile.Source, @"app\.config", Path.GetFileName(exeFileAsWixFile.Source) + ".config", RegexOptions.IgnoreCase); | ||
576 | } | ||
577 | } | ||
578 | |||
579 | return fileCount; | ||
580 | } | ||
581 | |||
582 | 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) | ||
583 | { | ||
584 | string varFormat = VariableFormat; | ||
585 | if (this.generateWixVars) | ||
586 | { | ||
587 | varFormat = WixVariableFormat; | ||
588 | } | ||
589 | |||
590 | if (pogName.Equals("Satellites", StringComparison.OrdinalIgnoreCase)) | ||
591 | { | ||
592 | Wix.Directory locDirectory = new Wix.Directory(); | ||
593 | |||
594 | locDirectory.Name = Path.GetFileName(Path.GetDirectoryName(Path.GetFullPath(filePath))); | ||
595 | file.Source = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", locDirectory.Name, "\\", Path.GetFileName(filePath)); | ||
596 | |||
597 | if (!seenList.ContainsKey(file.Source)) | ||
598 | { | ||
599 | parentDir.AddChild(locDirectory); | ||
600 | locDirectory.AddChild(component); | ||
601 | component.AddChild(file); | ||
602 | seenList.Add(file.Source, true); | ||
603 | |||
604 | if (this.setUniqueIdentifiers) | ||
605 | { | ||
606 | locDirectory.Id = this.Core.GenerateIdentifier(DirectoryPrefix, parentDirId, locDirectory.Name); | ||
607 | file.Id = this.Core.GenerateIdentifier(FilePrefix, locDirectory.Id, fileName); | ||
608 | component.Id = this.Core.GenerateIdentifier(ComponentPrefix, locDirectory.Id, file.Id); | ||
609 | } | ||
610 | else | ||
611 | { | ||
612 | locDirectory.Id = this.Core.CreateIdentifierFromFilename(String.Format(DirectoryIdFormat, (parentDir is Wix.DirectoryRef) ? ((Wix.DirectoryRef)parentDir).Id : parentDirId, locDirectory.Name)); | ||
613 | file.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.FileIdFormat, projectName, pogName, String.Concat(locDirectory.Name, ".", fileName))); | ||
614 | component.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.ComponentIdFormat, projectName, pogName, String.Concat(locDirectory.Name, ".", fileName))); | ||
615 | } | ||
616 | } | ||
617 | } | ||
618 | else | ||
619 | { | ||
620 | file.Source = GenerateSourceFilePath(baseDir, projectName, pogFileSource, filePath, link, varFormat); | ||
621 | |||
622 | if (!seenList.ContainsKey(file.Source)) | ||
623 | { | ||
624 | component.AddChild(file); | ||
625 | parentDir.AddChild(component); | ||
626 | seenList.Add(file.Source, true); | ||
627 | |||
628 | if (this.setUniqueIdentifiers) | ||
629 | { | ||
630 | file.Id = this.Core.GenerateIdentifier(FilePrefix, parentDirId, fileName); | ||
631 | component.Id = this.Core.GenerateIdentifier(ComponentPrefix, parentDirId, file.Id); | ||
632 | } | ||
633 | else | ||
634 | { | ||
635 | file.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.FileIdFormat, projectName, pogName, fileName)); | ||
636 | component.Id = this.Core.CreateIdentifierFromFilename(String.Format(CultureInfo.InvariantCulture, VSProjectHarvester.ComponentIdFormat, projectName, pogName, fileName)); | ||
637 | } | ||
638 | } | ||
639 | } | ||
640 | } | ||
641 | |||
642 | private void HarvestProjectOutputGroupPackage(string projectName, string pogName, string pogFileSource, string filePath, string fileName, string link, Wix.IParentElement parentDir, Dictionary<string, bool> seenList) | ||
643 | { | ||
644 | string varFormat = VariableFormat; | ||
645 | if (this.generateWixVars) | ||
646 | { | ||
647 | varFormat = WixVariableFormat; | ||
648 | } | ||
649 | |||
650 | if (pogName.Equals("Binaries", StringComparison.OrdinalIgnoreCase)) | ||
651 | { | ||
652 | if (String.Equals(Path.GetExtension(filePath), ".exe", StringComparison.OrdinalIgnoreCase)) | ||
653 | { | ||
654 | Wix.ExePackage exePackage = new Wix.ExePackage(); | ||
655 | exePackage.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath)); | ||
656 | if (!seenList.ContainsKey(exePackage.SourceFile)) | ||
657 | { | ||
658 | parentDir.AddChild(exePackage); | ||
659 | seenList.Add(exePackage.SourceFile, true); | ||
660 | } | ||
661 | } | ||
662 | else if (String.Equals(Path.GetExtension(filePath), ".msi", StringComparison.OrdinalIgnoreCase)) | ||
663 | { | ||
664 | Wix.MsiPackage msiPackage = new Wix.MsiPackage(); | ||
665 | msiPackage.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath)); | ||
666 | if (!seenList.ContainsKey(msiPackage.SourceFile)) | ||
667 | { | ||
668 | parentDir.AddChild(msiPackage); | ||
669 | seenList.Add(msiPackage.SourceFile, true); | ||
670 | } | ||
671 | } | ||
672 | } | ||
673 | } | ||
674 | |||
675 | 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) | ||
676 | { | ||
677 | string varFormat = VariableFormat; | ||
678 | if (this.generateWixVars) | ||
679 | { | ||
680 | varFormat = WixVariableFormat; | ||
681 | } | ||
682 | |||
683 | if (pogName.Equals("Satellites", StringComparison.OrdinalIgnoreCase)) | ||
684 | { | ||
685 | string locDirectoryName = Path.GetFileName(Path.GetDirectoryName(Path.GetFullPath(filePath))); | ||
686 | file.SourceFile = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", locDirectoryName, "\\", Path.GetFileName(filePath)); | ||
687 | |||
688 | if (!seenList.ContainsKey(file.SourceFile)) | ||
689 | { | ||
690 | parentDir.AddChild(file); | ||
691 | seenList.Add(file.SourceFile, true); | ||
692 | } | ||
693 | } | ||
694 | else | ||
695 | { | ||
696 | file.SourceFile = GenerateSourceFilePath(baseDir, projectName, pogFileSource, filePath, link, varFormat); | ||
697 | |||
698 | if (!seenList.ContainsKey(file.SourceFile)) | ||
699 | { | ||
700 | parentDir.AddChild(file); | ||
701 | seenList.Add(file.SourceFile, true); | ||
702 | } | ||
703 | } | ||
704 | } | ||
705 | |||
706 | /// <summary> | ||
707 | /// Helper function to generates a source file path when harvesting files. | ||
708 | /// </summary> | ||
709 | /// <param name="baseDir"></param> | ||
710 | /// <param name="projectName"></param> | ||
711 | /// <param name="pogFileSource"></param> | ||
712 | /// <param name="filePath"></param> | ||
713 | /// <param name="link"></param> | ||
714 | /// <param name="varFormat"></param> | ||
715 | /// <returns></returns> | ||
716 | private static string GenerateSourceFilePath(string baseDir, string projectName, string pogFileSource, string filePath, string link, string varFormat) | ||
717 | { | ||
718 | string ret; | ||
719 | |||
720 | if (null == baseDir && !String.IsNullOrEmpty(link)) | ||
721 | { | ||
722 | // This needs to be the absolute path as a link can be located anywhere. | ||
723 | ret = filePath; | ||
724 | } | ||
725 | else if (null == baseDir) | ||
726 | { | ||
727 | ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", Path.GetFileName(filePath)); | ||
728 | } | ||
729 | else if (filePath.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase)) | ||
730 | { | ||
731 | ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", filePath.Substring(baseDir.Length)); | ||
732 | } | ||
733 | else | ||
734 | { | ||
735 | // come up with a relative path to the file | ||
736 | Uri sourcePathUri = new Uri(filePath); | ||
737 | Uri baseDirUri = new Uri(baseDir); | ||
738 | Uri sourceRelativeUri = baseDirUri.MakeRelativeUri(sourcePathUri); | ||
739 | string relativePath = sourceRelativeUri.ToString().Replace('/', Path.DirectorySeparatorChar); | ||
740 | if (!sourceRelativeUri.UserEscaped) | ||
741 | { | ||
742 | relativePath = Uri.UnescapeDataString(relativePath); | ||
743 | } | ||
744 | |||
745 | ret = String.Concat(String.Format(CultureInfo.InvariantCulture, varFormat, projectName, pogFileSource), "\\", relativePath); | ||
746 | } | ||
747 | |||
748 | return ret; | ||
749 | } | ||
750 | |||
751 | /// <summary> | ||
752 | /// Gets a Directory element corresponding to a relative subdirectory within the project, | ||
753 | /// either by locating a suitable existing Directory or creating a new one. | ||
754 | /// </summary> | ||
755 | /// <param name="parentDir">The parent element which the subdirectory is relative to.</param> | ||
756 | /// <param name="relativeUri">Relative path of the subdirectory.</param> | ||
757 | /// <returns>Directory element for the relative path.</returns> | ||
758 | private Wix.IParentElement GetSubDirElement(Wix.IParentElement parentDir, Uri relativeUri) | ||
759 | { | ||
760 | string[] segments = relativeUri.ToString().Split('\\', '/'); | ||
761 | string firstSubDirName = Uri.UnescapeDataString(segments[0]); | ||
762 | DirectoryAttributeAccessor subDir = null; | ||
763 | |||
764 | if (String.Equals(firstSubDirName, "..", StringComparison.Ordinal)) | ||
765 | { | ||
766 | return parentDir; | ||
767 | } | ||
768 | |||
769 | Type directoryType; | ||
770 | Type directoryRefType; | ||
771 | if (parentDir is Wix.Directory || parentDir is Wix.DirectoryRef) | ||
772 | { | ||
773 | directoryType = typeof(Wix.Directory); | ||
774 | directoryRefType = typeof(Wix.DirectoryRef); | ||
775 | } | ||
776 | else | ||
777 | { | ||
778 | throw new ArgumentException("GetSubDirElement parentDir"); | ||
779 | } | ||
780 | |||
781 | // Search for an existing directory element. | ||
782 | foreach (Wix.ISchemaElement childElement in parentDir.Children) | ||
783 | { | ||
784 | if(VSProjectHarvester.AreTypesEquivalent(childElement.GetType(), directoryType)) | ||
785 | { | ||
786 | DirectoryAttributeAccessor childDir = new DirectoryAttributeAccessor(childElement); | ||
787 | if (String.Equals(childDir.Name, firstSubDirName, StringComparison.OrdinalIgnoreCase)) | ||
788 | { | ||
789 | subDir = childDir; | ||
790 | break; | ||
791 | } | ||
792 | } | ||
793 | } | ||
794 | |||
795 | if (subDir == null) | ||
796 | { | ||
797 | string parentId = null; | ||
798 | DirectoryAttributeAccessor parentDirectory = null; | ||
799 | DirectoryAttributeAccessor parentDirectoryRef = null; | ||
800 | |||
801 | if (VSProjectHarvester.AreTypesEquivalent(parentDir.GetType(), directoryType)) | ||
802 | { | ||
803 | parentDirectory = new DirectoryAttributeAccessor((Wix.ISchemaElement)parentDir); | ||
804 | } | ||
805 | else if (VSProjectHarvester.AreTypesEquivalent(parentDir.GetType(), directoryRefType)) | ||
806 | { | ||
807 | parentDirectoryRef = new DirectoryAttributeAccessor((Wix.ISchemaElement)parentDir); | ||
808 | } | ||
809 | |||
810 | if (parentDirectory != null) | ||
811 | { | ||
812 | parentId = parentDirectory.Id; | ||
813 | } | ||
814 | else if (parentDirectoryRef != null) | ||
815 | { | ||
816 | if (this.setUniqueIdentifiers) | ||
817 | { | ||
818 | //Use the GUID of the project instead of the project name to help keep things stable. | ||
819 | parentId = this.directoryRefSeed; | ||
820 | } | ||
821 | else | ||
822 | { | ||
823 | parentId = parentDirectoryRef.Id; | ||
824 | } | ||
825 | } | ||
826 | |||
827 | Wix.ISchemaElement newDirectory = (Wix.ISchemaElement)directoryType.GetConstructor(new Type[] { }).Invoke(null); | ||
828 | subDir = new DirectoryAttributeAccessor(newDirectory); | ||
829 | |||
830 | if (this.setUniqueIdentifiers) | ||
831 | { | ||
832 | subDir.Id = this.Core.GenerateIdentifier(DirectoryPrefix, parentId, firstSubDirName); | ||
833 | } | ||
834 | else | ||
835 | { | ||
836 | subDir.Id = String.Format(DirectoryIdFormat, parentId, firstSubDirName); | ||
837 | } | ||
838 | |||
839 | subDir.Name = firstSubDirName; | ||
840 | |||
841 | parentDir.AddChild(subDir.Element); | ||
842 | } | ||
843 | |||
844 | if (segments.Length == 1) | ||
845 | { | ||
846 | return subDir.ElementAsParent; | ||
847 | } | ||
848 | else | ||
849 | { | ||
850 | Uri nextRelativeUri = new Uri(Uri.UnescapeDataString(relativeUri.ToString()).Substring(firstSubDirName.Length + 1), UriKind.Relative); | ||
851 | return this.GetSubDirElement(subDir.ElementAsParent, nextRelativeUri); | ||
852 | } | ||
853 | } | ||
854 | |||
855 | private MSBuildProject GetMsbuildProject(string projectFile) | ||
856 | { | ||
857 | XmlDocument document = new XmlDocument(); | ||
858 | try | ||
859 | { | ||
860 | document.Load(projectFile); | ||
861 | } | ||
862 | catch (Exception e) | ||
863 | { | ||
864 | throw new WixException(HarvesterErrors.CannotLoadProject(projectFile, e.Message)); | ||
865 | } | ||
866 | |||
867 | string version = null; | ||
868 | |||
869 | if (this.UseToolsVersion) | ||
870 | { | ||
871 | foreach (XmlNode child in document.ChildNodes) | ||
872 | { | ||
873 | if (String.Equals(child.Name, "Project", StringComparison.Ordinal) && child.Attributes != null) | ||
874 | { | ||
875 | XmlNode toolsVersionAttribute = child.Attributes["ToolsVersion"]; | ||
876 | if (toolsVersionAttribute != null) | ||
877 | { | ||
878 | version = toolsVersionAttribute.Value; | ||
879 | this.Core.Messaging.Write(HarvesterVerboses.FoundToolsVersion(version)); | ||
880 | |||
881 | break; | ||
882 | } | ||
883 | } | ||
884 | } | ||
885 | |||
886 | switch (version) | ||
887 | { | ||
888 | case "4.0": | ||
889 | version = "4.0.0.0"; | ||
890 | break; | ||
891 | case "12.0": | ||
892 | version = "12.0.0.0"; | ||
893 | break; | ||
894 | case "14.0": | ||
895 | version = "14.0.0.0"; | ||
896 | break; | ||
897 | default: | ||
898 | if (String.IsNullOrEmpty(this.MsbuildBinPath)) | ||
899 | { | ||
900 | throw new WixException(HarvesterErrors.MsbuildBinPathRequired(version ?? "(none)")); | ||
901 | } | ||
902 | |||
903 | version = null; | ||
904 | break; | ||
905 | } | ||
906 | } | ||
907 | |||
908 | var project = this.ConstructMsbuild40Project(version); | ||
909 | return project; | ||
910 | } | ||
911 | |||
912 | private Assembly ResolveFromMsbuildBinPath(object sender, ResolveEventArgs args) | ||
913 | { | ||
914 | var assemblyName = new AssemblyName(args.Name); | ||
915 | |||
916 | var assemblyPath = Path.Combine(this.MsbuildBinPath, $"{assemblyName.Name}.dll"); | ||
917 | if (!File.Exists(assemblyPath)) | ||
918 | { | ||
919 | return null; | ||
920 | } | ||
921 | |||
922 | return Assembly.LoadFrom(assemblyPath); | ||
923 | } | ||
924 | |||
925 | private MSBuildProject ConstructMsbuild40Project(string loadVersion) | ||
926 | { | ||
927 | const string MSBuildEngineAssemblyName = "Microsoft.Build, Version={0}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; | ||
928 | const string MSBuildFrameworkAssemblyName = "Microsoft.Build.Framework, Version={0}, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; | ||
929 | Assembly msbuildAssembly; | ||
930 | Assembly msbuildFrameworkAssembly; | ||
931 | |||
932 | if (loadVersion == null) | ||
933 | { | ||
934 | this.Core.Messaging.Write(HarvesterVerboses.LoadingProjectWithBinPath(this.MsbuildBinPath)); | ||
935 | AppDomain.CurrentDomain.AssemblyResolve += this.ResolveFromMsbuildBinPath; | ||
936 | |||
937 | try | ||
938 | { | ||
939 | msbuildAssembly = Assembly.Load("Microsoft.Build"); | ||
940 | } | ||
941 | catch (Exception e) | ||
942 | { | ||
943 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
944 | } | ||
945 | |||
946 | try | ||
947 | { | ||
948 | msbuildFrameworkAssembly = Assembly.Load("Microsoft.Build.Framework"); | ||
949 | } | ||
950 | catch (Exception e) | ||
951 | { | ||
952 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
953 | } | ||
954 | } | ||
955 | else | ||
956 | { | ||
957 | this.Core.Messaging.Write(HarvesterVerboses.LoadingProjectWithVersion(loadVersion)); | ||
958 | |||
959 | try | ||
960 | { | ||
961 | msbuildAssembly = Assembly.Load(String.Format(MSBuildEngineAssemblyName, loadVersion)); | ||
962 | } | ||
963 | catch (Exception e) | ||
964 | { | ||
965 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
966 | } | ||
967 | |||
968 | try | ||
969 | { | ||
970 | msbuildFrameworkAssembly = Assembly.Load(String.Format(MSBuildFrameworkAssemblyName, loadVersion)); | ||
971 | } | ||
972 | catch (Exception e) | ||
973 | { | ||
974 | throw new WixException(HarvesterErrors.CannotLoadMSBuildAssembly(e.Message)); | ||
975 | } | ||
976 | } | ||
977 | |||
978 | Type projectType; | ||
979 | Type buildItemType; | ||
980 | |||
981 | Type buildManagerType; | ||
982 | Type buildParametersType; | ||
983 | Type buildRequestDataFlagsType; | ||
984 | Type buildRequestDataType; | ||
985 | Type hostServicesType; | ||
986 | Type projectCollectionType; | ||
987 | Type projectInstanceType; | ||
988 | |||
989 | Type writeHandlerType; | ||
990 | Type colorSetterType; | ||
991 | Type colorResetterType; | ||
992 | Type loggerVerbosityType; | ||
993 | Type consoleLoggerType; | ||
994 | Type iLoggerType; | ||
995 | |||
996 | try | ||
997 | { | ||
998 | buildItemType = msbuildAssembly.GetType("Microsoft.Build.Execution.ProjectItemInstance", true); | ||
999 | projectType = msbuildAssembly.GetType("Microsoft.Build.Evaluation.Project", true); | ||
1000 | |||
1001 | buildManagerType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildManager", true); | ||
1002 | buildParametersType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildParameters", true); | ||
1003 | buildRequestDataFlagsType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildRequestDataFlags", true); | ||
1004 | buildRequestDataType = msbuildAssembly.GetType("Microsoft.Build.Execution.BuildRequestData", true); | ||
1005 | hostServicesType = msbuildAssembly.GetType("Microsoft.Build.Execution.HostServices", true); | ||
1006 | projectCollectionType = msbuildAssembly.GetType("Microsoft.Build.Evaluation.ProjectCollection", true); | ||
1007 | projectInstanceType = msbuildAssembly.GetType("Microsoft.Build.Execution.ProjectInstance", true); | ||
1008 | |||
1009 | writeHandlerType = msbuildAssembly.GetType("Microsoft.Build.Logging.WriteHandler", true); | ||
1010 | colorSetterType = msbuildAssembly.GetType("Microsoft.Build.Logging.ColorSetter", true); | ||
1011 | colorResetterType = msbuildAssembly.GetType("Microsoft.Build.Logging.ColorResetter", true); | ||
1012 | loggerVerbosityType = msbuildFrameworkAssembly.GetType("Microsoft.Build.Framework.LoggerVerbosity", true); | ||
1013 | consoleLoggerType = msbuildAssembly.GetType("Microsoft.Build.Logging.ConsoleLogger", true); | ||
1014 | iLoggerType = msbuildFrameworkAssembly.GetType("Microsoft.Build.Framework.ILogger", true); | ||
1015 | } | ||
1016 | catch (TargetInvocationException tie) | ||
1017 | { | ||
1018 | throw new WixException(HarvesterErrors.CannotLoadMSBuildEngine(tie.InnerException.Message)); | ||
1019 | } | ||
1020 | catch (Exception e) | ||
1021 | { | ||
1022 | throw new WixException(HarvesterErrors.CannotLoadMSBuildEngine(e.Message)); | ||
1023 | } | ||
1024 | |||
1025 | MSBuild40Types types = new MSBuild40Types(); | ||
1026 | types.buildManagerType = buildManagerType; | ||
1027 | types.buildParametersType = buildParametersType; | ||
1028 | types.buildRequestDataFlagsType = buildRequestDataFlagsType; | ||
1029 | types.buildRequestDataType = buildRequestDataType; | ||
1030 | types.hostServicesType = hostServicesType; | ||
1031 | types.projectCollectionType = projectCollectionType; | ||
1032 | types.projectInstanceType = projectInstanceType; | ||
1033 | types.writeHandlerType = writeHandlerType; | ||
1034 | types.colorSetterType = colorSetterType; | ||
1035 | types.colorResetterType = colorResetterType; | ||
1036 | types.loggerVerbosityType = loggerVerbosityType; | ||
1037 | types.consoleLoggerType = consoleLoggerType; | ||
1038 | types.iLoggerType = iLoggerType; | ||
1039 | return new MSBuild40Project(null, projectType, buildItemType, loadVersion, types, this.Core, this.configuration, this.platform); | ||
1040 | } | ||
1041 | |||
1042 | private static bool AreTypesEquivalent(Type a, Type b) | ||
1043 | { | ||
1044 | return (a == b) || (a.IsAssignableFrom(b) && b.IsAssignableFrom(a)); | ||
1045 | } | ||
1046 | |||
1047 | private abstract class MSBuildProject | ||
1048 | { | ||
1049 | protected Type projectType; | ||
1050 | protected Type buildItemType; | ||
1051 | protected object project; | ||
1052 | private string loadVersion; | ||
1053 | |||
1054 | public MSBuildProject(object project, Type projectType, Type buildItemType, string loadVersion) | ||
1055 | { | ||
1056 | this.project = project; | ||
1057 | this.projectType = projectType; | ||
1058 | this.buildItemType = buildItemType; | ||
1059 | this.loadVersion = loadVersion; | ||
1060 | } | ||
1061 | |||
1062 | public string LoadVersion | ||
1063 | { | ||
1064 | get { return this.loadVersion; } | ||
1065 | } | ||
1066 | |||
1067 | public abstract bool Build(string projectFileName, string[] targetNames, IDictionary targetOutputs); | ||
1068 | |||
1069 | public abstract MSBuildProjectItemType GetBuildItem(object buildItem); | ||
1070 | |||
1071 | public abstract IEnumerable GetEvaluatedItemsByName(string itemName); | ||
1072 | |||
1073 | public abstract string GetEvaluatedProperty(string propertyName); | ||
1074 | |||
1075 | public abstract void Load(string projectFileName); | ||
1076 | } | ||
1077 | |||
1078 | private abstract class MSBuildProjectItemType | ||
1079 | { | ||
1080 | public MSBuildProjectItemType(object buildItem) | ||
1081 | { | ||
1082 | this.buildItem = buildItem; | ||
1083 | } | ||
1084 | |||
1085 | public abstract override string ToString(); | ||
1086 | |||
1087 | public abstract string GetMetadata(string name); | ||
1088 | |||
1089 | protected object buildItem; | ||
1090 | } | ||
1091 | |||
1092 | |||
1093 | private struct MSBuild40Types | ||
1094 | { | ||
1095 | public Type buildManagerType; | ||
1096 | public Type buildParametersType; | ||
1097 | public Type buildRequestDataFlagsType; | ||
1098 | public Type buildRequestDataType; | ||
1099 | public Type hostServicesType; | ||
1100 | public Type projectCollectionType; | ||
1101 | public Type projectInstanceType; | ||
1102 | public Type writeHandlerType; | ||
1103 | public Type colorSetterType; | ||
1104 | public Type colorResetterType; | ||
1105 | public Type loggerVerbosityType; | ||
1106 | public Type consoleLoggerType; | ||
1107 | public Type iLoggerType; | ||
1108 | } | ||
1109 | |||
1110 | private class MSBuild40Project : MSBuildProject | ||
1111 | { | ||
1112 | private MSBuild40Types types; | ||
1113 | private object projectCollection; | ||
1114 | private object currentProjectInstance; | ||
1115 | private object buildManager; | ||
1116 | private object buildParameters; | ||
1117 | private IHarvesterCore harvesterCore; | ||
1118 | |||
1119 | public MSBuild40Project(object project, Type projectType, Type buildItemType, string loadVersion, MSBuild40Types types, IHarvesterCore harvesterCore, string configuration, string platform) | ||
1120 | : base(project, projectType, buildItemType, loadVersion) | ||
1121 | { | ||
1122 | this.types = types; | ||
1123 | this.harvesterCore = harvesterCore; | ||
1124 | |||
1125 | this.buildParameters = this.types.buildParametersType.GetConstructor(new Type[] { }).Invoke(null); | ||
1126 | |||
1127 | try | ||
1128 | { | ||
1129 | var loggers = this.CreateLoggers(); | ||
1130 | |||
1131 | // this.buildParameters.Loggers = loggers; | ||
1132 | this.types.buildParametersType.GetProperty("Loggers").SetValue(this.buildParameters, loggers, null); | ||
1133 | } | ||
1134 | catch (TargetInvocationException tie) | ||
1135 | { | ||
1136 | if (this.harvesterCore != null) | ||
1137 | { | ||
1138 | this.harvesterCore.Messaging.Write(HarvesterWarnings.NoLogger(tie.InnerException.Message)); | ||
1139 | } | ||
1140 | } | ||
1141 | catch (Exception e) | ||
1142 | { | ||
1143 | if (this.harvesterCore != null) | ||
1144 | { | ||
1145 | this.harvesterCore.Messaging.Write(HarvesterWarnings.NoLogger(e.Message)); | ||
1146 | } | ||
1147 | } | ||
1148 | |||
1149 | this.buildManager = this.types.buildManagerType.GetConstructor(new Type[] { }).Invoke(null); | ||
1150 | |||
1151 | if (configuration != null || platform != null) | ||
1152 | { | ||
1153 | Dictionary<string, string> globalVariables = new Dictionary<string, string>(); | ||
1154 | if (configuration != null) | ||
1155 | { | ||
1156 | globalVariables.Add("Configuration", configuration); | ||
1157 | } | ||
1158 | |||
1159 | if (platform != null) | ||
1160 | { | ||
1161 | globalVariables.Add("Platform", platform); | ||
1162 | } | ||
1163 | |||
1164 | this.projectCollection = this.types.projectCollectionType.GetConstructor(new Type[] { typeof(IDictionary<string, string>) }).Invoke(new object[] { globalVariables }); | ||
1165 | } | ||
1166 | else | ||
1167 | { | ||
1168 | this.projectCollection = this.types.projectCollectionType.GetConstructor(new Type[] {}).Invoke(null); | ||
1169 | } | ||
1170 | } | ||
1171 | |||
1172 | private object CreateLoggers() | ||
1173 | { | ||
1174 | var logger = new HarvestLogger(this.harvesterCore.Messaging); | ||
1175 | var loggerVerbosity = Enum.Parse(this.types.loggerVerbosityType, "Minimal"); | ||
1176 | var writeHandler = Delegate.CreateDelegate(this.types.writeHandlerType, logger, nameof(logger.LogMessage)); | ||
1177 | var colorSetter = Delegate.CreateDelegate(this.types.colorSetterType, logger, nameof(logger.SetColor)); | ||
1178 | var colorResetter = Delegate.CreateDelegate(this.types.colorResetterType, logger, nameof(logger.ResetColor)); | ||
1179 | |||
1180 | var consoleLoggerCtor = this.types.consoleLoggerType.GetConstructor(new Type[] { | ||
1181 | this.types.loggerVerbosityType, | ||
1182 | this.types.writeHandlerType, | ||
1183 | this.types.colorSetterType, | ||
1184 | this.types.colorResetterType, | ||
1185 | }); | ||
1186 | var consoleLogger = consoleLoggerCtor.Invoke(new object[] { loggerVerbosity, writeHandler, colorSetter, colorResetter }); | ||
1187 | |||
1188 | var loggers = Array.CreateInstance(this.types.iLoggerType, 1); | ||
1189 | loggers.SetValue(consoleLogger, 0); | ||
1190 | |||
1191 | return loggers; | ||
1192 | } | ||
1193 | |||
1194 | public override bool Build(string projectFileName, string[] targetNames, IDictionary targetOutputs) | ||
1195 | { | ||
1196 | try | ||
1197 | { | ||
1198 | // this.buildManager.BeginBuild(this.buildParameters); | ||
1199 | this.types.buildManagerType.GetMethod("BeginBuild", new Type[] { this.types.buildParametersType }).Invoke(this.buildManager, new object[] { this.buildParameters }); | ||
1200 | |||
1201 | // buildRequestData = new BuildRequestData(this.currentProjectInstance, targetNames, null, BuildRequestData.BuildRequestDataFlags.ReplaceExistingProjectInstance); | ||
1202 | ConstructorInfo buildRequestDataCtor = this.types.buildRequestDataType.GetConstructor( | ||
1203 | new Type[] | ||
1204 | { | ||
1205 | this.types.projectInstanceType, typeof(string[]), this.types.hostServicesType, this.types.buildRequestDataFlagsType | ||
1206 | }); | ||
1207 | object buildRequestDataFlags = this.types.buildRequestDataFlagsType.GetField("ReplaceExistingProjectInstance").GetRawConstantValue(); | ||
1208 | object buildRequestData = buildRequestDataCtor.Invoke(new object[] { this.currentProjectInstance, targetNames, null, buildRequestDataFlags }); | ||
1209 | |||
1210 | // BuildSubmission submission = this.buildManager.PendBuildRequest(buildRequestData); | ||
1211 | object submission = this.types.buildManagerType.GetMethod("PendBuildRequest", new Type[] { this.types.buildRequestDataType }) | ||
1212 | .Invoke(this.buildManager, new object[] { buildRequestData }); | ||
1213 | |||
1214 | // BuildResult buildResult = submission.Execute(); | ||
1215 | object buildResult = submission.GetType().GetMethod("Execute", new Type[] { }).Invoke(submission, null); | ||
1216 | |||
1217 | // bool buildSucceeded = buildResult.OverallResult == BuildResult.Success; | ||
1218 | object overallResult = buildResult.GetType().GetProperty("OverallResult").GetValue(buildResult, null); | ||
1219 | bool buildSucceeded = String.Equals(overallResult.ToString(), "Success", StringComparison.Ordinal); | ||
1220 | |||
1221 | // this.buildManager.EndBuild(); | ||
1222 | this.types.buildManagerType.GetMethod("EndBuild", new Type[] { }).Invoke(this.buildManager, null); | ||
1223 | |||
1224 | // fill in empty lists for each target so that heat will look at the item group later | ||
1225 | foreach (string target in targetNames) | ||
1226 | { | ||
1227 | targetOutputs.Add(target, new List<object>()); | ||
1228 | } | ||
1229 | |||
1230 | return buildSucceeded; | ||
1231 | } | ||
1232 | catch (TargetInvocationException tie) | ||
1233 | { | ||
1234 | throw new WixException(HarvesterErrors.CannotBuildProject(projectFileName, tie.InnerException.Message)); | ||
1235 | } | ||
1236 | catch (Exception e) | ||
1237 | { | ||
1238 | throw new WixException(HarvesterErrors.CannotBuildProject(projectFileName, e.Message)); | ||
1239 | } | ||
1240 | } | ||
1241 | |||
1242 | public override MSBuildProjectItemType GetBuildItem(object buildItem) | ||
1243 | { | ||
1244 | return new MSBuild40ProjectItemType(buildItem); | ||
1245 | } | ||
1246 | |||
1247 | public override IEnumerable GetEvaluatedItemsByName(string itemName) | ||
1248 | { | ||
1249 | MethodInfo getEvaluatedItem = this.types.projectInstanceType.GetMethod("GetItems", new Type[] { typeof(string) }); | ||
1250 | return (IEnumerable)getEvaluatedItem.Invoke(this.currentProjectInstance, new object[] { itemName }); | ||
1251 | } | ||
1252 | |||
1253 | public override string GetEvaluatedProperty(string propertyName) | ||
1254 | { | ||
1255 | MethodInfo getProperty = this.types.projectInstanceType.GetMethod("GetPropertyValue", new Type[] { typeof(string) }); | ||
1256 | return (string)getProperty.Invoke(this.currentProjectInstance, new object[] { propertyName }); | ||
1257 | } | ||
1258 | |||
1259 | public override void Load(string projectFileName) | ||
1260 | { | ||
1261 | try | ||
1262 | { | ||
1263 | //this.project = this.projectCollection.LoadProject(projectFileName); | ||
1264 | this.project = this.types.projectCollectionType.GetMethod("LoadProject", new Type[] { typeof(string) }).Invoke(this.projectCollection, new object[] { projectFileName }); | ||
1265 | |||
1266 | // this.currentProjectInstance = this.project.CreateProjectInstance(); | ||
1267 | MethodInfo createProjectInstanceMethod = this.projectType.GetMethod("CreateProjectInstance", new Type[] { }); | ||
1268 | this.currentProjectInstance = createProjectInstanceMethod.Invoke(this.project, null); | ||
1269 | } | ||
1270 | catch (TargetInvocationException tie) | ||
1271 | { | ||
1272 | throw new WixException(HarvesterErrors.CannotLoadProject(projectFileName, tie.InnerException.Message)); | ||
1273 | } | ||
1274 | catch (Exception e) | ||
1275 | { | ||
1276 | throw new WixException(HarvesterErrors.CannotLoadProject(projectFileName, e.Message)); | ||
1277 | } | ||
1278 | } | ||
1279 | } | ||
1280 | |||
1281 | private class MSBuild40ProjectItemType : MSBuildProjectItemType | ||
1282 | { | ||
1283 | public MSBuild40ProjectItemType(object buildItem) | ||
1284 | : base(buildItem) | ||
1285 | { | ||
1286 | } | ||
1287 | |||
1288 | public override string ToString() | ||
1289 | { | ||
1290 | PropertyInfo includeProperty = this.buildItem.GetType().GetProperty("EvaluatedInclude"); | ||
1291 | return (string)includeProperty.GetValue(this.buildItem, null); | ||
1292 | } | ||
1293 | |||
1294 | public override string GetMetadata(string name) | ||
1295 | { | ||
1296 | MethodInfo getMetadataMethod = this.buildItem.GetType().GetMethod("GetMetadataValue"); | ||
1297 | if (null != getMetadataMethod) | ||
1298 | { | ||
1299 | return (string)getMetadataMethod.Invoke(this.buildItem, new object[] { name }); | ||
1300 | } | ||
1301 | return string.Empty; | ||
1302 | } | ||
1303 | } | ||
1304 | |||
1305 | /// <summary> | ||
1306 | /// Used internally in the VSProjectHarvester class to encapsulate | ||
1307 | /// the settings for a particular MSBuild "project output group". | ||
1308 | /// </summary> | ||
1309 | private struct ProjectOutputGroup | ||
1310 | { | ||
1311 | public readonly string Name; | ||
1312 | public readonly string BuildOutputGroup; | ||
1313 | public readonly string FileSource; | ||
1314 | |||
1315 | /// <summary> | ||
1316 | /// Creates a new project output group. | ||
1317 | /// </summary> | ||
1318 | /// <param name="name">Friendly name used by heat.</param> | ||
1319 | /// <param name="buildOutputGroup">MSBuild's name of the project output group.</param> | ||
1320 | /// <param name="fileSource">VS directory token containing the files of the POG.</param> | ||
1321 | public ProjectOutputGroup(string name, string buildOutputGroup, string fileSource) | ||
1322 | { | ||
1323 | this.Name = name; | ||
1324 | this.BuildOutputGroup = buildOutputGroup; | ||
1325 | this.FileSource = fileSource; | ||
1326 | } | ||
1327 | } | ||
1328 | |||
1329 | /// <summary> | ||
1330 | /// Internal class for getting and setting common attrbiutes on | ||
1331 | /// directory elements. | ||
1332 | /// </summary> | ||
1333 | internal class DirectoryAttributeAccessor | ||
1334 | { | ||
1335 | public Wix.ISchemaElement directoryElement; | ||
1336 | |||
1337 | public DirectoryAttributeAccessor(Wix.ISchemaElement directoryElement) | ||
1338 | { | ||
1339 | this.directoryElement = directoryElement; | ||
1340 | } | ||
1341 | |||
1342 | /// <summary> | ||
1343 | /// Gets the element as a ISchemaElement. | ||
1344 | /// </summary> | ||
1345 | public Wix.ISchemaElement Element | ||
1346 | { | ||
1347 | get { return this.directoryElement; } | ||
1348 | } | ||
1349 | |||
1350 | /// <summary> | ||
1351 | /// Gets the element as a IParentElement. | ||
1352 | /// </summary> | ||
1353 | public Wix.IParentElement ElementAsParent | ||
1354 | { | ||
1355 | get { return (Wix.IParentElement)this.directoryElement; } | ||
1356 | } | ||
1357 | |||
1358 | /// <summary> | ||
1359 | /// Gets or sets the Id attrbiute. | ||
1360 | /// </summary> | ||
1361 | public string Id | ||
1362 | { | ||
1363 | get | ||
1364 | { | ||
1365 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1366 | { | ||
1367 | return wixDirectory.Id; | ||
1368 | } | ||
1369 | else if (this.directoryElement is Wix.DirectoryRef wixDirectoryRef) | ||
1370 | { | ||
1371 | return wixDirectoryRef.Id; | ||
1372 | } | ||
1373 | else | ||
1374 | { | ||
1375 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Id")); | ||
1376 | } | ||
1377 | } | ||
1378 | set | ||
1379 | { | ||
1380 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1381 | { | ||
1382 | wixDirectory.Id = value; | ||
1383 | } | ||
1384 | else if (this.directoryElement is Wix.DirectoryRef wixDirectoryRef) | ||
1385 | { | ||
1386 | wixDirectoryRef.Id = value; | ||
1387 | } | ||
1388 | else | ||
1389 | { | ||
1390 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Id")); | ||
1391 | } | ||
1392 | } | ||
1393 | } | ||
1394 | |||
1395 | /// <summary> | ||
1396 | /// Gets or sets the Name attribute. | ||
1397 | /// </summary> | ||
1398 | public string Name | ||
1399 | { | ||
1400 | get | ||
1401 | { | ||
1402 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1403 | { | ||
1404 | return wixDirectory.Name; | ||
1405 | } | ||
1406 | else | ||
1407 | { | ||
1408 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Name")); | ||
1409 | } | ||
1410 | } | ||
1411 | set | ||
1412 | { | ||
1413 | if (this.directoryElement is Wix.Directory wixDirectory) | ||
1414 | { | ||
1415 | wixDirectory.Name = value; | ||
1416 | } | ||
1417 | else | ||
1418 | { | ||
1419 | throw new WixException(HarvesterErrors.DirectoryAttributeAccessorBadType("Name")); | ||
1420 | } | ||
1421 | } | ||
1422 | } | ||
1423 | } | ||
1424 | |||
1425 | internal class HarvestLogger | ||
1426 | { | ||
1427 | public HarvestLogger(IMessaging messaging) | ||
1428 | { | ||
1429 | this.Color = ConsoleColor.Black; | ||
1430 | this.Messaging = messaging; | ||
1431 | } | ||
1432 | |||
1433 | private ConsoleColor Color { get; set; } | ||
1434 | private IMessaging Messaging { get; } | ||
1435 | |||
1436 | public void LogMessage(string message) | ||
1437 | { | ||
1438 | if (this.Color == ConsoleColor.Red) | ||
1439 | { | ||
1440 | this.Messaging.Write(HarvesterErrors.BuildErrorDuringHarvesting(message)); | ||
1441 | } | ||
1442 | } | ||
1443 | |||
1444 | public void SetColor(ConsoleColor color) | ||
1445 | { | ||
1446 | this.Color = color; | ||
1447 | } | ||
1448 | |||
1449 | public void ResetColor() | ||
1450 | { | ||
1451 | this.Color = ConsoleColor.Black; | ||
1452 | } | ||
1453 | } | ||
1454 | } | ||
1455 | } | ||