/// <summary> /// Add a PropertyGroup to the project for a particular Asp.Net configuration. This PropertyGroup /// will have the correct values for all the Asp.Net properties for this project and this configuration. /// </summary> private void AddPropertyGroupForAspNetConfiguration ( ProjectInstance traversalProject, ProjectInstance metaprojectInstance, ProjectInSolution project, string configurationName, AspNetCompilerParameters aspNetCompilerParameters, string solutionFile ) { // If the configuration doesn't match, don't add the properties. if (!traversalProject.EvaluateCondition(String.Format(CultureInfo.InvariantCulture, " '$(AspNetConfiguration)' == '{0}' ", EscapingUtilities.Escape(configurationName)))) { return; } // Add properties into the property group for each of the AspNetCompiler properties. // REVIEW: SetProperty takes an evaluated value. Are we doing the right thing here? metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetVirtualPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetVirtualPath)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetPhysicalPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetPhysicalPath)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetTargetPath"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetTargetPath)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetForce"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetForce)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetUpdateable"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetUpdateable)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetDebug"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetDebug)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetKeyFile"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetKeyFile)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetKeyContainer"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetKeyContainer)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetDelaySign"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetDelaySign)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetAPTCA"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetAPTCA)); metaprojectInstance.SetProperty(GenerateSafePropertyName(project, "AspNetFixedNames"), EscapingUtilities.Escape(aspNetCompilerParameters.aspNetFixedNames)); string aspNetPhysicalPath = aspNetCompilerParameters.aspNetPhysicalPath; if (!String.IsNullOrEmpty(aspNetPhysicalPath)) { // Trim the trailing slash if one exists. if ( (aspNetPhysicalPath[aspNetPhysicalPath.Length - 1] == Path.AltDirectorySeparatorChar) || (aspNetPhysicalPath[aspNetPhysicalPath.Length - 1] == Path.DirectorySeparatorChar) ) { aspNetPhysicalPath = aspNetPhysicalPath.Substring(0, aspNetPhysicalPath.Length - 1); } // This gets us the last folder in the physical path. string lastFolderInPhysicalPath = null; try { lastFolderInPhysicalPath = Path.GetFileName(aspNetPhysicalPath); } catch (Exception e) { if (ExceptionHandling.NotExpectedException(e)) { throw; } ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile ( false, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solutionFile), e, "SolutionParseInvalidProjectFileName", project.RelativePath, e.Message ); } if (!String.IsNullOrEmpty(lastFolderInPhysicalPath)) { // If there is a global property called "OutDir" set, that means the caller is trying to // override the AspNetTargetPath. What we want to do in this case is concatenate: // $(OutDir) + "\_PublishedWebsites" + (the last portion of the folder in the AspNetPhysicalPath). if (traversalProject.EvaluateCondition(" '$(OutDir)' != '' ")) { string outDirValue = String.Empty; ProjectPropertyInstance outdir = metaprojectInstance.GetProperty("OutDir"); if (outdir != null) { outDirValue = ProjectInstance.GetPropertyValueEscaped(outdir); } // Make sure the path we are appending to has no leading slash to prevent double slashes. string publishWebsitePath = EscapingUtilities.Escape(WebProjectOverrideFolder) + Path.DirectorySeparatorChar + EscapingUtilities.Escape(lastFolderInPhysicalPath) + Path.DirectorySeparatorChar; metaprojectInstance.SetProperty ( GenerateSafePropertyName(project, "AspNetTargetPath"), outDirValue + publishWebsitePath ); } } } }
/// <summary> /// Start MSBuild build submission /// </summary> /// If buildKind is ASYNC, this method starts the submission ane returns. uiThreadCallback will be called on UI thread once submissions completes. /// if buildKind is SYNC, this method executes the submission and runs uiThreadCallback /// <param name="buildKind">Is it a SYNC or ASYNC build</param> /// <param name="target">target to build</param> /// <param name="projectInstance">project instance to build; if null, this.BuildProject.CreateProjectInstance() is used to populate</param> /// <param name="uiThreadCallback">callback to be run UI thread </param> /// <returns></returns> internal virtual BuildSubmission DoMSBuildSubmission(BuildKind buildKind, string target, ref ProjectInstance projectInstance, MSBuildCoda uiThreadCallback) { UIThread.MustBeCalledFromUIThread(); bool designTime = BuildKind.SYNC == buildKind; //projectInstance = null; var accessor = (IVsBuildManagerAccessor)this.Site.GetService(typeof(SVsBuildManagerAccessor)); if (!TryBeginBuild(designTime)) { if (null != uiThreadCallback) { uiThreadCallback(MSBuildResult.Failed, projectInstance); } return null; } string[] targetsToBuild = new string[target != null ? 1 : 0]; if (target != null) { targetsToBuild[0] = target; } if (null == projectInstance) { projectInstance = BuildProject.CreateProjectInstance(); } projectInstance.SetProperty(GlobalProperty.VisualStudioStyleErrors.ToString(), "true"); projectInstance.SetProperty("UTFOutput", "true"); projectInstance.SetProperty(GlobalProperty.BuildingInsideVisualStudio.ToString(), "true"); this.BuildProject.ProjectCollection.HostServices.SetNodeAffinity(projectInstance.FullPath, NodeAffinity.InProc); BuildRequestData requestData = new BuildRequestData(projectInstance, targetsToBuild, this.BuildProject.ProjectCollection.HostServices, BuildRequestDataFlags.ReplaceExistingProjectInstance); BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(requestData); try { if (useProvidedLogger && buildLogger != null) { ErrorHandler.ThrowOnFailure(accessor.RegisterLogger(submission.SubmissionId, buildLogger)); } if (buildKind == BuildKind.ASYNC) { ProjectInstance projectInstanceCopy = projectInstance; submission.ExecuteAsync(sub => { UIThread.Run(() => { this.FlushBuildLoggerContent(); EndBuild(sub, designTime); uiThreadCallback((sub.BuildResult.OverallResult == BuildResultCode.Success) ? MSBuildResult.Successful : MSBuildResult.Failed, projectInstanceCopy); }); }, null); } else { submission.Execute(); EndBuild(submission, designTime); MSBuildResult msbuildResult = (submission.BuildResult.OverallResult == BuildResultCode.Success) ? MSBuildResult.Successful : MSBuildResult.Failed; if (uiThreadCallback != null) { uiThreadCallback(msbuildResult, projectInstance); } } } catch (Exception e) { Debug.Fail(e.ToString()); EndBuild(submission, designTime); if (uiThreadCallback != null) { uiThreadCallback(MSBuildResult.Failed, projectInstance); } throw; } return submission; }
// To NOT reuse this IBuildEngine instance for different build, we create another BuildManager and BuildSubmisson and then run it. public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) { toolsVersion = string.IsNullOrEmpty (toolsVersion) ? project.ToolsVersion : toolsVersion; var globalPropertiesThatMakeSense = new Dictionary<string,string> (); foreach (DictionaryEntry p in globalProperties) globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value; var projectToBuild = new ProjectInstance (ProjectRootElement.Create (XmlReader.Create (projectFileName)), globalPropertiesThatMakeSense, toolsVersion, Projects); // Not very sure if ALL of these properties should be added, but some are certainly needed. foreach (var p in this.project.Properties.Where (p => !globalProperties.Contains (p.Name))) projectToBuild.SetProperty (p.Name, p.EvaluatedValue); IDictionary<string,TargetResult> outs; var ret = projectToBuild.Build (targetNames ?? new string [] {"Build"}, Projects.Loggers, out outs); foreach (var p in outs) targetOutputs [p.Key] = p.Value.Items ?? new ITaskItem [0]; return ret; }
/// <summary> /// Called before the first project configuration is about to be built. /// </summary> /// <returns> /// If the method succeeds, it returns <see cref="F:Microsoft.VisualStudio.VSConstants.S_OK"/>. If it fails, it returns an error code. /// </returns> /// <param name="pfCancelUpdate">[in, out] Pointer to a flag indicating cancel update.</param> int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) { // This method is called when the entire solution starts to build. this.WriteLine(LoggerVerbosity.Detailed, "CommonSolutionTargets: UpdateSolution_StartUpdate"); string solutionFilePath = this.dte2.Solution.FullName; string solutionFolder = Path.GetDirectoryName(solutionFilePath); string solutionFileName = Path.GetFileName(solutionFilePath); string targetsFileName = "after." + solutionFileName + ".targets"; if (solutionFolder == null) return VSConstants.E_UNEXPECTED; string targetsFilePath = Path.Combine(solutionFolder, targetsFileName); if (!File.Exists(targetsFilePath)) return VSConstants.S_OK; string solutionConfigurationName = this.dte2.Solution.SolutionBuild.ActiveConfiguration.Name; this.WriteLine(LoggerVerbosity.Detailed, "CommonSolutionTargets: active solution configuration name is \"{0}\"", solutionConfigurationName); List<ILogger> loggers = new List<ILogger> { this.MakeBuildLogger() }; ProjectInstance solutionInitProjectInstance; try { solutionInitProjectInstance = new ProjectInstance(targetsFilePath); } catch (Exception ex) { this.WriteLine(LoggerVerbosity.Detailed, "CommonSolutionTargets: failed to load targets file \"{0}\", Exception: {1}", targetsFilePath, ex); return VSConstants.E_FAIL; } solutionInitProjectInstance.SetProperty("Configuration", solutionConfigurationName); solutionInitProjectInstance.SetProperty("BuildingInsideVisualStudio", "true"); int numberOfPropertiesBeforeBuild = solutionInitProjectInstance.Properties.Count; this.WriteLine(LoggerVerbosity.Detailed, "CommonSolutionTargets: building targets file \"{0}\", target \"{1}\"", targetsFilePath, this.activeBuildTarget); solutionInitProjectInstance.Build(this.activeBuildTarget, loggers); // If solution targets build produced new custom properties, fetch those and add them to the global properties collection. // Most typical usage for this feature is setting "CustomAfterMicrosoftCommontargets" property. for (int propertyNumber = numberOfPropertiesBeforeBuild; propertyNumber < solutionInitProjectInstance.Properties.Count; propertyNumber++) { ProjectPropertyInstance property = solutionInitProjectInstance.Properties.ElementAt(propertyNumber); if (property.Name.StartsWith("Custom")) { this.WriteLine(LoggerVerbosity.Detailed, "CommonSolutionTargets: defined global build property {0} = {1}", property.Name, property.EvaluatedValue); ProjectCollection.GlobalProjectCollection.SetGlobalProperty(property.Name, property.EvaluatedValue); } } return VSConstants.S_OK; }