aboutsummaryrefslogtreecommitdiff
path: root/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs')
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs
new file mode 100644
index 00000000..cf1e21c2
--- /dev/null
+++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs
@@ -0,0 +1,187 @@
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
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq;
10 using WixToolset.Core.Native;
11 using WixToolset.Data;
12 using WixToolset.Data.WindowsInstaller;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback
17 {
18 // Set of ICEs that have equivalent-or-better checks in WiX.
19 private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" };
20
21 public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable<string> cubeFiles, IEnumerable<string> ices, IEnumerable<string> suppressedIces)
22 {
23 this.Messaging = messaging;
24 this.BackendHelper = backendHelper;
25 this.Data = data;
26 this.OutputPath = outputPath;
27 this.CubeFiles = cubeFiles;
28 this.Ices = ices;
29 this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces);
30
31 this.IntermediateFolder = intermediateFolder;
32 this.OutputSourceLineNumber = new SourceLineNumber(outputPath);
33 }
34
35 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
36
37 /// <summary>
38 /// Encountered error implementation for <see cref="IWindowsInstallerValidatorCallback"/>.
39 /// </summary>
40 public bool EncounteredError => this.Messaging.EncounteredError;
41
42 private IMessaging Messaging { get; }
43
44 private IBackendHelper BackendHelper { get; }
45
46 private WindowsInstallerData Data { get; }
47
48 private string OutputPath { get; }
49
50 private IEnumerable<string> CubeFiles { get; }
51
52 private IEnumerable<string> Ices { get; }
53
54 private IEnumerable<string> SuppressedIces { get; }
55
56 private string IntermediateFolder { get; }
57
58 /// <summary>
59 /// Fallback when an exact source line number cannot be calculated for a validation error.
60 /// </summary>
61 private SourceLineNumber OutputSourceLineNumber { get; set; }
62
63 private Dictionary<string, SourceLineNumber> SourceLineNumbersByTablePrimaryKey { get; set; }
64
65 public void Execute()
66 {
67 var trackedFiles = new List<ITrackedFile>();
68 var stopwatch = Stopwatch.StartNew();
69
70 this.Messaging.Write(VerboseMessages.ValidatingDatabase());
71
72 // Ensure the temporary files can be created the working folder.
73 var workingFolder = Path.Combine(this.IntermediateFolder, "_validate");
74 Directory.CreateDirectory(workingFolder);
75
76 // Copy the database to a temporary location so it can be manipulated.
77 // Ensure it is not read-only.
78 var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath));
79 FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false);
80
81 var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary);
82 trackedFiles.Add(trackWorkingDatabase);
83
84 var attributes = File.GetAttributes(workingDatabasePath);
85 File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly);
86
87 var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces);
88 validator.Execute();
89
90 stopwatch.Stop();
91 this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
92
93
94 this.TrackedFiles = trackedFiles;
95 }
96
97 private void LogValidationMessage(ValidationMessage message)
98 {
99 var messageSourceLineNumbers = this.OutputSourceLineNumber;
100 if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null)
101 {
102 messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys);
103 }
104
105 switch (message.Type)
106 {
107 case ValidationMessageType.InternalFailure:
108 case ValidationMessageType.Error:
109 this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description));
110 break;
111 case ValidationMessageType.Warning:
112 this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description));
113 break;
114 case ValidationMessageType.Info:
115 this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description));
116 break;
117 default:
118 throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString()));
119 }
120 }
121
122 /// <summary>
123 /// Validation blocked by other installation operation for <see cref="IWindowsInstallerValidatorCallback"/>.
124 /// </summary>
125 public void ValidationBlocked()
126 {
127 this.Messaging.Write(VerboseMessages.ValidationSerialized());
128 }
129
130 /// <summary>
131 /// Validation message implementation for <see cref="IWindowsInstallerValidatorCallback"/>.
132 /// </summary>
133 public bool ValidationMessage(ValidationMessage message)
134 {
135 this.LogValidationMessage(message);
136 return true;
137 }
138
139 /// <summary>
140 /// Gets the source line information (if available) for a row by its table name and primary key.
141 /// </summary>
142 /// <param name="tableName">The table name of the row.</param>
143 /// <param name="primaryKeys">The primary keys of the row.</param>
144 /// <returns>The source line number information if found; null otherwise.</returns>
145 private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable<string> primaryKeys)
146 {
147 // Source line information only exists if an output file was supplied
148 if (this.Data == null)
149 {
150 // Use the file name as the source line information.
151 return this.OutputSourceLineNumber;
152 }
153
154 // Index the source line information if it hasn't been indexed already.
155 if (this.SourceLineNumbersByTablePrimaryKey == null)
156 {
157 this.SourceLineNumbersByTablePrimaryKey = new Dictionary<string, SourceLineNumber>();
158
159 // Index each real table
160 foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal))
161 {
162 // Index each row that contain source line information
163 foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null))
164 {
165 // Index the row using its table name and primary key
166 var primaryKey = row.GetPrimaryKey(';');
167
168 if (!String.IsNullOrEmpty(primaryKey))
169 {
170 try
171 {
172 var key = String.Concat(table.Name, ":", primaryKey);
173 this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers);
174 }
175 catch (ArgumentException)
176 {
177 this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name));
178 }
179 }
180 }
181 }
182 }
183
184 return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null;
185 }
186 }
187}