Beispiel #1
0
        /// <summary>
        ///     Update well-known version files.
        /// </summary>
        /// <param name="versionProps">Versions.props xml document</param>
        /// <param name="token">Global.json document</param>
        /// <param name="itemToUpdate">Item that needs an update.</param>
        /// <remarks>
        ///     TODO: https://github.com/dotnet/arcade/issues/1095
        /// </remarks>
        private void UpdateVersionFiles(XmlDocument versionProps, JToken token, DependencyDetail itemToUpdate)
        {
            string versionElementName          = VersionFiles.GetVersionPropsPackageVersionElementName(itemToUpdate.Name);
            string alternateVersionElementName = VersionFiles.GetVersionPropsAlternatePackageVersionElementName(itemToUpdate.Name);

            // Select nodes case insensitively, then update the name.
            XmlNode packageVersionNode = versionProps.DocumentElement.SelectSingleNode(
                $"//*[translate(local-name(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')=" +
                $"'{versionElementName.ToLower()}']");
            string foundElementName = versionElementName;

            // Find alternate names
            if (packageVersionNode == null)
            {
                packageVersionNode = versionProps.DocumentElement.SelectSingleNode(
                    $"//*[translate(local-name(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')=" +
                    $"'{alternateVersionElementName.ToLower()}']");
                foundElementName = alternateVersionElementName;
            }

            if (packageVersionNode != null)
            {
                packageVersionNode.InnerText = itemToUpdate.Version;
                // If the node name was updated, then create a new node with the new name, unlink this node
                // and create a new one in the same location.
                if (packageVersionNode.LocalName != foundElementName)
                {
                    {
                        XmlNode parentNode = packageVersionNode.ParentNode;
                        XmlNode newPackageVersionElement = versionProps.CreateElement(
                            foundElementName,
                            versionProps.DocumentElement.NamespaceURI);
                        newPackageVersionElement.InnerText = itemToUpdate.Version;
                        parentNode.ReplaceChild(newPackageVersionElement, packageVersionNode);
                    }
                    {
                        // Update the package name element too.
                        string  packageNameElementName = VersionFiles.GetVersionPropsPackageElementName(itemToUpdate.Name);
                        XmlNode packageNameNode        = versionProps.DocumentElement.SelectSingleNode(
                            $"//*[translate(local-name(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')=" +
                            $"'{packageNameElementName.ToLower()}']");
                        if (packageNameNode != null)
                        {
                            XmlNode parentNode            = packageNameNode.ParentNode;
                            XmlNode newPackageNameElement = versionProps.CreateElement(
                                packageNameElementName,
                                versionProps.DocumentElement.NamespaceURI);
                            newPackageNameElement.InnerText = itemToUpdate.Name;
                            parentNode.ReplaceChild(newPackageNameElement, packageNameNode);
                        }
                    }
                }
            }

            // Update the global json too, even if there was an element in the props file, in case
            // it was listed in both
            UpdateVersionGlobalJson(itemToUpdate, token);
        }
Beispiel #2
0
        private void UpdateVersionGlobalJson(DependencyDetail itemToUpdate, JToken token)
        {
            string versionElementName = VersionFiles.CalculateGlobalJsonElementName(itemToUpdate.Name);

            foreach (JProperty property in token.Children <JProperty>())
            {
                if (property.Name == versionElementName)
                {
                    property.Value = new JValue(itemToUpdate.Version);
                    break;
                }

                UpdateVersionGlobalJson(itemToUpdate, property.Value);
            }
        }
Beispiel #3
0
        /// <summary>
        ///     Verify that any dependency that exists in global.json and Version.Details.xml (e.g. Arcade SDK)
        ///     has matching version numbers.
        /// </summary>
        /// <param name="dependencies">Parsed dependencies in the repository.</param>
        /// <param name="rootToken">Root global.json token.</param>
        /// <returns></returns>
        private Task <bool> VerifyMatchingGlobalJson(
            IEnumerable <DependencyDetail> dependencies,
            JObject rootToken,
            out Task <HashSet <string> > utilizedDependencies)
        {
            HashSet <string> utilizedSet = new HashSet <string>();
            bool             result      = true;

            foreach (var dependency in dependencies)
            {
                string versionedName  = VersionFiles.CalculateGlobalJsonElementName(dependency.Name);
                JToken dependencyNode = FindDependency(rootToken, versionedName);
                if (dependencyNode != null)
                {
                    // Should be a string with matching version.
                    if (dependencyNode.Type != JTokenType.Property || ((JProperty)dependencyNode).Value.Type != JTokenType.String)
                    {
                        _logger.LogError($"The element '{dependency.Name}' in '{VersionFiles.GlobalJson}' should be a property " +
                                         $"with a value of type string.");
                        result = false;
                        continue;
                    }
                    JProperty property = (JProperty)dependencyNode;
                    // Validate that the casing matches for consistency
                    if (property.Name != versionedName)
                    {
                        _logger.LogError($"The dependency '{dependency.Name}' has a case mismatch between " +
                                         $"'{VersionFiles.GlobalJson}' and '{VersionFiles.VersionDetailsXml}' " +
                                         $"('{property.Name}' vs. '{versionedName}')");
                        result = false;
                    }
                    // Validate version
                    JToken value = (JToken)property.Value;
                    if (value.Value <string>() != dependency.Version)
                    {
                        _logger.LogError($"The dependency '{dependency.Name}' has a version mismatch between " +
                                         $"'{VersionFiles.GlobalJson}' and '{VersionFiles.VersionDetailsXml}' " +
                                         $"('{value.Value<string>()}' vs. '{dependency.Version}')");
                    }

                    utilizedSet.Add(dependency.Name);
                }
            }
            utilizedDependencies = Task.FromResult(utilizedSet);
            return(Task.FromResult(result));
        }
