diff --git a/Sitefinity CLI/Commands/UpgradeCommand.cs b/Sitefinity CLI/Commands/UpgradeCommand.cs index 5a4165cd..250e901e 100644 --- a/Sitefinity CLI/Commands/UpgradeCommand.cs +++ b/Sitefinity CLI/Commands/UpgradeCommand.cs @@ -133,7 +133,7 @@ protected virtual async Task ExecuteUpgrade() await this.GeneratePowershellConfig(sitefinityProjectFilePaths, newSitefinityPackage); - var updaterPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, PowershellFolderName, "Updater.ps1"); + var updaterPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, Constants.SitefinityUpgradePowershellFolderName, "Updater.ps1"); this.visualStudioWorker.Initialize(this.SolutionPath); this.visualStudioWorker.ExecuteScript(updaterPath); this.EnsureOperationSuccess(); @@ -215,15 +215,16 @@ private string DetectSitefinityVersion(string sitefinityProjectPath) private bool ContainsSitefinityRefKeyword(CsProjectFileReference projectReference) { - return (projectReference.Include.Contains(TelerikSitefinityReferenceKeyWords) || projectReference.Include.Contains(ProgressSitefinityReferenceKeyWords)) && !projectReference.Include.Contains(ProgressSitefinityRendererReferenceKeyWords); + return (projectReference.Include.Contains(Constants.TelerikSitefinityReferenceKeyWords) || projectReference.Include.Contains(Constants.ProgressSitefinityReferenceKeyWords)) && + !projectReference.Include.Contains(Constants.ProgressSitefinityRendererReferenceKeyWords); } private void EnsureOperationSuccess() { this.logger.LogInformation("Waiting for operation to complete..."); - var resultFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PowershellFolderName, "result.log"); - var progressFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PowershellFolderName, "progress.log"); + var resultFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Constants.SitefinityUpgradePowershellFolderName, "result.log"); + var progressFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Constants.SitefinityUpgradePowershellFolderName, "progress.log"); File.Delete(resultFile); int waitStep = 500; int iterations = 0; @@ -280,8 +281,6 @@ private async Task GeneratePowershellConfig(IEnumerable projectFilePaths { this.logger.LogInformation("Exporting powershell config..."); - this.packagesPerProject = new Dictionary>(); - var powerShellXmlConfig = new XmlDocument(); var powerShellXmlConfigNode = powerShellXmlConfig.CreateElement("config"); powerShellXmlConfig.AppendChild(powerShellXmlConfigNode); @@ -294,8 +293,6 @@ private async Task GeneratePowershellConfig(IEnumerable projectFilePaths projectNode.Attributes.Append(projectNameAttr); powerShellXmlConfigNode.AppendChild(projectNode); - packagesPerProject[projectFilePath] = new List(); - var currentSitefinityVersion = this.DetectSitefinityVersion(projectFilePath); if (string.IsNullOrEmpty(currentSitefinityVersion)) @@ -321,12 +318,10 @@ private async Task GeneratePowershellConfig(IEnumerable projectFilePaths packageNode.Attributes.Append(nameAttr); packageNode.Attributes.Append(versionAttr); projectNode.AppendChild(packageNode); - - packagesPerProject[projectFilePath].Add(package); }); } - powerShellXmlConfig.Save(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PowershellFolderName, "config.xml")); + powerShellXmlConfig.Save(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Constants.SitefinityUpgradePowershellFolderName, "config.xml")); this.logger.LogInformation("Successfully exported powershell config!"); } @@ -385,9 +380,7 @@ private void SyncProjectReferencesWithPackages(IEnumerable projectFilePa { foreach (string projectFilePath in projectFilePaths) { - var packages = new List(this.packagesPerProject[projectFilePath]); - packages.Reverse(); - this.sitefinityPackageManager.SyncReferencesWithPackages(projectFilePath, solutionFolder, packages, this.Version); + this.sitefinityPackageManager.SyncReferencesWithPackages(projectFilePath, solutionFolder); } } @@ -405,13 +398,13 @@ private IList GetProjectsPathsFromSolution(string solutionPath, bool onl if (onlySitefinityProjects) { projectFilesAbsolutePaths = projectFilesAbsolutePaths - .Where(ap => this.HasSitefinityReferences(ap) && this.ValidateSfVersion(ap)); + .Where(ap => this.HasSitefinityReferences(ap) && this.HasValidSitefinityVersion(ap)); } return projectFilesAbsolutePaths.ToList(); } - private bool ValidateSfVersion(string projectFilePath) + private bool HasValidSitefinityVersion(string projectFilePath) { var currentSfVersionString = this.DetectSitefinityVersion(projectFilePath); var currentVersion = System.Version.Parse(currentSfVersionString); @@ -424,7 +417,9 @@ private bool ValidateSfVersion(string projectFilePath) var projectName = Path.GetFileName(projectFilePath); if (versionToUpgrade <= currentVersion) { - throw new UpgradeException(string.Format(Constants.VersionIsGreaterThanOrEqual, projectName, currentSfVersionString, versionToUpgrade)); + this.logger.LogWarning(string.Format(Constants.VersionIsGreaterThanOrEqual, projectName, currentSfVersionString, versionToUpgrade)); + + return false; } return true; @@ -440,7 +435,7 @@ private bool HasSitefinityReferences(string projectFilePath) private bool IsSitefinityReference(CsProjectFileReference reference) { - return this.ContainsSitefinityRefKeyword(reference) && reference.Include.Contains($"PublicKeyToken={SitefinityPublicKeyToken}"); + return this.ContainsSitefinityRefKeyword(reference) && reference.Include.Contains($"PublicKeyToken={Constants.SitefinityPublicKeyToken}"); } private readonly IPromptService promptService; @@ -456,17 +451,5 @@ private bool IsSitefinityReference(CsProjectFileReference reference) private readonly IVisualStudioWorker visualStudioWorker; private readonly IDictionary> processedPackagesPerProjectCache; - - private Dictionary> packagesPerProject; - - private const string TelerikSitefinityReferenceKeyWords = "Telerik.Sitefinity"; - - private const string ProgressSitefinityReferenceKeyWords = "Progress.Sitefinity"; - - private const string ProgressSitefinityRendererReferenceKeyWords = "Progress.Sitefinity.Renderer"; - - private const string PowershellFolderName = "PowerShell"; - - private const string SitefinityPublicKeyToken = "b28c218413bdf563"; } } diff --git a/Sitefinity CLI/Constants.cs b/Sitefinity CLI/Constants.cs index 8058856b..9fe9ce88 100644 --- a/Sitefinity CLI/Constants.cs +++ b/Sitefinity CLI/Constants.cs @@ -47,7 +47,7 @@ public class Constants public const string FileIsNotSolutionMessage = "File \"{0}\" is not a sln file"; public const string ErrorOccuredWhileCreatingItemFromTemplate = "An error occured while creating an item from template. Path: {0}"; public const string VersionNotFound = "Version: {0} was not found in https://nuget.sitefinity.com"; - public const string VersionIsGreaterThanOrEqual = "{0} Sitefinity version ({1}) is >= than the version you are trying to upgrade to ({2})!"; + public const string VersionIsGreaterThanOrEqual = "{0} Sitefinity version ({1}) is >= than the version you are trying to upgrade to ({2})"; // Warning messages public const string EnterResourcePackagePromptMessage = "Enter the name of the resource package where the resource should be added:"; @@ -161,5 +161,10 @@ public class Constants public const string DependenciesElem = "Dependencies"; public const string VersionElem = "Version"; public const string TitleElem = "title"; + public const string TelerikSitefinityReferenceKeyWords = "Telerik.Sitefinity"; + public const string ProgressSitefinityReferenceKeyWords = "Progress.Sitefinity"; + public const string ProgressSitefinityRendererReferenceKeyWords = "Progress.Sitefinity.Renderer"; + public const string SitefinityUpgradePowershellFolderName = "PowerShell"; + public const string SitefinityPublicKeyToken = "b28c218413bdf563"; } } diff --git a/Sitefinity CLI/PackageManagement/AssemblyReference.cs b/Sitefinity CLI/PackageManagement/AssemblyReference.cs new file mode 100644 index 00000000..8853ee5b --- /dev/null +++ b/Sitefinity CLI/PackageManagement/AssemblyReference.cs @@ -0,0 +1,15 @@ +using System; + +namespace Sitefinity_CLI.PackageManagement +{ + internal class AssemblyReference + { + public string Name { get; set; } + + public string FullName { get; set; } + + public Version Version { get; set; } + + public string HintPath { get; set; } + } +} diff --git a/Sitefinity CLI/PackageManagement/IPackagesConfigFileEditor.cs b/Sitefinity CLI/PackageManagement/IPackagesConfigFileEditor.cs index ee45b6e4..3f96041e 100644 --- a/Sitefinity CLI/PackageManagement/IPackagesConfigFileEditor.cs +++ b/Sitefinity CLI/PackageManagement/IPackagesConfigFileEditor.cs @@ -1,7 +1,11 @@ -namespace Sitefinity_CLI.PackageManagement +using System.Collections.Generic; + +namespace Sitefinity_CLI.PackageManagement { internal interface IPackagesConfigFileEditor { + IEnumerable GetPackages(string packagesConfigFilePath); + NuGetPackage FindPackage(string packagesConfigFilePath, string packageId); void RemovePackage(string packagesConfigFilePath, string packageId); diff --git a/Sitefinity CLI/PackageManagement/ISitefinityPackageManager.cs b/Sitefinity CLI/PackageManagement/ISitefinityPackageManager.cs index 594841c5..1c6065f3 100644 --- a/Sitefinity CLI/PackageManagement/ISitefinityPackageManager.cs +++ b/Sitefinity CLI/PackageManagement/ISitefinityPackageManager.cs @@ -17,7 +17,7 @@ internal interface ISitefinityPackageManager Task GetSitefinityPackageTree(string version, IEnumerable packageSources); - void SyncReferencesWithPackages(string projectPath, string solutionFolder, IEnumerable packages, string sitefinityVersion); + void SyncReferencesWithPackages(string projectFilePath, string solutionFolder); IEnumerable DefaultPackageSource { get; } diff --git a/Sitefinity CLI/PackageManagement/NuGetApiClient.cs b/Sitefinity CLI/PackageManagement/NuGetApiClient.cs index 68b1ceb2..8b4acdf7 100644 --- a/Sitefinity CLI/PackageManagement/NuGetApiClient.cs +++ b/Sitefinity CLI/PackageManagement/NuGetApiClient.cs @@ -7,8 +7,6 @@ using System.Linq; using System.Text.RegularExpressions; using System.IO; -using System.Security.Cryptography; -using System.Text; using Newtonsoft.Json; namespace Sitefinity_CLI.PackageManagement @@ -114,7 +112,8 @@ private async Task GetPackageXmlDocument(string id, string version, I HttpResponseMessage response = null; foreach (string source in sources) { - response = await this.httpClient.GetAsync($"{source}/Packages(Id='{id}',Version='{version}')"); + string sourceUrl = source.TrimEnd('/'); + response = await this.httpClient.GetAsync($"{sourceUrl}/Packages(Id='{id}',Version='{version}')"); if (response.StatusCode == HttpStatusCode.OK) { break; diff --git a/Sitefinity CLI/PackageManagement/PackagesConfigFileEditor.cs b/Sitefinity CLI/PackageManagement/PackagesConfigFileEditor.cs index 8597f9c9..6cdade28 100644 --- a/Sitefinity CLI/PackageManagement/PackagesConfigFileEditor.cs +++ b/Sitefinity CLI/PackageManagement/PackagesConfigFileEditor.cs @@ -6,22 +6,35 @@ namespace Sitefinity_CLI.PackageManagement { internal class PackagesConfigFileEditor : XmlFileEditorBase, IPackagesConfigFileEditor { + public IEnumerable GetPackages(string packagesConfigFilePath) + { + IEnumerable nuGetPackages = null; + + base.ReadFile(packagesConfigFilePath, (doc) => + { + IEnumerable xmlPackageElements = doc.Element(Constants.PackagesElem) + .Elements(Constants.PackageElem); + + nuGetPackages = xmlPackageElements.Select(xpe => this.CreateNuGetPackageFromXmlPackageElement(xpe)); + }); + + return nuGetPackages; + } + public NuGetPackage FindPackage(string packagesConfigFilePath, string packageId) { NuGetPackage nuGetPackage = null; base.ReadFile(packagesConfigFilePath, (doc) => { - IEnumerable packages = doc.Element(Constants.PackagesElem) + IEnumerable xmlPackageElements = doc.Element(Constants.PackagesElem) .Elements(Constants.PackageElem); - XElement package = packages.FirstOrDefault(p => p.Attribute(Constants.IdAttribute).Value == packageId); + XElement xmlPackageElement = xmlPackageElements.FirstOrDefault(p => p.Attribute(Constants.IdAttribute).Value == packageId); - if (package != null) + if (xmlPackageElement != null) { - nuGetPackage = new NuGetPackage(); - nuGetPackage.Id = package.Attribute(Constants.IdAttribute).Value; - nuGetPackage.Version = package.Attribute(Constants.VersionAttribute).Value; + nuGetPackage = this.CreateNuGetPackageFromXmlPackageElement(xmlPackageElement); } }); @@ -32,14 +45,23 @@ public void RemovePackage(string packagesConfigFilePath, string packageId) { base.ModifyFile(packagesConfigFilePath, (doc) => { - IEnumerable packages = doc.Element(Constants.PackagesElem) + IEnumerable xmlPackageElements = doc.Element(Constants.PackagesElem) .Elements(Constants.PackageElem); - XElement packageToRemove = packages.FirstOrDefault(p => p.Attribute(Constants.IdAttribute).Value == packageId); - packageToRemove.Remove(); + XElement xmlPackageElementToRemove = xmlPackageElements.FirstOrDefault(p => p.Attribute(Constants.IdAttribute).Value == packageId); + xmlPackageElementToRemove.Remove(); return doc; }); } + + private NuGetPackage CreateNuGetPackageFromXmlPackageElement(XElement xmlPackageElement) + { + NuGetPackage nuGetPackage = new NuGetPackage(); + nuGetPackage.Id = xmlPackageElement.Attribute(Constants.IdAttribute).Value; + nuGetPackage.Version = xmlPackageElement.Attribute(Constants.VersionAttribute).Value; + + return nuGetPackage; + } } } diff --git a/Sitefinity CLI/PackageManagement/SitefinityPackageManager.cs b/Sitefinity CLI/PackageManagement/SitefinityPackageManager.cs index c6d4cba6..33897936 100644 --- a/Sitefinity CLI/PackageManagement/SitefinityPackageManager.cs +++ b/Sitefinity CLI/PackageManagement/SitefinityPackageManager.cs @@ -33,13 +33,13 @@ public void Install(string packageId, string version, string solutionFilePath, I { string solutionDirectory = Path.GetDirectoryName(solutionFilePath); - this.logger.LogInformation(string.Format("[{0}] Installing package \"{1}\"...", solutionDirectory, packageId)); + this.logger.LogInformation($"[{solutionDirectory}] Installing package '{packageId}'..."); var sourcesUsed = string.Join(',', nugetPackageSources); - this.logger.LogInformation(string.Format("Package sources used: {0}", sourcesUsed)); + this.logger.LogInformation($"Package sources used: {sourcesUsed}"); this.nuGetCliClient.InstallPackage(packageId, version, solutionDirectory, nugetPackageSources); - this.logger.LogInformation(string.Format("[{0}] Install for package \"{1}\" is complete", solutionDirectory, packageId)); + this.logger.LogInformation($"[{solutionDirectory}] Install for package '{packageId}' completed"); } public void Install(string packageId, string version, string solutionFilePath) @@ -49,11 +49,11 @@ public void Install(string packageId, string version, string solutionFilePath) public void Restore(string solutionFilePath) { - this.logger.LogInformation(string.Format("[{0}] Restoring packages started...", solutionFilePath)); + this.logger.LogInformation($"[{solutionFilePath}] Restoring packages started..."); this.nuGetCliClient.Restore(solutionFilePath); - this.logger.LogInformation(string.Format("[{0}] Restoring packages is complete", solutionFilePath)); + this.logger.LogInformation($"[{solutionFilePath}] Restoring packages completed"); } public bool PackageExists(string packageId, string projectFilePath) @@ -77,22 +77,25 @@ public async Task GetSitefinityPackageTree(string version) public async Task GetSitefinityPackageTree(string version, IEnumerable nugetPackageSources) { var sourcesUsed = string.Join(',', nugetPackageSources); - this.logger.LogInformation(string.Format("Package sources used: {0}", sourcesUsed)); + this.logger.LogInformation($"Package sources used: {sourcesUsed}"); return await nuGetApiClient.GetPackageWithFullDependencyTree(Constants.SitefinityAllNuGetPackageId, version, nugetPackageSources, this.supportedFrameworksRegex); } - public void SyncReferencesWithPackages(string projectPath, string solutionFolder, IEnumerable packages, string sitefinityVersion) + public void SyncReferencesWithPackages(string projectFilePath, string solutionDir) { - this.logger.LogInformation(string.Format("Synchronizing packages and references for project '{0}'", projectPath)); + this.logger.LogInformation($"Synchronizing packages and references for project '{projectFilePath}'"); - XmlDocument doc = new XmlDocument(); - doc.Load(projectPath); + string packagesConfigFilePath = this.GetPackagesConfigFilePathForProject(projectFilePath); + IEnumerable packages = this.packagesConfigFileEditor.GetPackages(packagesConfigFilePath); + + XmlDocument projectFileXmlDocument = new XmlDocument(); + projectFileXmlDocument.Load(projectFilePath); var processedAssemblies = new HashSet(); - var projectLocation = projectPath.Substring(0, projectPath.LastIndexOf("\\") + 1); + var projectDir = Path.GetDirectoryName(projectFilePath); - var projectConfigPath = this.projectConfigFileEditor.GetProjectConfigPath(projectLocation); + var projectConfigPath = this.projectConfigFileEditor.GetProjectConfigPath(projectDir); XmlNodeList bindingRedirectNodes = null; XmlDocument projectConfig = null; if (!string.IsNullOrEmpty(projectConfigPath)) @@ -102,128 +105,207 @@ public void SyncReferencesWithPackages(string projectPath, string solutionFolder bindingRedirectNodes = projectConfig.GetElementsByTagName("dependentAssembly"); } - var references = doc.GetElementsByTagName(Constants.ReferenceElem); - var targetFramework = this.GetTargetFrameworkForVersion(sitefinityVersion); + XmlNodeList referenceElements = projectFileXmlDocument.GetElementsByTagName(Constants.ReferenceElem); + string targetFramework = this.GetTargetFramework(projectFileXmlDocument); + + IEnumerable nugetPackageAssemblyReferences = this.GetAssemblyReferencesFromNuGetPackages(packages, targetFramework, projectDir, solutionDir); + IEnumerable> nuGetPackageAssemblyReferenceGroups = nugetPackageAssemblyReferences + .Where(ar => ar.Version != null) + .GroupBy(ar => ar.Name); // Foreach package installed for this project, check if all DLLs are included. If not - include missing ones. Fix binding redirects in web.config if necessary. - foreach (var package in packages) + foreach (IGrouping nuGetPackageAssemblyReferenceGroup in nuGetPackageAssemblyReferenceGroups) + { + this.AddOrUpdateReferencesForAssembly(projectFileXmlDocument, referenceElements, bindingRedirectNodes, projectConfig, nuGetPackageAssemblyReferenceGroup.Key, nuGetPackageAssemblyReferenceGroup); + } + + IEnumerable nugetPackageRelativeFileReferences = this.GetRelativeFilePathsFromNuGetPackages(packages, projectDir, solutionDir); + this.RemoveReferencesToMissingNuGetPackageDlls(projectDir, solutionDir, projectFileXmlDocument, nugetPackageRelativeFileReferences); + + projectFileXmlDocument.Save(projectFilePath); + projectConfig?.Save(projectConfigPath); + + this.logger.LogInformation($"Synchronization completed for project '{projectFilePath}'"); + } + + private void RemoveReferencesToMissingNuGetPackageDlls(string projectDir, string solutionDir, XmlDocument projectFileXmlDocument, IEnumerable nugetPackageRelativeFileReferences) + { + string packagesDir = Path.Combine(solutionDir, PackagesFolderName); + string relativePackagesDirPath = this.GetRelativePathTo(projectDir + "\\", packagesDir); + + XmlNodeList elementsWithIncludeAttribute = projectFileXmlDocument.SelectNodes("//*[@Include]"); + for (int i = 0; i < elementsWithIncludeAttribute.Count; i++) { - var packageDir = string.Format("{0}\\{1}\\{2}.{3}", solutionFolder, PackagesFolderName, package.Id, package.Version); - foreach (var dllFile in this.GetPackageDlls(packageDir, targetFramework)) + XmlNode elementWithIncludeAttribute = elementsWithIncludeAttribute[i]; + XmlAttribute includeAttr = elementWithIncludeAttribute.Attributes[Constants.IncludeAttribute]; + string includeAttributeValue = includeAttr.Value; + + if (includeAttributeValue.StartsWith(relativePackagesDirPath, StringComparison.OrdinalIgnoreCase) && + !nugetPackageRelativeFileReferences.Any(fr => fr.Equals(includeAttributeValue, StringComparison.OrdinalIgnoreCase))) { - var assembly = Assembly.LoadFile(dllFile); - string assemblyVersion = assembly.GetName().Version.ToString(); - var assemblyFullName = assembly.FullName; - if (!processedAssemblies.Contains(assemblyFullName)) + this.logger.LogInformation($"Removing '{elementWithIncludeAttribute.Name}' element with include attribute '{includeAttributeValue}', because file cannot be found in NuGet packages installed for this project."); + elementWithIncludeAttribute.ParentNode.RemoveChild(elementWithIncludeAttribute); + } + } + } + + private void AddOrUpdateReferencesForAssembly(XmlDocument projectFileXmlDocument, XmlNodeList referenceElements, XmlNodeList bindingRedirectNodes, XmlDocument projectConfig, string assemblyName, IEnumerable nugetPackageAssemblyReferences) + { + AssemblyReference nugetPackageAssemblyReferenceWithNewestVersion = nugetPackageAssemblyReferences.OrderByDescending(ar => ar.Version).First(); + + bool isAssemblyReferenceFound = false; + for (int i = 0; i < referenceElements.Count; i++) + { + XmlNode referenceElement = referenceElements[i]; + XmlAttribute includeAttribute = referenceElement.Attributes[Constants.IncludeAttribute]; + + if (!string.IsNullOrWhiteSpace(includeAttribute.Value) && + (includeAttribute.Value.Equals(assemblyName, StringComparison.OrdinalIgnoreCase) || includeAttribute.Value.StartsWith(assemblyName + ",", StringComparison.OrdinalIgnoreCase))) + { + Version currentAssemblyVersion = this.ExtractAssemblyVersionFromIncludeAttribute(includeAttribute.Value); + + if (currentAssemblyVersion != null && currentAssemblyVersion > nugetPackageAssemblyReferenceWithNewestVersion.Version) { - processedAssemblies.Add(assemblyFullName); + this.logger.LogInformation($"The assembly reference '{assemblyName}' is on version '{currentAssemblyVersion}'. It won't be downgraded to version '{nugetPackageAssemblyReferenceWithNewestVersion.Version}'."); + isAssemblyReferenceFound = true; + break; + } - var assemblyName = assemblyFullName.Split(",").First(); - bool assemblyReferenceFound = false; - for (int i = 0; i < references.Count; i++) - { - var referenceElement = references[i]; - var includeAttr = referenceElement.Attributes[Constants.IncludeAttribute]; - var includeAttrValue = includeAttr.Value; + string proccesorArchitecture = includeAttribute.Value.Split(',').FirstOrDefault(x => x.Contains(ProcessorArchitectureAttribute)); + string includeAttributeNewValue = string.IsNullOrEmpty(proccesorArchitecture) ? nugetPackageAssemblyReferenceWithNewestVersion.FullName : $"{nugetPackageAssemblyReferenceWithNewestVersion.FullName},{proccesorArchitecture}"; - if (includeAttrValue.StartsWith(assemblyName + ",", StringComparison.OrdinalIgnoreCase) || includeAttrValue == assemblyName) - { - var currentPackageVersion = this.ExtractPackageVersionFromIncludeAttribute(includeAttrValue); - var newPackageVersion = assembly.GetName().Version; - if (currentPackageVersion != null && currentPackageVersion > newPackageVersion) - { - this.logger.LogInformation(string.Format("The {0} is on version {1}. It won't be downgraded to {2}", assemblyName, currentPackageVersion, newPackageVersion)); - assemblyReferenceFound = true; - break; - } - - var proccesorArchitecture = includeAttrValue.Split(',').FirstOrDefault(x => x.Contains(ProcessorArchitectureAttribute)); - var includeAttributeNewValue = string.IsNullOrEmpty(proccesorArchitecture) ? assemblyFullName : $"{assemblyFullName},{proccesorArchitecture}"; - - if (!includeAttr.Value.Equals(includeAttributeNewValue, StringComparison.OrdinalIgnoreCase)) - { - includeAttr.Value = includeAttributeNewValue; - } - - var childNodes = referenceElement.ChildNodes; - XmlNode hintPathNode = null; - for (int j = 0; j < childNodes.Count; j++) - { - var childNode = childNodes[j]; - if (childNode.Name == Constants.HintPathElem) - { - hintPathNode = childNode; - break; - } - } - - var hintPathValue = GetRelativePathTo(projectLocation, dllFile); - - // Hint path missing, so we add it - if (hintPathNode == null) - { - this.logger.LogInformation(string.Format("Added hint path for reference assembly '{0}'", assemblyFullName)); - - hintPathNode = doc.CreateElement(Constants.HintPathElem, doc.DocumentElement.NamespaceURI); - referenceElement.AppendChild(hintPathNode); - hintPathNode.InnerText = hintPathValue; - } - else if (hintPathNode.InnerText != hintPathValue) - { - // TODO: we can load the currently referenced assembly and replace the hint path only if the assemblie version is different. There are cases when one dll is located in multiple packages - this.logger.LogInformation(string.Format("Fixing broken hint path for reference assembly '{0}' from '{1}' to '{2}'", assemblyFullName, hintPathNode.InnerText, hintPathValue)); - - hintPathNode.InnerText = hintPathValue; - } - - assemblyReferenceFound = true; - break; - } - } + if (!includeAttribute.Value.Equals(includeAttributeNewValue, StringComparison.OrdinalIgnoreCase)) + { + this.logger.LogInformation($"Updated include attribue '{includeAttribute.Value}' to '{includeAttributeNewValue}'."); + includeAttribute.Value = includeAttributeNewValue; + } - // DLL reference is missing, so we add it. - if (!assemblyReferenceFound) - { - this.logger.LogInformation(string.Format("Added missing assembly reference '{0}' from package '{1}'", assemblyFullName, package.Id)); - - var referencesGroup = doc.GetElementsByTagName(Constants.ItemGroupElem)[0]; - var referenceNode = doc.CreateElement(Constants.ReferenceElem, doc.DocumentElement.NamespaceURI); - var includeAttr = doc.CreateAttribute(Constants.IncludeAttribute); - includeAttr.Value = assemblyFullName; - referenceNode.Attributes.Append(includeAttr); - var hintPathNode = doc.CreateElement(Constants.HintPathElem, doc.DocumentElement.NamespaceURI); - hintPathNode.InnerText = GetRelativePathTo(projectLocation, dllFile); - referenceNode.AppendChild(hintPathNode); - referencesGroup.AppendChild(referenceNode); - } + XmlNode hintPathNode = this.GetChildNode(referenceElement, Constants.HintPathElem); + if (hintPathNode == null) + { + this.logger.LogInformation($"Added hint path '{nugetPackageAssemblyReferenceWithNewestVersion.HintPath}' for reference assembly '{nugetPackageAssemblyReferenceWithNewestVersion.FullName}'."); + + hintPathNode = projectFileXmlDocument.CreateElement(Constants.HintPathElem, projectFileXmlDocument.DocumentElement.NamespaceURI); + referenceElement.AppendChild(hintPathNode); + hintPathNode.InnerText = nugetPackageAssemblyReferenceWithNewestVersion.HintPath; } + else if (!nugetPackageAssemblyReferences.Any(ar => ar.Version == nugetPackageAssemblyReferenceWithNewestVersion.Version && ar.HintPath.Equals(hintPathNode.InnerText, StringComparison.OrdinalIgnoreCase))) + { + this.logger.LogInformation($"Updated hint path '{hintPathNode.InnerText}' to '{nugetPackageAssemblyReferenceWithNewestVersion.HintPath}' for reference assembly '{nugetPackageAssemblyReferenceWithNewestVersion.FullName}'."); - this.SyncBindingRedirects(projectConfig, bindingRedirectNodes, assembly.GetName().Name, assemblyVersion); + hintPathNode.InnerText = nugetPackageAssemblyReferenceWithNewestVersion.HintPath; + } + + isAssemblyReferenceFound = true; + break; } } - doc.Save(projectPath); - projectConfig?.Save(projectConfigPath); + if (!isAssemblyReferenceFound) + { + this.logger.LogInformation($"Added missing assembly reference '{nugetPackageAssemblyReferenceWithNewestVersion.FullName}' with hint path '{nugetPackageAssemblyReferenceWithNewestVersion.HintPath}'."); + + XmlNode referencesGroup = projectFileXmlDocument.GetElementsByTagName(Constants.ItemGroupElem)[0]; + XmlElement referenceNode = projectFileXmlDocument.CreateElement(Constants.ReferenceElem, projectFileXmlDocument.DocumentElement.NamespaceURI); + + XmlAttribute includeAttr = projectFileXmlDocument.CreateAttribute(Constants.IncludeAttribute); + includeAttr.Value = nugetPackageAssemblyReferenceWithNewestVersion.FullName; + referenceNode.Attributes.Append(includeAttr); - this.logger.LogInformation(string.Format("Synchronization completed for project '{0}'", projectPath)); + XmlElement hintPathNode = projectFileXmlDocument.CreateElement(Constants.HintPathElem, projectFileXmlDocument.DocumentElement.NamespaceURI); + hintPathNode.InnerText = nugetPackageAssemblyReferenceWithNewestVersion.HintPath; + + referenceNode.AppendChild(hintPathNode); + referencesGroup.AppendChild(referenceNode); + } + + this.SyncBindingRedirects(projectConfig, bindingRedirectNodes, assemblyName, nugetPackageAssemblyReferenceWithNewestVersion.Version.ToString()); } - private Version ExtractPackageVersionFromIncludeAttribute(string includeAttrValue) + private XmlNode GetChildNode(XmlNode node, string childNodeName) { - var versionChunk = includeAttrValue.Split(',') + XmlNodeList childNodes = node.ChildNodes; + for (int i = 0; i < childNodes.Count; i++) + { + var childNode = childNodes[i]; + if (childNode.Name == childNodeName) + { + return childNode; + } + } + + return null; + } + + private IEnumerable GetRelativeFilePathsFromNuGetPackages(IEnumerable nuGetPackages, string projectDir, string solutionDir) + { + List filePaths = new List(); + foreach (NuGetPackage nuGetPackage in nuGetPackages) + { + string packageDir = this.GetNuGetPackageDir(solutionDir, nuGetPackage); + filePaths.AddRange(Directory.GetFiles(packageDir, "*.*", SearchOption.AllDirectories)); + } + + return filePaths.Select(fp => this.GetRelativePathTo(projectDir + "\\", fp)); + } + + private IEnumerable GetAssemblyReferencesFromNuGetPackages(IEnumerable nuGetPackages, string targetFramework, string projectDir, string solutionDir) + { + List dllFilePaths = new List(); + foreach (NuGetPackage nuGetPackage in nuGetPackages) + { + string packageDir = this.GetNuGetPackageDir(solutionDir, nuGetPackage); + dllFilePaths.AddRange(this.GetPackageDlls(packageDir, targetFramework)); + } + + IEnumerable assemblyReferences = dllFilePaths.Distinct().Select(d => this.GetAssemblyReferenceFromDllFilePath(d, projectDir)); + + return assemblyReferences; + } + + private string GetNuGetPackageDir(string solutionDir, NuGetPackage nuGetPackage) + { + string nuGetPackageFolderName = $"{nuGetPackage.Id}.{nuGetPackage.Version}"; + + return Path.Combine(solutionDir, PackagesFolderName, nuGetPackageFolderName); + } + + private AssemblyReference GetAssemblyReferenceFromDllFilePath(string dllFilePath, string projectDir) + { + Assembly assembly = Assembly.LoadFile(dllFilePath); + AssemblyName assemblyName = assembly.GetName(); + + AssemblyReference assemblyReference = new AssemblyReference(); + assemblyReference.Name = assemblyName.Name; + assemblyReference.Version = assemblyName.Version; + assemblyReference.FullName = assemblyName.FullName; + assemblyReference.HintPath = this.GetRelativePathTo(projectDir + "\\", dllFilePath); + + return assemblyReference; + } + + private Version ExtractAssemblyVersionFromIncludeAttribute(string includeAttributeValue) + { + string versionChunk = includeAttributeValue + .Split(',') .FirstOrDefault(x => x.Contains("Version")); - if (versionChunk == null) + if (string.IsNullOrWhiteSpace(versionChunk)) { - this.logger.LogInformation($"Unable to get the version in {includeAttrValue}"); + this.logger.LogInformation($"Unable to extract the version from '{includeAttributeValue}'."); + return null; } - var packageVersionString = versionChunk + string assemblyVersionString = versionChunk .Split("=") .ToList()[1]; - var parsedVersion = Version.Parse(packageVersionString); + Version parsedVersion = null; + if (!Version.TryParse(assemblyVersionString, out parsedVersion)) + { + this.logger.LogInformation($"Unable to parse version string '{assemblyVersionString}'."); + } return parsedVersion; } @@ -246,10 +328,10 @@ private void SyncBindingRedirects(XmlDocument configDoc, XmlNodeList bindingRedi XmlNode bindingRedirect = null; foreach (XmlNode childNode in node.ChildNodes) { - if (childNode.Name == assemblyIdentityAttributeName) + if (childNode.Name == AssemblyIdentityAttributeName) assemblyIdentity = childNode; - if (childNode.Name == bindingRedirectAttributeName) + if (childNode.Name == BindingRedirectAttributeName) bindingRedirect = childNode; } @@ -258,7 +340,7 @@ private void SyncBindingRedirects(XmlDocument configDoc, XmlNodeList bindingRedi var name = assemblyIdentity.Attributes["name"]?.Value; if (name == assemblyFullName) { - var newVersionAttribute = bindingRedirect.Attributes[newVersionAttributeName]; + var newVersionAttribute = bindingRedirect.Attributes[NewVersionAttributeName]; if (newVersionAttribute != null && !this.ShouldUpdateBindingRedirect(newVersionAttribute.Value, assemblyVersion)) { break; @@ -273,7 +355,7 @@ private void SyncBindingRedirects(XmlDocument configDoc, XmlNodeList bindingRedi newVersionAttribute.Value = assemblyVersion; - var oldVersionAttribute = bindingRedirect.Attributes[oldVersionAttributeName]; + var oldVersionAttribute = bindingRedirect.Attributes[OldVersionAttributeName]; if (oldVersionAttribute == null) { oldVersionAttribute = configDoc.CreateAttribute(Constants.IncludeAttribute); @@ -281,7 +363,7 @@ private void SyncBindingRedirects(XmlDocument configDoc, XmlNodeList bindingRedi bindingRedirect.Attributes.Append(oldVersionAttribute); } - oldVersionAttribute.Value = string.Format("0.0.0.0-{0}", assemblyVersion); + oldVersionAttribute.Value = $"0.0.0.0-{assemblyVersion}"; break; } @@ -309,13 +391,25 @@ private bool TrySetTargetFramework(XmlDocument doc, string targetFramework) if (targetFrameworkVersionElems[0].InnerText != targetFramework) { targetFrameworkVersionElems[0].InnerText = targetFramework; + return true; } return false; } - private string GetTargetFrameworkForVersion(string version) + private string GetTargetFramework(XmlDocument doc) + { + XmlNodeList targetFrameworkVersionElems = doc.GetElementsByTagName(Constants.TargetFrameworkVersionElem); + if (targetFrameworkVersionElems.Count != 1) + { + throw new InvalidOperationException("Unable to get the target framework"); + } + + return targetFrameworkVersionElems[0].InnerText; + } + + private string GetTargetFrameworkForSitefinityVersion(string version) { var versionWithoutSeperator = version.Replace(".", string.Empty).Substring(0, 3); var versionAsInt = int.Parse(versionWithoutSeperator); @@ -351,12 +445,14 @@ private string GetTargetFrameworkForVersion(string version) private IEnumerable GetPackageDlls(string packagePath, string targetVersion) { // Target framework convention looks like v4.7.2 - var versionPart = targetVersion.Replace(".", string.Empty).Replace("v", string.Empty); + string versionPart = targetVersion.Replace(".", string.Empty).Replace("v", string.Empty); int.TryParse(versionPart, out int targetVersionNumber); if (targetVersionNumber == 0) + { return new List(); + } - var libDir = Path.Combine(packagePath, LibFolderName); + string libDir = Path.Combine(packagePath, LibFolderName); string dllStorageDir = null; if (Directory.Exists(libDir)) { @@ -408,13 +504,13 @@ private string GetPackagesConfigFilePathForProject(string projectFilePath) if (!File.Exists(packagesConfigFilePath)) { - throw new FileNotFoundException(string.Format("File \"{0}\" not found in project directory \"{1}\". Cannot proceed with upgrade.", Constants.PackagesConfigFileName, projectDirectory)); + throw new FileNotFoundException($"File '{Constants.PackagesConfigFileName}' not found in project directory '{projectDirectory}'. Cannot proceed with the upgrade."); } return packagesConfigFilePath; } - private static string GetRelativePathTo(string fromPath, string toPath) + private string GetRelativePathTo(string fromPath, string toPath) { var fromUri = new Uri(fromPath); var toUri = new Uri(toPath); @@ -432,7 +528,7 @@ public void SetTargetFramework(IEnumerable sitefinityProjectFilePaths, s throw new ArgumentException($"Invalid version: {version}"); } - var targetFramework = this.GetTargetFrameworkForVersion(version); + var targetFramework = this.GetTargetFrameworkForSitefinityVersion(version); foreach (var projectFilePath in sitefinityProjectFilePaths) { @@ -464,6 +560,8 @@ public void SetTargetFramework(IEnumerable sitefinityProjectFilePaths, s private readonly IEnumerable defaultSources; + private readonly Regex supportedFrameworksRegex; + private const string SitefinityPublicNuGetSource = "https://nuget.sitefinity.com/nuget/"; private const string PublicNuGetSource = "https://nuget.org/api/v2/"; @@ -478,11 +576,12 @@ public void SetTargetFramework(IEnumerable sitefinityProjectFilePaths, s private const string ProcessorArchitectureAttribute = "processorArchitecture"; - private readonly Regex supportedFrameworksRegex; + private const string AssemblyIdentityAttributeName = "assemblyIdentity"; + + private const string BindingRedirectAttributeName = "bindingRedirect"; + + private const string OldVersionAttributeName = "oldVersion"; - private const string assemblyIdentityAttributeName = "assemblyIdentity"; - private const string bindingRedirectAttributeName = "bindingRedirect"; - private const string oldVersionAttributeName = "oldVersion"; - private const string newVersionAttributeName = "newVersion"; + private const string NewVersionAttributeName = "newVersion"; } } diff --git a/Sitefinity CLI/Sitefinity CLI.csproj b/Sitefinity CLI/Sitefinity CLI.csproj index 8e1293d6..10c17c64 100644 --- a/Sitefinity CLI/Sitefinity CLI.csproj +++ b/Sitefinity CLI/Sitefinity CLI.csproj @@ -5,9 +5,9 @@ netcoreapp3.0 sf Sitefinity_CLI - 1.1.0.19 + 1.1.0.20 1.1.0 - 1.1.0.19 + 1.1.0.20 false false diff --git a/Sitefinity CLI/VisualStudio/ProjectConfigFileEditor.cs b/Sitefinity CLI/VisualStudio/ProjectConfigFileEditor.cs index d282628d..8643f336 100644 --- a/Sitefinity CLI/VisualStudio/ProjectConfigFileEditor.cs +++ b/Sitefinity CLI/VisualStudio/ProjectConfigFileEditor.cs @@ -4,13 +4,11 @@ namespace Sitefinity_CLI.VisualStudio { internal class ProjectConfigFileEditor : IProjectConfigFileEditor { - public string GetProjectConfigPath(string projectLocation) + public string GetProjectConfigPath(string projectDirectory) { - if (string.IsNullOrEmpty(projectLocation)) + if (string.IsNullOrEmpty(projectDirectory)) return null; - var projectDirectory = Path.GetDirectoryName(projectLocation); - var webConfigPath = Path.Combine(projectDirectory, WebConfigName); if (File.Exists(webConfigPath)) return webConfigPath;