private void ValidateSupport() { var runtimeFxSuppression = GetSuppressionValues(Suppression.PermitRuntimeTargetMonikerMismatch) ?? new HashSet <string>(); // validate support for each TxM:RID foreach (var validateFramework in _frameworks.Values) { NuGetFramework fx = validateFramework.Framework; Version supportedVersion = validateFramework.SupportedVersion; Target compileTarget; if (!_report.Targets.TryGetValue(fx.ToString(), out compileTarget)) { Log.LogError($"Missing target {fx.ToString()} from validation report {ReportFile}"); continue; } var compileAssetPaths = compileTarget.CompileAssets.Select(ca => ca.PackagePath); bool hasCompileAsset, hasCompilePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Compile", ContractName, fx.ToString(), compileAssetPaths, out hasCompileAsset, out hasCompilePlaceHolder); // resolve/test for each RID associated with this framework. foreach (string runtimeId in validateFramework.RuntimeIds) { string target = String.IsNullOrEmpty(runtimeId) ? fx.ToString() : $"{fx}/{runtimeId}"; Target runtimeTarget; if (!_report.Targets.TryGetValue(target, out runtimeTarget)) { Log.LogError($"Missing target {target} from validation report {ReportFile}"); continue; } var runtimeAssetPaths = runtimeTarget.RuntimeAssets.Select(ra => ra.PackagePath); bool hasRuntimeAsset, hasRuntimePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Runtime", ContractName, target, runtimeAssetPaths, out hasRuntimeAsset, out hasRuntimePlaceHolder); if (null == supportedVersion) { // Contract should not be supported on this platform. bool permitImplementation = HasSuppression(Suppression.PermitImplementation, target); if (hasCompileAsset && (hasRuntimeAsset & !permitImplementation)) { Log.LogError($"{ContractName} should not be supported on {target} but has both compile and runtime assets."); } else if (hasRuntimeAsset & !permitImplementation) { Log.LogError($"{ContractName} should not be supported on {target} but has runtime assets."); } if (hasRuntimePlaceHolder && hasCompilePlaceHolder) { Log.LogError($"{ContractName} should not be supported on {target} but has placeholders for both compile and runtime which will permit the package to install."); } } else { if (validateFramework.IsInbox) { if (!hasCompileAsset && !hasCompilePlaceHolder) { Log.LogError($"Framework {fx} should support {ContractName} inbox but was missing a placeholder for compile-time. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } else if (hasCompileAsset) { Log.LogError($"Framework {fx} should support {ContractName} inbox but contained a reference assemblies: {String.Join(", ", compileAssetPaths)}. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } if (!hasRuntimeAsset && !hasRuntimePlaceHolder) { Log.LogError($"Framework {fx} should support {ContractName} inbox but was missing a placeholder for run-time. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } else if (hasRuntimeAsset) { Log.LogError($"Framework {fx} should support {ContractName} inbox but contained a implementation assemblies: {String.Join(", ", runtimeAssetPaths)}. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } } else { Version referenceAssemblyVersion = null; if (!hasCompileAsset) { Log.LogError($"{ContractName} should be supported on {target} but has no compile assets."); } else { var referenceAssemblies = compileTarget.CompileAssets.Where(ca => IsDll(ca.PackagePath)); if (referenceAssemblies.Count() > 1) { Log.LogError($"{ContractName} should only contain a single compile asset for {target}."); } foreach (var referenceAssembly in referenceAssemblies) { referenceAssemblyVersion = referenceAssembly.Version; if (!VersionUtility.IsCompatibleApiVersion(supportedVersion, referenceAssemblyVersion)) { Log.LogError($"{ContractName} should support API version {supportedVersion} on {target} but {referenceAssembly.LocalPath} was found to support {referenceAssemblyVersion?.ToString() ?? "<unknown version>"}."); } } } if (!hasRuntimeAsset) { if (HasSuppression(Suppression.PermitMissingImplementation, target)) { Log.LogMessage($"Suppressed: {ContractName} should be supported on {target} but has no runtime assets."); } else { // Contract should not be supported on this platform. Log.LogError($"{ContractName} should be supported on {target} but has no runtime assets."); } } else { var implementationAssemblies = runtimeTarget.RuntimeAssets.Where(ra => IsDll(ra.PackagePath)); Dictionary <string, PackageAsset> implementationFiles = new Dictionary <string, PackageAsset>(); foreach (var implementationAssembly in implementationAssemblies) { Version implementationVersion = implementationAssembly.Version; if (!VersionUtility.IsCompatibleApiVersion(supportedVersion, implementationVersion)) { Log.LogError($"{ContractName} should support API version {supportedVersion} on {target} but {implementationAssembly.LocalPath} was found to support {implementationVersion?.ToString() ?? "<unknown version>"}."); } // Previously we only permitted compatible mismatch if Suppression.PermitHigherCompatibleImplementationVersion was specified // this is a permitted thing on every framework but desktop (which requires exact match to ensure bindingRedirects exist) // Now make this the default, we'll check desktop, where it matters, more strictly if (referenceAssemblyVersion != null && !VersionUtility.IsCompatibleApiVersion(referenceAssemblyVersion, implementationVersion)) { Log.LogError($"{ContractName} has mismatched compile ({referenceAssemblyVersion}) and runtime ({implementationVersion}) versions on {target}."); } if (fx.Framework == FrameworkConstants.FrameworkIdentifiers.Net && referenceAssemblyVersion != null && !referenceAssemblyVersion.Equals(implementationVersion)) { Log.LogError($"{ContractName} has a higher runtime version ({implementationVersion}) than compile version ({referenceAssemblyVersion}) on .NET Desktop framework {target}. This will break bindingRedirects. If the live reference was replaced with a harvested reference you may need to set <Preserve>true</Preserve> on your reference assembly ProjectReference."); } string fileName = Path.GetFileName(implementationAssembly.PackagePath); if (implementationFiles.ContainsKey(fileName)) { Log.LogError($"{ContractName} includes both {implementationAssembly.LocalPath} and {implementationFiles[fileName].LocalPath} an on {target} which have the same name and will clash when both packages are used."); } else { implementationFiles[fileName] = implementationAssembly; } if (!implementationAssembly.TargetFramework.Equals(fx) && !runtimeFxSuppression.Contains(fx.ToString())) { // the selected asset wasn't an exact framework match, let's see if we have an exact match in any other runtime asset. var matchingFxAssets = _report.UnusedAssets.Where(i => i.TargetFramework != null && i.TargetFramework.Equals(fx) && // exact framework // Same file Path.GetFileName(i.PackagePath).Equals(fileName, StringComparison.OrdinalIgnoreCase) && // Is implementation (i.PackagePath.StartsWith("lib") || i.PackagePath.StartsWith("runtimes")) && // is not the same source file as was already selected i.LocalPath != implementationAssembly.LocalPath); if (matchingFxAssets.Any()) { Log.LogError($"When targeting {target} {ContractName} will use {implementationAssembly.LocalPath} which targets {implementationAssembly.TargetFramework.GetShortFolderName()} but {String.Join(";", matchingFxAssets.Select(i => i.PackagePath))} targets {fx.GetShortFolderName()} specifically."); } } } } } } } } // Set output items AllSupportedFrameworks = _frameworks.Values.Where(fx => fx.SupportedVersion != null).Select(fx => fx.ToItem()).OrderBy(i => i.ItemSpec).ToArray(); }
public override bool Execute() { LoadFiles(); LoadFrameworks(); var report = new PackageReport() { Id = PackageId, Version = PackageVersion, SupportedFrameworks = new Dictionary <string, string>() }; string package = $"{PackageId}/{PackageVersion}"; foreach (var framework in _frameworks.OrderBy(f => f.Key.ToString())) { var fx = framework.Key; var runtimeIds = framework.Value; var compileAssets = _resolver.ResolveCompileAssets(fx, PackageId); bool hasCompileAsset, hasCompilePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Compile", package, fx.ToString(), compileAssets, out hasCompileAsset, out hasCompilePlaceHolder); MarkUsed(compileAssets); // start by making sure it has some asset available for compile var isSupported = hasCompileAsset || hasCompilePlaceHolder; if (runtimeIds.All(rid => !String.IsNullOrEmpty(rid))) { // Add Framework only (compile) target if all RIDs are non-empty. // This acts as a compile target for a framework that requires a RID for runtime. var reportTarget = new Target() { Framework = fx.ToString(), RuntimeID = null, CompileAssets = compileAssets.Select(c => GetPackageAssetFromTargetPath(c)).ToArray() }; report.Targets.Add(fx.ToString(), reportTarget); } foreach (var runtimeId in runtimeIds) { string target = String.IsNullOrEmpty(runtimeId) ? fx.ToString() : $"{fx}/{runtimeId}"; var runtimeAssets = _resolver.ResolveRuntimeAssets(fx, runtimeId); bool hasRuntimeAsset, hasRuntimePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Runtime", package, target, runtimeAssets, out hasRuntimeAsset, out hasRuntimePlaceHolder); MarkUsed(runtimeAssets); if (!FrameworkUtilities.IsGenerationMoniker(fx) && !fx.IsPCL) { // only look at runtime assets for runnable frameworks. isSupported &= (hasCompileAsset && hasRuntimeAsset) || // matching assets (hasCompilePlaceHolder && hasRuntimeAsset) || // private runtime (hasCompilePlaceHolder && hasRuntimePlaceHolder); // placeholders } var nativeAssets = _resolver.ResolveNativeAssets(fx, runtimeId); MarkUsed(nativeAssets); var reportTarget = new Target() { Framework = fx.ToString(), RuntimeID = runtimeId, CompileAssets = compileAssets.Select(c => GetPackageAssetFromTargetPath(c)).ToArray(), RuntimeAssets = runtimeAssets.Select(r => GetPackageAssetFromTargetPath(r)).ToArray(), NativeAssets = nativeAssets.Select(n => GetPackageAssetFromTargetPath(n)).ToArray() }; report.Targets[target] = reportTarget; } if (isSupported) { // Find version // first try the resolved compile asset for this package var refAssm = compileAssets.FirstOrDefault(r => !NuGetAssetResolver.IsPlaceholder(r))?.Substring(PackageId.Length + 1); if (refAssm == null) { // if we didn't have a compile asset it means this framework is supported inbox with a placeholder // resolve the assets without placeholders to pick up the netstandard reference assembly. compileAssets = _resolverWithoutPlaceholders.ResolveCompileAssets(fx); refAssm = compileAssets.FirstOrDefault(r => !NuGetAssetResolver.IsPlaceholder(r)); } var version = "unknown"; if (refAssm != null) { version = _targetPathToPackageItem[AggregateNuGetAssetResolver.AsPackageSpecificTargetPath(PackageId, refAssm)].Version?.ToString() ?? version; } report.SupportedFrameworks.Add(fx.ToString(), version); } } report.UnusedAssets = _unusedTargetPaths.Select(tp => GetPackageAssetFromTargetPath(tp)).ToArray(); report.Save(ReportFile); return(!Log.HasLoggedErrors); }
private void HarvestSupportedFrameworks() { List <ITaskItem> supportedFrameworks = new List <ITaskItem>(); AggregateNuGetAssetResolver resolver = new AggregateNuGetAssetResolver(RuntimeFile); string packagePath = Path.Combine(PackagesFolder, PackageId, PackageVersion); // add the primary package resolver.AddPackageItems(PackageId, GetPackageItems(packagePath)); if (RuntimePackages != null) { // add any split runtime packages foreach (var runtimePackage in RuntimePackages) { var runtimePackageId = runtimePackage.ItemSpec; var runtimePackageVersion = runtimePackage.GetMetadata("Version"); resolver.AddPackageItems(runtimePackageId, GetPackageItems(PackagesFolder, runtimePackageId, runtimePackageVersion)); } } // create a resolver that can be used to determine the API version for inbox assemblies // since inbox assemblies are represented with placeholders we can remove the placeholders // and use the netstandard reference assembly to determine the API version var filesWithoutPlaceholders = GetPackageItems(packagePath) .Where(f => !NuGetAssetResolver.IsPlaceholder(f)); NuGetAssetResolver resolverWithoutPlaceholders = new NuGetAssetResolver(RuntimeFile, filesWithoutPlaceholders); string package = $"{PackageId}/{PackageVersion}"; foreach (var framework in Frameworks) { var runtimeIds = framework.GetMetadata("RuntimeIDs")?.Split(';'); NuGetFramework fx; try { fx = FrameworkUtilities.ParseNormalized(framework.ItemSpec); } catch (Exception ex) { Log.LogError($"Could not parse Framework {framework.ItemSpec}. {ex}"); continue; } if (fx.Equals(NuGetFramework.UnsupportedFramework)) { Log.LogError($"Did not recognize {framework.ItemSpec} as valid Framework."); continue; } var compileAssets = resolver.ResolveCompileAssets(fx, PackageId); bool hasCompileAsset, hasCompilePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Compile", package, fx.ToString(), compileAssets, out hasCompileAsset, out hasCompilePlaceHolder); // start by making sure it has some asset available for compile var isSupported = hasCompileAsset || hasCompilePlaceHolder; if (!isSupported) { Log.LogMessage(LogImportance.Low, $"Skipping {fx} because it is not supported."); continue; } foreach (var runtimeId in runtimeIds) { string target = String.IsNullOrEmpty(runtimeId) ? fx.ToString() : $"{fx}/{runtimeId}"; var runtimeAssets = resolver.ResolveRuntimeAssets(fx, runtimeId); bool hasRuntimeAsset, hasRuntimePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Runtime", package, target, runtimeAssets, out hasRuntimeAsset, out hasRuntimePlaceHolder); isSupported &= hasCompileAsset == hasRuntimeAsset; isSupported &= hasCompilePlaceHolder == hasRuntimePlaceHolder; if (!isSupported) { Log.LogMessage(LogImportance.Low, $"Skipping {fx} because it is not supported on {target}."); break; } } if (isSupported) { var supportedFramework = new TaskItem(framework.ItemSpec); supportedFramework.SetMetadata("HarvestedFromPackage", package); // set version // first try the resolved compile asset for this package var refAssm = compileAssets.FirstOrDefault(r => !NuGetAssetResolver.IsPlaceholder(r))?.Substring(PackageId.Length + 1); if (refAssm == null) { // if we didn't have a compile asset it means this framework is supported inbox with a placeholder // resolve the assets without placeholders to pick up the netstandard reference assembly. compileAssets = resolverWithoutPlaceholders.ResolveCompileAssets(fx); refAssm = compileAssets.FirstOrDefault(r => !NuGetAssetResolver.IsPlaceholder(r)); } string version = "unknown"; if (refAssm != null) { version = VersionUtility.GetAssemblyVersion(Path.Combine(packagePath, refAssm))?.ToString() ?? version; } supportedFramework.SetMetadata("Version", version); Log.LogMessage($"Validating version {version} for {supportedFramework.ItemSpec} because it was supported by {PackageId}/{PackageVersion}."); supportedFrameworks.Add(supportedFramework); } } SupportedFrameworks = supportedFrameworks.ToArray(); }
private void ValidateSupport() { var runtimeFxSuppression = GetSuppressionValues(Suppression.PermitRuntimeTargetMonikerMismatch) ?? new HashSet <string>(); ValidationReport report = null; if (ValidationReport != null) { report = CreateValidationReport(); } // validate support for each TxM:RID foreach (var validateFramework in _frameworks.Values) { NuGetFramework fx = validateFramework.Framework; Version supportedVersion = validateFramework.SupportedVersion; var compileAssetPaths = _resolver.ResolveCompileAssets(fx, PackageId); bool hasCompileAsset, hasCompilePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Compile", ContractName, fx.ToString(), compileAssetPaths, out hasCompileAsset, out hasCompilePlaceHolder); if (report != null && validateFramework.RuntimeIds.All(rid => !String.IsNullOrEmpty(rid))) { // Add Framework only (compile) target if all RIDs are non-empty. // This acts as a compile target for a framework that requires a RID for runtime. var reportTarget = new Target() { Framework = fx.ToString(), RuntimeID = null, CompileAssets = compileAssetPaths.Where(c => !NuGetAssetResolver.IsPlaceholder(c)).Select(c => GetPackageAssetFromTargetPath(c)).ToArray() }; report.Targets.Add(fx.ToString(), reportTarget); } // resolve/test for each RID associated with this framework. foreach (string runtimeId in validateFramework.RuntimeIds) { string target = String.IsNullOrEmpty(runtimeId) ? fx.ToString() : $"{fx}/{runtimeId}"; var runtimeAssetPaths = _resolver.ResolveRuntimeAssets(fx, runtimeId); bool hasRuntimeAsset, hasRuntimePlaceHolder; NuGetAssetResolver.ExamineAssets(Log, "Runtime", ContractName, target, runtimeAssetPaths, out hasRuntimeAsset, out hasRuntimePlaceHolder); if (null == supportedVersion) { // Contract should not be supported on this platform. bool permitImplementation = HasSuppression(Suppression.PermitImplementation, target); if (hasCompileAsset && (hasRuntimeAsset & !permitImplementation)) { Log.LogError($"{ContractName} should not be supported on {target} but has both compile and runtime assets."); } else if (hasRuntimeAsset & !permitImplementation) { Log.LogError($"{ContractName} should not be supported on {target} but has runtime assets."); } if (hasRuntimePlaceHolder && hasCompilePlaceHolder) { Log.LogError($"{ContractName} should not be supported on {target} but has placeholders for both compile and runtime which will permit the package to install."); } } else { if (report != null) { var reportTarget = new Target() { Framework = fx.ToString(), RuntimeID = runtimeId, CompileAssets = compileAssetPaths.Where(c => !NuGetAssetResolver.IsPlaceholder(c)).Select(c => GetPackageAssetFromTargetPath(c)).ToArray(), RuntimeAssets = runtimeAssetPaths.Where(r => !NuGetAssetResolver.IsPlaceholder(r)).Select(r => GetPackageAssetFromTargetPath(r)).ToArray() }; report.Targets.Add(target, reportTarget); } if (validateFramework.IsInbox) { if (!hasCompileAsset && !hasCompilePlaceHolder) { Log.LogError($"Framework {fx} should support {ContractName} inbox but was missing a placeholder for compile-time. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } else if (hasCompileAsset) { Log.LogError($"Framework {fx} should support {ContractName} inbox but contained a reference assemblies: {String.Join(", ", compileAssetPaths)}. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } if (!hasRuntimeAsset && !hasRuntimePlaceHolder) { Log.LogError($"Framework {fx} should support {ContractName} inbox but was missing a placeholder for run-time. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } else if (hasRuntimeAsset) { Log.LogError($"Framework {fx} should support {ContractName} inbox but contained a implementation assemblies: {String.Join(", ", runtimeAssetPaths)}. You may need to add <InboxOnTargetFramework Include=\"{fx.GetShortFolderName()}\" /> to your project."); } } else { Version referenceAssemblyVersion = null; if (!hasCompileAsset) { Log.LogError($"{ContractName} should be supported on {target} but has no compile assets."); } else { var referenceAssemblies = compileAssetPaths.Where(IsDll); if (referenceAssemblies.Count() > 1) { Log.LogError($"{ContractName} should only contain a single compile asset for {target}."); } foreach (var referenceAssembly in referenceAssemblies) { referenceAssemblyVersion = _targetPathToPackageItem[referenceAssembly].Version; if (!VersionUtility.IsCompatibleApiVersion(supportedVersion, referenceAssemblyVersion)) { Log.LogError($"{ContractName} should support API version {supportedVersion} on {target} but {referenceAssembly} was found to support {referenceAssemblyVersion?.ToString() ?? "<unknown version>"}."); } } } if (!hasRuntimeAsset && !FrameworkUtilities.IsGenerationMoniker(validateFramework.Framework)) { Log.LogError($"{ContractName} should be supported on {target} but has no runtime assets."); } else { var implementationAssemblies = runtimeAssetPaths.Where(IsDll); Dictionary <string, string> implementationFiles = new Dictionary <string, string>(); foreach (var implementationAssembly in implementationAssemblies) { var packageItem = _targetPathToPackageItem[implementationAssembly]; Version implementationVersion = packageItem.Version; if (!VersionUtility.IsCompatibleApiVersion(supportedVersion, implementationVersion)) { Log.LogError($"{ContractName} should support API version {supportedVersion} on {target} but {implementationAssembly} was found to support {implementationVersion?.ToString() ?? "<unknown version>"}."); } // Previously we only permitted compatible mismatch if Suppression.PermitHigherCompatibleImplementationVersion was specified // this is a permitted thing on every framework but desktop (which requires exact match to ensure bindingRedirects exist) // Now make this the default, we'll check desktop, where it matters, more strictly if (referenceAssemblyVersion != null && !VersionUtility.IsCompatibleApiVersion(referenceAssemblyVersion, implementationVersion)) { Log.LogError($"{ContractName} has mismatched compile ({referenceAssemblyVersion}) and runtime ({implementationVersion}) versions on {target}."); } if (fx.Framework == FrameworkConstants.FrameworkIdentifiers.Net && !referenceAssemblyVersion.Equals(implementationVersion)) { Log.LogError($"{ContractName} has a higher runtime version ({implementationVersion}) than compile version ({referenceAssemblyVersion}) on .NET Desktop framework {target}. This will break bindingRedirects."); } string fileName = Path.GetFileName(implementationAssembly); if (implementationFiles.ContainsKey(fileName)) { Log.LogError($"{ContractName} includes both {implementationAssembly} and {implementationFiles[fileName]} an on {target} which have the same name and will clash when both packages are used."); } else { implementationFiles[fileName] = implementationAssembly; } if (packageItem.TargetFramework != fx && !runtimeFxSuppression.Contains(fx.ToString())) { // the selected asset wasn't an exact framework match, let's see if we have an exact match in any other runtime asset. var matchingFxAssets = _targetPathToPackageItem.Values.Where(i => i.TargetFramework == fx && // exact framework // Same file Path.GetFileName(i.TargetPath).Equals(fileName, StringComparison.OrdinalIgnoreCase) && // Is implementation (i.TargetPath.StartsWith("lib") || i.TargetPath.StartsWith("runtimes")) && // is not the same source file as was already selected i.SourcePath != packageItem.SourcePath); if (matchingFxAssets.Any()) { Log.LogError($"When targeting {target} {ContractName} will use {implementationAssembly} which targets {packageItem.TargetFramework.GetShortFolderName()} but {String.Join(";", matchingFxAssets.Select(i => i.TargetPath))} targets {fx.GetShortFolderName()} specifically."); } } } } } } } } // Set output items AllSupportedFrameworks = _frameworks.Values.Where(fx => fx.SupportedVersion != null).Select(fx => fx.ToItem()).OrderBy(i => i.ItemSpec).ToArray(); if (!String.IsNullOrEmpty(ValidationReport)) { report.Save(ValidationReport); } }