microsoft / VSProjectSystem

Documentation for extending Visual Studio with new types of projects.
Other
313 stars 87 forks source link

Tyring to alter VCProject global properties doesn't cause the VCProject to evaluate build properties #235

Open ceztko opened 7 years ago

ceztko commented 7 years ago

I'm developer of a extension for VS to add $(SolutionConfiguration) and $(SolutionPlatform) macros (the values that can be seen on the IDE combobox) to VS project settings. Since altering ProjectCollection is becoming less and less predictable, I am trying to follow the documentation to define a IProjectGlobalPropertiesProvider MEF component that will plug this macros without accessing directly the MSBuild project or collection. This is my code:

    [Export(typeof(IProjectGlobalPropertiesProvider))]
    [AppliesTo("VisualC + VCProjectEngineFactory")] // Copied from internal VCGlobalPropertiesProvider
    public class SolutionConfigurationPropertiesProvider :
        ProjectValueDataSourceBase<IImmutableDictionary<string, string>>,
        IProjectGlobalPropertiesProvider
    {
        const string NAME_IDENTITY = "SolutionConfiguration";

        private static object _lock;
        private static SolutionConfigurationPropertiesProvider _instance;   // Lock guarded
        private static IImmutableDictionary<string, string> _properties;    // Lock guarded
        private static IComparable _version;                                // Lock guarded
        private static BroadcastBlock<IProjectVersionedValue<IImmutableDictionary<string, string>>> _brodcastBlock; // Lock guarded

        private static IReceivableSourceBlock<IProjectVersionedValue<IImmutableDictionary<string, string>>> _publicBlock;
        private static NamedIdentity _dataSourceKey;

        static SolutionConfigurationPropertiesProvider()
        {
            _version = 0L;
            _lock = new object();
            _properties = ImmutableDictionary<string, string>.Empty;
            _dataSourceKey = new NamedIdentity(NAME_IDENTITY);
        }

        // The scope is the solution ProjectCollection for VC projects
        // https://github.com/Microsoft/VSProjectSystem/blob/master/doc/overview/scopes.md
        [ImportingConstructor]
        protected SolutionConfigurationPropertiesProvider(IProjectService service)
            : base(service.Services)
        {
            lock (_lock)
            {
                _instance = this;
            }
        }

        public static void SetSolutionConfiguration(string configurationName, string platformName)
        {
            lock (_lock)
            {
                _properties = GetDictionary(configurationName, platformName);

                if (_instance != null)
                    _instance.PostSolutionConfiguration();
            }
        }

        private void PostSolutionConfiguration()
        {
            _version = (long)_version + 1;
            postSolutionConfiguration();
        }

        private void postSolutionConfiguration()
        {
            _brodcastBlock.Post(
                new ProjectVersionedValue<IImmutableDictionary<string, string>>(_properties,
                    ImmutableDictionary<NamedIdentity, IComparable>.Empty.Add(_dataSourceKey, _version)));
        }

        public override NamedIdentity DataSourceKey => _dataSourceKey;

        public override IComparable DataSourceVersion
        {
            get { lock (_lock) { return _version; } }
        }

        public override IReceivableSourceBlock<IProjectVersionedValue<IImmutableDictionary<string, string>>> SourceBlock
        {
            get
            {
                EnsureInitialized();
                return _publicBlock;
            }
        }

        public Task<IImmutableDictionary<string, string>> GetGlobalPropertiesAsync(CancellationToken cancellationToken)
        {
            lock (_lock)
            {
                return Task.FromResult(_properties);
            }
        }

        static IImmutableDictionary<string, string> GetDictionary(string configurationName, string platformName)
        {
            var builder = Microsoft.VisualStudio.ProjectSystem.Empty.PropertiesMap.ToBuilder();
            builder.Add("SolutionConfiguration", configurationName);
            builder.Add("SolutionPlatform", platformName);
            return builder.ToImmutable();
        }

        protected override void Initialize()
        {
            base.Initialize();

            lock (_lock)
            {
                _brodcastBlock = new BroadcastBlock<IProjectVersionedValue<IImmutableDictionary<string, string>>>(null,
                    new DataflowBlockOptions() { NameFormat = NAME_IDENTITY + ": {1}" });
                _publicBlock = _brodcastBlock.SafePublicize();

                postSolutionConfiguration();
            }
        }
    }

1) This is code is not working: the macros are correctly passed to the project settings GUI (I can see them in the macros editor) but are ignored during build. If I invalidate project settings with dummy modification, reverted immediately after, the build system is able to recognize the change in the properties and builds correctly. The versioning system that is in place seems to be correct to me, I also got inspiration from here; 2) EDIT: Answered How do I ensure the provider will be a singleton for all VCProject(s)? The imported constructor is called multiple times.

Note: the capability I'm using, "VisualC + VCProjectEngineFactory", is taken from inspected VS code and seems to apply correctly to VCProject(s). Same problems happen if using the (supposedly) global capability "".

ceztko commented 7 years ago

For the second question, I can answer my self. According to the scope documentaton, the solution scope can be obtained importing an IProjectService reference (and not IProjectServices <- note the final "s"). So the constructor becomes:

        [ImportingConstructor]
        protected SolutionConfigurationPropertiesProvider(IProjectService service)
            : base(service.Services)
        {

and the provider is per solution global.

The first question is still open: so far I couldn't invalidate a VCProject by modifying its global properties with IProjectGlobalPropertiesProvider. When doing so, VS still think the project is up-to-date.

JunielKatarn commented 6 years ago

Hey @ceztko, sorry to post an unrelated question here, but, did you find a way to instantiate a VCProjectEngine, or ultimately a VCProject? I'm looking to write some tooling for VCXPROJ files, but can't find any documentation to get started.

ceztko commented 6 years ago

@JunielKatarn I'm really not so thankful to you for giving me the illusion that someone answered this long time unanswered question :) I never played with VCProject to that extent but it seems this[1] is a good start. I can't help you further.

[1] https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.vcprojectengine.vcprojectengine.createproject?view=visualstudiosdk-2017#Microsoft_VisualStudio_VCProjectEngine_VCProjectEngine_CreateProject_System_String_

kylereedmsft commented 6 years ago

Hi @ceztko - I believe you're hitting a problem with the VC project cache. Open "Tools->Options->Projects and Solutions->VC++ Project Settings" and then set "Enable Project Caching" to "false" then try the scenario again. You will need to restart VS.

That's not a reasonable workaround - but may unblock you. If this scenario is important, consider reporting a bug against the VC team: https://developercommunity.visualstudio.com/content/problem/post.html?space=62