File: PackageCreation\Authoring\ManifestMetadata.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using NuGet.Packaging.Core;
using NuGet.Packaging.PackageCreation.Resources;
using NuGet.Versioning;

namespace NuGet.Packaging
{
    /// <summary>
    /// Manifest (user created .nuspec) file metadata model
    /// </summary>
    public class ManifestMetadata : IPackageMetadata
    {
        private string? _minClientVersionString;

        private IEnumerable<string> _authors = Enumerable.Empty<string>();
        private IEnumerable<string> _owners = Enumerable.Empty<string>();

        private string? _iconUrl;
        private string? _licenseUrl;
        private string? _projectUrl;

        public ManifestMetadata()
        {
        }

        /// <summary>
        /// Constructs a ManifestMetadata instance from an IPackageMetadata instance
        /// </summary>
        public ManifestMetadata(IPackageMetadata copy)
        {
            Id = copy.Id?.Trim();
            Version = copy.Version;
            Title = copy.Title?.Trim();
            Authors = copy.Authors;
            Owners = copy.Owners;
            Tags = string.IsNullOrEmpty(copy.Tags) ? null : copy.Tags!.Trim();
            Serviceable = copy.Serviceable;
            _licenseUrl = copy.LicenseUrl?.OriginalString;
            _projectUrl = copy.ProjectUrl?.OriginalString;
            _iconUrl = copy.IconUrl?.OriginalString;
            RequireLicenseAcceptance = copy.RequireLicenseAcceptance;
            EmitRequireLicenseAcceptance = (copy as PackageBuilder)?.EmitRequireLicenseAcceptance ?? true;
            Description = copy.Description?.Trim();
            Copyright = copy.Copyright?.Trim();
            Summary = copy.Summary?.Trim();
            ReleaseNotes = copy.ReleaseNotes?.Trim();
            Language = copy.Language?.Trim();
            DependencyGroups = copy.DependencyGroups;
            FrameworkReferences = copy.FrameworkReferences;
            FrameworkReferenceGroups = copy.FrameworkReferenceGroups;
            PackageAssemblyReferences = copy.PackageAssemblyReferences;
            PackageTypes = copy.PackageTypes;
            MinClientVersionString = copy.MinClientVersion?.ToString();
            ContentFiles = copy.ContentFiles;
            DevelopmentDependency = copy.DevelopmentDependency;
            Repository = copy.Repository;
            LicenseMetadata = copy.LicenseMetadata;
            Icon = copy.Icon;
            Readme = copy.Readme;
        }

        public string? MinClientVersionString
        {
            get { return _minClientVersionString; }
            set
            {
                Version? version = null;
                if (!String.IsNullOrEmpty(value) && !System.Version.TryParse(value, out version))
                {
                    throw new InvalidDataException(NuGetResources.Manifest_InvalidMinClientVersion);
                }

                _minClientVersionString = value;
                MinClientVersion = version;
            }
        }

        public Version? MinClientVersion { get; private set; }

        public string? Id { get; set; }

        public NuGetVersion? Version { get; set; }

        public string? Title { get; set; }

        public IEnumerable<string> Authors
        {
            get { return _authors; }
            set { _authors = value ?? Enumerable.Empty<string>(); }
        }

        public IEnumerable<string> Owners
        {
            get { return _owners; }
            set { _owners = value ?? Enumerable.Empty<string>(); }
        }

        // The (Icon/License/Project)Url properties have backing strings as we need to be able to differentiate
        //   between the property not being set (valid) and set to an empty value (invalid).
        public void SetIconUrl(string? iconUrl)
        {
            _iconUrl = iconUrl;
        }

        public Uri? IconUrl
        {
            get
            {
                if (_iconUrl == null)
                {
                    return null;
                }

                return new Uri(_iconUrl);
            }
        }

        public string? Icon { get; set; }

        public void SetLicenseUrl(string? licenseUrl)
        {
            _licenseUrl = licenseUrl;
        }

        public Uri? LicenseUrl
        {
            get
            {
                if (_licenseUrl == null)
                {
                    return null;
                }

                return new Uri(_licenseUrl);
            }
        }

        public void SetProjectUrl(string? projectUrl)
        {
            _projectUrl = projectUrl;
        }

        public Uri? ProjectUrl
        {
            get
            {
                if (_projectUrl == null)
                {
                    return null;
                }

                return new Uri(_projectUrl);
            }
        }

        public bool RequireLicenseAcceptance { get; set; }

        public bool EmitRequireLicenseAcceptance { get; set; } = true;

        public bool DevelopmentDependency { get; set; }

        public string? Description { get; set; }

        public string? Summary { get; set; }

        public string? ReleaseNotes { get; set; }

        public string? Copyright { get; set; }

        public string? Language { get; set; }

        public string? Tags { get; set; }

        public string? Readme { get; set; }

        public bool Serviceable { get; set; }

        public RepositoryMetadata? Repository { get; set; }

        private IEnumerable<PackageDependencyGroup> _dependencyGroups = [];
        public IEnumerable<PackageDependencyGroup> DependencyGroups
        {
            get
            {
                return _dependencyGroups;
            }
            set
            {
                _dependencyGroups = MergeDependencyGroups(value);
            }
        }

        public IEnumerable<FrameworkReferenceGroup> FrameworkReferenceGroups { get; set; } = [];

        public IEnumerable<FrameworkAssemblyReference> FrameworkReferences { get; set; } = [];

        private IEnumerable<PackageReferenceSet> _packageAssemblyReferences = [];

        public IEnumerable<PackageReferenceSet> PackageAssemblyReferences
        {
            get
            {
                return _packageAssemblyReferences;
            }

            set
            {
                _packageAssemblyReferences = MergePackageAssemblyReferences(value);
            }
        }