Beispiel #4
0
        /// <summary>
        ///     Verify that any dependency that exists in both Version.props and Version.Details.xml has matching version numbers.
        /// </summary>
        /// <param name="dependencies">Parsed dependencies in the repository.</param>
        /// <param name="versionProps">Parsed version props file</param>
        /// <returns></returns>
        private Task <bool> VerifyMatchingVersionProps(IEnumerable <DependencyDetail> dependencies, XmlDocument versionProps, out Task <HashSet <string> > utilizedDependencies)
        {
            HashSet <string> utilizedSet = new HashSet <string>();
            bool             result      = true;

            foreach (var dependency in dependencies)
            {
                string  versionElementName          = VersionFiles.GetVersionPropsPackageVersionElementName(dependency.Name);
                string  alternateVersionElementName = VersionFiles.GetVersionPropsAlternatePackageVersionElementName(dependency.Name);
                XmlNode versionNode = versionProps.DocumentElement.SelectSingleNode($"//*[local-name()='{versionElementName}']");
                if (versionNode == null)
                {
                    versionNode        = versionProps.DocumentElement.SelectSingleNode($"//*[local-name()='{alternateVersionElementName}']");
                    versionElementName = alternateVersionElementName;
                }

                if (versionNode != null)
                {
                    // Validate that the casing matches for consistency
                    if (versionNode.Name != versionElementName)
                    {
                        _logger.LogError($"The dependency '{dependency.Name}' has a case mismatch between " +
                                         $"'{VersionFiles.VersionProps}' and '{VersionFiles.VersionDetailsXml}' " +
                                         $"('{versionNode.Name}' vs. '{versionElementName}')");
                        result = false;
                    }
                    // Validate innner version matches
                    if (versionNode.InnerText != dependency.Version)
                    {
                        _logger.LogError($"The dependency '{dependency.Name}' has a version mismatch between " +
                                         $"'{VersionFiles.VersionProps}' and '{VersionFiles.VersionDetailsXml}' " +
                                         $"('{versionNode.InnerText}' vs. '{dependency.Version}')");
                        result = false;
                    }
                    utilizedSet.Add(dependency.Name);
                }
            }
            utilizedDependencies = Task.FromResult(utilizedSet);
            return(Task.FromResult(result));
        }
