Наша сборка Qt VS Tools
giy
2022-09-02 ca47896204482bf4a6979e3838bf7f09f61cebeb
QtVsTools.Package/QtMsBuild/QtProjectBuild.cs
@@ -40,36 +40,42 @@
using Microsoft.VisualStudio.TaskStatusCenter;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.VCProjectEngine;
using EnvDTE;
using Thread = System.Threading.Thread;
namespace QtVsTools.QtMsBuild
{
    using Common;
    using Core;
    using VisualStudio;
    using Thread = System.Threading.Thread;
    using static Common.EnumExt;
    class QtProjectBuild : Concurrent<QtProjectBuild>
    {
        static PunisherQueue<QtProjectBuild> _BuildQueue;
        static PunisherQueue<QtProjectBuild> BuildQueue =>
            StaticThreadSafeInit(() => _BuildQueue,
                () => _BuildQueue = new PunisherQueue<QtProjectBuild>(
                    getItemKey: (QtProjectBuild build) =>
                    {
                        return build.ConfiguredProject;
                    })
                );
        static LazyFactory StaticLazy { get; } = new LazyFactory();
        static ConcurrentStopwatch _RequestTimer;
        static ConcurrentStopwatch RequestTimer =>
            StaticThreadSafeInit(() => _RequestTimer, () => _RequestTimer = new ConcurrentStopwatch());
        public enum Target
        {
            // Mark project as dirty, but do not request a build
            [String("QtVsTools.QtMsBuild.QtProjectBuild.Target.SetOutdated")] SetOutdated
        }
        static IVsTaskStatusCenterService _StatusCenter;
        static IVsTaskStatusCenterService StatusCenter => StaticThreadSafeInit(() => _StatusCenter,
                () => _StatusCenter = VsServiceProvider
                    .GetService<SVsTaskStatusCenterService, IVsTaskStatusCenterService>());
        static PunisherQueue<QtProjectBuild> BuildQueue => StaticLazy.Get(() =>
            BuildQueue, () => new PunisherQueue<QtProjectBuild>(
                getItemKey: (QtProjectBuild build) =>
                {
                    return build.ConfiguredProject;
                }));
        static ConcurrentStopwatch RequestTimer => StaticLazy.Get(() =>
            RequestTimer, () => new ConcurrentStopwatch());
        static IVsTaskStatusCenterService StatusCenter => StaticLazy.Get(() =>
            StatusCenter, () => VsServiceProvider
                .GetService<SVsTaskStatusCenterService, IVsTaskStatusCenterService>());
        EnvDTE.Project Project { get; set; }
        VCProject VcProject { get; set; }
        UnconfiguredProject UnconfiguredProject { get; set; }
        ConfiguredProject ConfiguredProject { get; set; }
        Dictionary<string, string> Properties { get; set; }
@@ -80,6 +86,7 @@
        public static void StartBuild(
            EnvDTE.Project project,
            string projectPath,
            string configName,
            Dictionary<string, string> properties,
            IEnumerable<string> targets,
@@ -90,11 +97,13 @@
            if (configName == null)
                throw new ArgumentException("Configuration name cannot be null.");
            Task.Run(() => StartBuildAsync(project, configName, properties, targets, verbosity));
            _ = Task.Run(() => StartBuildAsync(
                project, projectPath, configName, properties, targets, verbosity));
        }
        public static async Task StartBuildAsync(
            EnvDTE.Project project,
            string projectPath,
            string configName,
            Dictionary<string, string> properties,
            IEnumerable<string> targets,
@@ -106,15 +115,15 @@
                throw new ArgumentException("Configuration name cannot be null.");
            RequestTimer.Restart();
            var tracker = QtProjectTracker.Get(project, projectPath);
            await tracker.Initialized;
            if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                Messages.Print(string.Format(
                "{0:HH:mm:ss.FFF} QtProjectBuild({1}): Request [{2}] {3}",
                DateTime.Now, Thread.CurrentThread.ManagedThreadId,
                configName, project.FullName));
                configName, tracker.UnconfiguredProject.FullPath));
            }
            var tracker = QtProjectTracker.Get(project);
            await tracker.Initialized;
            var knownConfigs = await tracker.UnconfiguredProject.Services
                .ProjectConfigurationsService.GetKnownProjectConfigurationsAsync();
