/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; using MSBuildExecution = Microsoft.Build.Execution; namespace Microsoft.VisualStudio.Project { /// /// Allows projects to group outputs according to usage. /// [ComVisible(true)] public class OutputGroup : IVsOutputGroup2 { private readonly Config _projectCfg; private readonly ProjectNode _project; private readonly List _outputs = new List(); private readonly string _name; private readonly string _targetName; private Output _keyOutput; /// /// Constructor for IVSOutputGroup2 implementation /// /// Name of the output group. See VS_OUTPUTGROUP_CNAME_Build in vsshell.idl for the list of standard values /// MSBuild target name /// Project that produce this output /// Configuration that produce this output public OutputGroup(string outputName, string msBuildTargetName, ProjectNode projectManager, Config configuration) { VsUtilities.ArgumentNotNull("outputName", outputName); VsUtilities.ArgumentNotNull("msBuildTargetName", msBuildTargetName); VsUtilities.ArgumentNotNull("projectManager", projectManager); VsUtilities.ArgumentNotNull("configuration", configuration); _name = outputName; _targetName = msBuildTargetName; _project = projectManager; _projectCfg = configuration; } /// /// Get the project configuration object associated with this output group /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cfg")] protected Config ProjectCfg { get { return _projectCfg; } } /// /// Get the project object that produces this output group. /// public ProjectNode Project { get { return _project; } } /// /// Gets the msbuild target name which is assciated to the outputgroup. /// ProjectNode defines a static collection of output group names and their associated MsBuild target /// protected string TargetName { get { return _targetName; } } #region virtual methods protected virtual void Refresh() { IVsCfg cfg; ErrorHandler.ThrowOnFailure(_projectCfg.GetCfg(out cfg)); string displayName; ErrorHandler.ThrowOnFailure(cfg.get_DisplayName(out displayName)); string[] parts = displayName.Split('|'); string configName = parts[0].Trim(); string platformName = string.Empty; if (parts.Length > 1) platformName = parts[1].Trim(); // Let MSBuild know which configuration we are working with _project.SetConfiguration(configName, platformName); // Generate dependencies if such a task exist const string generateDependencyList = "AllProjectOutputGroups"; if (_project.BuildProject.Targets.ContainsKey(generateDependencyList)) { //bool succeeded = false; //project.BuildTarget(generateDependencyList, out succeeded); //Debug.Assert(succeeded, "Failed to build target: " + generateDependencyList); } // Rebuild the content of our list of output string outputType = this._targetName + "Output"; this._outputs.Clear(); if (_project.CurrentConfig != null) { foreach (MSBuildExecution.ProjectItemInstance assembly in _project.CurrentConfig.GetItems(outputType)) { Output output = new Output(_project, assembly); _outputs.Add(output); // See if it is our key output if (_keyOutput == null || String.Compare(assembly.GetMetadataValue("IsKeyOutput"), true.ToString(), StringComparison.OrdinalIgnoreCase) == 0) { _keyOutput = output; } } } _project.SetCurrentConfiguration(); // Now that the group is built we have to check if it is invalidated by a property // change on the project. _project.OnProjectPropertyChanged += new EventHandler(OnProjectPropertyChanged); } public virtual void InvalidateGroup() { // Set keyOutput to null so that a refresh will be performed the next time // a property getter is called. if (null != _keyOutput) { // Once the group is invalidated there is no more reason to listen for events. _project.OnProjectPropertyChanged -= new EventHandler(OnProjectPropertyChanged); } _keyOutput = null; } #endregion #region event handlers private void OnProjectPropertyChanged(object sender, ProjectPropertyChangedArgs args) { // In theory here we should decide if we have to invalidate the group according with the kind of property // that is changed. InvalidateGroup(); } #endregion #region IVsOutputGroup2 Members public virtual int get_CanonicalName(out string pbstrCanonicalName) { pbstrCanonicalName = this._name; return VSConstants.S_OK; } public virtual int get_DeployDependencies(uint celt, IVsDeployDependency[] rgpdpd, uint[] pcActual) { return VSConstants.E_NOTIMPL; } public virtual int get_Description(out string pbstrDescription) { pbstrDescription = null; string description; int hr = this.get_CanonicalName(out description); if (ErrorHandler.Succeeded(hr)) pbstrDescription = this.Project.GetOutputGroupDescription(description); return hr; } public virtual int get_DisplayName(out string pbstrDisplayName) { pbstrDisplayName = null; string displayName; int hr = this.get_CanonicalName(out displayName); if (ErrorHandler.Succeeded(hr)) pbstrDisplayName = this.Project.GetOutputGroupDisplayName(displayName); return hr; } public virtual int get_KeyOutput(out string pbstrCanonicalName) { pbstrCanonicalName = null; if (_keyOutput == null) Refresh(); if (_keyOutput == null) { pbstrCanonicalName = String.Empty; return VSConstants.S_FALSE; } return _keyOutput.get_CanonicalName(out pbstrCanonicalName); } public virtual int get_KeyOutputObject(out IVsOutput2 ppKeyOutput) { if (_keyOutput == null) { Refresh(); if (_keyOutput == null) { // horrible hack: we don't really have outputs but the Cider designer insists // that we have an output so it can figure out our output assembly name. So we // lie here, and then lie again to give a path in Output.get_Property _keyOutput = new Output(_project, null); } } ppKeyOutput = _keyOutput; if (ppKeyOutput == null) return VSConstants.S_FALSE; return VSConstants.S_OK; } public virtual int get_Outputs(uint celt, IVsOutput2[] rgpcfg, uint[] pcActual) { // Ensure that we are refreshed. This is somewhat of a hack that enables project to // project reference scenarios to work. Normally, output groups are populated as part // of build. However, in the project to project reference case, what ends up happening // is that the referencing projects requests the referenced project's output group // before a build is done on the referenced project. // // Furthermore, the project auto toolbox manager requires output groups to be populated // on project reopen as well... // // In the end, this is probably the right thing to do, though -- as it keeps the output // groups always up to date. Refresh(); // See if only the caller only wants to know the count if (celt == 0 || rgpcfg == null) { if (pcActual != null && pcActual.Length > 0) pcActual[0] = (uint)_outputs.Count; return VSConstants.S_OK; } // Fill the array with our outputs uint count = 0; foreach (Output output in _outputs) { if (rgpcfg.Length > count && celt > count && output != null) { rgpcfg[count] = output; ++count; } } if (pcActual != null && pcActual.Length > 0) pcActual[0] = count; // If the number asked for does not match the number returned, return S_FALSE return (count == celt) ? VSConstants.S_OK : VSConstants.S_FALSE; } public virtual int get_ProjectCfg(out IVsProjectCfg2 ppIVsProjectCfg2) { ppIVsProjectCfg2 = (IVsProjectCfg2)this._projectCfg; return VSConstants.S_OK; } public virtual int get_Property(string pszProperty, out object pvar) { pvar = _project.GetProjectProperty(pszProperty); return VSConstants.S_OK; } #endregion } }