aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-05-09 22:18:19 -0700
committerRob Mensching <rob@firegiant.com>2022-05-10 13:03:03 -0700
commit905a6b0c4a214a373cb437ca28ea5610b3ad7654 (patch)
tree510ee13cb0b0120ea271da848f3f8dbf7ff1ab51
parentd5744da0117199f23bf72f5c2ba7cd1c6f52e173 (diff)
downloadwix-905a6b0c4a214a373cb437ca28ea5610b3ad7654.tar.gz
wix-905a6b0c4a214a373cb437ca28ea5610b3ad7654.tar.bz2
wix-905a6b0c4a214a373cb437ca28ea5610b3ad7654.zip
Add WixVersion implementation to WixToolset.Data
-rw-r--r--src/api/wix/WixToolset.Data/WixVersion.cs275
-rw-r--r--src/api/wix/WixToolset.Data/WixVersionLabel.cs43
-rw-r--r--src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs204
3 files changed, 522 insertions, 0 deletions
diff --git a/src/api/wix/WixToolset.Data/WixVersion.cs b/src/api/wix/WixToolset.Data/WixVersion.cs
new file mode 100644
index 00000000..2b02bd7d
--- /dev/null
+++ b/src/api/wix/WixToolset.Data/WixVersion.cs
@@ -0,0 +1,275 @@
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.Data
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 [CLSCompliant(false)]
15 public class WixVersion
16 {
17 /// <summary>
18 /// Gets the prefix of the version if present when parsed. Usually, 'v' or 'V'.
19 /// </summary>
20 public char? Prefix { get; set; }
21
22 /// <summary>
23 /// Gets or sets the major version.
24 /// </summary>
25 public uint? Major { get; set; }
26
27 /// <summary>
28 /// Gets or sets the minor version.
29 /// </summary>
30 public uint? Minor { get; set; }
31
32 /// <summary>
33 /// Gets or sets the patch version.
34 /// </summary>
35 public uint? Patch { get; set; }
36
37 /// <summary>
38 /// Gets or sets the revision version.
39 /// </summary>
40 public uint? Revision { get; set; }
41
42 /// <summary>
43 /// Gets or sets the labels in the version.
44 /// </summary>
45 public WixVersionLabel[] Labels { get; set; }
46
47 /// <summary>
48 /// Gets or sets the metadata in the version.
49 /// </summary>
50 public string Metadata { get; set; }
51
52 /// <summary>
53 /// Tries to parse a string value into a valid <c>WixVersion</c>.
54 /// </summary>
55 /// <param name="parse">String value to parse into a version.</param>
56 /// <param name="version">Parsed version.</param>
57 /// <returns>True if the version was successfully parsed, or false otherwise.</returns>
58 public static bool TryParse(string parse, out WixVersion version)
59 {
60 version = new WixVersion();
61
62 var labels = new List<WixVersionLabel>();
63 var start = 0;
64 var end = parse.Length;
65
66 if (end > 0 && (parse[0] == 'v' || parse[0] == 'V'))
67 {
68 version.Prefix = parse[0];
69
70 ++start;
71 }
72
73 var partBegin = start;
74 var partEnd = start;
75 var lastPart = false;
76 var trailingDot = false;
77 var invalid = false;
78 var currentPart = 0;
79 var parsedVersionNumber = false;
80 var expectedReleaseLabels = false;
81
82 // Parse version number
83 while (start < end)
84 {
85 trailingDot = false;
86
87 // Find end of part.
88 for (; ; )
89 {
90 if (partEnd >= end)
91 {
92 lastPart = true;
93 break;
94 }
95
96 var ch = parse[partEnd];
97
98 switch (ch)
99 {
100 case '0':
101 case '1':
102 case '2':
103 case '3':
104 case '4':
105 case '5':
106 case '6':
107 case '7':
108 case '8':
109 case '9':
110 ++partEnd;
111 continue;
112 case '.':
113 trailingDot = true;
114 break;
115 case '-':
116 case '+':
117 lastPart = true;
118 break;
119 default:
120 invalid = true;
121 break;
122 }
123
124 break;
125 }
126
127 var partLength = partEnd - partBegin;
128 if (invalid || partLength <= 0)
129 {
130 invalid = true;
131 break;
132 }
133
134 // Parse version part.
135 var s = parse.Substring(partBegin, partLength);
136 if (!UInt32.TryParse(s, out var part))
137 {
138 invalid = true;
139 break;
140 }
141
142 switch (currentPart)
143 {
144 case 0:
145 version.Major = part;
146 break;
147 case 1:
148 version.Minor = part;
149 break;
150 case 2:
151 version.Patch = part;
152 break;
153 case 3:
154 version.Revision = part;
155 break;
156 }
157
158 if (trailingDot)
159 {
160 ++partEnd;
161 }
162 partBegin = partEnd;
163 ++currentPart;
164
165 if (4 <= currentPart || lastPart)
166 {
167 parsedVersionNumber = true;
168 break;
169 }
170 }
171
172 invalid |= !parsedVersionNumber || trailingDot;
173
174 if (!invalid && partBegin < end && parse[partBegin] == '-')
175 {
176 partBegin = partEnd = partBegin + 1;
177 expectedReleaseLabels = true;
178 lastPart = false;
179 }
180
181 while (expectedReleaseLabels && partBegin < end)
182 {
183 trailingDot = false;
184
185 // Find end of part.
186 for (; ; )
187 {
188 if (partEnd >= end)
189 {
190 lastPart = true;
191 break;
192 }
193
194 var ch = parse[partEnd];
195 if (ch >= '0' && ch <= '9' ||
196 ch >= 'A' && ch <= 'Z' ||
197 ch >= 'a' && ch <= 'z' ||
198 ch == '-')
199 {
200 ++partEnd;
201 continue;
202 }
203 else if (ch == '+')
204 {
205 lastPart = true;
206 }
207 else if (ch == '.')
208 {
209 trailingDot = true;
210 }
211 else
212 {
213 invalid = true;
214 }
215
216 break;
217 }
218
219 var partLength = partEnd - partBegin;
220 if (invalid || partLength <= 0)
221 {
222 invalid = true;
223 break;
224 }
225
226 WixVersionLabel label;
227 var partString = parse.Substring(partBegin, partLength);
228 if (UInt32.TryParse(partString, out var numericPart))
229 {
230 label = new WixVersionLabel(partString, numericPart);
231 }
232 else
233 {
234 label = new WixVersionLabel(partString);
235 }
236
237 labels.Add(label);
238
239 if (trailingDot)
240 {
241 ++partEnd;
242 }
243 partBegin = partEnd;
244
245 if (lastPart)
246 {
247 break;
248 }
249 }
250
251 invalid |= expectedReleaseLabels && (labels.Count == 0 || trailingDot);
252
253 if (!invalid && partBegin < end)
254 {
255 if (parse[partBegin] == '+')
256 {
257 version.Metadata = parse.Substring(partBegin + 1);
258 }
259 else
260 {
261 invalid = true;
262 }
263 }
264
265 if (invalid)
266 {
267 return false;
268 }
269
270 version.Labels = labels.Count == 0 ? null : labels.ToArray();
271
272 return true;
273 }
274 }
275}
diff --git a/src/api/wix/WixToolset.Data/WixVersionLabel.cs b/src/api/wix/WixToolset.Data/WixVersionLabel.cs
new file mode 100644
index 00000000..c4227c49
--- /dev/null
+++ b/src/api/wix/WixToolset.Data/WixVersionLabel.cs
@@ -0,0 +1,43 @@
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.Data
4{
5 using System;
6
7 /// <summary>
8 /// Label in a <c>WixVersion</c>.
9 /// </summary>
10 [CLSCompliant(false)]
11 public class WixVersionLabel
12 {
13 /// <summary>
14 /// Creates a string only version label.
15 /// </summary>
16 /// <param name="label">String value for version label.</param>
17 public WixVersionLabel(string label)
18 {
19 this.Label = label;
20 }
21
22 /// <summary>
23 /// Creates a string version label with numeric value.
24 /// </summary>
25 /// <param name="label">String value for version label.</param>
26 /// <param name="numeric">Numeric value for the version label.</param>
27 public WixVersionLabel(string label, uint? numeric)
28 {
29 this.Label = label;
30 this.Numeric = numeric;
31 }
32
33 /// <summary>
34 /// Gets the string label value.
35 /// </summary>
36 public string Label { get; set; }
37
38 /// <summary>
39 /// Gets the optional numeric label value.
40 /// </summary>
41 public uint? Numeric { get; set; }
42 }
43}
diff --git a/src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs b/src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs
new file mode 100644
index 00000000..45253460
--- /dev/null
+++ b/src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs
@@ -0,0 +1,204 @@
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 WixToolsetTest.Data
4{
5 using System;
6 using WixToolset.Data;
7 using Xunit;
8
9 public class WixVerFixture
10 {
11 [Fact]
12 public void CannotParseEmptyStringAsVersion()
13 {
14 Assert.False(WixVersion.TryParse(String.Empty, out var _));
15 }
16
17 [Fact]
18 public void CannotParseInvalidStringAsVersion()
19 {
20 Assert.False(WixVersion.TryParse("invalid", out var _));
21 }
22
23 [Fact]
24 public void CanParseFourPartVersion()
25 {
26 Assert.True(WixVersion.TryParse("1.2.3.4", out var version));
27 Assert.Null(version.Prefix);
28 Assert.Equal((uint)1, version.Major);
29 Assert.Equal((uint)2, version.Minor);
30 Assert.Equal((uint)3, version.Patch);
31 Assert.Equal((uint)4, version.Revision);
32 Assert.Null(version.Labels);
33 Assert.Null(version.Metadata);
34 }
35
36 [Fact]
37 public void CanParseThreePartVersion()
38 {
39 Assert.True(WixVersion.TryParse("1.2.3", out var version));
40 Assert.Null(version.Prefix);
41 Assert.Equal((uint)1, version.Major);
42 Assert.Equal((uint)2, version.Minor);
43 Assert.Equal((uint)3, version.Patch);
44 Assert.Null(version.Revision);
45 Assert.Null(version.Labels);
46 Assert.Null(version.Metadata);
47 }
48
49 [Fact]
50 public void CanParseFourPartVersionWithTrailingZero()
51 {
52 Assert.True(WixVersion.TryParse("1.2.3.0", out var version));
53 Assert.Null(version.Prefix);
54 Assert.Equal((uint)1, version.Major);
55 Assert.Equal((uint)2, version.Minor);
56 Assert.Equal((uint)3, version.Patch);
57 Assert.Equal((uint)0, version.Revision);
58 Assert.Null(version.Labels);
59 Assert.Null(version.Metadata);
60 }
61
62 [Fact]
63 public void CanParseNumericReleaseLabels()
64 {
65 Assert.True(WixVersion.TryParse("1.2-19", out var version));
66 Assert.Null(version.Prefix);
67 Assert.Equal((uint)1, version.Major);
68 Assert.Equal((uint)2, version.Minor);
69 Assert.Null(version.Patch);
70 Assert.Null(version.Revision);
71 Assert.Equal("19", version.Labels[0].Label);
72 Assert.Equal((uint)19, version.Labels[0].Numeric);
73 Assert.Null(version.Metadata);
74 }
75
76 [Fact]
77 public void CanParseDottedNumericReleaseLabels()
78 {
79 Assert.True(WixVersion.TryParse("1.2-2.0", out var version));
80 Assert.Null(version.Prefix);
81 Assert.Equal((uint)1, version.Major);
82 Assert.Equal((uint)2, version.Minor);
83 Assert.Null(version.Patch);
84 Assert.Null(version.Revision);
85 Assert.Equal("2", version.Labels[0].Label);
86 Assert.Equal((uint)2, version.Labels[0].Numeric);
87 Assert.Equal("0", version.Labels[1].Label);
88 Assert.Equal((uint)0, version.Labels[1].Numeric);
89 Assert.Null(version.Metadata);
90 }
91
92 [Fact]
93 public void CanParseHyphenAsVersionSeparator()
94 {
95 Assert.True(WixVersion.TryParse("0.0.1-a", out var version));
96 Assert.Null(version.Prefix);
97 Assert.Equal((uint)0, version.Major);
98 Assert.Equal((uint)0, version.Minor);
99 Assert.Equal((uint)1, version.Patch);
100 Assert.Equal("a", version.Labels[0].Label);
101 Assert.Null(version.Labels[0].Numeric);
102 Assert.Null(version.Metadata);
103 }
104
105 [Fact]
106 public void CanParseIgnoringLeadingZeros()
107 {
108 Assert.True(WixVersion.TryParse("0.01-a.000", out var version));
109 Assert.Null(version.Prefix);
110 Assert.Equal((uint)0, version.Major);
111 Assert.Equal((uint)1, version.Minor);
112 Assert.Null(version.Patch);
113 Assert.Null(version.Revision);
114 Assert.Equal("a", version.Labels[0].Label);
115 Assert.Null(version.Labels[0].Numeric);
116 Assert.Equal("000", version.Labels[1].Label);
117 Assert.Equal((uint)0, version.Labels[1].Numeric);
118 Assert.Null(version.Metadata);
119 }
120
121 [Fact]
122 public void CanParseMetadata()
123 {
124 Assert.True(WixVersion.TryParse("1.2.3+abcd", out var version));
125 Assert.Null(version.Prefix);
126 Assert.Equal((uint)1, version.Major);
127 Assert.Equal((uint)2, version.Minor);
128 Assert.Equal((uint)3, version.Patch);
129 Assert.Null(version.Revision);
130 Assert.Null(version.Labels);
131 Assert.Equal("abcd", version.Metadata);
132 }
133
134 [Fact]
135 public void CannotParseUnexpectedContentAsMetadata()
136 {
137 Assert.False(WixVersion.TryParse("1.2.3.abcd", out var _));
138 Assert.False(WixVersion.TryParse("1.2.3.-abcd", out var _));
139 }
140
141 [Fact]
142 public void CanParseLeadingPrefix()
143 {
144 Assert.True(WixVersion.TryParse("v10.20.30.40", out var version));
145 Assert.Equal('v', version.Prefix);
146 Assert.Equal((uint)10, version.Major);
147 Assert.Equal((uint)20, version.Minor);
148 Assert.Equal((uint)30, version.Patch);
149 Assert.Equal((uint)40, version.Revision);
150 Assert.Null(version.Labels);
151 Assert.Null(version.Metadata);
152
153 Assert.True(WixVersion.TryParse("V100.200.300.400", out var version2));
154 Assert.Equal('V', version2.Prefix);
155 Assert.Equal((uint)100, version2.Major);
156 Assert.Equal((uint)200, version2.Minor);
157 Assert.Equal((uint)300, version2.Patch);
158 Assert.Equal((uint)400, version2.Revision);
159 Assert.Null(version2.Labels);
160 Assert.Null(version2.Metadata);
161 }
162
163 [Fact]
164 public void CanParseVeryLargeNumbers()
165 {
166 Assert.True(WixVersion.TryParse("4294967295.4294967295.4294967295.4294967295", out var version));
167 Assert.Null(version.Prefix);
168 Assert.Equal(4294967295, version.Major);
169 Assert.Equal(4294967295, version.Minor);
170 Assert.Equal(4294967295, version.Patch);
171 Assert.Equal(4294967295, version.Revision);
172 Assert.Null(version.Labels);
173 Assert.Null(version.Metadata);
174 }
175
176 [Fact]
177 public void CannotParseTooLargeNumbers()
178 {
179 Assert.False(WixVersion.TryParse("4294967296.4294967296.4294967296.4294967296", out var _));
180 }
181
182 [Fact]
183 public void CanParseLabelsWithMetadata()
184 {
185 Assert.True(WixVersion.TryParse("1.2.3.4-a.b.c.d.5+abc123", out var version));
186 Assert.Null(version.Prefix);
187 Assert.Equal((uint)1, version.Major);
188 Assert.Equal((uint)2, version.Minor);
189 Assert.Equal((uint)3, version.Patch);
190 Assert.Equal((uint)4, version.Revision);
191 Assert.Equal("a", version.Labels[0].Label);
192 Assert.Null(version.Labels[0].Numeric);
193 Assert.Equal("b", version.Labels[1].Label);
194 Assert.Null(version.Labels[1].Numeric);
195 Assert.Equal("c", version.Labels[2].Label);
196 Assert.Null(version.Labels[2].Numeric);
197 Assert.Equal("d", version.Labels[3].Label);
198 Assert.Null(version.Labels[3].Numeric);
199 Assert.Equal("5", version.Labels[4].Label);
200 Assert.Equal((uint)5, version.Labels[4].Numeric);
201 Assert.Equal("abc123", version.Metadata);
202 }
203 }
204}