#pragma warning disable SA1512,SA1124 // Single-line comments should not be followed by blank line using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Xml; using System.Xml.Linq; using JetBrains.Annotations; using NugetForUnity.Models; using NugetForUnity.PluginAPI.Models; using UnityEngine; #region No ReShaper // ReSharper disable All // needed because 'JetBrains.Annotations.NotNull' and 'System.Diagnostics.CodeAnalysis.NotNull' collide if this file is compiled with a never version of Unity / C# using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; // ReSharper restore All #endregion #pragma warning restore SA1512,SA1124 // Single-line comments should not be followed by blank line namespace NugetForUnity { /// /// Represents a .nuspec file used to store metadata for a NuGet package. /// /// /// At a minimum, Id, Version, Description, and Authors is required. Everything else is optional. /// See more info here: https://docs.microsoft.com/en-us/nuget/schema/nuspec. /// public class NuspecFile : NugetPackageIdentifier, INuspecFile { /// /// Initializes a new instance of the class. /// public NuspecFile() { Dependencies = new List(); Files = new List(); } /// /// Project can register its own method that sets default values for newly created nuspec file. /// public static event Action ProjectSpecificNuspecInitializer; /// /// Gets or sets the source control branch the package is from. /// public string RepositoryBranch { get; set; } /// /// Gets or sets the source control commit the package is from. /// public string RepositoryCommit { get; set; } /// /// Gets or sets the type of source control software that the package's source code resides in. /// public string RepositoryType { get; set; } /// /// Gets or sets the url for the location of the package's source code. /// public string RepositoryUrl { get; set; } /// /// Gets or sets the title of the NuGet package. /// public string Title { get; set; } /// /// Gets or sets the owners of the NuGet package. /// public string Owners { get; set; } /// /// Gets or sets the URL for the location of the license of the NuGet package. /// public string LicenseUrl { get; set; } /// /// Gets or sets the URL for the location of the project web-page of the NuGet package. /// public string ProjectUrl { get; set; } /// /// Gets or sets the URL for the location of the icon of the NuGet package. /// public string IconUrl { get; set; } /// /// Gets the path to a icon file. The path is relative to the root folder of the package. This is a alternative to using a URL /// . /// public string Icon { get; private set; } /// /// Gets the full path to a icon file. This is only set if the .nuspec file contains a . This is a alternative to using a URL /// . /// public string IconFilePath { get; private set; } /// /// Gets or sets a value indicating whether the license of the NuGet package needs to be accepted in order to use it. /// public bool RequireLicenseAcceptance { get; set; } /// /// Gets the NuGet packages that this NuGet package depends on. /// [NotNull] public List Dependencies { get; } /// /// Gets or sets the release notes of the NuGet package. /// public string ReleaseNotes { get; set; } /// /// Gets or sets the copyright of the NuGet package. /// public string Copyright { get; set; } /// /// Gets or sets the tags of the NuGet package. /// public string Tags { get; set; } /// /// Gets the list of content files listed in the .nuspec file. /// [NotNull] public List Files { get; } /// /// Gets or sets the description of the NuGet package. /// public string Description { get; set; } /// /// Gets or sets the description of the NuGet package. /// public string Summary { get; set; } /// /// Gets or sets the authors of the NuGet package. /// public string Authors { get; set; } = string.Empty; /// /// Creates a new nuspec file with default values for a new package. /// /// Optional name for the new package. Defaults to "MyPackage". /// Newly created nuspec file. public static NuspecFile CreateDefault(string packageName = "MyPackage") { var result = new NuspecFile { Id = "CompanyName." + packageName, Title = packageName, Version = "0.0.1", Authors = "Your Name", Owners = "Your Name", LicenseUrl = "http://your_license_url_here", ProjectUrl = "http://your_project_url_here", Description = "A description of what this package is and does.", Summary = "A brief description of what this package is and does.", ReleaseNotes = "Notes for this specific release", Copyright = "Copyright 2017", IconUrl = "https://www.nuget.org/Content/Images/packageDefaultIcon-50x50.png", }; ProjectSpecificNuspecInitializer?.Invoke(result); return result; } /// /// Loads the .nuspec file inside the .nupkg file at the given file path. /// /// The file path to the .nupkg file to load. /// The .nuspec file loaded from inside the .nupkg file. [NotNull] public static NuspecFile FromNupkgFile([NotNull] string nupkgFilePath) { var nuspec = new NuspecFile(); try { // get the .nuspec file from inside the .nupkg using (var zip = ZipFile.OpenRead(nupkgFilePath)) { var entry = zip.Entries.First(x => x.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase)); using (var stream = entry.Open()) { nuspec = Load(stream).SetIconFilePath(nupkgFilePath); } } } catch (Exception e) { Debug.LogException(e); Debug.LogErrorFormat("Package could not be read: {0}", nupkgFilePath); nuspec.Description = $"COULD NOT LOAD {nupkgFilePath}"; } return nuspec; } /// /// Loads a .nuspec file at the given file path. /// /// The full file path to the .nuspec file to load. /// The newly loaded . [NotNull] public static NuspecFile Load([NotNull] string filePath) { return Load(XDocument.Load(filePath)).SetIconFilePath(filePath); } /// /// Loads a .nuspec file inside the given stream. /// /// The stream containing the .nuspec file to load. /// The newly loaded . [NotNull] public static NuspecFile Load([NotNull] Stream stream) { using (var reader = XmlReader.Create(stream, new XmlReaderSettings { XmlResolver = null })) { var document = XDocument.Load(reader); return Load(document); } } /// /// Loads a .nuspec file inside the given . /// /// The .nuspec file as an . /// The newly loaded . [NotNull] public static NuspecFile Load([NotNull] XDocument nuspecDocument) { var nuspec = new NuspecFile(); var nuspecNamespace = nuspecDocument.Root?.GetDefaultNamespace().ToString() ?? string.Empty; var package = nuspecDocument.Element(XName.Get("package", nuspecNamespace)); var metadata = package?.Element(XName.Get("metadata", nuspecNamespace)) ?? throw new InvalidOperationException("There is on package->metadata element inside the nuspec file."); nuspec.Id = (string)metadata.Element(XName.Get("id", nuspecNamespace)) ?? string.Empty; nuspec.Version = (string)metadata.Element(XName.Get("version", nuspecNamespace)) ?? string.Empty; nuspec.Title = (string)metadata.Element(XName.Get("title", nuspecNamespace)) ?? string.Empty; nuspec.Authors = (string)metadata.Element(XName.Get("authors", nuspecNamespace)) ?? string.Empty; nuspec.Owners = (string)metadata.Element(XName.Get("owners", nuspecNamespace)) ?? string.Empty; nuspec.LicenseUrl = (string)metadata.Element(XName.Get("licenseUrl", nuspecNamespace)) ?? string.Empty; nuspec.ProjectUrl = (string)metadata.Element(XName.Get("projectUrl", nuspecNamespace)) ?? string.Empty; nuspec.IconUrl = (string)metadata.Element(XName.Get("iconUrl", nuspecNamespace)) ?? string.Empty; nuspec.Icon = (string)metadata.Element(XName.Get("icon", nuspecNamespace)) ?? string.Empty; nuspec.RequireLicenseAcceptance = bool.Parse((string)metadata.Element(XName.Get("requireLicenseAcceptance", nuspecNamespace)) ?? "False"); nuspec.Description = (string)metadata.Element(XName.Get("description", nuspecNamespace)) ?? string.Empty; nuspec.Summary = (string)metadata.Element(XName.Get("summary", nuspecNamespace)) ?? string.Empty; nuspec.ReleaseNotes = (string)metadata.Element(XName.Get("releaseNotes", nuspecNamespace)) ?? string.Empty; nuspec.Copyright = (string)metadata.Element(XName.Get("copyright", nuspecNamespace)); nuspec.Tags = (string)metadata.Element(XName.Get("tags", nuspecNamespace)) ?? string.Empty; var repositoryElement = metadata.Element(XName.Get("repository", nuspecNamespace)); if (repositoryElement != null) { nuspec.RepositoryType = (string)repositoryElement.Attribute(XName.Get("type")) ?? string.Empty; nuspec.RepositoryUrl = (string)repositoryElement.Attribute(XName.Get("url")) ?? string.Empty; nuspec.RepositoryBranch = (string)repositoryElement.Attribute(XName.Get("branch")) ?? string.Empty; nuspec.RepositoryCommit = (string)repositoryElement.Attribute(XName.Get("commit")) ?? string.Empty; } var dependenciesElement = metadata.Element(XName.Get("dependencies", nuspecNamespace)); if (dependenciesElement != null) { // Dependencies specified for specific target frameworks foreach (var frameworkGroup in dependenciesElement.Elements(XName.Get("group", nuspecNamespace))) { var group = new NugetFrameworkGroup { TargetFramework = ConvertFromNupkgTargetFrameworkName((string)frameworkGroup.Attribute("targetFramework") ?? string.Empty), }; foreach (var dependencyElement in frameworkGroup.Elements(XName.Get("dependency", nuspecNamespace))) { var id = (string)dependencyElement.Attribute("id") ?? string.Empty; var version = (string)dependencyElement.Attribute("version") ?? string.Empty; var dependency = new NugetPackageIdentifier(id, version); group.Dependencies.Add(dependency); } nuspec.Dependencies.Add(group); } // Flat dependency list if (nuspec.Dependencies.Count == 0) { var group = new NugetFrameworkGroup(); foreach (var dependencyElement in dependenciesElement.Elements(XName.Get("dependency", nuspecNamespace))) { var id = (string)dependencyElement.Attribute("id") ?? string.Empty; var version = (string)dependencyElement.Attribute("version") ?? string.Empty; var dependency = new NugetPackageIdentifier(id, version); group.Dependencies.Add(dependency); } nuspec.Dependencies.Add(group); } } var filesElement = package.Element(XName.Get("files", nuspecNamespace)); if (filesElement != null) { foreach (var fileElement in filesElement.Elements(XName.Get("file", nuspecNamespace))) { var file = new NuspecContentFile { Source = (string)fileElement.Attribute("src") ?? string.Empty, Target = (string)fileElement.Attribute("target") ?? string.Empty, }; nuspec.Files.Add(file); } } return nuspec; } /// /// Full filename, including full path, of this NuGet package's file in a local NuGet repository. /// /// /// The existence of the file is verified. /// /// Path to the local repository's root directory. /// The full path to the file, if it exists in the repository, or null otherwise. [CanBeNull] public string GetLocalPackageFilePath([NotNull] string baseDirectoryPath) { // Find this package's file in the repository. var files = Directory.GetFiles(baseDirectoryPath, PackageFileName, SearchOption.AllDirectories); // If we found any, return the first found; otherwise return null. return files.FirstOrDefault(); } /// /// Saves a to the given file-path, automatically overwriting. /// /// The full file-path to the .nuspec file to save. public void Save([NotNull] string filePath) { var file = new XDocument(); var packageElement = new XElement("package"); file.Add(packageElement); var metadata = new XElement("metadata"); // required metadata.Add(new XElement("id", Id)); metadata.Add(new XElement("version", Version)); metadata.Add(new XElement("description", Description)); metadata.Add(new XElement("authors", Authors)); if (!string.IsNullOrEmpty(Title)) { metadata.Add(new XElement("title", Title)); } if (!string.IsNullOrEmpty(Owners)) { metadata.Add(new XElement("owners", Owners)); } if (!string.IsNullOrEmpty(LicenseUrl)) { metadata.Add(new XElement("licenseUrl", LicenseUrl)); } if (!string.IsNullOrEmpty(ProjectUrl)) { metadata.Add(new XElement("projectUrl", ProjectUrl)); } if (!string.IsNullOrEmpty(IconUrl)) { metadata.Add(new XElement("iconUrl", IconUrl)); } if (RequireLicenseAcceptance) { metadata.Add(new XElement("requireLicenseAcceptance", RequireLicenseAcceptance)); } if (!string.IsNullOrEmpty(ReleaseNotes)) { metadata.Add(new XElement("releaseNotes", ReleaseNotes)); } if (!string.IsNullOrEmpty(Copyright)) { metadata.Add(new XElement("copyright", Copyright)); } if (!string.IsNullOrEmpty(Tags)) { metadata.Add(new XElement("tags", Tags)); } if (Dependencies.Count > 0) { var dependenciesElement = new XElement("dependencies"); foreach (var frameworkGroup in Dependencies) { var group = new XElement("group"); if (!string.IsNullOrEmpty(frameworkGroup.TargetFramework)) { group.Add(new XAttribute("targetFramework", frameworkGroup.TargetFramework)); } foreach (var dependency in frameworkGroup.Dependencies) { var dependencyElement = new XElement("dependency"); dependencyElement.Add(new XAttribute("id", dependency.Id)); dependencyElement.Add(new XAttribute("version", dependency.Version)); dependenciesElement.Add(dependencyElement); } } metadata.Add(dependenciesElement); } file.Root.Add(metadata); if (Files.Count > 0) { var filesElement = new XElement("files"); foreach (var contentFile in Files) { var fileElement = new XElement("file"); fileElement.Add(new XAttribute("src", contentFile.Source)); fileElement.Add(new XAttribute("target", contentFile.Target)); filesElement.Add(fileElement); } packageElement.Add(filesElement); } // remove the read only flag on the file, if there is one. if (File.Exists(filePath)) { var attributes = File.GetAttributes(filePath); if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { attributes &= ~FileAttributes.ReadOnly; File.SetAttributes(filePath, attributes); } } file.Save(filePath); } [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "We intentionally use lower case.")] [NotNull] private static string ConvertFromNupkgTargetFrameworkName([NotNull] string targetFramework) { var convertedTargetFramework = targetFramework.ToLowerInvariant().Replace(".netstandard", "netstandard").Replace("native0.0", "native"); convertedTargetFramework = convertedTargetFramework.StartsWith(".netframework", StringComparison.Ordinal) ? convertedTargetFramework.Replace(".netframework", "net").Replace(".", string.Empty) : convertedTargetFramework; return convertedTargetFramework; } [NotNull] private NuspecFile SetIconFilePath([NotNull] string containingFilePath) { if (!string.IsNullOrEmpty(Icon)) { IconFilePath = Path.Combine( Path.GetDirectoryName(Path.GetFullPath(containingFilePath)) ?? throw new ArgumentException($"Path doesn't contain a directory name '{containingFilePath}'.", nameof(containingFilePath)), Icon); } return this; } } }