summaryrefslogtreecommitdiff
path: root/src/libs/WixToolset.Versioning
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/WixToolset.Versioning')
-rw-r--r--src/libs/WixToolset.Versioning/WixToolset.Versioning.csproj14
-rw-r--r--src/libs/WixToolset.Versioning/WixVersion.cs463
-rw-r--r--src/libs/WixToolset.Versioning/WixVersionComparer.cs253
-rw-r--r--src/libs/WixToolset.Versioning/WixVersionLabel.cs40
4 files changed, 770 insertions, 0 deletions
diff --git a/src/libs/WixToolset.Versioning/WixToolset.Versioning.csproj b/src/libs/WixToolset.Versioning/WixToolset.Versioning.csproj
new file mode 100644
index 00000000..2c23f391
--- /dev/null
+++ b/src/libs/WixToolset.Versioning/WixToolset.Versioning.csproj
@@ -0,0 +1,14 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netstandard2.0</TargetFrameworks>
7 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net472</TargetFrameworks>
8 <LangVersion>7.3</LangVersion>
9 <Description>WiX Toolset Versioning</Description>
10 <DebugType>embedded</DebugType>
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 <CreateDocumentationFile>true</CreateDocumentationFile>
13 </PropertyGroup>
14</Project>
diff --git a/src/libs/WixToolset.Versioning/WixVersion.cs b/src/libs/WixToolset.Versioning/WixVersion.cs
new file mode 100644
index 00000000..7333701f
--- /dev/null
+++ b/src/libs/WixToolset.Versioning/WixVersion.cs
@@ -0,0 +1,463 @@
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.Versioning
4{
5 using System;
6 using System.Collections.Generic;
7
8 /// <summary>
9 /// WiX Toolset's representation of a version designed to support
10 /// a different version types including 4-part versions with very
11 /// large numbers and semantic versions with 4-part versions with
12 /// or without leading "v" indicators.
13 /// </summary>
14 public class WixVersion : IComparable, IComparable<WixVersion>, IEquatable<WixVersion>
15 {
16 /// <summary>
17 /// Gets the prefix of the version if present when parsed. Usually, 'v' or 'V'.
18 /// </summary>
19 public char? Prefix { get; set; }
20
21 /// <summary>
22 /// Gets or sets the major version.
23 /// </summary>
24 public uint Major { get; set; }
25
26 /// <summary>
27 /// Gets or sets the minor version.
28 /// </summary>
29 public uint Minor { get; set; }
30
31 /// <summary>
32 /// Gets or sets the patch version.
33 /// </summary>
34 public uint Patch { get; set; }
35
36 /// <summary>
37 /// Gets or sets the revision version.
38 /// </summary>
39 public uint Revision { get; set; }
40
41 /// <summary>
42 /// Gets or sets whether the version did not parse correctly.
43 /// </summary>
44 public bool Invalid { get; set; }
45
46 /// <summary>
47 /// Gets or sets whether the major version was defined.
48 /// </summary>
49 public bool HasMajor { get; set; }
50
51 /// <summary>
52 /// Gets or sets the whether the minor version was defined.
53 /// </summary>
54 public bool HasMinor { get; set; }
55
56 /// <summary>
57 /// Gets or sets the whether the patch version was defined.
58 /// </summary>
59 public bool HasPatch { get; set; }
60
61 /// <summary>
62 /// Gets or sets the whether the revision version was defined.
63 /// </summary>
64 public bool HasRevision { get; set; }
65
66 /// <summary>
67 /// Gets or sets the labels in the version.
68 /// </summary>
69 public WixVersionLabel[] Labels { get; set; }
70
71 /// <summary>
72 /// Gets or sets the metadata in the version.
73 /// </summary>
74 public string Metadata { get; set; }
75
76 /// <summary>
77 /// Compare to another WixVersion.
78 /// </summary>
79 /// <param name="version">WixVersion to compare.</param>
80 /// <returns>A comparison between versions.</returns>
81 public int CompareTo(WixVersion version)
82 {
83 return WixVersionComparer.Default.Compare(this, version);
84 }
85
86 /// <summary>
87 /// Compare to another object.
88 /// </summary>
89 /// <param name="version">Object to compare.</param>
90 /// <returns>A comparison between objects.</returns>
91 public int CompareTo(object version)
92 {
93 return WixVersionComparer.Default.Compare(this, version as WixVersion);
94 }
95
96 /// <summary>
97 /// Returns a value indicating whether the current System.Version object is equal to a specified object.
98 /// </summary>
99 /// <param name="version">An WixVersion to compare with the current WixVersion object, or null.</param>
100 /// <returns>
101 /// true if the current WixVersion object and obj are both WixVersion objects,
102 /// and every component of the current System.Version object matches the corresponding
103 /// component of obj; otherwise, false.
104 /// </returns>
105 public bool Equals(WixVersion version)
106 {
107 return WixVersionComparer.Default.Equals(this, version);
108 }
109
110 /// <summary>
111 /// Returns a value indicating whether the current WixVersion object is equal to a specified object.
112 /// </summary>
113 /// <param name="obj">An object to compare with the current WixVersion object, or null.</param>
114 /// <returns>
115 /// true if the current WixVersion object and obj are both WixVersion objects,
116 /// and every component of the current System.Version object matches the corresponding
117 /// component of obj; otherwise, false.
118 /// </returns>
119 public override bool Equals(object obj)
120 {
121 return WixVersionComparer.Default.Equals(this, obj as WixVersion);
122 }
123
124 /// <summary>
125 /// Returns a hash code for the current WixVersion object.
126 /// </summary>
127 /// <returns>A 32-bit signed integer hash code.</returns>
128 public override int GetHashCode()
129 {
130 return WixVersionComparer.Default.GetHashCode(this);
131 }
132
133 /// <summary>
134 /// Parse a string value into a <c>WixVersion</c>. The returned version may be invalid.
135 /// </summary>
136 /// <param name="parse">String value to parse into a version.</param>
137 /// <returns>Parsed version.</returns>
138 public static WixVersion Parse(string parse)
139 {
140 var version = new WixVersion();
141
142 var labels = new List<WixVersionLabel>();
143 var start = 0;
144 var end = parse.Length;
145
146 if (end > 0 && (parse[0] == 'v' || parse[0] == 'V'))
147 {
148 version.Prefix = parse[0];
149
150 ++start;
151 }
152
153 var partBegin = start;
154 var partEnd = start;
155 var lastPart = false;
156 var trailingDot = false;
157 var invalid = false;
158 var currentPart = 0;
159 var parsedVersionNumber = false;
160 var expectedReleaseLabels = false;
161
162 // Parse version number
163 while (start < end)
164 {
165 trailingDot = false;
166
167 // Find end of part.
168 for (; ; )
169 {
170 if (partEnd >= end)
171 {
172 lastPart = true;
173 break;
174 }
175
176 var ch = parse[partEnd];
177
178 switch (ch)
179 {
180 case '0':
181 case '1':
182 case '2':
183 case '3':
184 case '4':
185 case '5':
186 case '6':
187 case '7':
188 case '8':
189 case '9':
190 ++partEnd;
191 continue;
192 case '.':
193 trailingDot = true;
194 break;
195 case '-':
196 case '+':
197 lastPart = true;
198 break;
199 default:
200 invalid = true;
201 break;
202 }
203
204 break;
205 }
206
207 var partLength = partEnd - partBegin;
208 if (invalid || partLength <= 0)
209 {
210 invalid = true;
211 break;
212 }
213
214 // Parse version part.
215 var s = parse.Substring(partBegin, partLength);
216 if (!UInt32.TryParse(s, out var part))
217 {
218 invalid = true;
219 break;
220 }
221
222 switch (currentPart)
223 {
224 case 0:
225 version.Major = part;
226 version.HasMajor = true;
227 break;
228 case 1:
229 version.Minor = part;
230 version.HasMinor = true;
231 break;
232 case 2:
233 version.Patch = part;
234 version.HasPatch = true;
235 break;
236 case 3:
237 version.Revision = part;
238 version.HasRevision = true;
239 break;
240 }
241
242 if (trailingDot)
243 {
244 ++partEnd;
245 }
246 partBegin = partEnd;
247 ++currentPart;
248
249 if (4 <= currentPart || lastPart)
250 {
251 parsedVersionNumber = true;
252 break;
253 }
254 }
255
256 invalid |= !parsedVersionNumber || trailingDot;
257
258 if (!invalid && partBegin < end && parse[partBegin] == '-')
259 {
260 partBegin = partEnd = partBegin + 1;
261 expectedReleaseLabels = true;
262 lastPart = false;
263 }
264
265 while (expectedReleaseLabels && partBegin < end)
266 {
267 trailingDot = false;
268
269 // Find end of part.
270 for (; ; )
271 {
272 if (partEnd >= end)
273 {
274 lastPart = true;
275 break;
276 }
277
278 var ch = parse[partEnd];
279 if (ch >= '0' && ch <= '9' ||
280 ch >= 'A' && ch <= 'Z' ||
281 ch >= 'a' && ch <= 'z' ||
282 ch == '-')
283 {
284 ++partEnd;
285 continue;
286 }
287 else if (ch == '+')
288 {
289 lastPart = true;
290 }
291 else if (ch == '.')
292 {
293 trailingDot = true;
294 }
295 else
296 {
297 invalid = true;
298 }
299
300 break;
301 }
302
303 var partLength = partEnd - partBegin;
304 if (invalid || partLength <= 0)
305 {
306 invalid = true;
307 break;
308 }
309
310 WixVersionLabel label;
311 var partString = parse.Substring(partBegin, partLength);
312 if (UInt32.TryParse(partString, out var numericPart))
313 {
314 label = new WixVersionLabel(partString, numericPart);
315 }
316 else
317 {
318 label = new WixVersionLabel(partString);
319 }
320
321 labels.Add(label);
322
323 if (trailingDot)
324 {
325 ++partEnd;
326 }
327 partBegin = partEnd;
328
329 if (lastPart)
330 {
331 break;
332 }
333 }
334
335 invalid |= expectedReleaseLabels && (labels.Count == 0 || trailingDot);
336
337 if (!invalid && partBegin < end)
338 {
339 if (parse[partBegin] == '+')
340 {
341 version.Metadata = parse.Substring(partBegin + 1);
342 }
343 else
344 {
345 invalid = true;
346 }
347 }
348
349 version.Labels = labels.Count == 0 ? null : labels.ToArray();
350
351 if (invalid)
352 {
353 // If the prefix was parsed but the rest of the version was
354 // invalid, store the full invalid version in the Metadata
355 // and clear the prefix.
356 if (version.Prefix.HasValue && partBegin == 1)
357 {
358 version.Prefix = null;
359 version.Metadata = parse;
360 }
361 else // store the remaining invalid content in Metadata.
362 {
363 version.Metadata = (partBegin < end) ? parse.Substring(partBegin) : String.Empty;
364 }
365
366 version.Invalid = true;
367 }
368
369 return version;
370 }
371
372 /// <summary>
373 /// Tries to parse a string value into a valid <c>WixVersion</c>.
374 /// </summary>
375 /// <param name="parse">String value to parse into a version.</param>
376 /// <param name="version">Parsed version.</param>
377 /// <returns>True if the version was successfully parsed, or false otherwise.</returns>
378 public static bool TryParse(string parse, out WixVersion version)
379 {
380 version = WixVersion.Parse(parse);
381
382 if (version.Invalid)
383 {
384 version = null;
385 return false;
386 }
387
388 return true;
389 }
390
391 /// <summary>
392 /// Determines whether two specified WixVersion objects are equal.
393 /// </summary>
394 /// <param name="v1">The first WixVersion object.</param>
395 /// <param name="v2">The second WixVersion object.</param>
396 /// <returns>true if v1 equals v2; otherwise, false.</returns>
397 public static bool operator ==(WixVersion v1, WixVersion v2)
398 {
399 return WixVersionComparer.Default.Equals(v1, v2);
400 }
401
402 /// <summary>
403 /// Determines whether two specified System.Version objects are not equal.
404 /// </summary>
405 /// <param name="v1">The first WixVersion object.</param>
406 /// <param name="v2">The second WixVersion object.</param>
407 /// <returns>true if v1 does not equal v2; otherwise, false.</returns>
408 public static bool operator !=(WixVersion v1, WixVersion v2)
409 {
410 return !WixVersionComparer.Default.Equals(v1, v2);
411 }
412
413 /// <summary>
414 /// Determines whether the first specified System.Version object is less than the second specified System.Version object.
415 /// </summary>
416 /// <param name="v1">The first WixVersion object.</param>
417 /// <param name="v2">The second WixVersion object.</param>
418 /// <returns>true if v1 is less than v2; otherwise, false.</returns>
419 /// <exception cref="ArgumentNullException">v1 is null.</exception>
420 public static bool operator <(WixVersion v1, WixVersion v2)
421 {
422 return WixVersionComparer.Default.Compare(v1, v2) == -1;
423 }
424
425 /// <summary>
426 /// Determines whether the first specified System.Version object is greater than the second specified System.Version object.
427 /// </summary>
428 /// <param name="v1">The first WixVersion object.</param>
429 /// <param name="v2">The second WixVersion object.</param>
430 /// <returns>true if v1 is greater than v2; otherwise, false.</returns>
431 public static bool operator >(WixVersion v1, WixVersion v2)
432 {
433 return WixVersionComparer.Default.Compare(v1, v2) == 1;
434 }
435
436 /// <summary>
437 /// Determines whether the first specified System.Version object is less than or equal to the second System.Version object.
438 /// </summary>
439 /// <param name="v1">The first WixVersion object.</param>
440 /// <param name="v2">The second WixVersion object.</param>
441 /// <returns>true if v1 is less than or equal to v2; otherwise, false.</returns>
442 /// <exception cref="ArgumentNullException">v1 is null.</exception>
443 public static bool operator <=(WixVersion v1, WixVersion v2)
444 {
445 var result = WixVersionComparer.Default.Compare(v1, v2);
446
447 return result == 0 || result == -1;
448 }
449
450 /// <summary>
451 /// Determines whether the first specified System.Version object is greater than or equal to the second specified System.Version object.
452 /// </summary>
453 /// <param name="v1">The first WixVersion object.</param>
454 /// <param name="v2">The second WixVersion object.</param>
455 /// <returns>true if v1 is greater than or equal to v2; otherwise, false.</returns>
456 public static bool operator >=(WixVersion v1, WixVersion v2)
457 {
458 var result = WixVersionComparer.Default.Compare(v1, v2);
459
460 return result == 0 || result == 1;
461 }
462 }
463}
diff --git a/src/libs/WixToolset.Versioning/WixVersionComparer.cs b/src/libs/WixToolset.Versioning/WixVersionComparer.cs
new file mode 100644
index 00000000..be203634
--- /dev/null
+++ b/src/libs/WixToolset.Versioning/WixVersionComparer.cs
@@ -0,0 +1,253 @@
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.Versioning
4{
5 using System;
6 using System.Collections.Generic;
7
8 /// <summary>
9 /// WixVersion comparer.
10 /// </summary>
11 public class WixVersionComparer : IEqualityComparer<WixVersion>, IComparer<WixVersion>
12 {
13 /// <summary>
14 /// Default WixVersion comparer.
15 /// </summary>
16 public static readonly WixVersionComparer Default = new WixVersionComparer();
17
18 /// <inheritdoc />
19 public int Compare(WixVersion x, WixVersion y)
20 {
21 if ((object)x == y)
22 {
23 return 0;
24 }
25
26 if (x is null)
27 {
28 return -1;
29 }
30
31 if (y is null)
32 {
33 return 1;
34 }
35
36 var result = x.Major.CompareTo(y.Major);
37 if (result != 0)
38 {
39 return result;
40 }
41
42 result = x.Minor.CompareTo(y.Minor);
43 if (result != 0)
44 {
45 return result;
46 }
47
48 result = x.Patch.CompareTo(y.Patch);
49 if (result != 0)
50 {
51 return result;
52 }
53
54 result = x.Revision.CompareTo(y.Revision);
55 if (result != 0)
56 {
57 return result;
58 }
59
60 var xLabelCount = x.Labels?.Length ?? 0;
61 var yLabelCount = y.Labels?.Length ?? 0;
62 var maxLabelCount = Math.Max(xLabelCount, yLabelCount);
63
64 if (xLabelCount > 0)
65 {
66 if (yLabelCount == 0)
67 {
68 return -1;
69 }
70 }
71 else if (yLabelCount > 0)
72 {
73 return 1;
74 }
75
76 for (var i = 0; i < maxLabelCount; ++i)
77 {
78 var xLabel = i < xLabelCount ? x.Labels[i] : null;
79 var yLabel = i < yLabelCount ? y.Labels[i] : null;
80
81 result = CompareReleaseLabel(xLabel, yLabel);
82 if (result != 0)
83 {
84 return result;
85 }
86 }
87
88 var compareMetadata = false;
89
90 if (x.Invalid)
91 {
92 if (!y.Invalid)
93 {
94 return -1;
95 }
96 else
97 {
98 compareMetadata = true;
99 }
100 }
101 else if (y.Invalid)
102 {
103 return 1;
104 }
105
106 if (compareMetadata)
107 {
108 result = String.Compare(x.Metadata, y.Metadata, StringComparison.OrdinalIgnoreCase);
109 }
110
111 return (result == 0) ? 0 : (result < 0) ? -1 : 1;
112 }
113
114 /// <inheritdoc />
115 public bool Equals(WixVersion x, WixVersion y)
116 {
117 if ((object)x == y)
118 {
119 return true;
120 }
121
122 if (x is null)
123 {
124 return false;
125 }
126
127 if (y is null)
128 {
129 return false;
130 }
131
132 if (x.Major != y.Major)
133 {
134 return false;
135 }
136
137 if (x.Minor != y.Minor)
138 {
139 return false;
140 }
141
142 if (x.Patch != y.Patch)
143 {
144 return false;
145 }
146
147 if (x.Revision != y.Revision)
148 {
149 return false;
150 }
151
152 var labelCount = x.Labels?.Length ?? 0;
153 if (labelCount != (y.Labels?.Length ?? 0))
154 {
155 return false;
156 }
157
158 for (var i = 0; i < labelCount; ++i)
159 {
160 var result = CompareReleaseLabel(x.Labels[i], y.Labels[i]);
161 if (result != 0)
162 {
163 return false;
164 }
165 }
166
167 if (x.Invalid)
168 {
169 if (y.Invalid)
170 {
171 return String.Equals(x.Metadata, y.Metadata, StringComparison.OrdinalIgnoreCase);
172 }
173 else
174 {
175 return false;
176 }
177 }
178 else if (y.Invalid)
179 {
180 return false;
181 }
182
183 return true;
184 }
185
186 /// <inheritdoc />
187 public int GetHashCode(WixVersion version)
188 {
189 var hash = 23L;
190 hash = hash * 37 + (version.Prefix ?? '\0');
191 hash = hash * 37 + version.Major;
192 hash = hash * 37 + version.Minor;
193 hash = hash * 37 + version.Patch;
194 hash = hash * 37 + version.Revision;
195 hash = hash * 37 + (version.Invalid ? 1 : 0);
196 hash = hash * 37 + (version.HasMajor ? 1 : 0);
197 hash = hash * 37 + (version.HasMinor ? 1 : 0);
198 hash = hash * 37 + (version.HasPatch ? 1 : 0);
199 hash = hash * 37 + (version.HasRevision ? 1 : 0);
200
201 if (version.Labels != null)
202 {
203 foreach (var label in version.Labels)
204 {
205 hash = hash * 37 + label.Label.GetHashCode();
206 }
207 }
208
209 hash = hash * 37 + version.Metadata?.GetHashCode() ?? 0;
210
211 return unchecked((int)hash);
212 }
213
214 private static int CompareReleaseLabel(WixVersionLabel l1, WixVersionLabel l2)
215 {
216 if (l1 == l2)
217 {
218 return 0;
219 }
220 else if (l2 == null)
221 {
222 return 1;
223 }
224 else if (l1 == null)
225 {
226 return -1;
227 }
228
229 if (l1.Numeric.HasValue)
230 {
231 if (l2.Numeric.HasValue)
232 {
233 return l1.Numeric.Value.CompareTo(l2.Numeric.Value);
234 }
235 else
236 {
237 return -1;
238 }
239 }
240 else
241 {
242 if (l2.Numeric.HasValue)
243 {
244 return 1;
245 }
246 else
247 {
248 return String.Compare(l1.Label, l2.Label, StringComparison.OrdinalIgnoreCase);
249 }
250 }
251 }
252 }
253}
diff --git a/src/libs/WixToolset.Versioning/WixVersionLabel.cs b/src/libs/WixToolset.Versioning/WixVersionLabel.cs
new file mode 100644
index 00000000..c5dcb875
--- /dev/null
+++ b/src/libs/WixToolset.Versioning/WixVersionLabel.cs
@@ -0,0 +1,40 @@
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.Versioning
4{
5 /// <summary>
6 /// Label in a <c>WixVersion</c>.
7 /// </summary>
8 public class WixVersionLabel
9 {
10 /// <summary>
11 /// Creates a string only version label.
12 /// </summary>
13 /// <param name="label">String value for version label.</param>
14 public WixVersionLabel(string label)
15 {
16 this.Label = label;
17 }
18
19 /// <summary>
20 /// Creates a string version label with numeric value.
21 /// </summary>
22 /// <param name="label">String value for version label.</param>
23 /// <param name="numeric">Numeric value for the version label.</param>
24 public WixVersionLabel(string label, uint? numeric)
25 {
26 this.Label = label;
27 this.Numeric = numeric;
28 }
29
30 /// <summary>
31 /// Gets the string label value.
32 /// </summary>
33 public string Label { get; set; }
34
35 /// <summary>
36 /// Gets the optional numeric label value.
37 /// </summary>
38 public uint? Numeric { get; set; }
39 }
40}