1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections.Generic; 6 using System.IO; 7 using System.Text; 8 using System.Xml; 9 using Microsoft.Build.BuildEngine.Shared; 10 using Microsoft.Win32; 11 12 namespace Microsoft.Build.BuildEngine 13 { 14 internal class VCWrapperProject 15 { 16 /// <summary> 17 /// Add a target for a VC project file into the XML doc that's being generated. 18 /// This is used only when building standalone VC projects 19 /// </summary> 20 /// <param name="msbuildProject"></param> 21 /// <param name="projectPath"></param> 22 /// <param name="targetName"></param> 23 /// <param name="subTargetName"></param> 24 /// <owner>RGoel</owner> AddVCBuildTarget( Project msbuildProject, string projectPath, string targetName, string subTargetName )25 static private void AddVCBuildTarget 26 ( 27 Project msbuildProject, 28 string projectPath, 29 string targetName, 30 string subTargetName 31 ) 32 { 33 Target newTarget = msbuildProject.Targets.AddNewTarget(targetName); 34 if (subTargetName == "Publish") 35 { 36 // Well, hmmm. The VCBuild doesn't support any kind of 37 // a "Publish" operation. The best we can really do is offer up a 38 // message saying so. 39 SolutionWrapperProject.AddErrorWarningMessageElement(newTarget, XMakeElements.error, true, "SolutionVCProjectNoPublish"); 40 } 41 else 42 { 43 SolutionWrapperProject.AddErrorWarningMessageElement(newTarget, XMakeElements.warning, true, "StandaloneVCProjectP2PRefsBroken"); 44 45 string projectFullPath = Path.GetFullPath(projectPath); 46 AddVCBuildTaskElement(msbuildProject, newTarget, "$(VCBuildSolutionFile)", projectFullPath, subTargetName, "$(PlatformName)", "$(ConfigurationName)"); 47 } 48 } 49 50 /// <summary> 51 /// Adds a new VCBuild task element to the specified target 52 /// </summary> 53 /// <param name="target">The target to add the VCBuild task to</param> 54 /// <param name="solutionPath">Path to the solution if any</param> 55 /// <param name="projectPath">Path to the solution if any</param> 56 /// <param name="vcbuildTargetName">The VCBuild target name</param> 57 /// <param name="platformName">The platform parameter to VCBuild</param> 58 /// <param name="fullConfigurationName">Configuration property value</param> 59 /// <returns></returns> AddVCBuildTaskElement( Project msbuildProject, Target target, string solutionPath, string projectPath, string vcbuildTargetName, string platformName, string fullConfigurationName )60 static internal BuildTask AddVCBuildTaskElement 61 ( 62 Project msbuildProject, 63 Target target, 64 string solutionPath, 65 string projectPath, 66 string vcbuildTargetName, 67 string platformName, 68 string fullConfigurationName 69 ) 70 { 71 // The VCBuild task (which we already shipped) has a bug - it cannot 72 // find vcbuild.exe when running in MSBuild 64 bit unless it's on the path. 73 // So, pass it here, unless some explicit path was passed. 74 // Note, we have to do this even if we're in a 32 bit MSBuild, because we save the .sln.cache 75 // file, and the next build of the solution could be a 64 bit MSBuild. 76 77 if (VCBuildLocationHint != null) // Should only be null if vcbuild truly isn't installed; in that case, let the task log its error 78 { 79 BuildTask createProperty = target.AddNewTask("CreateProperty"); 80 81 createProperty.SetParameterValue("Value", VCBuildLocationHint); 82 createProperty.Condition = "'$(VCBuildToolPath)' == ''"; 83 createProperty.AddOutputProperty("Value", "VCBuildToolPath"); 84 } 85 86 BuildTask newTask = target.AddNewTask("VCBuild"); 87 88 newTask.SetParameterValue("Projects", projectPath, true /* treat as literal */); 89 90 // Add the toolpath so that the user can override if necessary 91 newTask.SetParameterValue("ToolPath", "$(VCBuildToolPath)"); 92 93 newTask.SetParameterValue("Configuration", fullConfigurationName); 94 95 if (!string.IsNullOrEmpty(platformName)) 96 { 97 newTask.SetParameterValue("Platform", platformName); 98 } 99 100 newTask.SetParameterValue("SolutionFile", solutionPath); 101 102 if ((vcbuildTargetName != null) && (vcbuildTargetName.Length > 0)) 103 { 104 newTask.SetParameterValue(vcbuildTargetName, "true"); 105 } 106 107 // Add the override switch so that the user can supply one if necessary 108 newTask.SetParameterValue("Override", "$(VCBuildOverride)"); 109 110 // Add any additional lib paths 111 newTask.SetParameterValue("AdditionalLibPaths", "$(VCBuildAdditionalLibPaths)"); 112 113 // Only use new properties if we're not emitting a 2.0 project 114 if (!String.Equals(msbuildProject.ToolsVersion, "2.0", StringComparison.OrdinalIgnoreCase)) 115 { 116 // Add any additional link library paths 117 newTask.SetParameterValue("AdditionalLinkLibraryPaths", "$(VCBuildAdditionalLinkLibraryPaths)"); 118 119 // Add the useenv switch so that the user can supply one if necessary 120 // Note: "VCBuildUserEnvironment" is included for backwards-compatibility; the correct 121 // property name is "VCBuildUseEnvironment" to match the task parameter. When the old name is 122 // used the task will emit a warning. 123 newTask.SetParameterValue("UseEnvironment", "$(VCBuildUseEnvironment)"); 124 } 125 126 newTask.SetParameterValue("UserEnvironment", "$(VCBuildUserEnvironment)"); 127 128 // Add the additional options switches 129 newTask.SetParameterValue("AdditionalOptions", "$(VCBuildAdditionalOptions)"); 130 131 return newTask; 132 } 133 134 /// <summary> 135 /// This method generates an XmlDocument representing an MSBuild project wrapper for a VC project 136 /// </summary> 137 /// <owner>LukaszG</owner> GenerateVCWrapperProject(Engine parentEngine, string vcProjectFilename, string toolsVersion)138 static internal XmlDocument GenerateVCWrapperProject(Engine parentEngine, string vcProjectFilename, string toolsVersion) 139 { 140 string projectPath = Path.GetFullPath(vcProjectFilename); 141 Project msbuildProject = null; 142 143 try 144 { 145 msbuildProject = new Project(parentEngine, toolsVersion); 146 } 147 catch (InvalidOperationException) 148 { 149 BuildEventFileInfo fileInfo = new BuildEventFileInfo(projectPath); 150 string errorCode; 151 string helpKeyword; 152 string message = ResourceUtilities.FormatResourceString(out errorCode, out helpKeyword, "UnrecognizedToolsVersion", toolsVersion); 153 throw new InvalidProjectFileException(projectPath, fileInfo.Line, fileInfo.Column, fileInfo.EndLine, fileInfo.EndColumn, message, null, errorCode, helpKeyword); 154 } 155 156 msbuildProject.IsLoadedByHost = false; 157 msbuildProject.DefaultTargets = "Build"; 158 159 string wrapperProjectToolsVersion = SolutionWrapperProject.DetermineWrapperProjectToolsVersion(toolsVersion); 160 msbuildProject.DefaultToolsVersion = wrapperProjectToolsVersion; 161 162 BuildPropertyGroup propertyGroup = msbuildProject.AddNewPropertyGroup(true /* insertAtEndOfProject = true */); 163 propertyGroup.Condition = " ('$(Configuration)' != '') and ('$(Platform)' == '') "; 164 propertyGroup.AddNewProperty("ConfigurationName", "$(Configuration)"); 165 166 propertyGroup = msbuildProject.AddNewPropertyGroup(true /* insertAtEndOfProject = true */); 167 propertyGroup.Condition = " ('$(Configuration)' != '') and ('$(Platform)' != '') "; 168 propertyGroup.AddNewProperty("ConfigurationName", "$(Configuration)|$(Platform)"); 169 170 // only use PlatformName if we only have the platform part 171 propertyGroup = msbuildProject.AddNewPropertyGroup(true /* insertAtEndOfProject = true */); 172 propertyGroup.Condition = " ('$(Configuration)' == '') and ('$(Platform)' != '') "; 173 propertyGroup.AddNewProperty("PlatformName", "$(Platform)"); 174 175 AddVCBuildTarget(msbuildProject, projectPath, "Build", null); 176 AddVCBuildTarget(msbuildProject, projectPath, "Clean", "Clean"); 177 AddVCBuildTarget(msbuildProject, projectPath, "Rebuild", "Rebuild"); 178 AddVCBuildTarget(msbuildProject, projectPath, "Publish", "Publish"); 179 180 // Special environment variable to allow people to see the in-memory MSBuild project generated 181 // to represent the VC project. 182 if (Environment.GetEnvironmentVariable("MSBuildEmitSolution") != null) 183 { 184 msbuildProject.Save(vcProjectFilename + ".proj"); 185 } 186 187 return msbuildProject.XmlDocument; 188 } 189 190 /// <summary> 191 /// Hint to give the VCBuild task to help it find vcbuild.exe. 192 /// </summary> 193 static private string path; 194 195 /// <summary> 196 /// Hint to give the VCBuild task to help it find vcbuild.exe. 197 /// Directory in which vcbuild.exe is found. 198 /// </summary> 199 static internal string VCBuildLocationHint 200 { 201 get 202 { 203 if (path == null) 204 { 205 path = GenerateFullPathToTool(RegistryView.Default); 206 207 if (path == null && Environment.Is64BitProcess) 208 { 209 path = GenerateFullPathToTool(RegistryView.Registry32); 210 } 211 212 if (path != null) 213 { 214 path = Path.GetDirectoryName(path); 215 } 216 } 217 218 return path; 219 } 220 } 221 222 // The code below is mostly copied from the VCBuild task that we shipped in 3.5. 223 // It is the logic it uses to find vcbuild.exe. That logic had a flaw - 224 // in 64 bit MSBuild, in a vanilla command window (like in Team Build) it would not 225 // find vcbuild.exe. We use the logic below to predict whether VCBuild will find it, 226 // and if it won't, we will pass the "hint" to use the 64 bit program files location. 227 228 /// <summary> 229 /// constants for VS9 Pro and above SKUs 230 /// </summary> 231 232 // root registry key for VS9 233 private const string vs9RegKey = @"SOFTWARE\Microsoft\VisualStudio\9.0"; 234 // the name of the value containing disk install directory for the IDE components 235 // ("...\common7\ide" for layouts) 236 private const string vs9InstallDirValueName = "InstallDir"; 237 // relative path from the above directory to vcbuild.exe on layouts 238 private const string vs9RelativePathToVCBuildLayouts = @"..\..\vc\vcpackages\vcbuild.exe"; 239 // relative path from the above directory to vcbuild.exe on batch 240 private const string vs9RelativePathToVCBuildBatch = @"vcbuild.exe"; 241 242 /// <summary> 243 /// constants for the VC9 Express SKU 244 /// </summary> 245 246 // root registry key for VC9 247 private const string vc9RegKey = @"SOFTWARE\Microsoft\VCExpress\9.0"; 248 // the name of the value containing disk install directory for the IDE components 249 // ("...\common7\ide" for layouts) 250 private const string vc9InstallDirValueName = "InstallDir"; 251 // relative path from the above directory to vcbuild.exe on layouts 252 private const string vc9RelativePathToVCBuildLayouts = @"..\..\vc\vcpackages\vcbuild.exe"; 253 // relative path from the above directory to vcbuild.exe on batch 254 private const string vc9RelativePathToVCBuildBatch = @"vcbuild.exe"; 255 256 // name of the tool 257 private const string vcbuildName = "vcbuild.exe"; 258 259 /// <summary> 260 /// Determing the path to vcbuild.exe 261 /// </summary> 262 /// <returns>path to vcbuild.exe, or null if it's not found</returns> GenerateFullPathToTool(RegistryView registryView)263 private static string GenerateFullPathToTool(RegistryView registryView) 264 { 265 using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView)) 266 { 267 // try VS9 professional and above SKUs first 268 string location = TryLocationFromRegistry(baseKey, vs9RegKey, vs9InstallDirValueName, 269 vs9RelativePathToVCBuildLayouts, vs9RelativePathToVCBuildBatch); 270 271 if (null != location) 272 { 273 return location; 274 } 275 276 // fall back to the VC Express SKU 277 location = TryLocationFromRegistry(baseKey, vc9RegKey, vc9InstallDirValueName, 278 vc9RelativePathToVCBuildLayouts, vc9RelativePathToVCBuildBatch); 279 280 if (null != location) 281 { 282 return location; 283 } 284 285 // finally, try looking in the system path 286 if (Microsoft.Build.BuildEngine.Shared.NativeMethods.FindOnPath(vcbuildName) == null) 287 { 288 // If SearchPath didn't find the file, it's not on the system path and we have no chance of finding it. 289 return null; 290 } 291 292 return null; 293 } 294 } 295 296 /// <summary> 297 /// Looks up a path from the registry if present, and checks whether VCBuild.exe is there. 298 /// </summary> 299 /// <param name="subKey">Registry key to open</param> 300 /// <param name="valueName">Value under that key to read</param> 301 /// <param name="messageToLogIfNotFound">Low-pri message to log if registry key isn't found</param> 302 /// <param name="relativePathFromValueOnLayout">Relative path from the key value to vcbuild.exe for layout installs</param> 303 /// <param name="relativePathFromValueOnBatch">Relative path from the key value to vcbuild.exe for batch installs</param> 304 /// <returns>Path to vcbuild.exe, or null if it's not found</returns> 305 /// <owner>danmose</owner> TryLocationFromRegistry(RegistryKey root, string subKeyName, string valueName, string relativePathFromValueOnLayout, string relativePathFromValueOnBatch)306 private static string TryLocationFromRegistry(RegistryKey root, string subKeyName, string valueName, 307 string relativePathFromValueOnLayout, string relativePathFromValueOnBatch) 308 { 309 using (RegistryKey subKey = root.OpenSubKey(subKeyName)) 310 { 311 if (subKey == null) 312 { 313 // We couldn't find an installation of the product we were looking for. 314 return null; 315 } 316 else 317 { 318 string rootDir = (string)subKey.GetValue(valueName); 319 320 if (rootDir != null) 321 { 322 // first try the location for layouts 323 string vcBuildPath = Path.Combine(rootDir, relativePathFromValueOnLayout); 324 if (File.Exists(vcBuildPath)) 325 { 326 return vcBuildPath; 327 } 328 329 // if not found in layouts location, try the alternate dir if any, 330 // which contains vcbuild for batch installs 331 if (null != relativePathFromValueOnBatch) 332 { 333 vcBuildPath = Path.Combine(rootDir, relativePathFromValueOnBatch); 334 if (File.Exists(vcBuildPath)) 335 { 336 return vcBuildPath; 337 } 338 } 339 } 340 } 341 342 // Didn't find it 343 return null; 344 } 345 } 346 } 347 } 348