diff options
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 | } | ||