        private static IEnumerable<PackageReferenceSet> MergePackageAssemblyReferences(IEnumerable<PackageReferenceSet> referenceSets)
        {
            if (referenceSets == null)
            {
                return Enumerable.Empty<PackageReferenceSet>();
            }

            var referenceSetGroups = referenceSets.GroupBy(set => set.TargetFramework);
            var groupedReferenceSets = referenceSetGroups.Select(group => new PackageReferenceSet(group.Key, group.SelectMany(g => g.References)))
                                                            .ToList();

            int nullTargetFrameworkIndex = groupedReferenceSets.FindIndex(set => set.TargetFramework == null);
            if (nullTargetFrameworkIndex > -1)
            {
                var nullFxReferenceSet = groupedReferenceSets[nullTargetFrameworkIndex];
                groupedReferenceSets.RemoveAt(nullTargetFrameworkIndex);
                groupedReferenceSets.Insert(0, nullFxReferenceSet);
            }

            return groupedReferenceSets;
        }

        public IEnumerable<ManifestContentFiles> ContentFiles { get; set; } = new List<ManifestContentFiles>();

        public IEnumerable<PackageType> PackageTypes { get; set; } = new List<PackageType>();

        public LicenseMetadata? LicenseMetadata { get; set; } = null;

        private static IEnumerable<PackageDependencyGroup> MergeDependencyGroups(IEnumerable<PackageDependencyGroup> actualDependencyGroups)
        {
            if (actualDependencyGroups == null)
            {
                return Enumerable.Empty<PackageDependencyGroup>();
            }

            var dependencyGroups = actualDependencyGroups.Select(CreatePackageDependencyGroup);

            // group the dependency sets with the same target framework together.
            var dependencySetGroups = dependencyGroups.GroupBy(set => set.TargetFramework);
            var groupedDependencySets = dependencySetGroups.Select(group => new PackageDependencyGroup(group.Key, new HashSet<PackageDependency>(group.SelectMany(g => g.Packages))))
                                                            .ToList();
            // move the group with the any target framework (if any) to the front just for nicer display in UI
            int anyTargetFrameworkIndex = groupedDependencySets.FindIndex(set => set.TargetFramework.IsAny);
            if (anyTargetFrameworkIndex > -1)
            {
                var anyFxDependencySet = groupedDependencySets[anyTargetFrameworkIndex];
                groupedDependencySets.RemoveAt(anyTargetFrameworkIndex);
                groupedDependencySets.Insert(0, anyFxDependencySet);
            }

            return groupedDependencySets;
        }

        private static PackageDependencyGroup CreatePackageDependencyGroup(PackageDependencyGroup dependencyGroup)
        {
            ISet<PackageDependency> dependencies;

            if (dependencyGroup.Packages == null)
            {
                dependencies = new HashSet<PackageDependency>();
            }
            else
            {
                var dependenciesList = dependencyGroup.Packages.Select(dependency =>
                    new PackageDependency(
                        dependency.Id.Trim(),
                        dependency.VersionRange,
                        dependency.Include,
                        dependency.Exclude)).ToList();
                dependencies = new HashSet<PackageDependency>(dependenciesList);
            }

            return new PackageDependencyGroup(dependencyGroup.TargetFramework, dependencies);
        }

        /// <summary>
        /// Checks that the metadata in the nuspec is enough to create a package
        /// </summary>
        /// <returns>A iterable collection with the validation error messages</returns>
        /// <remarks>Error codes are not associated with the validation error messages returned</remarks>
        public IEnumerable<string> Validate()
        {
            if (String.IsNullOrEmpty(Id))
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Id");
            }
            else
            {
                if (Id!.Length > PackageIdValidator.MaxPackageIdLength)
                {
                    yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_IdMaxLengthExceeded);
                }
                else if (!PackageIdValidator.IsValidPackageId(Id))
                {
                    yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.InvalidPackageId, Id);
                }
            }

            if (Version == null)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Version");
            }

            if ((Authors == null || !Authors.Any(author => !String.IsNullOrEmpty(author))) && !PackageTypes.Contains(PackageType.SymbolsPackage))
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Authors");
            }

            if (String.IsNullOrEmpty(Description))
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_RequiredMetadataMissing, "Description");
            }

            if (_licenseUrl == String.Empty)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_UriCannotBeEmpty, "LicenseUrl");
            }

            if (_iconUrl == String.Empty)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_UriCannotBeEmpty, "IconUrl");
            }

            if (_projectUrl == String.Empty)
            {
                yield return String.Format(CultureInfo.CurrentCulture, NuGetResources.Manifest_UriCannotBeEmpty, "ProjectUrl");
            }

            if (Icon == string.Empty)
            {
                yield return NuGetResources.IconMissingRequiredValue;
            }

            if (Readme == string.Empty)
            {
                yield return NuGetResources.ReadmeMissingRequiredValue;
            }

            if (RequireLicenseAcceptance)
            {
                if ((string.IsNullOrWhiteSpace(_licenseUrl) && LicenseMetadata == null))
                {
                    yield return NuGetResources.Manifest_RequireLicenseAcceptanceRequiresLicenseUrl;
                }

                if (!EmitRequireLicenseAcceptance)
                {
                    yield return NuGetResources.Manifest_RequireLicenseAcceptanceRequiresEmit;
                }
            }

            if (_licenseUrl != null && LicenseMetadata != null && (string.IsNullOrWhiteSpace(_licenseUrl) || !LicenseUrl!.Equals(LicenseMetadata.LicenseUrl)))
            {
                yield return NuGetResources.Manifest_LicenseUrlCannotBeUsedWithLicenseMetadata;
            }
        }
    }
}