@@ -134,6 +143,7 @@
            BuildQueue.Enqueue(new QtProjectBuild()
            {
                Project = project,
                VcProject = tracker.VcProject,
                UnconfiguredProject = tracker.UnconfiguredProject,
                ConfiguredProject = configuredProject,
                Properties = properties?.ToDictionary(x => x.Key, x => x.Value),
@@ -143,6 +153,35 @@
            StaticThreadSafeInit(() => BuildDispatcher,
                () => BuildDispatcher = Task.Run(BuildDispatcherLoopAsync))
                .Forget();
        }
        public static void SetOutdated(
            EnvDTE.Project project,
            string projectPath,
            string configName,
            LoggerVerbosity verbosity = LoggerVerbosity.Quiet)
        {
            if (project == null)
                throw new ArgumentException("Project cannot be null.");
            if (configName == null)
                throw new ArgumentException("Configuration name cannot be null.");
            _ = Task.Run(() => SetOutdatedAsync(project, projectPath, configName, verbosity));
        }
        public static async Task SetOutdatedAsync(
            EnvDTE.Project project,
            string projectPath,
            string configName,
            LoggerVerbosity verbosity = LoggerVerbosity.Quiet)
        {
            await StartBuildAsync(
                project,
                projectPath,
                configName,
                null,
                new[] { Target.SetOutdated.Cast<string>() },
                verbosity);
        }
        public static void Reset()
@@ -161,8 +200,7 @@
                    }
                    await Task.Delay(100);
                }
                QtProjectBuild buildRequest;
                if (BuildQueue.TryDequeue(out buildRequest)) {
                if (BuildQueue.TryDequeue(out QtProjectBuild buildRequest)) {
                    if (dispatchStatus == null) {
                        dispatchStatus = StatusCenter.PreRegister(
                            new TaskHandlerOptions
@@ -202,6 +240,140 @@
            }
        }
        async Task<bool> BuildProjectAsync(ProjectWriteLockReleaser writeAccess)
        {
            var msBuildProject = await writeAccess.GetProjectAsync(ConfiguredProject);
            if (Targets.Any(t => t == Target.SetOutdated.Cast<string>())) {
                msBuildProject.MarkDirty();
                await writeAccess.ReleaseAsync();
                return true;
            }
            var solutionPath = QtProjectTracker.SolutionPath;
            var configProps = new Dictionary<string, string>(
                ConfiguredProject.ProjectConfiguration.Dimensions.ToImmutableDictionary())
                {
                    { "SolutionPath", solutionPath },
                    { "SolutionFileName", Path.GetFileName(solutionPath) },
                    { "SolutionName", Path.GetFileNameWithoutExtension(solutionPath) },
                    { "SolutionExt", Path.GetExtension(solutionPath) },
                    { "SolutionDir", Path.GetDirectoryName(solutionPath).TrimEnd('\\') + '\\'  }
                };
            foreach (var property in Properties)
                configProps[property.Key] = property.Value;
            var projectInstance = new ProjectInstance(msBuildProject.Xml,
                configProps, null, new ProjectCollection());
            var loggerVerbosity = LoggerVerbosity;
            if (QtVsToolsPackage.Instance.Options.BuildDebugInformation)
                loggerVerbosity = QtVsToolsPackage.Instance.Options.BuildLoggerVerbosity;
            var buildParams = new BuildParameters()
            {
                Loggers = (loggerVerbosity != LoggerVerbosity.Quiet)
                        ? new[] { new QtProjectLogger() { Verbosity = loggerVerbosity } }
                        : null
            };
            var buildRequest = new BuildRequestData(projectInstance,
                Targets.ToArray(),
                hostServices: null,
                flags: BuildRequestDataFlags.ProvideProjectStateAfterBuild);
            if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                Messages.Print(string.Format(
                    "{0:HH:mm:ss.FFF} QtProjectBuild({1}): Build [{2}] {3}",
                    DateTime.Now, Thread.CurrentThread.ManagedThreadId,
                    ConfiguredProject.ProjectConfiguration.Name,
                    UnconfiguredProject.FullPath));
                Messages.Print("=== Targets");
                foreach (var target in buildRequest.TargetNames)
                    Messages.Print(string.Format("    {0}", target));
                Messages.Print("=== Properties");
                foreach (var property in Properties) {
                    Messages.Print(string.Format("    {0}={1}",
                        property.Key, property.Value));
                }
            }
            BuildResult result = null;
            while (result == null) {
                try {
                    result = BuildManager.DefaultBuildManager.Build(
                        buildParams, buildRequest);
                } catch (InvalidOperationException) {
                    if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                        Messages.Print(string.Format(
                            "{0:HH:mm:ss.FFF} QtProjectBuild({1}): [{2}] "
                            + "Warning: Another build is in progress; waiting...",
                            DateTime.Now,
                            Thread.CurrentThread.ManagedThreadId,
                            ConfiguredProject.ProjectConfiguration.Name));
                    }
                    await Task.Delay(3000);
                }
            }
            if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                string resMsg;
                StringBuilder resInfo = new StringBuilder();
                if (result?.OverallResult == BuildResultCode.Success) {
                    resMsg = "Build ok";
                } else {
                    resMsg = "Build FAIL";
                    if (result == null) {
                        resInfo.AppendLine("####### Build returned 'null'");
                    } else {
                        resInfo.AppendLine("####### Build returned 'Failure' code");
                        if (result.ResultsByTarget != null) {
                            foreach (var tr in result.ResultsByTarget) {
                                var res = tr.Value;
                                if (res.ResultCode != TargetResultCode.Failure)
                                    continue;
                                resInfo.AppendFormat("### Target '{0}' FAIL\r\n", tr.Key);
                                if (res.Items != null && res.Items.Length > 0) {
                                    resInfo.AppendFormat(
                                        "Items: {0}\r\n", string.Join(", ", res.Items
                                            .Select(it => it.ItemSpec)));
                                }
                                var e = tr.Value?.Exception;
                                if (e != null) {
                                    resInfo.AppendFormat(
                                        "Exception: {0}\r\nStacktrace:\r\n{1}\r\n",
                                        e.Message, e.StackTrace);
                                }
                            }
                        }
                    }
                }
                Messages.Print(string.Format(
                    "{0:HH:mm:ss.FFF} QtProjectBuild({1}): [{2}] {3}\r\n{4}",
                    DateTime.Now, Thread.CurrentThread.ManagedThreadId,
                    ConfiguredProject.ProjectConfiguration.Name,
                    resMsg, resInfo.ToString()));
            }
            bool ok = false;
            if (result == null
                || result.ResultsByTarget == null
                || result.OverallResult != BuildResultCode.Success) {
                Messages.Print(string.Format("{0}: background build FAILED!",
                        Path.GetFileName(UnconfiguredProject.FullPath)));
            } else {
                var checkResults = result.ResultsByTarget
                    .Where(x => Targets.Contains(x.Key))
                    .Select(x => x.Value);
                ok = checkResults.Any()
                    && checkResults.All(x => x.ResultCode == TargetResultCode.Success);
                if (ok)
                    msBuildProject.MarkDirty();
            }
            await writeAccess.ReleaseAsync();
            return ok;
        }
        async Task BuildAsync()
        {
            if (LoggerVerbosity != LoggerVerbosity.Quiet) {
@@ -211,7 +383,7 @@
  * Properties: {1}
  * Targets: {2}
",
                    /*{0}*/ Project.Name,
                    /*{0}*/ Path.GetFileNameWithoutExtension(UnconfiguredProject.FullPath),
                    /*{1}*/ string.Join("", Properties
                        .Select(property => string.Format(@"
        {0} = {1}",     /*{0}*/ property.Key, /*{1}*/ property.Value))),
@@ -222,151 +394,41 @@
            bool ok = false;
            try {
                ProjectWriteLockReleaser writeAccess;
                var timer = ConcurrentStopwatch.StartNew();
                while (timer.IsRunning) {
                    try {
                        writeAccess = await lockService.WriteLockAsync();
#if VS2017
                        using (var writeAccess = await lockService.WriteLockAsync())
                            ok = await BuildProjectAsync(writeAccess);
#else
                        await lockService.WriteLockAsync(
                            async (ProjectWriteLockReleaser writeAccess) =>
                            {
                                ok = await BuildProjectAsync(writeAccess);
                            });
#endif
                        timer.Stop();
                    } catch (InvalidOperationException) {
                        if (timer.ElapsedMilliseconds >= 5000)
                            throw;
#if VS2017
                        using (var readAccess = await lockService.ReadLockAsync())
                            await readAccess.ReleaseAsync();
#else
                        await lockService.ReadLockAsync(
                            async (ProjectLockReleaser readAccess) =>
                            {
                                await readAccess.ReleaseAsync();
                            });
#endif
                    }
                }
                using (writeAccess) {
                    var msBuildProject = await writeAccess.GetProjectAsync(ConfiguredProject);
                    var solutionPath = QtProjectTracker.SolutionPath;
                    var configProps = new Dictionary<string, string>(
                        ConfiguredProject.ProjectConfiguration.Dimensions.ToImmutableDictionary())
                    {
                        { "SolutionPath", solutionPath },
                        { "SolutionFileName", Path.GetFileName(solutionPath) },
                        { "SolutionName", Path.GetFileNameWithoutExtension(solutionPath) },
                        { "SolutionExt", Path.GetExtension(solutionPath) },
                        { "SolutionDir", Path.GetDirectoryName(solutionPath).TrimEnd('\\') + '\\'  }
                    };
                    foreach (var property in Properties)
                        configProps[property.Key] = property.Value;
                    var projectInstance = new ProjectInstance(msBuildProject.Xml,
                        configProps, null, new ProjectCollection());
                    var loggerVerbosity = LoggerVerbosity;
                    if (QtVsToolsPackage.Instance.Options.BuildDebugInformation)
                        loggerVerbosity = QtVsToolsPackage.Instance.Options.BuildLoggerVerbosity;
                    var buildParams = new BuildParameters()
                    {
                        Loggers = (loggerVerbosity != LoggerVerbosity.Quiet)
                                ? new[] { new QtProjectLogger() { Verbosity = loggerVerbosity } }
                                : null
                    };
                    var buildRequest = new BuildRequestData(projectInstance,
                        Targets.ToArray(),
                        hostServices: null,
                        flags: BuildRequestDataFlags.ProvideProjectStateAfterBuild);
                    if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                        Messages.Print(string.Format(
                            "{0:HH:mm:ss.FFF} QtProjectBuild({1}): Build [{2}] {3}",
                            DateTime.Now, Thread.CurrentThread.ManagedThreadId,
                            ConfiguredProject.ProjectConfiguration.Name,
                            UnconfiguredProject.FullPath));
                        Messages.Print("=== Targets");
                        foreach (var target in buildRequest.TargetNames)
                            Messages.Print(string.Format("    {0}", target));
                        Messages.Print("=== Properties");
                        foreach (var property in Properties) {
                            Messages.Print(string.Format("    {0}={1}",
                                property.Key, property.Value));
                        }
                    }
                    BuildResult result = null;
                    while (result == null) {
                        try {
                            result = BuildManager.DefaultBuildManager.Build(
                                buildParams, buildRequest);
                        } catch (InvalidOperationException) {
                            if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                                Messages.Print(string.Format(
                                    "{0:HH:mm:ss.FFF} QtProjectBuild({1}): [{2}] "
                                    + "Warning: Another build is in progress; waiting...",
                                    DateTime.Now,
                                    Thread.CurrentThread.ManagedThreadId,
                                    ConfiguredProject.ProjectConfiguration.Name));
                            }
                            await Task.Delay(3000);
                        }
                    }
                    if (QtVsToolsPackage.Instance.Options.BuildDebugInformation) {
                        string resMsg;
                        StringBuilder resInfo = new StringBuilder();
                        if (result?.OverallResult == BuildResultCode.Success) {
                            resMsg = "Build ok";
                        } else {
                            resMsg = "Build FAIL";
                            if (result == null) {
                                resInfo.AppendLine("####### Build returned 'null'");
                            } else {
                                resInfo.AppendLine("####### Build returned 'Failure' code");
                                if (result.ResultsByTarget != null) {
                                    foreach (var tr in result.ResultsByTarget) {
                                        var res = tr.Value;
                                        if (res.ResultCode != TargetResultCode.Failure)
                                            continue;
                                        resInfo.AppendFormat("### Target '{0}' FAIL\r\n", tr.Key);
                                        if (res.Items != null && res.Items.Length > 0) {
                                            resInfo.AppendFormat(
                                                "Items: {0}\r\n", string.Join(", ", res.Items
                                                    .Select(it => it.ItemSpec)));
                                        }
                                        var e = tr.Value?.Exception;
                                        if (e != null) {
                                            resInfo.AppendFormat(
                                                "Exception: {0}\r\nStacktrace:\r\n{1}\r\n",
                                                e.Message, e.StackTrace);
                                        }
                                    }
                                }
                            }
                        }
                        Messages.Print(string.Format(
                            "{0:HH:mm:ss.FFF} QtProjectBuild({1}): [{2}] {3}\r\n{4}",
                            DateTime.Now, Thread.CurrentThread.ManagedThreadId,
                            ConfiguredProject.ProjectConfiguration.Name,
                            resMsg, resInfo.ToString()));
                    }
                    if (result == null
                        || result.ResultsByTarget == null
                        || result.OverallResult != BuildResultCode.Success) {
                        Messages.Print(string.Format("{0}: background build FAILED!",
                                Path.GetFileName(UnconfiguredProject.FullPath)));
                    } else {
                        var checkResults = result.ResultsByTarget
                            .Where(x => Targets.Contains(x.Key))
                            .Select(x => x.Value);
                        ok = checkResults.Any()
                            && checkResults.All(x => x.ResultCode == TargetResultCode.Success);
                        if (ok)
                            msBuildProject.MarkDirty();
                    }
                    await writeAccess.ReleaseAsync();
                }
                if (ok) {
                    var vcProject = Project.Object as VCProject;
                    var vcConfigs = vcProject.Configurations as IVCCollection;
                    var vcConfigs = VcProject.Configurations as IVCCollection;
                    var vcConfig = vcConfigs.Item(ConfiguredProject.ProjectConfiguration.Name) as VCConfiguration;
                    var props = vcConfig.Rules.Item("QtRule10_Settings") as IVCRulePropertyStorage;
                    props.SetPropertyValue("QtLastBackgroundBuild", DateTime.UtcNow.ToString("o"));
                    props?.SetPropertyValue("QtLastBackgroundBuild", DateTime.UtcNow.ToString("o"));
                }
            } catch (Exception e) {
                Messages.Print(string.Format("{0}: background build ERROR: {1}",
@@ -377,7 +439,8 @@
                Messages.Print(string.Format(
@"
== {0}: build {1}",
                    Project.Name, ok ? "successful" : "ERROR"));
                    Path.GetFileNameWithoutExtension(UnconfiguredProject.FullPath),
                    ok ? "successful" : "ERROR"));
            }
        }
    }