Rasagar/Library/PackageCache/com.unity.package-validation-suite/Editor/ValidationSuite/ValidationTests/ManifestValidation.cs
2024-08-26 23:07:20 +03:00

180 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using PvpXray;
using Semver;
using UnityEditor.PackageManager.ValidationSuite.ValidationTests.Standards;
namespace UnityEditor.PackageManager.ValidationSuite.ValidationTests
{
internal class ManifestValidation : BaseValidation
{
readonly SemVerCheckUS0005 semVerCheckUS0005 = new SemVerCheckUS0005();
readonly PackageNamingConventionUS0006 packageNamingConventionUs0006 = new PackageNamingConventionUS0006();
readonly ValidPackageManifestUS0007 validPackageManifestUs0007 = new ValidPackageManifestUS0007();
readonly DependencyVersionsCorrectlyIndicatedUS0084 dependencyVersionsCorrectlyIndicatedUs0084 = new DependencyVersionsCorrectlyIndicatedUS0084();
internal override List<IStandardChecker> ImplementedStandardsList =>
new List<IStandardChecker>
{
semVerCheckUS0005,
packageNamingConventionUs0006,
validPackageManifestUs0007,
dependencyVersionsCorrectlyIndicatedUs0084,
};
internal static readonly int MaxDisplayNameLength = 50;
internal static readonly string k_DocsFilePath = "manifest_validation_error.html";
public ManifestValidation()
{
TestName = "Manifest Validation";
TestDescription = "Validate that the information found in the manifest is well formatted.";
TestCategory = TestCategory.DataValidation;
SupportedValidations = new[] { ValidationType.CI, ValidationType.LocalDevelopment, ValidationType.LocalDevelopmentInternal, ValidationType.Promotion, ValidationType.VerifiedSet };
//TODO: this validation contains many standards check, not sure if all of them should be run against FeatureSet
//We might need to somehow split the checks into their own classes so we can filter on that.
SupportedPackageTypes = new[] { PackageType.Template, PackageType.Tooling, PackageType.FeatureSet };
}
protected override void Run()
{
// Start by declaring victory
TestState = TestState.Succeeded;
var manifestData = Context.ProjectPackageInfo;
if (manifestData == null)
{
AddError("Manifest not available. Not validating manifest contents.");
return;
}
ValidateManifestMarshalling(manifestData);
validPackageManifestUs0007.Check(manifestData, Context.ValidationType);
packageNamingConventionUs0006.Check(manifestData.name, manifestData.displayName);
ValidateAuthor(manifestData);
semVerCheckUS0005.Check(manifestData.version);
ValidateDependencies();
dependencyVersionsCorrectlyIndicatedUs0084.Check(manifestData);
}
private void ValidateManifestMarshalling(ManifestData manifestData)
{
foreach (var marshallError in manifestData.decodingErrors)
{
AddError(marshallError.Message);
}
}
private void ValidateDependencies()
{
// Check if there are dependencies to check and exit early
if (Context.ProjectPackageInfo.dependencies.Count == 0)
return;
var isFeature = Context.ProjectPackageInfo.PackageType == PackageType.FeatureSet;
// Make sure all dependencies are already published in production.
foreach (var dependency in Context.ProjectPackageInfo.dependencies)
{
// Check if the dependency semver is valid before doing anything else
SemVersion depVersion;
if (!isFeature && !SemVersion.TryParse(dependency.Value, out depVersion))
{
AddError(@"In package.json, dependency ""{0}"" : ""{1}"" needs to be a valid ""Semver"". {2}", dependency.Key, dependency.Value, ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "dependency-needs-to-be-a-valid-semver"));
continue;
}
var dependencyInfo = Utilities.UpmListOffline(dependency.Key).FirstOrDefault();
// Built in packages are shipped with the editor, and will therefore never be published to production.
if (dependencyInfo != null && dependencyInfo.source == PackageSource.BuiltIn)
{
continue;
}
// Check if this package's dependencies are in production. That is a requirement for promotion.
// make sure to check the version actually resolved by upm and not the one necessarily listed by the package
// If the packageId is included in Context.packageIdsForPromotion then we can skip this check, as the package
// is expected to be promoted by another process
var version = dependencyInfo != null ? dependencyInfo.version : dependency.Value;
var packageId = new PackageId(dependency.Key, version);
if (Context.ValidationType != ValidationType.VerifiedSet && (Context.packageIdsForPromotion == null || Context.packageIdsForPromotion.Length < 1 || !Context.packageIdsForPromotion.Contains(packageId.Id)) && !Utilities.PackageExistsOnProduction(packageId))
{
// ignore if package is part of the context already
if (Context.ValidationType == ValidationType.Promotion || Context.ValidationType == ValidationType.AssetStore)
AddError("Package dependency {0} is not promoted in production. {1}", packageId, ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "package-dependency-[packageID]-is-not-published-in-production"));
else
AddWarning("Package dependency {0} must be promoted to production before this package is promoted to production. (Except for core packages)", packageId);
}
// only check this in CI or internal local development
// Make sure the dependencies I ask for that exist in the project have the good version
if (Context.ValidationType == ValidationType.CI || Context.ValidationType == ValidationType.LocalDevelopmentInternal)
{
PackageInfo packageInfo = Utilities.UpmListOffline(dependency.Key).FirstOrDefault();
if (packageInfo != null && packageInfo.version != dependency.Value)
{
AddWarning("Package {2} depends on {0}, which is found locally but with another version. To remove this warning, in the package.json file of {2}, change the dependency of {0}@{1} to {0}@{3}.", dependency.Key, dependency.Value, Context.ProjectPackageInfo.name, packageInfo.version);
}
}
}
// TODO: Validate the Package dependencies meet the minimum editor requirement (eg: 2018.3 minimum for package A is 2, make sure I don't use 1)
}
private void ValidateAuthor(ManifestData manifestData)
{
if (manifestData.IsAuthoredByUnity())
{
ValidateUnityAuthor(manifestData);
}
else
{
ValidateNonUnityAuthor(manifestData);
}
}
private void ValidateUnityAuthor(ManifestData manifestData)
{
if (manifestData.author != null)
{
// make sure it is not present in order to have a unified presentation of the author for all of our packages
AddError("A Unity package must not have an author field. Please remove the field. {0}",
ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath,
"a_unity_package_must_not_have_an_author_field"));
}
}
// the author field is required for non-unity packages
private void ValidateNonUnityAuthor(ManifestData manifestData)
{
// if authordetails is set, then author == ""
if (String.IsNullOrEmpty(manifestData.author) && manifestData.authorDetails == null)
{
AddError(
"The `author` field is mandatory. Please add an `author` field in your package.json file",
ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "author_is_mandatory"));
return;
}
if (!String.IsNullOrEmpty(manifestData.author) && manifestData.authorDetails == null)
{
return; // valid
}
// non unity packages should have either a string or AuthorDetails { name: ""*, email: "", url: ""}
if (String.IsNullOrEmpty(manifestData.authorDetails.name))
{
AddError(
"Invalid `author` field. The `author` field in your package.json file can be a string or an object ( name, email, url ), where `name` is mandatory. {0}",
ErrorDocumentation.GetLinkMessage(ManifestValidation.k_DocsFilePath, "author_is_invalid"));
}
}
}
}