/// <summary> /// Find the unique name for this project, e.g. SolutionFolder\SubSolutionFolder\ProjectName /// </summary> /// <owner>RGoel</owner> internal string GetUniqueProjectName() { if (this.uniqueProjectName == null) { // EtpSubProject and Venus projects have names that are already unique. No need to prepend the SLN folder. if ((this.ProjectType == SolutionProjectType.WebProject) || (this.ProjectType == SolutionProjectType.EtpSubProject)) { this.uniqueProjectName = CleanseProjectName(this.ProjectName); } else { // This is "normal" project, which in this context means anything non-Venus and non-EtpSubProject. // If this project has a parent SLN folder, first get the full unique name for the SLN folder, // and tack on trailing backslash. string uniqueName = String.Empty; if (this.ParentProjectGuid != null) { ProjectInSolution proj = (ProjectInSolution)this.ParentSolution.ProjectsByGuid[this.ParentProjectGuid]; ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(parentSolution.SolutionFile), "SolutionParseNestedProjectError"); uniqueName = proj.GetUniqueProjectName() + "\\"; } // Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access. this.uniqueProjectName = CleanseProjectName(uniqueName + this.ProjectName); } } return(this.uniqueProjectName); }
public void ParseFirstProjectLineWithDifferentSpacing() { SolutionParser p = new SolutionParser(); p.SolutionFile = "foobar.sln"; ProjectInSolution proj = new ProjectInSolution(p); p.ParseFirstProjectLine ( "Project(\" {Project GUID} \") = \" Project name \", \" Relative path to project file \" , \" Unique name-GUID \"", proj ); Assertion.AssertEquals(SolutionProjectType.Unknown, proj.ProjectType); Assertion.AssertEquals("Project name", proj.ProjectName); Assertion.AssertEquals("Relative path to project file", proj.RelativePath); Assertion.AssertEquals("Unique name-GUID", proj.ProjectGuid); }
/// <summary> /// Takes a project in the solution and a base property name, and creates a new property name /// that can safely be used as an XML element name, and is also unique to that project (by /// embedding the project's GUID into the property name. /// </summary> /// <param name="proj"></param> /// <param name="propertyName"></param> /// <returns>A safe property name that can be used as an XML element name.</returns> /// <owner>RGoel</owner> static private string GenerateSafePropertyName ( ProjectInSolution proj, string propertyName ) { // XML element names cannot contain curly braces, so get rid of them from the project guid. string projectGuid = proj.ProjectGuid.Substring(1, proj.ProjectGuid.Length - 2); return "Project_" + projectGuid + "_" + propertyName; }
/// <summary> /// Add a target for a Venus project into the XML doc that's being generated. This /// target will call the AspNetCompiler task. /// </summary> /// <param name="msbuildProject"></param> /// <param name="solution"></param> /// <param name="proj"></param> /// <param name="subTargetName"></param> /// <owner>RGoel</owner> static private void AddTargetForWebProject ( Project msbuildProject, SolutionParser solution, ProjectInSolution proj, string subTargetName ) { // Add a supporting target called "GetFrameworkPathAndRedistList". AddTargetForGetFrameworkPathAndRedistList(msbuildProject); string targetName = ProjectInSolution.DisambiguateProjectTargetName(proj.GetUniqueProjectName()); if (subTargetName != null && subTargetName.Length > 0) { targetName = targetName + ":" + subTargetName; } Target newTarget = msbuildProject.Targets.AddNewTarget(targetName); newTarget.DependsOnTargets = GetProjectDependencies(proj.ParentSolution, proj, subTargetName) + ";GetFrameworkPathAndRedistList"; if (subTargetName == "Clean") { // Well, hmmm. The AspNetCompiler task doesn't support any kind of // a "Clean" operation. The best we can really do is offer up a // message saying so. AddErrorWarningMessageElement(newTarget, XMakeElements.message, true, "SolutionVenusProjectNoClean"); } else if (subTargetName == "Publish") { // Well, hmmm. The AspNetCompiler task doesn't support any kind of // a "Publish" operation. The best we can really do is offer up a // message saying so. AddErrorWarningMessageElement(newTarget, XMakeElements.message, true, "SolutionVenusProjectNoPublish"); } else { // Add a Condition onto the Target that will cause it only to get executed for those solution configurations // in which this web project is active. newTarget.Condition = ComputeTargetConditionForWebProject(solution, proj); // For normal build and "Rebuild", just call the AspNetCompiler task with the // correct parameters. But before calling the AspNetCompiler task, we need to // do a bunch of prep work regarding references. // We're going to build up an MSBuild condition string that represents the valid Configurations. // We do this by OR'ing together individual conditions, each of which compares $(Configuration) // with a valid configuration name. We init our condition string to "false", so we can easily // OR together more stuff as we go, and also easily take the negation of the condition by putting // a ! around the whole thing. StringBuilder conditionDescribingValidConfigurations = new StringBuilder("(false)"); // Loop through all the valid configurations and add a PropertyGroup for each one. foreach (DictionaryEntry aspNetConfiguration in proj.AspNetConfigurations) { string configurationName = (string)aspNetConfiguration.Key; AspNetCompilerParameters aspNetCompilerParameters = (AspNetCompilerParameters)aspNetConfiguration.Value; // We only add the PropertyGroup once per Venus project. Without the following "if", we would add // the same identical PropertyGroup twice, once when AddTargetForWebProject is called with // subTargetName=null and once when subTargetName="Rebuild". if (subTargetName == null) { AddPropertyGroupForAspNetConfiguration(msbuildProject, proj, configurationName, aspNetCompilerParameters, solution.SolutionFile); } // Update our big condition string to include this configuration. conditionDescribingValidConfigurations.Append(" or "); conditionDescribingValidConfigurations.Append( String.Format(CultureInfo.InvariantCulture, "('$(AspNetConfiguration)' == '{0}')", EscapingUtilities.Escape(configurationName))); } StringBuilder referenceItemName = new StringBuilder(GenerateSafePropertyName(proj, "References")); if (!string.IsNullOrEmpty(subTargetName)) { referenceItemName.Append('_'); referenceItemName.Append(subTargetName); } // Add tasks to resolve project references of this web project, if any if (proj.ProjectReferences.Count > 0) { // This is a bit tricky. Even though web projects don't use solution configurations, // we want to use the current solution configuration to build the proper configurations // of referenced projects. foreach (ConfigurationInSolution solutionConfiguration in solution.SolutionConfigurations) { string referenceProjectGuids = null; AddResolveProjectReferenceTasks(solution, msbuildProject, newTarget, proj, solutionConfiguration, referenceItemName.ToString(), null /* don't care about native references */, out referenceProjectGuids); } } // Add tasks to capture the auto-refreshed file references (those .REFRESH files). AddTasksToResolveAutoRefreshFileReferences(newTarget, proj, referenceItemName.ToString()); // Add a call to RAR (ResolveAssemblyReference) and the Copy task to put the referenced // project outputs in the right place AddTasksToCopyAllDependenciesIntoBinDir(newTarget, proj, referenceItemName.ToString(), conditionDescribingValidConfigurations.ToString()); // Add a call to the AspNetCompiler task, conditioned on having a valid Configuration. AddTaskForAspNetCompiler(newTarget, proj, conditionDescribingValidConfigurations.ToString()); // Add a call to the <Message> task, conditioned on having an *invalid* Configuration. The // message says that we're skipping the Venus project because it's either not enabled // for precompilation, or doesn't support the given configuration. BuildTask newMessageTag = AddErrorWarningMessageElement(newTarget, XMakeElements.message, false, "SolutionVenusProjectSkipped"); newMessageTag.Condition = "!(" + conditionDescribingValidConfigurations.ToString() + ")"; } }
/// <summary> /// Add a target for a project into the XML doc that's being generated. /// </summary> /// <param name="msbuildProject"></param> /// <param name="solution"></param> /// <param name="proj"></param> /// <param name="subTargetName"></param> /// <owner>LukaszG, RGoel</owner> static private void AddTargetForVCProject ( Project msbuildProject, SolutionParser solution, ProjectInSolution proj, string subTargetName ) { string targetName = ProjectInSolution.DisambiguateProjectTargetName(proj.GetUniqueProjectName()); if (subTargetName != null && subTargetName.Length > 0) { targetName = targetName + ":" + subTargetName; } Target newTarget = msbuildProject.Targets.AddNewTarget(targetName); newTarget.DependsOnTargets = GetProjectDependencies(proj.ParentSolution, proj, subTargetName); newTarget.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; if (subTargetName == "Publish") { // Well, hmmm. The VCBuild doesn't support any kind of // a "Publish" operation. The best we can really do is offer up a // message saying so. AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "SolutionVCProjectNoPublish"); // ... and now pretend it's a Build subtarget. This way references to VC projects from projects // that are about to publish will at least get built. subTargetName = null; } string projectPath = null; try { projectPath = proj.AbsolutePath; } catch (Exception e) { if (ExceptionHandling.NotExpectedException(e)) throw; ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solution.SolutionFile), "SolutionParseInvalidProjectFileName", proj.RelativePath, e.Message); } foreach (ConfigurationInSolution solutionConfiguration in solution.SolutionConfigurations) { string solutionConfigurationCondition = GetConditionStringForConfiguration(solutionConfiguration); ProjectConfigurationInSolution vcProjectConfiguration = null; BuildTask newTask = null; if (proj.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out vcProjectConfiguration)) { if (vcProjectConfiguration.IncludeInBuild) { // Create a temporary VC project with references to MSBuild projects replaced with file references. if (proj.ProjectReferences.Count > 0) { projectPath = AddCreateTemporaryVCProjectTasks(solution, msbuildProject, newTarget, proj, solutionConfiguration, subTargetName, vcProjectConfiguration.FullName); } newTask = VCWrapperProject.AddVCBuildTaskElement( msbuildProject, newTarget, EscapingUtilities.Escape(Path.Combine(solution.SolutionFileDirectory, Path.GetFileName(solution.SolutionFile))), projectPath, subTargetName, null, EscapingUtilities.Escape(vcProjectConfiguration.FullName)); // Delete the temporary VC project if (proj.ProjectReferences.Count > 0) { BuildTask deleteTask = newTarget.AddNewTask("Delete"); deleteTask.SetParameterValue("Files", projectPath, true /* treat as literal */); deleteTask.Condition = solutionConfigurationCondition; } } else { newTask = AddErrorWarningMessageElement(newTarget, XMakeElements.message, true, "SolutionProjectSkippedForBuilding", proj.ProjectName, solutionConfiguration.FullName); } } else { newTask = AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "SolutionProjectConfigurationMissing", proj.ProjectName, solutionConfiguration.FullName); } if (newTask != null) { newTask.Condition = solutionConfigurationCondition; } } }
/// <summary> /// Adds MSBuild and ResolveVCProjectOutput tasks to a project target to pre-resolve its project references /// </summary> /// <param name="solution"></param> /// <param name="target"></param> /// <param name="proj"></param> /// <param name="solutionConfiguration"></param> /// <param name="outputReferenceItemName"></param> /// <param name="outputImportLibraryItemName"></param> /// <param name="addedReferenceGuids"></param> /// <owner>LukaszG</owner> static private void AddResolveProjectReferenceTasks ( SolutionParser solution, Project msbuildProject, Target target, ProjectInSolution proj, ConfigurationInSolution solutionConfiguration, string outputReferenceItemName, string outputImportLibraryItemName, out string addedReferenceGuids ) { StringBuilder referenceGuids = new StringBuilder(); string message = null; // Suffix for the reference item name. Since we need to attach additional (different) metadata to every // reference item, we need to have helper item lists each with only one item int outputReferenceItemNameSuffix = 0; // Pre-resolve the MSBuild/VC project references foreach (string projectReferenceGuid in proj.ProjectReferences) { ProjectInSolution referencedProject = (ProjectInSolution)solution.ProjectsByGuid[projectReferenceGuid]; ProjectConfigurationInSolution referencedProjectConfiguration = null; if ((referencedProject != null) && (referencedProject.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out referencedProjectConfiguration)) && (referencedProjectConfiguration != null)) { string outputReferenceItemNameWithSuffix = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", outputReferenceItemName, outputReferenceItemNameSuffix); bool addCreateItem = false; if ((referencedProject.ProjectType == SolutionProjectType.ManagedProject) || ((referencedProject.ProjectType == SolutionProjectType.Unknown) && (referencedProject.CanBeMSBuildProjectFile(out message)))) { string condition = GetConditionStringForConfiguration(solutionConfiguration); bool specifyProjectToolsVersion = String.Equals(msbuildProject.ToolsVersion, "2.0", StringComparison.OrdinalIgnoreCase) ? false : true; BuildTask msbuildTask = AddMSBuildTaskElement(target, referencedProject.RelativePath, "GetTargetPath", referencedProjectConfiguration.ConfigurationName, referencedProjectConfiguration.PlatformName, specifyProjectToolsVersion); msbuildTask.Condition = condition; msbuildTask.AddOutputItem("TargetOutputs", outputReferenceItemNameWithSuffix); if (referenceGuids.Length > 0) { referenceGuids.Append(';'); } referenceGuids.Append(projectReferenceGuid); addCreateItem = true; } else if (referencedProject.ProjectType == SolutionProjectType.VCProject) { BuildTask vcbuildTask = null; try { vcbuildTask = AddResolveVCProjectOutputTaskElement(target, Path.Combine(solution.SolutionFileDirectory, Path.GetFileName(solution.SolutionFile)), referencedProject.AbsolutePath, referencedProjectConfiguration.FullName); } catch (Exception e) { if (ExceptionHandling.NotExpectedException(e)) throw; ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solution.SolutionFile), "SolutionParseInvalidProjectFileName", referencedProject.RelativePath, e.Message); } vcbuildTask.Condition = GetConditionStringForConfiguration(solutionConfiguration); vcbuildTask.AddOutputItem("ResolvedOutputPaths", outputReferenceItemNameWithSuffix); if (outputImportLibraryItemName != null) { vcbuildTask.AddOutputItem("ResolvedImportLibraryPaths", outputImportLibraryItemName); } if (referenceGuids.Length > 0) { referenceGuids.Append(';'); } referenceGuids.Append(projectReferenceGuid); addCreateItem = true; } // Add create item if either of the conditions above was true. // This merges the one-item item list into the main list, adding the appropriate guid metadata if (addCreateItem) { BuildTask createItemTask = target.AddNewTask("CreateItem"); createItemTask.SetParameterValue("Include", "@(" + outputReferenceItemNameWithSuffix + ")", false /* do not treat as literal */); createItemTask.SetParameterValue("AdditionalMetadata", "Guid=" + projectReferenceGuid, false /* do not treat as literal */); createItemTask.AddOutputItem("Include", outputReferenceItemName); } outputReferenceItemNameSuffix++; } } addedReferenceGuids = referenceGuids.ToString(); }
/// <summary> /// Recursive helper for AddVirtualReferencesForStaticLibraries /// </summary> private static void GatherChildReferencesForStaticLibraries(SolutionParser solution, ProjectInSolution project) { // We don't need to worry about cycles since we've already run the dependency level assignment // which already checked for them. if (!project.ChildReferencesGathered) { List<string> referenceGuidsToAdd = new List<string>(); foreach (string referenceGuid in project.ProjectReferences) { ProjectInSolution referencedProject = (ProjectInSolution)solution.ProjectsByGuid[referenceGuid]; // Gather references for all child projects recursively... GatherChildReferencesForStaticLibraries(solution, referencedProject); // ... and pass on references from any static lib children we have to ourselves if (referencedProject.IsStaticLibrary) { foreach (string childReferenceGuid in referencedProject.ProjectReferences) { if (!project.ProjectReferences.Contains(childReferenceGuid) && !referenceGuidsToAdd.Contains(childReferenceGuid)) { referenceGuidsToAdd.Add(childReferenceGuid); } } } } project.ProjectReferences.AddRange(referenceGuidsToAdd); project.ChildReferencesGathered = true; } }
/// <summary> /// Adds a dependency to the project based on the specified guid string. /// </summary> /// <remarks> /// If the string is null or empty, no dependency is added and this is not considered an error. /// </remarks> /// <param name="solution">The solution in which the project exists</param> /// <param name="project">The project to which the dependency will be added</param> /// <param name="parentEngine">The engine handling the conversion</param> /// <param name="projectBuildEventContext">The build event context</param> /// <param name="dependencyGuid">The guid, in string form, of the dependency project</param> static private void AddDependencyByGuid(SolutionParser solution, ProjectInSolution project, Engine parentEngine, BuildEventContext projectBuildEventContext, string dependencyGuid) { if (!String.IsNullOrEmpty(dependencyGuid)) { if (solution.ProjectsByGuid.ContainsKey(dependencyGuid)) { project.Dependencies.Add(dependencyGuid); } else { parentEngine.LoggingServices.LogWarning(projectBuildEventContext, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solution.SolutionFile), "SolutionParseProjectDepNotFoundError", project.ProjectGuid, dependencyGuid); } } }
/// <summary> /// Takes a property name / value that comes from the SLN file for a Venus project, and /// stores it appropriately in our data structures. /// </summary> /// <param name="proj"></param> /// <param name="propertyName"></param> /// <param name="propertyValue"></param> /// <owner>RGoel</owner> private void ParseAspNetCompilerProperty ( ProjectInSolution proj, string propertyName, string propertyValue ) { // What we expect to find in the SLN file is something that looks like this: // // Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "c:\...\myfirstwebsite\", "..\..\..\..\..\..\rajeev\temp\websites\myfirstwebsite", "{956CC04E-FD59-49A9-9099-96888CB6F366}" // ProjectSection(WebsiteProperties) = preProject // ProjectReferences = "{FD705688-88D1-4C22-9BFF-86235D89C2FC}|CSClassLibrary1.dll;{F0726D09-042B-4A7A-8A01-6BED2422BD5D}|VCClassLibrary1.dll;" // Debug.AspNetCompiler.VirtualPath = "/publishfirst" // Debug.AspNetCompiler.PhysicalPath = "..\..\..\..\..\..\rajeev\temp\websites\myfirstwebsite\" // Debug.AspNetCompiler.TargetPath = "..\..\..\..\..\..\rajeev\temp\publishfirst\" // Debug.AspNetCompiler.ForceOverwrite = "true" // Debug.AspNetCompiler.Updateable = "true" // Debug.AspNetCompiler.Enabled = "true" // Debug.AspNetCompiler.Debug = "true" // Debug.AspNetCompiler.KeyFile = "" // Debug.AspNetCompiler.KeyContainer = "" // Debug.AspNetCompiler.DelaySign = "true" // Debug.AspNetCompiler.AllowPartiallyTrustedCallers = "true" // Debug.AspNetCompiler.FixedNames = "true" // Release.AspNetCompiler.VirtualPath = "/publishfirst" // Release.AspNetCompiler.PhysicalPath = "..\..\..\..\..\..\rajeev\temp\websites\myfirstwebsite\" // Release.AspNetCompiler.TargetPath = "..\..\..\..\..\..\rajeev\temp\publishfirst\" // Release.AspNetCompiler.ForceOverwrite = "true" // Release.AspNetCompiler.Updateable = "true" // Release.AspNetCompiler.Enabled = "true" // Release.AspNetCompiler.Debug = "false" // Release.AspNetCompiler.KeyFile = "" // Release.AspNetCompiler.KeyContainer = "" // Release.AspNetCompiler.DelaySign = "true" // Release.AspNetCompiler.AllowPartiallyTrustedCallers = "true" // Release.AspNetCompiler.FixedNames = "true" // EndProjectSection // EndProject // // This method is responsible for parsing each of the lines within the "WebsiteProperties" section. // The first component of each property name is actually the configuration for which that // property applies. int indexOfFirstDot = propertyName.IndexOf('.'); if (indexOfFirstDot != -1) { // The portion before the first dot is the configuration name. string configurationName = propertyName.Substring(0, indexOfFirstDot); // The rest of it is the actual property name. string aspNetPropertyName = propertyName.Substring(indexOfFirstDot + 1, propertyName.Length - indexOfFirstDot - 1); // And the part after the <equals> sign is the property value (which was parsed out for us prior // to calling this method). propertyValue = TrimQuotes(propertyValue); // Grab the parameters for this specific configuration if they exist. object aspNetCompilerParametersObject = proj.AspNetConfigurations[configurationName]; AspNetCompilerParameters aspNetCompilerParameters; if (aspNetCompilerParametersObject == null) { // If it didn't exist, create a new one. aspNetCompilerParameters = new AspNetCompilerParameters(); aspNetCompilerParameters.aspNetVirtualPath = String.Empty; aspNetCompilerParameters.aspNetPhysicalPath = String.Empty; aspNetCompilerParameters.aspNetTargetPath = String.Empty; aspNetCompilerParameters.aspNetForce = String.Empty; aspNetCompilerParameters.aspNetUpdateable = String.Empty; aspNetCompilerParameters.aspNetDebug = String.Empty; aspNetCompilerParameters.aspNetKeyFile = String.Empty; aspNetCompilerParameters.aspNetKeyContainer = String.Empty; aspNetCompilerParameters.aspNetDelaySign = String.Empty; aspNetCompilerParameters.aspNetAPTCA = String.Empty; aspNetCompilerParameters.aspNetFixedNames = String.Empty; } else { // Otherwise just unbox it. aspNetCompilerParameters = (AspNetCompilerParameters)aspNetCompilerParametersObject; } // Update the appropriate field within the parameters struct. if (aspNetPropertyName == "AspNetCompiler.VirtualPath") { aspNetCompilerParameters.aspNetVirtualPath = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.PhysicalPath") { aspNetCompilerParameters.aspNetPhysicalPath = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.TargetPath") { aspNetCompilerParameters.aspNetTargetPath = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.ForceOverwrite") { aspNetCompilerParameters.aspNetForce = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.Updateable") { aspNetCompilerParameters.aspNetUpdateable = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.Debug") { aspNetCompilerParameters.aspNetDebug = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.KeyFile") { aspNetCompilerParameters.aspNetKeyFile = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.KeyContainer") { aspNetCompilerParameters.aspNetKeyContainer = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.DelaySign") { aspNetCompilerParameters.aspNetDelaySign = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.AllowPartiallyTrustedCallers") { aspNetCompilerParameters.aspNetAPTCA = propertyValue; } else if (aspNetPropertyName == "AspNetCompiler.FixedNames") { aspNetCompilerParameters.aspNetFixedNames = propertyValue; } // Store the updated parameters struct back into the hashtable by configuration name. proj.AspNetConfigurations[configurationName] = aspNetCompilerParameters; } else { // ProjectReferences = "{FD705688-88D1-4C22-9BFF-86235D89C2FC}|CSClassLibrary1.dll;{F0726D09-042B-4A7A-8A01-6BED2422BD5D}|VCClassLibrary1.dll;" if (string.Compare(propertyName, "ProjectReferences", StringComparison.OrdinalIgnoreCase) == 0) { string[] projectReferenceEntries = propertyValue.Split(new char[] { ';' }); foreach (string projectReferenceEntry in projectReferenceEntries) { int indexOfBar = projectReferenceEntry.IndexOf('|'); // indexOfBar could be -1 if we had semicolons in the file names, so skip entries that // don't contain a guid. File names may not contain the '|' character if (indexOfBar != -1) { int indexOfOpeningBrace = projectReferenceEntry.IndexOf('{'); int indexOfClosingBrace = projectReferenceEntry.IndexOf('}', indexOfOpeningBrace); // Cut out the guid part if ((indexOfOpeningBrace != -1) && (indexOfClosingBrace != -1)) { string referencedProjectGuid = projectReferenceEntry.Substring(indexOfOpeningBrace, indexOfClosingBrace - indexOfOpeningBrace + 1); proj.Dependencies.Add(referencedProjectGuid); proj.ProjectReferences.Add(referencedProjectGuid); } } } } } }
/// <summary> /// Validate relative path of a project /// </summary> /// <param name="proj">proj</param> private void ValidateProjectRelativePath(ProjectInSolution proj) { // Verify the relative path is not null ErrorUtilities.VerifyThrow(proj.RelativePath != null, "Project relative path cannot be null."); // Verify the relative path does not contain invalid characters ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.IndexOfAny(Path.GetInvalidPathChars()) == -1, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(SolutionFile, this.currentLineNumber, 0), "SolutionParseInvalidProjectFileNameCharacters", proj.ProjectName, proj.RelativePath); // Verify the relative path is not empty string ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.Length > 0, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(SolutionFile, this.currentLineNumber, 0), "SolutionParseInvalidProjectFileNameEmpty", proj.ProjectName); }
/// <summary> /// Adds a given project to the project collections of this class /// </summary> /// <param name="proj">proj</param> private void AddProjectToSolution(ProjectInSolution proj) { if (!String.IsNullOrEmpty(proj.ProjectGuid)) { projects[proj.ProjectGuid] = proj; } projectsInOrder.Add(proj); }
} // ParseProject() /// <summary> /// This method will parse a .etp project recursively and /// add all the projects found to projects and projectsInOrder /// </summary> /// <param name="etpProj">ETP Project</param> internal void ParseEtpProject(ProjectInSolution etpProj) { XmlDocument etpProjectDocument = new XmlDocument(); // Get the full path to the .etp project file string fullPathToEtpProj = Path.Combine(solutionFileDirectory, etpProj.RelativePath); string etpProjectRelativeDir = Path.GetDirectoryName(etpProj.RelativePath); try { /**************************************************************************** * A Typical .etp project file will look like this *<?xml version="1.0"?> *<EFPROJECT> * <GENERAL> * <BANNER>Microsoft Visual Studio Application Template File</BANNER> * <VERSION>1.00</VERSION> * <Views> * <ProjectExplorer> * <File>ClassLibrary2\ClassLibrary2.csproj</File> * </ProjectExplorer> * </Views> * <References> * <Reference> * <FILE>ClassLibrary2\ClassLibrary2.csproj</FILE> * <GUIDPROJECTID>{73D0F4CE-D9D3-4E8B-81E4-B26FBF4CC2FE}</GUIDPROJECTID> * </Reference> * </References> * </GENERAL> *</EFPROJECT> **********************************************************************************/ // Make sure the XML reader ignores DTD processing XmlReaderSettings readerSettings = new XmlReaderSettings(); readerSettings.DtdProcessing = DtdProcessing.Ignore; // Load the .etp project file thru the XML reader using (XmlReader xmlReader = XmlReader.Create(fullPathToEtpProj, readerSettings)) { etpProjectDocument.Load(xmlReader); } // We need to parse the .etp project file to get the names of projects contained // in the .etp Project. The projects are listed under /EFPROJECT/GENERAL/References/Reference node in the .etp project file. // The /EFPROJECT/GENERAL/Views/ProjectExplorer node will not necessarily contain // all the projects in the .etp project. Therefore, we need to look at // /EFPROJECT/GENERAL/References/Reference. // Find the /EFPROJECT/GENERAL/References/Reference node // Note that this is case sensitive XmlNodeList referenceNodes = etpProjectDocument.DocumentElement.SelectNodes("/EFPROJECT/GENERAL/References/Reference"); // Do the right thing for each <REference> element foreach (XmlNode referenceNode in referenceNodes) { // Get the relative path to the project file string fileElementValue = referenceNode.SelectSingleNode("FILE").InnerText; // If <FILE> element is not present under <Reference> then we don't do anything. if (fileElementValue != null) { // Create and populate a ProjectInSolution for the project ProjectInSolution proj = new ProjectInSolution(this); proj.RelativePath = Path.Combine(etpProjectRelativeDir, fileElementValue); // Verify the relative path specified in the .etp proj file ValidateProjectRelativePath(proj); proj.ProjectType = SolutionProjectType.EtpSubProject; proj.ProjectName = proj.RelativePath; XmlNode projGuidNode = referenceNode.SelectSingleNode("GUIDPROJECTID"); if (projGuidNode != null) { proj.ProjectGuid = projGuidNode.InnerText; } // It is ok for a project to not have a guid inside an etp project. // If a solution file contains a project without a guid it fails to // load in Everett. But if an etp project contains a project without // a guid it loads well in Everett and p2p references to/from this project // are preserved. So we should make sure that we don�t error in this // situation while upgrading. else { proj.ProjectGuid = String.Empty; } // Add the recently created proj to the collection of projects AddProjectToSolution(proj); // If the project is an etp project recurse if (IsEtpProjectFile(fileElementValue)) { ParseEtpProject(proj); } } } } // catch all sorts of exceptions - if we encounter any problems here, we just assume the .etp project file is not in the correct format // handle security errors catch (SecurityException e) { // Log a warning string errorCode, ignoredKeyword; string warning = ResourceUtilities.FormatResourceString(out errorCode, out ignoredKeyword, "Shared.ProjectFileCouldNotBeLoaded", etpProj.RelativePath, e.Message); solutionParserWarnings.Add(warning); solutionParserErrorCodes.Add(errorCode); } // handle errors in path resolution catch (NotSupportedException e) { // Log a warning string errorCode, ignoredKeyword; string warning = ResourceUtilities.FormatResourceString(out errorCode, out ignoredKeyword, "Shared.ProjectFileCouldNotBeLoaded", etpProj.RelativePath, e.Message); solutionParserWarnings.Add(warning); solutionParserErrorCodes.Add(errorCode); } // handle errors in loading project file catch (IOException e) { // Log a warning string errorCode, ignoredKeyword; string warning = ResourceUtilities.FormatResourceString(out errorCode, out ignoredKeyword, "Shared.ProjectFileCouldNotBeLoaded", etpProj.RelativePath, e.Message); solutionParserWarnings.Add(warning); solutionParserErrorCodes.Add(errorCode); } // handle errors in loading project file catch (UnauthorizedAccessException e) { // Log a warning string errorCode, ignoredKeyword; string warning = ResourceUtilities.FormatResourceString(out errorCode, out ignoredKeyword, "Shared.ProjectFileCouldNotBeLoaded", etpProj.RelativePath, e.Message); solutionParserWarnings.Add(warning); solutionParserErrorCodes.Add(errorCode); } // handle XML parsing errors catch (XmlException e) { // Log a warning string errorCode, ignoredKeyword; string warning = ResourceUtilities.FormatResourceString(out errorCode, out ignoredKeyword, "Shared.InvalidProjectFile", etpProj.RelativePath, e.Message); solutionParserWarnings.Add(warning); solutionParserErrorCodes.Add(errorCode); } }
/// <summary> /// /// This method processes a "Project" section in the solution file opened by the specified /// StreamReader, and returns a populated ProjectInSolution instance, if successful. /// An exception is thrown if the solution file is invalid. /// /// The format of the parts of a Project section that we care about is as follows: /// /// Project("{Project type GUID}") = "Project name", "Relative path to project file", "{Project GUID}" /// ProjectSection(ProjectDependencies) = postProject /// {Parent project unique name} = {Parent project unique name} /// ... /// EndProjectSection /// EndProject /// /// </summary> /// <param name="firstLine"></param> /// <returns></returns> /// <owner>RGoel</owner> private void ParseProject(string firstLine) { error.VerifyThrow((firstLine != null) && (firstLine.Length != 0), "ParseProject() got a null firstLine!"); error.VerifyThrow(reader != null, "ParseProject() got a null reader!"); ProjectInSolution proj = new ProjectInSolution(this); // Extract the important information from the first line. ParseFirstProjectLine(firstLine, proj); // Search for project dependencies. Keeping reading lines until we either 1.) reach // the end of the file, 2.) see "ProjectSection(ProjectDependencies)" at the beginning // of the line, or 3.) see "EndProject" at the beginning of the line. string line; while ((line = ReadLine()) != null) { // If we see an "EndProject", well ... that's the end of this project! if (line == "EndProject") { break; } else if (line.StartsWith("ProjectSection(ProjectDependencies)", StringComparison.Ordinal)) { // We have a ProjectDependencies section. Each subsequent line should identify // a dependency. line = ReadLine(); while ((line != null) && (!line.StartsWith("EndProjectSection", StringComparison.Ordinal))) { // This should be a dependency. The GUID identifying the parent project should // be both the property name and the property value. Match match = crackPropertyLine.Match(line); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(SolutionFile, this.currentLineNumber, 0), "SolutionParseProjectDepGuidError", proj.ProjectName); string parentGuid = match.Groups["PROPERTYNAME"].Value.Trim(); proj.Dependencies.Add(parentGuid); line = ReadLine(); } } else if (line.StartsWith("ProjectSection(WebsiteProperties)", StringComparison.Ordinal)) { // We have a WebsiteProperties section. This section is present only in Venus // projects, and contains properties that we'll need in order to call the // AspNetCompiler task. line = ReadLine(); while ((line != null) && (!line.StartsWith("EndProjectSection", StringComparison.Ordinal))) { Match match = crackPropertyLine.Match(line); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(SolutionFile, this.currentLineNumber, 0), "SolutionParseWebProjectPropertiesError", proj.ProjectName); string propertyName = match.Groups["PROPERTYNAME"].Value.Trim(); string propertyValue = match.Groups["PROPERTYVALUE"].Value.Trim(); ParseAspNetCompilerProperty(proj, propertyName, propertyValue); line = ReadLine(); } } } ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(line != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(SolutionFile), "SolutionParseProjectEofError", proj.ProjectName); if (proj != null) { // Add the project to the collection AddProjectToSolution(proj); // If the project is an etp project then parse the etp project file // to get the projects contained in it. if (IsEtpProjectFile(proj.RelativePath)) { ParseEtpProject(proj); } } } // ParseProject()
/// <summary> /// Parse the first line of a Project section of a solution file. This line should look like: /// /// Project("{Project type GUID}") = "Project name", "Relative path to project file", "{Project GUID}" /// /// </summary> /// <param name="firstLine"></param> /// <param name="proj"></param> /// <owner>RGoel</owner> internal void ParseFirstProjectLine ( string firstLine, ProjectInSolution proj ) { Match match = crackProjectLine.Match(firstLine); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(SolutionFile, this.currentLineNumber, 0), "SolutionParseProjectError"); string projectTypeGuid = match.Groups["PROJECTTYPEGUID"].Value.Trim(); proj.ProjectName = match.Groups["PROJECTNAME"].Value.Trim(); proj.RelativePath = match.Groups["RELATIVEPATH"].Value.Trim(); proj.ProjectGuid = match.Groups["PROJECTGUID"].Value.Trim(); // Validate project relative path ValidateProjectRelativePath(proj); // Figure out what type of project this is. if ((String.Compare(projectTypeGuid, vbProjectGuid, StringComparison.OrdinalIgnoreCase) == 0) || (String.Compare(projectTypeGuid, csProjectGuid, StringComparison.OrdinalIgnoreCase) == 0) || (String.Compare(projectTypeGuid, vjProjectGuid, StringComparison.OrdinalIgnoreCase) == 0)) { proj.ProjectType = SolutionProjectType.ManagedProject; } else if (String.Compare(projectTypeGuid, solutionFolderGuid, StringComparison.OrdinalIgnoreCase) == 0) { proj.ProjectType = SolutionProjectType.SolutionFolder; } else if (String.Compare(projectTypeGuid, vcProjectGuid, StringComparison.OrdinalIgnoreCase) == 0) { proj.ProjectType = SolutionProjectType.VCProject; } else if (String.Compare(projectTypeGuid, webProjectGuid, StringComparison.OrdinalIgnoreCase) == 0) { proj.ProjectType = SolutionProjectType.WebProject; solutionContainsWebProjects = true; } else { proj.ProjectType = SolutionProjectType.Unknown; } }
/// <summary> /// Emit warnings when the project type is unknown. /// </summary> /// <param name="msbuildProject">The project to add the target to</param> /// <param name="proj">The project to add as a target.</param> /// <param name="subTargetName">The target to call within the project that's being added.</param> /// <param name="errorMessage">Optional detailed error message to print out in case we already tried accessing the /// project file before and failed.</param> /// <owner>RGoel</owner> static private void AddTargetForUnknownProjectType ( Project msbuildProject, SolutionParser solution, ProjectInSolution proj, string subTargetName, string errorMessage ) { string targetName = ProjectInSolution.DisambiguateProjectTargetName(proj.GetUniqueProjectName()); if (subTargetName != null && subTargetName.Length > 0) { targetName = targetName + ":" + subTargetName; } Target newTarget = msbuildProject.Targets.AddNewTarget(targetName); newTarget.DependsOnTargets = GetProjectDependencies(proj.ParentSolution, proj, subTargetName); newTarget.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; foreach (ConfigurationInSolution solutionConfiguration in solution.SolutionConfigurations) { ProjectConfigurationInSolution projectConfiguration = null; BuildTask newTask = null; if (proj.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) { if (projectConfiguration.IncludeInBuild) { if (errorMessage == null) { // we haven't encountered any problems accessing the project file in the past, but do not support // building this project type newTask = AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "SolutionParseUnknownProjectType", proj.RelativePath); } else { // this project file may be of supported type, but we have encountered problems accessing it newTask = AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "SolutionParseErrorReadingProject", proj.RelativePath, errorMessage); } } else { newTask = AddErrorWarningMessageElement(newTarget, XMakeElements.message, true, "SolutionProjectSkippedForBuilding", proj.ProjectName, solutionConfiguration.FullName); } } else { newTask = AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "SolutionProjectConfigurationMissing", proj.ProjectName, solutionConfiguration.FullName); } if (newTask != null) { newTask.Condition = GetConditionStringForConfiguration(solutionConfiguration); } } }
/// <summary> /// This method returns a string containing a semicolon-separated list of "friendly" project names /// on which the specified project depends. If the null is specified, a list of all projects /// is returned. /// </summary> /// <param name="solution"></param> /// <param name="project"></param> /// <param name="subTargetName"></param> /// <returns></returns> /// <owner>RGoel</owner> static private string GetProjectDependencies(SolutionParser solution, ProjectInSolution project, string subTargetName) { ErrorUtilities.VerifyThrow(project != null, "We should always have a project for this method"); StringBuilder dependencies = new StringBuilder(); // Get all the dependencies for this project foreach (string dependency in project.Dependencies) { if (dependencies.Length != 0) { dependencies.Append(";"); } string projectUniqueName = solution.GetProjectUniqueNameByGuid(dependency); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(projectUniqueName != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solution.SolutionFile), "SolutionParseProjectDepNotFoundError", project.ProjectGuid, dependency); dependencies.Append(ProjectInSolution.DisambiguateProjectTargetName(projectUniqueName)); if (subTargetName != null && subTargetName.Length > 0) { dependencies.Append(":"); dependencies.Append(subTargetName); } } return dependencies.ToString(); }
/// <summary> /// Helper method to add a call to the AspNetCompiler task into the given target. /// </summary> /// <param name="target"></param> /// <param name="proj"></param> /// <param name="conditionDescribingValidConfigurations"></param> /// <owner>RGoel</owner> static private void AddTaskForAspNetCompiler ( Target target, ProjectInSolution proj, string conditionDescribingValidConfigurations ) { // Add a call to the AspNetCompiler task, conditioned on having a valid Configuration. BuildTask newTask = target.AddNewTask("AspNetCompiler"); newTask.SetParameterValue("VirtualPath", "$(" + GenerateSafePropertyName(proj, "AspNetVirtualPath") + ")"); newTask.SetParameterValue("PhysicalPath", "$(" + GenerateSafePropertyName(proj, "AspNetPhysicalPath") + ")"); newTask.SetParameterValue("TargetPath", "$(" + GenerateSafePropertyName(proj, "AspNetTargetPath") + ")"); newTask.SetParameterValue("Force", "$(" + GenerateSafePropertyName(proj, "AspNetForce") + ")"); newTask.SetParameterValue("Updateable", "$(" + GenerateSafePropertyName(proj, "AspNetUpdateable") + ")"); newTask.SetParameterValue("Debug", "$(" + GenerateSafePropertyName(proj, "AspNetDebug") + ")"); newTask.SetParameterValue("KeyFile", "$(" + GenerateSafePropertyName(proj, "AspNetKeyFile") + ")"); newTask.SetParameterValue("KeyContainer", "$(" + GenerateSafePropertyName(proj, "AspNetKeyContainer") + ")"); newTask.SetParameterValue("DelaySign", "$(" + GenerateSafePropertyName(proj, "AspNetDelaySign") + ")"); newTask.SetParameterValue("AllowPartiallyTrustedCallers", "$(" + GenerateSafePropertyName(proj, "AspNetAPTCA") + ")"); newTask.SetParameterValue("FixedNames", "$(" + GenerateSafePropertyName(proj, "AspNetFixedNames") + ")"); newTask.Condition = conditionDescribingValidConfigurations; }
/// <summary> /// Figure out the dependency level of the given project. /// </summary> /// <param name="project"></param> /// <param name="solution"></param> /// <param name="projectsByDependencyLevel"></param> static private void AssignDependencyLevel(ProjectInSolution project, SolutionParser solution, Dictionary<int, List<ProjectInSolution>> projectsByDependencyLevel) { // if we ever try to recurse into a project whose dependency level we're calculating above, // we have a circular dependency. if (project.DependencyLevel == ProjectInSolution.DependencyLevelBeingDetermined) { ProjectErrorUtilities.VerifyThrowInvalidProject(false, null, "SolutionCircularDependencyError", project.ProjectName); } if (project.DependencyLevel == ProjectInSolution.DependencyLevelUnknown) { project.DependencyLevel = ProjectInSolution.DependencyLevelBeingDetermined; int maxDependencyLevel = 0; // First, go through dependencies and ensure they have their dependency level set correctly. foreach (string dependencyGuid in project.Dependencies) { ProjectInSolution referencedProject = (ProjectInSolution) solution.ProjectsByGuid[dependencyGuid]; AssignDependencyLevel(referencedProject, solution, projectsByDependencyLevel); if (referencedProject.DependencyLevel + 1 > maxDependencyLevel) { maxDependencyLevel = referencedProject.DependencyLevel + 1; } } // Our dependency level is the highest dependency level of all our dependencies plus 1, or 0 if we had // no dependencies. project.DependencyLevel = maxDependencyLevel; if (!projectsByDependencyLevel.ContainsKey(maxDependencyLevel)) { projectsByDependencyLevel.Add(maxDependencyLevel, new List<ProjectInSolution>()); } projectsByDependencyLevel[maxDependencyLevel].Add(project); } }
/// <summary> /// Add a call to the ResolveAssemblyReference task to crack the pre-resolved referenced /// assemblies for the complete list of dependencies, PDBs, satellites, etc. The invoke /// the Copy task to copy all these files (or at least the ones that RAR determined should /// be copied local) into the web project's bin directory. /// </summary> /// <param name="target"></param> /// <param name="proj"></param> /// <param name="referenceItemName"></param> /// <param name="conditionDescribingValidConfigurations"></param> /// <owner>RGoel</owner> static private void AddTasksToCopyAllDependenciesIntoBinDir ( Target target, ProjectInSolution proj, string referenceItemName, string conditionDescribingValidConfigurations ) { string copyLocalFilesItemName = referenceItemName + "_CopyLocalFiles"; string destinationFolder = String.Format(CultureInfo.InvariantCulture, @"$({0})\Bin\", GenerateSafePropertyName(proj, "AspNetPhysicalPath")); // This is a bit of a hack. We're actually calling the "Copy" task on all of // the *non-existent* files. Why? Because we want to emit a warning in the // log for each non-existent file, and the Copy task does that nicely for us. // I would have used the <Warning> task except for the fact that we are in // string-resource lockdown. BuildTask copyNonExistentReferencesTask = target.AddNewTask("Copy"); copyNonExistentReferencesTask.SetParameterValue("SourceFiles", "@(" + referenceItemName + "->'%(FullPath)')", false /* Do not treat as literal */); copyNonExistentReferencesTask.SetParameterValue("DestinationFolder", destinationFolder); copyNonExistentReferencesTask.Condition = String.Format(CultureInfo.InvariantCulture, "!Exists('%({0}.Identity)')", referenceItemName); copyNonExistentReferencesTask.ContinueOnError = true; // Call ResolveAssemblyReference on each of the .DLL files that were found on // disk from the .REFRESH files as well as the P2P references. RAR will crack // the dependencies, find PDBs, satellite assemblies, etc., and determine which // files need to be copy-localed. BuildTask rarTask = target.AddNewTask("ResolveAssemblyReference"); rarTask.SetParameterValue("Assemblies", "@(" + referenceItemName + "->'%(FullPath)')", false /* Do not treat as literal */); rarTask.SetParameterValue("TargetFrameworkDirectories", "@(_CombinedTargetFrameworkDirectoriesItem)", false /* Do not treat as literal */); rarTask.SetParameterValue("InstalledAssemblyTables", "@(InstalledAssemblyTables)", false /* Do not treat as literal */); rarTask.SetParameterValue("SearchPaths", "{RawFileName};{TargetFrameworkDirectory};{GAC}"); rarTask.SetParameterValue("FindDependencies", "true"); rarTask.SetParameterValue("FindSatellites", "true"); rarTask.SetParameterValue("FindSerializationAssemblies", "true"); rarTask.SetParameterValue("FindRelatedFiles", "true"); rarTask.Condition = String.Format(CultureInfo.InvariantCulture, "Exists('%({0}.Identity)')", referenceItemName); rarTask.AddOutputItem("CopyLocalFiles", copyLocalFilesItemName); // Copy all the copy-local files (reported by RAR) to the web project's "bin" // directory. BuildTask copyTask = target.AddNewTask("Copy"); copyTask.SetParameterValue("SourceFiles", "@(" + copyLocalFilesItemName + ")", false /* DO NOT treat as literal */); copyTask.SetParameterValue("DestinationFiles", String.Format(CultureInfo.InvariantCulture, @"@({0}->'{1}%(DestinationSubDirectory)%(Filename)%(Extension)')", copyLocalFilesItemName, destinationFolder), false /* DO NOT treat as literal */); copyTask.Condition = conditionDescribingValidConfigurations; }
/// <summary> /// Add a target for a project into the XML doc that's being generated. /// </summary> /// <param name="msbuildProject"></param> /// <param name="solution"></param> /// <param name="proj"></param> /// <param name="targetOutputItemName">The name of the item exposing this target's outputs. May be null.</param> /// <param name="subTargetName"></param> /// <owner>RGoel, LukaszG</owner> static private void AddTargetForManagedProject ( Project msbuildProject, SolutionParser solution, ProjectInSolution proj, string targetOutputItemName, string subTargetName ) { string targetName = ProjectInSolution.DisambiguateProjectTargetName(proj.GetUniqueProjectName()); if (subTargetName != null && subTargetName.Length > 0) { targetName = targetName + ":" + subTargetName; } Target newTarget = msbuildProject.Targets.AddNewTarget(targetName); newTarget.DependsOnTargets = GetProjectDependencies(proj.ParentSolution, proj, subTargetName); newTarget.Condition = "'$(CurrentSolutionConfigurationContents)' != ''"; if (!String.IsNullOrEmpty(targetOutputItemName)) { newTarget.TargetElement.SetAttribute("Outputs", string.Format(CultureInfo.InvariantCulture, "@({0})", targetOutputItemName)); } // Only create build items if we're called with the null subtarget. We're getting called // a total of four times and only want to create the build items once. bool createBuildItems = (subTargetName == null); foreach (ConfigurationInSolution solutionConfiguration in solution.SolutionConfigurations) { ProjectConfigurationInSolution projectConfiguration = null; string condition = GetConditionStringForConfiguration(solutionConfiguration); // Create the build item group for this configuration if we haven't already if (solutionConfiguration.ProjectBuildItems == null) { solutionConfiguration.ProjectBuildItems = msbuildProject.AddNewItemGroup(); solutionConfiguration.ProjectBuildItems.Condition = condition; } if (proj.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) { if (projectConfiguration.IncludeInBuild) { // We want to specify ToolsVersion on the MSBuild task only if the solution // is building with a non-Whidbey toolset, because the Whidbey MSBuild task // does not support the ToolsVersion parameter. If the user explicitly requested // the 2.0 toolset be used to build the solution while specifying some value // for the ProjectToolsVersion property, then one of the InitialTargets should // have produced an error before reaching this point. // PERF: We could emit two <MSBuild> tasks, with a condition on them. But this doubles the size of // the solution wrapper project, and the cost is too high. The consequence is that when solution wrapper // projects are emitted to disk (with MSBUILDEMITSOLUION=1) they cannot be reused for tools version v2.0. bool specifyProjectToolsVersion = String.Equals(msbuildProject.ToolsVersion, "2.0", StringComparison.OrdinalIgnoreCase) ? false : true; BuildTask msbuildTask = AddMSBuildTaskElement(newTarget, proj.RelativePath, subTargetName, projectConfiguration.ConfigurationName, projectConfiguration.PlatformName, specifyProjectToolsVersion); msbuildTask.Condition = condition; if (!String.IsNullOrEmpty(targetOutputItemName)) { msbuildTask.AddOutputItem("TargetOutputs", targetOutputItemName); } if (createBuildItems) { string baseItemName = "BuildLevel" + proj.DependencyLevel; BuildItem projectItem = solutionConfiguration.ProjectBuildItems.AddNewItem(baseItemName, proj.RelativePath, true /* treat as literal */); projectItem.SetMetadata("Configuration", EscapingUtilities.Escape(projectConfiguration.ConfigurationName)); projectItem.SetMetadata("Platform", EscapingUtilities.Escape(projectConfiguration.PlatformName)); } } else { BuildTask messageTask = AddErrorWarningMessageElement(newTarget, XMakeElements.message, true, "SolutionProjectSkippedForBuilding", proj.ProjectName, solutionConfiguration.FullName); messageTask.Condition = condition; if (createBuildItems) { string baseItemName = "SkipLevel" + proj.DependencyLevel; BuildItem projectItem = solutionConfiguration.ProjectBuildItems.AddNewItem(baseItemName, proj.ProjectName, true /* treat as literal */); } } } else { BuildTask warningTask = AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "SolutionProjectConfigurationMissing", proj.ProjectName, solutionConfiguration.FullName); warningTask.Condition = condition; if (createBuildItems) { string baseItemName = "MissingConfigLevel" + proj.DependencyLevel; BuildItem projectItem = solutionConfiguration.ProjectBuildItems.AddNewItem(baseItemName, proj.ProjectName, true /* treat as literal */); } } } }
/// <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> /// <param name="msbuildProject"></param> /// <param name="proj"></param> /// <param name="configurationName"></param> /// <param name="aspNetCompilerParameters"></param> /// <param name="solutionFile"></param> /// <owner>RGoel</owner> static private void AddPropertyGroupForAspNetConfiguration ( Project msbuildProject, ProjectInSolution proj, string configurationName, AspNetCompilerParameters aspNetCompilerParameters, string solutionFile ) { // Add a new PropertyGroup that is condition'd on the Configuration. BuildPropertyGroup newPropertyGroup = msbuildProject.AddNewPropertyGroup(false /* insertAtEndOfProject = false */); newPropertyGroup.Condition = String.Format(CultureInfo.InvariantCulture, " '$(AspNetConfiguration)' == '{0}' ", EscapingUtilities.Escape(configurationName)); // Add properties into the property group for each of the AspNetCompiler properties. newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetVirtualPath"), aspNetCompilerParameters.aspNetVirtualPath, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetPhysicalPath"), aspNetCompilerParameters.aspNetPhysicalPath, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetTargetPath"), aspNetCompilerParameters.aspNetTargetPath, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetForce"), aspNetCompilerParameters.aspNetForce, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetUpdateable"), aspNetCompilerParameters.aspNetUpdateable, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetDebug"), aspNetCompilerParameters.aspNetDebug, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetKeyFile"), aspNetCompilerParameters.aspNetKeyFile, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetKeyContainer"), aspNetCompilerParameters.aspNetKeyContainer, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetDelaySign"), aspNetCompilerParameters.aspNetDelaySign, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetAPTCA"), aspNetCompilerParameters.aspNetAPTCA, true); newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetFixedNames"), aspNetCompilerParameters.aspNetFixedNames, true); 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), "SolutionParseInvalidProjectFileName", proj.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). BuildProperty targetPathOverrideProperty = newPropertyGroup.AddNewProperty(GenerateSafePropertyName(proj, "AspNetTargetPath"), @"$(OutDir)" + EscapingUtilities.Escape(webProjectOverrideFolder) + Path.DirectorySeparatorChar + EscapingUtilities.Escape(lastFolderInPhysicalPath) + Path.DirectorySeparatorChar); targetPathOverrideProperty.Condition = " '$(OutDir)' != '' "; } } }
/// <summary> /// Adds tasks that create a temporary VC project file with pre-resolved project references (that is, /// replaced with file references) /// </summary> /// <param name="solution"></param> /// <param name="target"></param> /// <param name="proj"></param> /// <param name="solutionConfiguration"></param> /// <param name="subTargetName"></param> /// <param name="projectConfigurationName"></param> /// <returns>The path to the temporary project file</returns> /// <owner>LukaszG</owner> static private string AddCreateTemporaryVCProjectTasks ( SolutionParser solution, Project msbuildProject, Target target, ProjectInSolution proj, ConfigurationInSolution solutionConfiguration, string subTargetName, string projectConfigurationName ) { StringBuilder referenceItemName = new StringBuilder(GenerateSafePropertyName(proj, "References")); if (!string.IsNullOrEmpty(subTargetName)) { referenceItemName.Append('_'); referenceItemName.Append(subTargetName); } StringBuilder importLibraryItemName = new StringBuilder(GenerateSafePropertyName(proj, "ImportLibraries")); if (!string.IsNullOrEmpty(subTargetName)) { importLibraryItemName.Append('_'); importLibraryItemName.Append(subTargetName); } string referenceGuidsToRemove = null; AddResolveProjectReferenceTasks(solution, msbuildProject, target, proj, solutionConfiguration, referenceItemName.ToString(), importLibraryItemName.ToString(), out referenceGuidsToRemove); if (string.IsNullOrEmpty(referenceGuidsToRemove)) referenceGuidsToRemove = string.Empty; string fullProjectPath = null; string tmpExtension = null; string projectPath = null; try { fullProjectPath = proj.AbsolutePath; tmpExtension = string.Format(CultureInfo.InvariantCulture, ".tmp_{0}_{1}.vcproj", solutionConfiguration.ConfigurationName, solutionConfiguration.PlatformName); projectPath = Path.ChangeExtension(fullProjectPath, tmpExtension); } catch (Exception e) { if (ExceptionHandling.NotExpectedException(e)) throw; ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(solution.SolutionFile), "SolutionParseInvalidProjectFileName", proj.RelativePath, e.Message); } // Create the temporary VC project BuildTask createVCProjectTask = target.AddNewTask("CreateTemporaryVCProject"); createVCProjectTask.SetParameterValue("ProjectFile", fullProjectPath, true /* treat as literal */); createVCProjectTask.SetParameterValue("Configuration", projectConfigurationName, true /* treat as literal */); createVCProjectTask.SetParameterValue("OutputProjectFile", projectPath, true /* treat as literal */); createVCProjectTask.SetParameterValue("ReferenceGuids", referenceGuidsToRemove, false /* Contains semicolon-separated list. DO NOT treat as literal */); createVCProjectTask.SetParameterValue("ReferenceAssemblies", string.Format(CultureInfo.InvariantCulture, "@({0})", referenceItemName.ToString()), false /* DO NOT treat as literal */); createVCProjectTask.SetParameterValue("ReferenceImportLibraries", string.Format(CultureInfo.InvariantCulture, "@({0})", importLibraryItemName.ToString()), false /* DO NOT treat as literal */); createVCProjectTask.Condition = GetConditionStringForConfiguration(solutionConfiguration); return projectPath; }
/// <summary> /// This code handles the *.REFRESH files that are in the "bin" subdirectory of /// a web project. These .REFRESH files are just text files that contain absolute or /// relative paths to the referenced assemblies. The goal of these tasks is to /// search all *.REFRESH files and extract fully-qualified absolute paths for /// each of the references. /// </summary> /// <param name="target"></param> /// <param name="proj"></param> /// <param name="referenceItemName"></param> /// <owner>RGoel</owner> static private void AddTasksToResolveAutoRefreshFileReferences ( Target target, ProjectInSolution proj, string referenceItemName ) { string webRoot = "$(" + GenerateSafePropertyName(proj, "AspNetPhysicalPath") + ")"; // Create an item list containing each of the .REFRESH files. BuildTask createItemTask = target.AddNewTask("CreateItem"); createItemTask.SetParameterValue("Include", webRoot + @"\Bin\*.refresh"); createItemTask.AddOutputItem("Include", referenceItemName + "_RefreshFile"); // Read the lines out of each .REFRESH file; they should be paths to .DLLs. Put these paths // into an item list. BuildTask readLinesTask = target.AddNewTask("ReadLinesFromFile"); readLinesTask.SetParameterValue("File", String.Format(CultureInfo.InvariantCulture, @"%({0}_RefreshFile.Identity)", referenceItemName)); readLinesTask.Condition = String.Format(CultureInfo.InvariantCulture, @" '%({0}_RefreshFile.Identity)' != '' ", referenceItemName); readLinesTask.AddOutputItem("Lines", referenceItemName + "_ReferenceRelPath"); // Take those paths and combine them with the root of the web project to form either // an absolute path or a path relative to the .SLN file. These paths can be passed // directly to RAR later. BuildTask combinePathTask = target.AddNewTask("CombinePath"); combinePathTask.SetParameterValue("BasePath", webRoot); combinePathTask.SetParameterValue("Paths", String.Format(CultureInfo.InvariantCulture, @"@({0}_ReferenceRelPath)", referenceItemName)); combinePathTask.AddOutputItem("CombinedPaths", referenceItemName); }
/// <summary> /// When adding a target to build a web project, we want to put a Condition on the Target node that /// effectively says "Only build this target if the web project is active (marked for building) in the /// current solution configuration. /// </summary> /// <param name="solution"></param> /// <param name="proj"></param> /// <returns></returns> /// <owner>RGoel</owner> static private string ComputeTargetConditionForWebProject ( SolutionParser solution, ProjectInSolution proj ) { StringBuilder condition = new StringBuilder(" ('$(CurrentSolutionConfigurationContents)' != '') and (false"); // Loop through all the solution configurations. foreach (ConfigurationInSolution solutionConfiguration in solution.SolutionConfigurations) { // Find out if the web project has a project configuration for this solution configuration. // (Actually, web projects only have one project configuration, so the TryGetValue should // pretty much always return "true". ProjectConfigurationInSolution projectConfiguration = null; if (proj.ProjectConfigurations.TryGetValue(solutionConfiguration.FullName, out projectConfiguration)) { // See if the web project is marked as active for this solution configuration. If so, // we'll build the target. Otherwise not. if (projectConfiguration.IncludeInBuild) { condition.Append(" or ("); condition.Append(GetConditionStringForConfiguration(solutionConfiguration)); condition.Append(")"); } } else if (String.Compare(solutionConfiguration.ConfigurationName, "Release", StringComparison.OrdinalIgnoreCase) == 0 || String.Compare(solutionConfiguration.ConfigurationName, "Debug", StringComparison.OrdinalIgnoreCase) == 0) { // we don't have a project configuration that matches the solution configuration but // the solution configuration is called "Release" or "Debug" which are standard AspNetConfigurations // so these should be available in the solution project condition.Append(" or ("); condition.Append(GetConditionStringForConfiguration(solutionConfiguration)); condition.Append(")"); } } condition.Append(") "); return condition.ToString(); }
public void ParseFirstProjectLineWhereProjectNameHasSpecialCharacters() { SolutionParser p = new SolutionParser(); p.SolutionFile = "foobar.sln"; ProjectInSolution proj = new ProjectInSolution(p); p.ParseFirstProjectLine ( "Project(\"{Project GUID}\") = \"MyProject,(=IsGreat)\", \"Relative path to project file\" , \"Unique name-GUID\"", proj ); Assertion.AssertEquals(SolutionProjectType.Unknown, proj.ProjectType); Assertion.AssertEquals("MyProject,(=IsGreat)", proj.ProjectName); Assertion.AssertEquals("Relative path to project file", proj.RelativePath); Assertion.AssertEquals("Unique name-GUID", proj.ProjectGuid); }