private string GetProjectVersion(VersionKind key)
        {
            var result = ProjectVersions.TryGetValue(key, out var value) ? value : Empty;

            Log.LogMessage(Low, $"Getting Project Version for '{key}': '{result}' ");
            return(result);
        }
Beispiel #2
0
        // ReSharper disable once MemberCanBeMadeStatic.Local
        /// <summary>
        /// Tries to Execute the Project File Bump Version operation given
        /// <paramref name="descriptors"/>. May also <paramref name="log"/> progress
        /// as appropriate to do so.
        /// </summary>
        /// <param name="descriptors"></param>
        /// <param name="log"></param>
        /// <returns></returns>
        /// <remarks>Key to this is trying to read the input project file then trying to parse the
        /// XML, including verification that it was a Project file with Sdk attribute. In previous
        /// versions we keyed too strongly on <c>Sdk=&quot;Microsoft.NET.Sdk&quot;</c>, however,
        /// we have since learned that Windows Forms dotnet migration paths expect it to be
        /// <c>Microsoft.NET.Sdk.WindowsDesktop</c>. So therefore, yes, we should still expect
        /// there to be an <c>Sdk</c> attribute, but we can probably ignore the value itself.
        /// If necessary, we might consider injecting a configuration that informs which
        /// <em>Sdk</em> we do support, but only if necessary; for now will ignore the value.</remarks>
        private bool TryExecuteProjectFileBumpVersion(IEnumerable <IBumpVersionDescriptor> descriptors
                                                      , TaskLoggingHelper log = null)
        {
            /* ProjectFilename should be based on ProjectFullPath,
             * which should be relayed to us as the $(ProjectPath). */
            var projectFilename = ProjectFilename;

#pragma warning disable IDE0002 // Simplify member access
            const VersionKind version = VersionKind.Version;
#pragma warning restore IDE0002 // Simplify member access

            // ReSharper disable once ConvertIfStatementToReturnStatement
            // Ensure that we are dealing only with the Descriptors we can.
            descriptors = descriptors.Where(descriptor => descriptor.Kind.ContainedBy(version
                                                                                      , AssemblyVersion, FileVersion, InformationalVersion, PackageVersion)).ToArray();

            // ReSharper disable once ConvertIfStatementToReturnStatement
            if (!descriptors.Any())
            {
                return(false);
            }

            void OnBumpResultFound(object sender, BumpResultEventArgs e)
            {
                // ReSharper disable once InvertIf
                if (e.Result is IProjectBumpResult projectResult)
                {
                    (projectResult.DidBump ? log : null)?.LogMessage(High, GetMessage());

                    string GetMessage()
                    => $"'{projectFilename}': Bumped '{projectResult.ProtectedElementName}'"
                    + $" from '{e.Result.OldVersionAndSemanticString}'"
                    + $" to '{e.Result.VersionAndSemanticString}'";
                }
            }

            bool TryParseDocument(string s, out XDocument result, Func <XElement, bool> verifyRoot)
            {
                var parsed = true;

                try
                {
                    result = XDocument.Parse(s, PreserveWhitespace);
                }
                catch (XmlException)
                {
                    parsed = false;
                    result = new XDocument();
                }

                // Now verify the Document and do a little vetting for Project criteria.
                return(parsed && verifyRoot(result?.Root));
            }

            bool DidBumpDocumentVersions <TNode>(TNode aDoc, TNode bDoc)
                where TNode : XNode
            => !Comparer.Equals(aDoc, bDoc);

            bool TryFormatNode <TNode>(TNode node, out string s)
                where TNode : XNode
            {
                s = node.ToString(SaveOptions.None);
                return(true);
            }

            var bumped = false;

            var services = descriptors.Select(d => d.MakeProjectBasedBumpVersionService(log))
                           .Where(x => x != null).ToArray();

            // TODO: TBD: I think there must also be BumpResultCreated, probably also for the AssemblyInfo version...
            services.ToList().ForEach(service => { service.BumpResultFound += OnBumpResultFound; });

            // TODO: TBD: what's nice about this is that minimum the XDocument work is nicely contained in the block
            // TODO: TBD: it may make sense to refactor this block into its own local function TryABC method
            // TODO: TBD: with the goal being to eliminate the need for a Bumped variable altogether
            // TODO: TBD: and then just string together the different TryXYZ local functions as in other places
            // TODO: TBD: i.e. try read from file, try matching, try bumping, etc
            if (TryReadingFile(projectFilename, out var given) &&
                TryParseDocument(given, out var givenDoc,
                                 root => root?.Name.LocalName == "Project" &&
                                 root.Attribute("Sdk") is XAttribute))
            {
                /* Process the Given Doc using the Services in the Aggregate.
                 * Pull the Tried Doc forward when the Bump did Try. */
                var resultDoc = services.Aggregate(new XDocument(givenDoc)
                                                   , (currentDoc, service) =>
                {
                    // TODO: TBD: sort of beggars the question at this point;
                    // TODO: TBD: if we did set the version property via the custom task
                    // TODO: TBD: then is it really that necessary to re-write the file afterwards?
                    // TODO: TBD: yes, for AssemblyInfo Assembly Attribute based work, of course
                    // TODO: TBD: but for CSPROJ Project based work? perhaps not... TBD, TBD, TBD ...
                    var tried = service.TryBumpDocument(currentDoc, out var triedDoc);

                    // ReSharper disable once InvertIf
                    if (tried)
                    {
                        // ReSharper disable once InconsistentNaming
                        const string PropertyGroup = nameof(PropertyGroup);

                        // TODO: TBD: do we need an actual property get/set pair for the Output?
                        if (TryGetVersionElement(triedDoc, PropertyGroup, $"{service.Descriptor.Kind}"
                                                 , out var elements))
                        {
                            // TODO: TBD: also take into consideration things like Configuration re: multiple returned elements...
                            // Do not miss this. These are the hooks that inform the Task Output properties.
                            ProjectVersions[service.Descriptor.Kind] = elements.Single().Value;
                        }
                    }

                    return(tried ? triedDoc : currentDoc);
                });

                // We Bumped when the Doc changed and we Formatted to Given.
                bumped = DidBumpDocumentVersions(givenDoc, resultDoc) && TryFormatNode(resultDoc, out given);
            }

            services.ToList().ForEach(service => { service.BumpResultFound -= OnBumpResultFound; });

            /* We do still need to re-write the file. But we also need to connect the
             * TryBumpDocument with the actual Custom Task Output(s), in order to realize
             * comprehensive integration at the time the Properties are being resolved. */
            return(bumped && TryWritingFile(projectFilename, given));
        }