Beispiel #5
0
        /// <summary>
        ///     Add a dependency to Versions.props.  This has the form:
        ///     <!-- Package names -->
        ///     <PropertyGroup>
        ///         <MicrosoftDotNetApiCompatPackage>Microsoft.DotNet.ApiCompat</MicrosoftDotNetApiCompatPackage>
        ///     </PropertyGroup>
        ///
        ///     <!-- Package versions -->
        ///     <PropertyGroup>
        ///         <MicrosoftDotNetApiCompatPackageVersion>1.0.0-beta.18478.5</MicrosoftDotNetApiCompatPackageVersion>
        ///     </PropertyGroup>
        ///
        ///     See https://github.com/dotnet/arcade/blob/master/Documentation/DependencyDescriptionFormat.md for more
        ///     information.
        /// </summary>
        /// <param name="repo">Path to Versions.props file</param>
        /// <param name="dependency">Dependency information to add.</param>
        /// <returns>Async task.</returns>
        public async Task AddDependencyToVersionsPropsAsync(string repo, string branch, DependencyDetail dependency)
        {
            XmlDocument versionProps = await ReadVersionPropsAsync(repo, null);

            string documentNamespaceUri = versionProps.DocumentElement.NamespaceURI;

            string packageNameElementName             = VersionFiles.GetVersionPropsPackageElementName(dependency.Name);
            string packageVersionElementName          = VersionFiles.GetVersionPropsPackageVersionElementName(dependency.Name);
            string packageVersionAlternateElementName = VersionFiles.GetVersionPropsAlternatePackageVersionElementName(
                dependency.Name);

            // Select elements by local name, since the Project (DocumentElement) element often has a default
            // xmlns set.
            XmlNodeList propertyGroupNodes = versionProps.DocumentElement.SelectNodes($"//*[local-name()='PropertyGroup']");

            XmlNode newPackageNameElement = versionProps.CreateElement(packageNameElementName, documentNamespaceUri);

            newPackageNameElement.InnerText = dependency.Name;

            bool addedPackageVersionElement = false;
            bool addedPackageNameElement    = false;

            // There can be more than one property group.  Find the appropriate one containing an existing element of
            // the same type, and add it to the parent.
            foreach (XmlNode propertyGroupNode in propertyGroupNodes)
            {
                if (propertyGroupNode.HasChildNodes)
                {
                    foreach (XmlNode propertyNode in propertyGroupNode.ChildNodes)
                    {
                        if (!addedPackageVersionElement && propertyNode.Name.EndsWith(VersionFiles.VersionPropsVersionElementSuffix))
                        {
                            XmlNode newPackageVersionElement = versionProps.CreateElement(
                                packageVersionElementName,
                                documentNamespaceUri);
                            newPackageVersionElement.InnerText = dependency.Version;

                            propertyGroupNode.AppendChild(newPackageVersionElement);
                            addedPackageVersionElement = true;
                            break;
                        }
                        // Test for alternate suffixes.  This will eventually be replaced by repo-level configuration:
                        // https://github.com/dotnet/arcade/issues/1095
                        else if (!addedPackageVersionElement && propertyNode.Name.EndsWith(
                                     VersionFiles.VersionPropsAlternateVersionElementSuffix))
                        {
                            XmlNode newPackageVersionElement = versionProps.CreateElement(
                                packageVersionAlternateElementName,
                                documentNamespaceUri);
                            newPackageVersionElement.InnerText = dependency.Version;

                            propertyGroupNode.AppendChild(newPackageVersionElement);
                            addedPackageVersionElement = true;
                            break;
                        }
                        else if (!addedPackageNameElement && propertyNode.Name.EndsWith(VersionFiles.VersionPropsPackageElementSuffix))
                        {
                            propertyGroupNode.AppendChild(newPackageNameElement);
                            addedPackageNameElement = true;
                            break;
                        }
                    }

                    if (addedPackageVersionElement && addedPackageNameElement)
                    {
                        break;
                    }
                }
            }

            // Add the property groups if none were present
            if (!addedPackageVersionElement)
            {
                // If the repository doesn't have any package version element, then
                // use the non-alternate element name.
                XmlNode newPackageVersionElement = versionProps.CreateElement(packageVersionElementName, documentNamespaceUri);
                newPackageVersionElement.InnerText = dependency.Version;

                XmlNode propertyGroupElement        = versionProps.CreateElement("PropertyGroup", documentNamespaceUri);
                XmlNode propertyGroupCommentElement = versionProps.CreateComment("Package versions");
                versionProps.DocumentElement.AppendChild(propertyGroupCommentElement);
                versionProps.DocumentElement.AppendChild(propertyGroupElement);
                propertyGroupElement.AppendChild(newPackageVersionElement);
            }

            if (!addedPackageNameElement)
            {
                XmlNode propertyGroupElement        = versionProps.CreateElement("PropertyGroup", documentNamespaceUri);
                XmlNode propertyGroupCommentElement = versionProps.CreateComment("Package names");
                versionProps.DocumentElement.AppendChild(propertyGroupCommentElement);
                versionProps.DocumentElement.AppendChild(propertyGroupElement);
                propertyGroupElement.AppendChild(newPackageNameElement);
            }

            // TODO: This should not be done here.  This should return some kind of generic file container to the caller,
            // who will gather up all updates and then call the git client to write the files all at once:
            // https://github.com/dotnet/arcade/issues/1095.  Today this is only called from the Local interface so
            // it's okay for now.
            var file = new GitFile(VersionFiles.VersionProps, versionProps);
            await _gitClient.PushFilesAsync(new List <GitFile> {
                file
            }, repo, branch, $"Add {dependency} to " +
                                            $"'{VersionFiles.VersionProps}'");

            _logger.LogInformation(
                $"Dependency '{dependency.Name}' with version '{dependency.Version}' successfully added to " +
                $"'{VersionFiles.VersionProps}'");
        }