1 using System; 2 using System.CodeDom; 3 using System.CodeDom.Compiler; 4 using System.Collections.Generic; 5 using System.Configuration; 6 using System.Security.Permissions; 7 using System.Text; 8 using System.Web.Configuration; 9 using System.Web.Hosting; 10 using System.Web.Util; 11 using Microsoft.Build.Utilities; 12 using Microsoft.CSharp; 13 using Microsoft.VisualBasic; 14 using Microsoft.Win32; 15 16 using FrameworkName=System.Runtime.Versioning.FrameworkName; 17 18 namespace System.Web.Compilation { 19 internal class MultiTargetingUtil { 20 21 // Well-known previous versions 22 static internal readonly FrameworkName FrameworkNameV20 = CreateFrameworkName(".NETFramework,Version=v2.0"); 23 static internal readonly FrameworkName FrameworkNameV30 = CreateFrameworkName(".NETFramework,Version=v3.0"); 24 static internal readonly FrameworkName FrameworkNameV35 = CreateFrameworkName(".NETFramework,Version=v3.5"); 25 static internal readonly FrameworkName FrameworkNameV40 = CreateFrameworkName(".NETFramework,Version=v4.0"); 26 static internal readonly FrameworkName FrameworkNameV45 = CreateFrameworkName(".NETFramework,Version=v4.5"); 27 28 internal static Version Version40 = new Version(4, 0); 29 internal static Version Version35 = new Version(3, 5); 30 private static FrameworkName s_targetFrameworkName = null; 31 private static string s_configTargetFrameworkMoniker = null; 32 private static object s_configTargetFrameworkMonikerLock = new object(); 33 private static bool s_initializedConfigTargetFrameworkMoniker = false; 34 private static object s_targetFrameworkNameLock = new object(); 35 private static string s_configTargetFrameworkAttributeName = "targetFramework"; 36 37 /// <summary> 38 /// Latest framework version. 39 /// </summary> 40 private static FrameworkName s_latestFrameworkName = null; 41 42 private static List<FrameworkName> s_knownFrameworkNames = null; 43 44 /// <summary> 45 /// Returns the target framework moniker, eg ".NETFramework,Version=3.5" 46 /// </summary> 47 internal static FrameworkName TargetFrameworkName { 48 get { 49 EnsureFrameworkNamesInitialized(); 50 return s_targetFrameworkName; 51 } 52 set { 53 s_targetFrameworkName = value; 54 } 55 } 56 57 /// <summary> 58 /// Returns the current latest known framework moniker, eg ".NETFramework,Version=4.0" 59 /// </summary> 60 internal static FrameworkName LatestFrameworkName { 61 get { 62 EnsureFrameworkNamesInitialized(); 63 return s_latestFrameworkName; 64 } 65 } 66 67 internal static List<FrameworkName> KnownFrameworkNames { 68 get { 69 EnsureFrameworkNamesInitialized(); 70 return s_knownFrameworkNames; 71 } 72 } 73 EnsureFrameworkNamesInitialized()74 internal static void EnsureFrameworkNamesInitialized() { 75 if (s_targetFrameworkName == null) { 76 lock (s_targetFrameworkNameLock) { 77 if (s_targetFrameworkName == null) { 78 InitializeKnownAndLatestFrameworkNames(); 79 InitializeTargetFrameworkName(); 80 Debug.Assert(s_targetFrameworkName != null, "s_targetFrameworkName should not be null"); 81 } 82 } 83 } 84 } 85 86 /// <summary> 87 /// Finds out what the known framework names and also the latest one 88 /// </summary> InitializeKnownAndLatestFrameworkNames()89 private static void InitializeKnownAndLatestFrameworkNames() { 90 IList<string> names = ToolLocationHelper.GetSupportedTargetFrameworks(); 91 Version latestVersion = null; 92 s_knownFrameworkNames = new List<FrameworkName>(); 93 foreach (string name in names) { 94 FrameworkName frameworkName = new FrameworkName(name); 95 s_knownFrameworkNames.Add(frameworkName); 96 Version version = GetFrameworkNameVersion(frameworkName); 97 if (s_latestFrameworkName == null || latestVersion < version) { 98 s_latestFrameworkName = frameworkName; 99 latestVersion = version; 100 } 101 } 102 } 103 104 /// <summary> 105 /// Returns the string for the target framework as specified in the 106 /// config. 107 /// </summary> 108 internal static string ConfigTargetFrameworkMoniker { 109 get { 110 if (!s_initializedConfigTargetFrameworkMoniker) { 111 lock (s_configTargetFrameworkMonikerLock) { 112 if (!s_initializedConfigTargetFrameworkMoniker) { 113 RuntimeConfig appConfig = RuntimeConfig.GetAppConfig(); 114 CompilationSection compConfig = appConfig.Compilation; 115 116 string targetFramework = compConfig.TargetFramework; 117 if (targetFramework != null) { 118 targetFramework = targetFramework.Trim(); 119 } 120 121 s_configTargetFrameworkMoniker = targetFramework; 122 s_initializedConfigTargetFrameworkMoniker = true; 123 } 124 } 125 } 126 return s_configTargetFrameworkMoniker; 127 } 128 } 129 130 /// <summary> 131 /// Checks what is the target framework version and initializes the targetFrameworkName 132 /// </summary> InitializeTargetFrameworkName()133 private static void InitializeTargetFrameworkName() { 134 string targetFrameworkMoniker = ConfigTargetFrameworkMoniker; 135 136 // Check if web.config exists, and if not, assume 4.0 137 if (!WebConfigExists) { 138 s_targetFrameworkName = FrameworkNameV40; 139 ValidateCompilerVersionFor40AndAbove(); 140 } 141 else if (targetFrameworkMoniker == null) { 142 if (BuildManagerHost.SupportsMultiTargeting) { 143 // We check for null because the user could have specified 144 // an empty string. 145 // TargetFrameworkMoniker was not specified in config, 146 // so we need to check codedom settings. 147 InitializeTargetFrameworkNameFor20Or35(); 148 } else { 149 // We are running in a 4.0 application pool or in the aspnet_compiler, 150 // but the target framework moniker is not specified. 151 // Assume it is 4.0 so that the application can run. 152 s_targetFrameworkName = FrameworkNameV40; 153 } 154 } else { 155 // The targetFrameworkMonike is specified, so we need to validate it. 156 InitializeTargetFrameworkNameFor40AndAbove(targetFrameworkMoniker); 157 } 158 } 159 160 /// <summary> 161 /// Verifies that the moniker is valid, and that the version is 4.0 and above. 162 /// </summary> ValidateTargetFrameworkMoniker(string targetFrameworkMoniker)163 private static void ValidateTargetFrameworkMoniker(string targetFrameworkMoniker) { 164 CompilationSection compConfig = RuntimeConfig.GetAppConfig().Compilation; 165 int lineNumber = compConfig.ElementInformation.LineNumber; 166 string source = compConfig.ElementInformation.Source; 167 try { 168 string moniker = targetFrameworkMoniker; 169 // Try treating it as a version, eg "4.0" first. 170 Version v = GetVersion(targetFrameworkMoniker); 171 if (v != null) { 172 // If it is of the form "4.0", construct the full moniker string, 173 // eg ".NETFramework,Version=v4.0" 174 moniker = ".NETFramework,Version=v" + moniker; 175 } 176 s_targetFrameworkName = CreateFrameworkName(moniker); 177 } 178 catch (ArgumentException e) { 179 throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_target_framework_version, 180 s_configTargetFrameworkAttributeName, targetFrameworkMoniker, e.Message), source, lineNumber); 181 } 182 Version ver = GetFrameworkNameVersion(s_targetFrameworkName); 183 if (ver < Version40) { 184 throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_lower_target_version, s_configTargetFrameworkAttributeName), 185 source, lineNumber); 186 } 187 188 // Check the specified version is no higher than the latest known framework for which we have 189 // reference assemblies installed. 190 Version latestVersion = GetFrameworkNameVersion(LatestFrameworkName); 191 if (latestVersion != null && latestVersion >= ver) { 192 // If the specified version is lower than the latest version installed, 193 // we are fine. 194 return; 195 } 196 197 // NOTE: This check is not entirely correct. See comments in GetInstalledTargetVersion(). 198 // It might be possible that the actual installed (runtime) version is of a higher version, 199 // but the reference assemblies are not installed, so latestFrameworkName might be lower. 200 // In that case we also need to check the registry key. 201 int majorVersion = ver.Major; 202 Version installedTargetVersion = GetInstalledTargetVersion(majorVersion); 203 if (installedTargetVersion != null && installedTargetVersion >= ver) { 204 return; 205 } 206 207 if (IsSupportedVersion(s_targetFrameworkName)) { 208 return; 209 } 210 211 // If the above checks failed, report that the version is invalid, higher than expected 212 throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_higher_target_version, s_configTargetFrameworkAttributeName), source, lineNumber); 213 } 214 215 [RegistryPermission(SecurityAction.Assert, Unrestricted = true)] GetInstalledTargetVersion(int majorVersion)216 private static Version GetInstalledTargetVersion(int majorVersion) { 217 // NOTE: This code is wrong to assume "Full", but it is left as is to avoid 218 // introducing any breaking change. The mitigation is handled by IsSupportedVersion which 219 // is more flexible with regards to framework profile. 220 221 // registry key is of the form: 222 // [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full] 223 // "TargetVersion"="4.0.0" 224 // The path includes the major version, eg "v4" or "v5", so we need to use a parameter. 225 string path = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v" + majorVersion + @"\Full"; 226 try { 227 object o = Registry.GetValue(path, "TargetVersion", null); 228 string targetVersion = o as string; 229 if (!string.IsNullOrEmpty(targetVersion)) { 230 Version ver = new Version(targetVersion); 231 return ver; 232 } 233 } 234 catch { // ignore exceptions 235 } 236 return null; 237 } 238 239 [RegistryPermission(SecurityAction.Assert, Unrestricted = true)] IsSupportedVersion(FrameworkName frameworkName)240 private static bool IsSupportedVersion(FrameworkName frameworkName) { 241 // Look under the following registry to get the list of supported keys, and check for matching 242 // identifier and version. 243 // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319\SKUs\[TFM] 244 try { 245 var name = new FrameworkName(frameworkName.Identifier, frameworkName.Version); 246 var runtime = Environment.Version; 247 var runtimeVersion = runtime.Major + "." + runtime.Minor + "." + runtime.Build; 248 string path = @"SOFTWARE\Microsoft\.NETFramework\v" + runtimeVersion + @"\SKUs"; 249 var baseKey = Registry.LocalMachine.OpenSubKey(path); 250 foreach (string subKey in baseKey.GetSubKeyNames()) { 251 try { 252 var subKeyName = CreateFrameworkName(subKey); 253 var supportedName = new FrameworkName(subKeyName.Identifier, subKeyName.Version); 254 if (String.Equals(name.FullName, supportedName.FullName, StringComparison.OrdinalIgnoreCase)) { 255 return true; 256 } 257 } 258 catch { 259 continue; 260 } 261 } 262 } 263 catch { 264 } 265 return false; 266 } 267 268 /// <summary> 269 /// Checks whether the application web.config exists or not 270 /// </summary> 271 private static bool WebConfigExists { 272 get { 273 VirtualPath vpath = HttpRuntime.AppDomainAppVirtualPathObject; 274 if (vpath != null) { 275 string path = vpath.SimpleCombine(HttpConfigurationSystem.WebConfigFileName).MapPath(); 276 return System.IO.File.Exists(path); 277 } 278 return false; 279 } 280 } 281 282 /// <summary> 283 /// Returns the higher compilerVersion specified in codedom for the case when targeting 2.0/3.5. 284 /// Either "v3.5" is returned, or "v2.0" is returned if the compilerVersion 285 /// is anything other that "v3.5". This is because the root web.config has compilerVersion=v4.0. If we 286 /// know that we are compiling for 2.0 or 3.5, then we override the value to 2.0 if it is not 3.5. 287 /// </summary> GetCompilerVersionFor20Or35()288 private static string GetCompilerVersionFor20Or35() { 289 string vbCompilerVersion = GetCSharpCompilerVersion(); 290 string csharpCompilerVersion = GetVisualBasicCompilerVersion(); 291 292 // The root web.config will have compilerVersion=4.0, so if we are targeting 2.0 or 3.5, we need to 293 // use compilerVersion=2.0 if the compilerVersion is NOT 3.5. 294 vbCompilerVersion = ReplaceCompilerVersionFor20Or35(vbCompilerVersion); 295 csharpCompilerVersion = ReplaceCompilerVersionFor20Or35(csharpCompilerVersion); 296 297 Version vbVersion = CompilationUtil.GetVersionFromVString(vbCompilerVersion); 298 Version csVersion = CompilationUtil.GetVersionFromVString(csharpCompilerVersion); 299 300 // Return the larger value as the intended version 301 if (vbVersion > csVersion) { 302 return vbCompilerVersion; 303 } 304 return csharpCompilerVersion; 305 } 306 307 /// <summary> 308 /// Checks codedom settings to determine whether we are targeting 2.0 or 3.5. 309 /// </summary> InitializeTargetFrameworkNameFor20Or35()310 private static void InitializeTargetFrameworkNameFor20Or35() { 311 string compilerVersion = GetCompilerVersionFor20Or35(); 312 313 // Make sure the compiler version is either 2.0 or 3.5 314 if (CompilationUtil.IsCompilerVersion35(compilerVersion)) { 315 s_targetFrameworkName = FrameworkNameV35; 316 } 317 else if (compilerVersion == "v2.0" || compilerVersion == null) { 318 // If the compiler version is null, it means the user did not set it 319 // in the codedom section. 320 // We use 3.0 because it is not possible to distinguish between 2.0 and 3.0 321 // by just looking at web.config. 322 s_targetFrameworkName = FrameworkNameV30; 323 } 324 else { 325 throw new ConfigurationErrorsException(SR.GetString(SR.Compiler_version_20_35_required, s_configTargetFrameworkAttributeName)); 326 } 327 } 328 329 /// <summary> 330 /// If the compilerVersion is anything other than "v3.5", return "v2.0". 331 /// </summary> ReplaceCompilerVersionFor20Or35(string compilerVersion)332 private static string ReplaceCompilerVersionFor20Or35(string compilerVersion) { 333 if (CompilationUtil.IsCompilerVersion35(compilerVersion)) { 334 return compilerVersion; 335 } 336 return "v2.0"; 337 } 338 GetCSharpCompilerVersion()339 private static string GetCSharpCompilerVersion() { 340 return CompilationUtil.GetCompilerVersion(typeof(CSharpCodeProvider)); 341 } 342 GetVisualBasicCompilerVersion()343 private static string GetVisualBasicCompilerVersion() { 344 return CompilationUtil.GetCompilerVersion(typeof(VBCodeProvider)); 345 } 346 ReportInvalidCompilerVersion(string compilerVersion)347 private static void ReportInvalidCompilerVersion(string compilerVersion) { 348 throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_attribute_value, compilerVersion, CompilationUtil.CodeDomProviderOptionPath + "CompilerVersion")); 349 } 350 InitializeTargetFrameworkNameFor40AndAbove(string targetFrameworkMoniker)351 private static void InitializeTargetFrameworkNameFor40AndAbove(string targetFrameworkMoniker) { 352 ValidateTargetFrameworkMoniker(targetFrameworkMoniker); 353 ValidateCompilerVersionFor40AndAbove(); 354 } 355 356 /// <summary> 357 /// Ensures that the compiler version is 4.0 and above. 358 /// </summary> ValidateCompilerVersionFor40AndAbove()359 private static void ValidateCompilerVersionFor40AndAbove() { 360 // Since the root web.config already specifies 4.0, we need to make sure both compilerVersions 361 // are actually greater than or equal to 4.0, in case the user only sets compilerVersion=3.5 362 // for one language. (Dev10 bug 738202) 363 ValidateCompilerVersionFor40AndAbove(GetCSharpCompilerVersion()); 364 ValidateCompilerVersionFor40AndAbove(GetVisualBasicCompilerVersion()); 365 } 366 ValidateCompilerVersionFor40AndAbove(string compilerVersion)367 private static void ValidateCompilerVersionFor40AndAbove(string compilerVersion) { 368 if (compilerVersion != null) { 369 Exception exception = null; 370 if (compilerVersion.Length < 4 || compilerVersion[0] != 'v') { 371 ReportInvalidCompilerVersion(compilerVersion); 372 } 373 try { 374 Version version = CompilationUtil.GetVersionFromVString(compilerVersion); 375 if (version < Version40) { 376 throw new ConfigurationErrorsException(SR.GetString(SR.Compiler_version_40_required, s_configTargetFrameworkAttributeName)); 377 } 378 } 379 catch (ArgumentNullException e) { 380 exception = e; 381 } 382 catch (ArgumentOutOfRangeException e) { 383 exception = e; 384 } 385 catch (ArgumentException e) { 386 exception = e; 387 } 388 catch (FormatException e) { 389 exception = e; 390 } 391 catch (OverflowException e) { 392 exception = e; 393 } 394 if (exception != null) { 395 ReportInvalidCompilerVersion(compilerVersion); 396 } 397 } 398 } 399 400 /// <summary> 401 /// Returns true if the target framework version is 3.5. 402 /// </summary> 403 internal static bool IsTargetFramework35 { 404 get { 405 return Object.Equals(TargetFrameworkName, FrameworkNameV35); 406 } 407 } 408 409 /// <summary> 410 /// Returns true if the target framework version is 2.0 or 3.0. 411 /// </summary> 412 internal static bool IsTargetFramework20 { 413 get { 414 return Object.Equals(TargetFrameworkName, FrameworkNameV20) || 415 Object.Equals(TargetFrameworkName, FrameworkNameV30); 416 } 417 } 418 419 // Gets the target framework version as a Version instance. 420 internal static Version TargetFrameworkVersion { 421 get { 422 return GetFrameworkNameVersion(TargetFrameworkName); 423 } 424 } 425 426 internal static bool IsTargetFramework40OrAbove { 427 get { 428 return MultiTargetingUtil.TargetFrameworkVersion.Major >= 4; 429 } 430 } 431 432 internal static bool IsTargetFramework45OrAbove { 433 get { 434 return IsTargetFramework40OrAbove && TargetFrameworkVersion.Minor >= 5; 435 } 436 } 437 438 /// <summary> 439 /// Enable use of RAR only in CBM scenarios 440 /// </summary> 441 internal static bool EnableReferenceAssemblyResolution { 442 get { 443 return BuildManagerHost.InClientBuildManager; // Enable only in CBM scenarios. 444 } 445 } 446 CreateFrameworkName(string name)447 internal static FrameworkName CreateFrameworkName(string name) { 448 return new FrameworkName(name); 449 } 450 GetFrameworkNameVersion(FrameworkName name)451 private static Version GetFrameworkNameVersion(FrameworkName name) { 452 if (name == null) { 453 return null; 454 } 455 return name.Version; 456 } 457 458 /// <summary> 459 /// Returns a Version instance if possible from the version string. 460 /// Otherwise returns null. 461 /// </summary> GetVersion(string version)462 private static Version GetVersion(string version) { 463 if (string.IsNullOrEmpty(version) || !char.IsDigit(version[0])) { 464 return null; 465 } 466 467 try { 468 Version ver = new Version(version); 469 return ver; 470 } 471 catch { } 472 return null; 473 } 474 } 475 } 476