diff --git a/Build/Installer.legacy.targets b/Build/Installer.legacy.targets
index fa50123978..bf0b9d2b42 100644
--- a/Build/Installer.legacy.targets
+++ b/Build/Installer.legacy.targets
@@ -323,8 +323,12 @@
-
+
+
+ <_CustomComponentsTemplate>$(fwrt)\FLExInstaller\CustomComponents.wxi
+ <_RenderedCustomComponentsPath>$(OverridesDestDir)\CustomComponents.wxi
+
+
+
+
+
+ <_CustomComponentsTemplateContent>@(_CustomComponentsTemplateLines, '%0A')
+ <_CustomComponentsRenderedContent>$(_CustomComponentsTemplateContent.Replace('__EncodingConvertersCoreVersion__', '$(EncodingConvertersCoreVersion)'))
+
+
diff --git a/Build/SilVersions.props b/Build/SilVersions.props
index ca47990be4..08d1393db3 100644
--- a/Build/SilVersions.props
+++ b/Build/SilVersions.props
@@ -20,6 +20,7 @@
3.8.2
1.1.1-beta0001
10.0.0-beta0004
+ 0.9.8
70.1.152
60.0.56
diff --git a/Build/Src/Directory.Packages.props b/Build/Src/Directory.Packages.props
index 0c038be2f9..efcc111784 100644
--- a/Build/Src/Directory.Packages.props
+++ b/Build/Src/Directory.Packages.props
@@ -17,7 +17,7 @@
-
+
diff --git a/Build/Windows.targets b/Build/Windows.targets
index ff71286a28..3f07c7ce79 100644
--- a/Build/Windows.targets
+++ b/Build/Windows.targets
@@ -84,7 +84,7 @@
- 0.9.7
+ $(EncodingConvertersCoreVersion)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3c9315077e..6bd6b911d7 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -37,7 +37,7 @@
-
+
@@ -131,7 +131,7 @@
-
+
diff --git a/FLExInstaller/CustomComponents.wxi b/FLExInstaller/CustomComponents.wxi
index e48406a4d3..2cd995bc8f 100644
--- a/FLExInstaller/CustomComponents.wxi
+++ b/FLExInstaller/CustomComponents.wxi
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/Obj/FwBuildTasks/FwBuildTasks.csproj.nuget.g.props b/Obj/FwBuildTasks/FwBuildTasks.csproj.nuget.g.props
new file mode 100644
index 0000000000..362b765020
--- /dev/null
+++ b/Obj/FwBuildTasks/FwBuildTasks.csproj.nuget.g.props
@@ -0,0 +1,27 @@
+
+
+
+ True
+ NuGet
+ $(MSBuildThisFileDirectory)project.assets.json
+ /home/dependabot/dependabot-updater/repo/packages
+ /home/dependabot/dependabot-updater/repo/packages
+ PackageReference
+ 7.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /home/dependabot/dependabot-updater/repo/packages/microsoft.build.framework/18.6.3
+ /home/dependabot/dependabot-updater/repo/packages/sil.buildtasks/3.2.0
+
+
\ No newline at end of file
diff --git a/Obj/FwBuildTasks/FwBuildTasks.csproj.nuget.g.targets b/Obj/FwBuildTasks/FwBuildTasks.csproj.nuget.g.targets
new file mode 100644
index 0000000000..04d9a4c016
--- /dev/null
+++ b/Obj/FwBuildTasks/FwBuildTasks.csproj.nuget.g.targets
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Src/InstallValidator/InstallValidatorTests/DependencyVersionConsistencyTests.cs b/Src/InstallValidator/InstallValidatorTests/DependencyVersionConsistencyTests.cs
new file mode 100644
index 0000000000..221da3a469
--- /dev/null
+++ b/Src/InstallValidator/InstallValidatorTests/DependencyVersionConsistencyTests.cs
@@ -0,0 +1,97 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using NUnit.Framework;
+
+namespace SIL.InstallValidator
+{
+ [TestFixture]
+ public sealed class DependencyVersionConsistencyTests
+ {
+ [Test]
+ public void EncodingConvertersPathsStayAlignedWithCentralPackageVersion()
+ {
+ var repoRoot = FindRepoRoot();
+ Assert.That(repoRoot, Is.Not.Null, "Could not locate repo root (FieldWorks.sln).");
+
+ var silVersionsPath = Path.Combine(repoRoot, "Build", "SilVersions.props");
+ var expectedVersion = ReadSingleMatch(
+ silVersionsPath,
+ @"(?[^<]+)",
+ "EncodingConvertersCoreVersion");
+
+ var packageVersionExpression = ReadCentralPackageVersion(repoRoot, "encoding-converters-core");
+ Assert.That(
+ packageVersionExpression,
+ Is.EqualTo("$(EncodingConvertersCoreVersion)"),
+ "Directory.Packages.props should reference the shared EncodingConvertersCoreVersion property.");
+
+ var windowsTargetsPath = Path.Combine(repoRoot, "Build", "Windows.targets");
+ var windowsTargetsVersion = ReadSingleMatch(
+ windowsTargetsPath,
+ @"]*>(?[^<]+)",
+ "ECNugetVersion");
+
+ Assert.That(
+ windowsTargetsVersion,
+ Is.EqualTo("$(EncodingConvertersCoreVersion)"),
+ $"{windowsTargetsPath} should reference the shared EncodingConvertersCoreVersion property.");
+
+ var customComponentsPath = Path.Combine(repoRoot, "FLExInstaller", "CustomComponents.wxi");
+ var customComponentsContent = File.ReadAllText(customComponentsPath);
+
+ Assert.That(
+ Regex.IsMatch(customComponentsContent, @"encoding-converters-core\\__EncodingConvertersCoreVersion__\\runtimes\\EcDistFiles", RegexOptions.IgnoreCase),
+ Is.True,
+ $"{customComponentsPath} should use the shared EncodingConvertersCoreVersion placeholder instead of a hardcoded version.");
+
+ Assert.That(
+ customComponentsContent.Contains(expectedVersion),
+ Is.False,
+ $"{customComponentsPath} should not hardcode version {expectedVersion}.");
+
+ var installerLegacyTargetsPath = Path.Combine(repoRoot, "Build", "Installer.legacy.targets");
+ var installerLegacyTargetsContent = File.ReadAllText(installerLegacyTargetsPath);
+
+ Assert.That(
+ installerLegacyTargetsContent.Contains("__EncodingConvertersCoreVersion__") && installerLegacyTargetsContent.Contains("$(EncodingConvertersCoreVersion)"),
+ Is.True,
+ $"{installerLegacyTargetsPath} should materialize the legacy installer CustomComponents.wxi from the shared EncodingConvertersCoreVersion property.");
+ }
+
+ private static string FindRepoRoot()
+ {
+ var dir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory);
+ while (dir != null)
+ {
+ if (File.Exists(Path.Combine(dir.FullName, "FieldWorks.sln")))
+ return dir.FullName;
+
+ dir = dir.Parent;
+ }
+
+ return null;
+ }
+
+ private static string ReadCentralPackageVersion(string repoRoot, string packageId)
+ {
+ var document = XDocument.Load(Path.Combine(repoRoot, "Directory.Packages.props"));
+ var package = document
+ .Descendants("PackageVersion")
+ .FirstOrDefault(element => string.Equals((string) element.Attribute("Include"), packageId, StringComparison.OrdinalIgnoreCase));
+
+ return package == null ? null : (string) package.Attribute("Version");
+ }
+
+ private static string ReadSingleMatch(string path, string pattern, string label)
+ {
+ var content = File.ReadAllText(path);
+ var match = Regex.Match(content, pattern, RegexOptions.IgnoreCase);
+ Assert.That(match.Success, Is.True, $"Could not find {label} in {path}.");
+
+ return match.Groups["version"].Value;
+ }
+ }
+}
\ No newline at end of file