commit
b115569d71
10 changed files with 916 additions and 7 deletions
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an empty framework folder in NuGet 2.0+ packages.
|
||||
/// An empty framework folder is represented by a file named "_._".
|
||||
/// </summary>
|
||||
internal sealed class EmptyFrameworkFolderFile : PhysicalPackageFile
|
||||
{
|
||||
public EmptyFrameworkFolderFile(string directoryPathInPackage) :
|
||||
base(() => Stream.Null)
|
||||
{
|
||||
if (directoryPathInPackage == null)
|
||||
{
|
||||
throw new ArgumentNullException("directoryPathInPackage");
|
||||
}
|
||||
|
||||
TargetPath = System.IO.Path.Combine(directoryPathInPackage, Constants.PackageEmptyFileName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
@ -12,18 +14,30 @@ namespace NuGet
|
|||
{
|
||||
private const string SchemaVersionAttributeName = "schemaVersion";
|
||||
|
||||
public Manifest(ManifestMetadata metadata)
|
||||
public Manifest(ManifestMetadata metadata) : this(metadata, null)
|
||||
{
|
||||
}
|
||||
|
||||
public Manifest(ManifestMetadata metadata, ICollection<ManifestFile> files)
|
||||
{
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
Metadata = metadata;
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
Files = files;
|
||||
}
|
||||
}
|
||||
|
||||
public ManifestMetadata Metadata { get; }
|
||||
|
||||
public ICollection<ManifestFile> Files { get; } = new List<ManifestFile>();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current manifest to the specified stream.
|
||||
/// </summary>
|
||||
|
@ -53,9 +67,37 @@ namespace NuGet
|
|||
int version = Math.Max(minimumManifestVersion, ManifestVersionUtility.GetManifestVersion(Metadata));
|
||||
var schemaNamespace = (XNamespace)ManifestSchemaUtility.GetSchemaNamespace(version);
|
||||
|
||||
new XDocument(
|
||||
var document = new XDocument(
|
||||
new XElement(schemaNamespace + "package",
|
||||
Metadata.ToXElement(schemaNamespace))).Save(stream);
|
||||
Metadata.ToXElement(schemaNamespace)));
|
||||
|
||||
var fileElement = Files.ToXElement(schemaNamespace);
|
||||
|
||||
if (fileElement != null)
|
||||
{
|
||||
document.Root.Add(fileElement);
|
||||
}
|
||||
|
||||
document.Save(stream);
|
||||
}
|
||||
|
||||
public static Manifest ReadFrom(Stream stream)
|
||||
{
|
||||
XDocument document = XDocument.Load(stream);
|
||||
var schemaNamespace = GetSchemaNamespace(document);
|
||||
|
||||
return ManifestReader.ReadManifest(document);
|
||||
}
|
||||
|
||||
private static string GetSchemaNamespace(XDocument document)
|
||||
{
|
||||
string schemaNamespace = ManifestSchemaUtility.SchemaVersionV1;
|
||||
var rootNameSpace = document.Root.Name.Namespace;
|
||||
if (rootNameSpace != null && !String.IsNullOrEmpty(rootNameSpace.NamespaceName))
|
||||
{
|
||||
schemaNamespace = rootNameSpace.NamespaceName;
|
||||
}
|
||||
return schemaNamespace;
|
||||
}
|
||||
|
||||
public static Manifest Create(PackageBuilder copy)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
using System.Xml.Serialization;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
public class ManifestContentFiles
|
||||
{
|
||||
public string Include { get; set; }
|
||||
|
||||
public string Exclude { get; set; }
|
||||
|
||||
public string BuildAction { get; set; }
|
||||
|
||||
public string CopyToOutput { get; set; }
|
||||
|
||||
public string Flatten { get; set; }
|
||||
}
|
||||
}
|
21
src/Microsoft.DotNet.Tools.Pack/NuGet/ManifestFile.cs
Normal file
21
src/Microsoft.DotNet.Tools.Pack/NuGet/ManifestFile.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
public class ManifestFile
|
||||
{
|
||||
public ManifestFile(string source, string target, string exclude)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
Exclude = exclude;
|
||||
}
|
||||
|
||||
public string Source { get; }
|
||||
|
||||
public string Target { get; }
|
||||
|
||||
public string Exclude { get; }
|
||||
}
|
||||
}
|
|
@ -61,6 +61,8 @@ namespace NuGet
|
|||
|
||||
public bool RequireLicenseAcceptance { get; set; }
|
||||
|
||||
public bool DevelopmentDependency { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Summary { get; set; }
|
||||
|
@ -80,5 +82,7 @@ namespace NuGet
|
|||
public ICollection<PackageReferenceSet> PackageAssemblyReferences { get; set; } = new List<PackageReferenceSet>();
|
||||
|
||||
public IEnumerable<FrameworkAssemblyReference> FrameworkAssemblies { get; set; } = new List<FrameworkAssemblyReference>();
|
||||
|
||||
public ICollection<ManifestContentFiles> ContentFiles { get; set; } = new List<ManifestContentFiles>();
|
||||
}
|
||||
}
|
||||
|
|
310
src/Microsoft.DotNet.Tools.Pack/NuGet/ManifestReader.cs
Normal file
310
src/Microsoft.DotNet.Tools.Pack/NuGet/ManifestReader.cs
Normal file
|
@ -0,0 +1,310 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Packaging.Core;
|
||||
using NuGet.Versioning;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
// TODO: Resources using NuGet.Resources;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
internal static class ManifestReader
|
||||
{
|
||||
private static readonly string[] RequiredElements = new string[] { "id", "version", "authors", "description" };
|
||||
|
||||
public static Manifest ReadManifest(XDocument document)
|
||||
{
|
||||
var metadataElement = document.Root.ElementsNoNamespace("metadata").FirstOrDefault();
|
||||
if (metadataElement == null)
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new InvalidDataException(
|
||||
String.Format(CultureInfo.CurrentCulture, "NuGetResources.Manifest_RequiredElementMissing {0}", "metadata"));
|
||||
}
|
||||
|
||||
return new Manifest(ReadMetadata(metadataElement),
|
||||
ReadFilesList(document.Root.ElementsNoNamespace("files").FirstOrDefault()));
|
||||
}
|
||||
|
||||
private static ManifestMetadata ReadMetadata(XElement xElement)
|
||||
{
|
||||
var manifestMetadata = new ManifestMetadata();
|
||||
manifestMetadata.DependencySets = new List<PackageDependencySet>();
|
||||
manifestMetadata.PackageAssemblyReferences = new List<PackageReferenceSet>();
|
||||
manifestMetadata.MinClientVersionString = xElement.GetOptionalAttributeValue("minClientVersion");
|
||||
|
||||
// we store all child elements under <metadata> so that we can easily check for required elements.
|
||||
var allElements = new HashSet<string>();
|
||||
|
||||
XNode node = xElement.FirstNode;
|
||||
while (node != null)
|
||||
{
|
||||
var element = node as XElement;
|
||||
if (element != null)
|
||||
{
|
||||
ReadMetadataValue(manifestMetadata, element, allElements);
|
||||
}
|
||||
node = node.NextNode;
|
||||
}
|
||||
|
||||
// now check for required elements, which include <id>, <version>, <authors> and <description>
|
||||
foreach (var requiredElement in RequiredElements)
|
||||
{
|
||||
if (!allElements.Contains(requiredElement))
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new InvalidDataException(
|
||||
String.Format(CultureInfo.CurrentCulture, "NuGetResources.Manifest_RequiredElementMissing {0}", requiredElement));
|
||||
}
|
||||
}
|
||||
|
||||
return manifestMetadata;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
private static void ReadMetadataValue(ManifestMetadata manifestMetadata, XElement element, HashSet<string> allElements)
|
||||
{
|
||||
if (element.Value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allElements.Add(element.Name.LocalName);
|
||||
|
||||
string value = element.Value?.Trim();
|
||||
switch (element.Name.LocalName)
|
||||
{
|
||||
case "id":
|
||||
manifestMetadata.Id = value;
|
||||
break;
|
||||
case "version":
|
||||
manifestMetadata.Version = NuGetVersion.Parse(value);
|
||||
break;
|
||||
case "authors":
|
||||
manifestMetadata.Authors = value.Split(',').Select(a => a.Trim());
|
||||
break;
|
||||
case "owners":
|
||||
manifestMetadata.Owners = value.Split(',').Select(a => a.Trim());
|
||||
break;
|
||||
case "licenseUrl":
|
||||
manifestMetadata.LicenseUrl = new Uri(value);
|
||||
break;
|
||||
case "projectUrl":
|
||||
manifestMetadata.ProjectUrl = new Uri(value);
|
||||
break;
|
||||
case "iconUrl":
|
||||
manifestMetadata.IconUrl = new Uri(value);
|
||||
break;
|
||||
case "requireLicenseAcceptance":
|
||||
manifestMetadata.RequireLicenseAcceptance = XmlConvert.ToBoolean(value);
|
||||
break;
|
||||
case "developmentDependency":
|
||||
manifestMetadata.DevelopmentDependency = XmlConvert.ToBoolean(value);
|
||||
break;
|
||||
case "description":
|
||||
manifestMetadata.Description = value;
|
||||
break;
|
||||
case "summary":
|
||||
manifestMetadata.Summary = value;
|
||||
break;
|
||||
case "releaseNotes":
|
||||
manifestMetadata.ReleaseNotes = value;
|
||||
break;
|
||||
case "copyright":
|
||||
manifestMetadata.Copyright = value;
|
||||
break;
|
||||
case "language":
|
||||
manifestMetadata.Language = value;
|
||||
break;
|
||||
case "title":
|
||||
manifestMetadata.Title = value;
|
||||
break;
|
||||
case "tags":
|
||||
manifestMetadata.Tags = value;
|
||||
break;
|
||||
case "dependencies":
|
||||
manifestMetadata.DependencySets = ReadDependencySets(element);
|
||||
break;
|
||||
case "frameworkAssemblies":
|
||||
manifestMetadata.FrameworkAssemblies = ReadFrameworkAssemblies(element);
|
||||
break;
|
||||
case "references":
|
||||
manifestMetadata.PackageAssemblyReferences = ReadReferenceSets(element);
|
||||
break;
|
||||
case "contentFiles":
|
||||
manifestMetadata.ContentFiles = ReadContentFiles(element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ManifestContentFiles> ReadContentFiles(XElement contentFilesElement)
|
||||
{
|
||||
if (!contentFilesElement.HasElements)
|
||||
{
|
||||
return new List<ManifestContentFiles>(0);
|
||||
}
|
||||
|
||||
var contentFileSets = (from element in contentFilesElement.ElementsNoNamespace("files")
|
||||
let includeAttribute = element.Attribute("include")
|
||||
where includeAttribute != null && !string.IsNullOrEmpty(includeAttribute.Value)
|
||||
let excludeAttribute = element.Attribute("exclude")
|
||||
let buildActionAttribute = element.Attribute("buildAction")
|
||||
let copyToOutputAttribute = element.Attribute("copyToOutput")
|
||||
let flattenAttribute = element.Attribute("flatten")
|
||||
select new ManifestContentFiles
|
||||
{
|
||||
Include = includeAttribute.Value?.Trim(),
|
||||
Exclude = excludeAttribute == null ? null : excludeAttribute.Value,
|
||||
BuildAction = buildActionAttribute == null ? null : buildActionAttribute.Value,
|
||||
CopyToOutput = copyToOutputAttribute == null ? null : copyToOutputAttribute.Value,
|
||||
Flatten = flattenAttribute == null ? null : flattenAttribute.Value
|
||||
}).ToList();
|
||||
|
||||
return contentFileSets;
|
||||
}
|
||||
|
||||
private static List<PackageReferenceSet> ReadReferenceSets(XElement referencesElement)
|
||||
{
|
||||
if (!referencesElement.HasElements)
|
||||
{
|
||||
return new List<PackageReferenceSet>(0);
|
||||
}
|
||||
|
||||
if (referencesElement.ElementsNoNamespace("group").Any() &&
|
||||
referencesElement.ElementsNoNamespace("reference").Any())
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new InvalidDataException("NuGetResources.Manifest_ReferencesHasMixedElements");
|
||||
}
|
||||
|
||||
var references = ReadReference(referencesElement, throwIfEmpty: false);
|
||||
if (references.Count > 0)
|
||||
{
|
||||
// old format, <reference> is direct child of <references>
|
||||
var referenceSet = new PackageReferenceSet(references);
|
||||
return new List<PackageReferenceSet> { referenceSet };
|
||||
}
|
||||
else
|
||||
{
|
||||
var groups = referencesElement.ElementsNoNamespace("group");
|
||||
return (from element in groups
|
||||
select new PackageReferenceSet(element.GetOptionalAttributeValue("targetFramework")?.Trim(),
|
||||
ReadReference(element, throwIfEmpty: true))).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> ReadReference(XElement referenceElement, bool throwIfEmpty)
|
||||
{
|
||||
var references = (from element in referenceElement.ElementsNoNamespace("reference")
|
||||
let fileAttribute = element.Attribute("file")
|
||||
where fileAttribute != null && !String.IsNullOrEmpty(fileAttribute.Value)
|
||||
select fileAttribute.Value?.Trim()
|
||||
).ToList();
|
||||
|
||||
if (throwIfEmpty && references.Count == 0)
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new InvalidDataException("NuGetResources.Manifest_ReferencesIsEmpty");
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
private static List<FrameworkAssemblyReference> ReadFrameworkAssemblies(XElement frameworkElement)
|
||||
{
|
||||
if (!frameworkElement.HasElements)
|
||||
{
|
||||
return new List<FrameworkAssemblyReference>(0);
|
||||
}
|
||||
|
||||
return (from element in frameworkElement.ElementsNoNamespace("frameworkAssembly")
|
||||
let assemblyNameAttribute = element.Attribute("assemblyName")
|
||||
where assemblyNameAttribute != null && !String.IsNullOrEmpty(assemblyNameAttribute.Value)
|
||||
select new FrameworkAssemblyReference(assemblyNameAttribute.Value?.Trim(),
|
||||
new[] { NuGetFramework.Parse(element.GetOptionalAttributeValue("targetFramework")?.Trim()) })
|
||||
).ToList();
|
||||
}
|
||||
|
||||
private static List<PackageDependencySet> ReadDependencySets(XElement dependenciesElement)
|
||||
{
|
||||
if (!dependenciesElement.HasElements)
|
||||
{
|
||||
return new List<PackageDependencySet>();
|
||||
}
|
||||
|
||||
// Disallow the <dependencies> element to contain both <dependency> and
|
||||
// <group> child elements. Unfortunately, this cannot be enforced by XSD.
|
||||
if (dependenciesElement.ElementsNoNamespace("dependency").Any() &&
|
||||
dependenciesElement.ElementsNoNamespace("group").Any())
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new InvalidDataException("NuGetResources.Manifest_DependenciesHasMixedElements");
|
||||
}
|
||||
|
||||
var dependencies = ReadDependencies(dependenciesElement);
|
||||
if (dependencies.Count > 0)
|
||||
{
|
||||
// old format, <dependency> is direct child of <dependencies>
|
||||
var dependencySet = new PackageDependencySet(dependencies);
|
||||
return new List<PackageDependencySet> { dependencySet };
|
||||
}
|
||||
else
|
||||
{
|
||||
var groups = dependenciesElement.ElementsNoNamespace("group");
|
||||
return (from element in groups
|
||||
select new PackageDependencySet(element.GetOptionalAttributeValue("targetFramework")?.Trim(),
|
||||
ReadDependencies(element))).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<PackageDependency> ReadDependencies(XElement containerElement)
|
||||
{
|
||||
|
||||
|
||||
// element is <dependency>
|
||||
return (from element in containerElement.ElementsNoNamespace("dependency")
|
||||
let idElement = element.Attribute("id")
|
||||
where idElement != null && !String.IsNullOrEmpty(idElement.Value)
|
||||
select new PackageDependency(
|
||||
idElement.Value?.Trim(),
|
||||
VersionRange.Parse(element.GetOptionalAttributeValue("version")?.Trim()),
|
||||
element.GetOptionalAttributeValue("include")?.Trim()?.Split(',').Select(a => a.Trim()).ToArray(),
|
||||
element.GetOptionalAttributeValue("exclude")?.Trim()?.Split(',').Select(a => a.Trim()).ToArray()
|
||||
)).ToList();
|
||||
}
|
||||
|
||||
private static List<ManifestFile> ReadFilesList(XElement xElement)
|
||||
{
|
||||
if (xElement == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ManifestFile> files = new List<ManifestFile>();
|
||||
foreach (var file in xElement.ElementsNoNamespace("file"))
|
||||
{
|
||||
var srcElement = file.Attribute("src");
|
||||
if (srcElement == null || String.IsNullOrEmpty(srcElement.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string target = file.GetOptionalAttributeValue("target")?.Trim();
|
||||
string exclude = file.GetOptionalAttributeValue("exclude")?.Trim();
|
||||
|
||||
// Multiple sources can be specified by using semi-colon separated values.
|
||||
files.AddRange(from source in srcElement.Value.Trim(';').Split(';')
|
||||
select new ManifestFile(source?.Trim(), target?.Trim(), exclude?.Trim() ));
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ namespace NuGet
|
|||
{
|
||||
private const string DefaultContentType = "application/octet";
|
||||
internal const string ManifestRelationType = "manifest";
|
||||
private bool _includeEmptyDirectories = false;
|
||||
|
||||
public PackageBuilder()
|
||||
{
|
||||
|
@ -26,6 +27,7 @@ namespace NuGet
|
|||
DependencySets = new List<PackageDependencySet>();
|
||||
FrameworkAssemblies = new List<FrameworkAssemblyReference>();
|
||||
PackageAssemblyReferences = new List<PackageReferenceSet>();
|
||||
ContentFiles = new List<ManifestContentFiles>();
|
||||
Authors = new List<string>();
|
||||
Owners = new List<string>();
|
||||
Tags = new List<string>();
|
||||
|
@ -151,6 +153,12 @@ namespace NuGet
|
|||
private set;
|
||||
}
|
||||
|
||||
public List<ManifestContentFiles> ContentFiles
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Version MinClientVersion
|
||||
{
|
||||
get;
|
||||
|
@ -264,6 +272,44 @@ namespace NuGet
|
|||
}
|
||||
}
|
||||
|
||||
public void Populate(ManifestMetadata manifestMetadata)
|
||||
{
|
||||
Id = manifestMetadata.Id;
|
||||
Version = manifestMetadata.Version;
|
||||
Title = manifestMetadata.Title;
|
||||
AppendIfNotNull(Authors, manifestMetadata.Authors);
|
||||
AppendIfNotNull(Owners, manifestMetadata.Owners);
|
||||
IconUrl = manifestMetadata.IconUrl;
|
||||
LicenseUrl = manifestMetadata.LicenseUrl;
|
||||
ProjectUrl = manifestMetadata.ProjectUrl;
|
||||
RequireLicenseAcceptance = manifestMetadata.RequireLicenseAcceptance;
|
||||
DevelopmentDependency = manifestMetadata.DevelopmentDependency;
|
||||
Description = manifestMetadata.Description;
|
||||
Summary = manifestMetadata.Summary;
|
||||
ReleaseNotes = manifestMetadata.ReleaseNotes;
|
||||
Language = manifestMetadata.Language;
|
||||
Copyright = manifestMetadata.Copyright;
|
||||
MinClientVersion = manifestMetadata.MinClientVersion;
|
||||
|
||||
if (manifestMetadata.Tags != null)
|
||||
{
|
||||
Tags.AddRange(ParseTags(manifestMetadata.Tags));
|
||||
}
|
||||
|
||||
AppendIfNotNull(DependencySets, manifestMetadata.DependencySets);
|
||||
AppendIfNotNull(FrameworkAssemblies, manifestMetadata.FrameworkAssemblies);
|
||||
AppendIfNotNull(PackageAssemblyReferences, manifestMetadata.PackageAssemblyReferences);
|
||||
AppendIfNotNull(ContentFiles, manifestMetadata.ContentFiles);
|
||||
}
|
||||
|
||||
public void PopulateFiles(string basePath, IEnumerable<ManifestFile> files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
AddFiles(basePath, file.Source, file.Target, file.Exclude);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteManifest(ZipArchive package, int minimumManifestVersion)
|
||||
{
|
||||
string path = Id + Constants.ManifestExtension;
|
||||
|
@ -310,6 +356,39 @@ namespace NuGet
|
|||
return extensions;
|
||||
}
|
||||
|
||||
private void AddFiles(string basePath, string source, string destination, string exclude = null)
|
||||
{
|
||||
List<PhysicalPackageFile> searchFiles = PathResolver.ResolveSearchPattern(basePath, source, destination, _includeEmptyDirectories).ToList();
|
||||
|
||||
ExcludeFiles(searchFiles, basePath, exclude);
|
||||
|
||||
if (!PathResolver.IsWildcardSearch(source) && !PathResolver.IsDirectoryPath(source) && !searchFiles.Any())
|
||||
{
|
||||
// TODO: Resources
|
||||
throw new FileNotFoundException(
|
||||
String.Format(CultureInfo.CurrentCulture, "NuGetResources.PackageAuthoring_FileNotFound {0}", source));
|
||||
}
|
||||
|
||||
|
||||
Files.AddRange(searchFiles);
|
||||
}
|
||||
|
||||
private static void ExcludeFiles(List<PhysicalPackageFile> searchFiles, string basePath, string exclude)
|
||||
{
|
||||
if (String.IsNullOrEmpty(exclude))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// One or more exclusions may be specified in the file. Split it and prepend the base path to the wildcard provided.
|
||||
var exclusions = exclude.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var item in exclusions)
|
||||
{
|
||||
string wildCard = PathResolver.NormalizeWildcardForExcludedFiles(basePath, item);
|
||||
PathResolver.FilterPackageFiles(searchFiles, p => p.SourcePath, new[] { wildCard });
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreatePart(ZipArchive package, string path, Stream sourceStream)
|
||||
{
|
||||
if (PackageHelper.IsManifest(path))
|
||||
|
@ -324,6 +403,15 @@ namespace NuGet
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tags come in this format. tag1 tag2 tag3 etc..
|
||||
/// </summary>
|
||||
private static IEnumerable<string> ParseTags(string tags)
|
||||
{
|
||||
return from tag in tags.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
select tag.Trim();
|
||||
}
|
||||
|
||||
private static bool IsPrereleaseDependency(PackageDependency dependency)
|
||||
{
|
||||
return dependency.VersionRange.MinVersion?.IsPrerelease == true ||
|
||||
|
@ -340,7 +428,7 @@ namespace NuGet
|
|||
return version == null || version.Release.Length <= 20;
|
||||
}
|
||||
|
||||
private void WriteOpcManifestRelationship(ZipArchive package, string path)
|
||||
private static void WriteOpcManifestRelationship(ZipArchive package, string path)
|
||||
{
|
||||
ZipArchiveEntry relsEntry = package.CreateEntry("_rels/.rels", CompressionLevel.Optimal);
|
||||
|
||||
|
@ -374,9 +462,17 @@ namespace NuGet
|
|||
}
|
||||
|
||||
// Generate a relationship id for compatibility
|
||||
private string GenerateRelationshipId()
|
||||
private static string GenerateRelationshipId()
|
||||
{
|
||||
return "R" + Guid.NewGuid().ToString("N").Substring(0, 16);
|
||||
}
|
||||
|
||||
private static void AppendIfNotNull<T>(List<T> collection, IEnumerable<T> toAdd)
|
||||
{
|
||||
if (toAdd != null)
|
||||
{
|
||||
collection.AddRange(toAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using System.Xml.Linq;
|
||||
using NuGet.Frameworks;
|
||||
using NuGet.Packaging.Core;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
|
@ -21,6 +22,7 @@ namespace NuGet
|
|||
private const string FrameworkAssembly = "frameworkAssembly";
|
||||
private const string AssemblyName = "assemblyName";
|
||||
private const string Dependencies = "dependencies";
|
||||
private const string Files = "files";
|
||||
|
||||
public static XElement ToXElement(this ManifestMetadata metadata, XNamespace ns)
|
||||
{
|
||||
|
@ -34,6 +36,7 @@ namespace NuGet
|
|||
elem.Add(new XElement(ns + "version", metadata.Version.ToString()));
|
||||
AddElementIfNotNull(elem, ns, "title", metadata.Title);
|
||||
elem.Add(new XElement(ns + "requireLicenseAcceptance", metadata.RequireLicenseAcceptance));
|
||||
elem.Add(new XElement(ns + "developmentDependency", metadata.DevelopmentDependency));
|
||||
AddElementIfNotNull(elem, ns, "authors", metadata.Authors, authors => string.Join(",", authors));
|
||||
AddElementIfNotNull(elem, ns, "owners", metadata.Owners, owners => string.Join(",", owners));
|
||||
AddElementIfNotNull(elem, ns, "licenseUrl", metadata.LicenseUrl);
|
||||
|
@ -67,10 +70,55 @@ namespace NuGet
|
|||
TargetFramework));
|
||||
|
||||
elem.Add(GetXElementFromFrameworkAssemblies(ns, metadata.FrameworkAssemblies));
|
||||
elem.Add(GetXElementFromManifestContentFiles(ns, metadata.ContentFiles));
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
|
||||
public static XElement ToXElement(this IEnumerable<ManifestFile> fileList, XNamespace ns)
|
||||
{
|
||||
return GetXElementFromManifestFiles(ns, fileList);
|
||||
}
|
||||
|
||||
public static string GetOptionalAttributeValue(this XElement element, string localName, string namespaceName = null)
|
||||
{
|
||||
XAttribute attr;
|
||||
if (string.IsNullOrEmpty(namespaceName))
|
||||
{
|
||||
attr = element.Attribute(localName);
|
||||
}
|
||||
else
|
||||
{
|
||||
attr = element.Attribute(XName.Get(localName, namespaceName));
|
||||
}
|
||||
return attr != null ? attr.Value : null;
|
||||
}
|
||||
|
||||
public static string GetOptionalElementValue(this XContainer element, string localName, string namespaceName = null)
|
||||
{
|
||||
XElement child;
|
||||
if (string.IsNullOrEmpty(namespaceName))
|
||||
{
|
||||
child = element.ElementsNoNamespace(localName).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
child = element.Element(XName.Get(localName, namespaceName));
|
||||
}
|
||||
return child != null ? child.Value : null;
|
||||
}
|
||||
|
||||
public static IEnumerable<XElement> ElementsNoNamespace(this XContainer container, string localName)
|
||||
{
|
||||
return container.Elements().Where(e => e.Name.LocalName == localName);
|
||||
}
|
||||
|
||||
public static IEnumerable<XElement> ElementsNoNamespace(this IEnumerable<XContainer> source, string localName)
|
||||
{
|
||||
return source.Elements().Where(e => e.Name.LocalName == localName);
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromGroupableItemSets<TSet, TItem>(
|
||||
XNamespace ns,
|
||||
IEnumerable<TSet> objectSets,
|
||||
|
@ -137,7 +185,9 @@ namespace NuGet
|
|||
{
|
||||
return new XElement(ns + "dependency",
|
||||
new XAttribute("id", dependency.Id),
|
||||
dependency.VersionRange != null ? new XAttribute("version", dependency.VersionRange.ToString()) : null);
|
||||
dependency.VersionRange != null ? new XAttribute("version", dependency.VersionRange.ToString()) : null,
|
||||
dependency.Include != null && dependency.Include.Any() ? new XAttribute("include", string.Join(",", dependency.Include)) : null,
|
||||
dependency.Exclude != null && dependency.Exclude.Any() ? new XAttribute("exclude", string.Join(",", dependency.Exclude)) : null);
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromFrameworkAssemblies(XNamespace ns, IEnumerable<FrameworkAssemblyReference> references)
|
||||
|
@ -157,6 +207,40 @@ namespace NuGet
|
|||
null)));
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromManifestFiles(XNamespace ns, IEnumerable<ManifestFile> files)
|
||||
{
|
||||
if (files == null || !files.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new XElement(ns + Files,
|
||||
files.Select(file =>
|
||||
new XElement(ns + File,
|
||||
new XAttribute("src", file.Source),
|
||||
new XAttribute("target", file.Source),
|
||||
new XAttribute("exclude", file.Exclude)
|
||||
)));
|
||||
}
|
||||
|
||||
private static XElement GetXElementFromManifestContentFiles(XNamespace ns, IEnumerable<ManifestContentFiles> contentFiles)
|
||||
{
|
||||
if (contentFiles == null || !contentFiles.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new XElement(ns + "contentFiles",
|
||||
contentFiles.Select(file =>
|
||||
new XElement(ns + File,
|
||||
new XAttribute("include", file.Include),
|
||||
new XAttribute("exclude", file.Exclude),
|
||||
new XAttribute("buildAction", file.BuildAction),
|
||||
new XAttribute("copyToOutput", file.CopyToOutput),
|
||||
new XAttribute("flatten", file.Flatten)
|
||||
)));
|
||||
}
|
||||
|
||||
private static void AddElementIfNotNull<T>(XElement parent, XNamespace ns, string name, T value)
|
||||
where T : class
|
||||
{
|
||||
|
|
|
@ -11,7 +11,12 @@ namespace NuGet
|
|||
public class PackageReferenceSet
|
||||
{
|
||||
public PackageReferenceSet(IEnumerable<string> references)
|
||||
: this(null, references)
|
||||
: this((NuGetFramework)null, references)
|
||||
{
|
||||
}
|
||||
|
||||
public PackageReferenceSet(string targetFramework, IEnumerable<string> references)
|
||||
: this(targetFramework != null ? NuGetFramework.Parse(targetFramework) : null, references)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
304
src/Microsoft.DotNet.Tools.Pack/NuGet/PathResolver.cs
Normal file
304
src/Microsoft.DotNet.Tools.Pack/NuGet/PathResolver.cs
Normal file
|
@ -0,0 +1,304 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NuGet
|
||||
{
|
||||
public static class PathResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a collection of files from the source that matches the wildcard.
|
||||
/// </summary>
|
||||
/// <param name="source">The collection of files to match.</param>
|
||||
/// <param name="getPath">Function that returns the path to filter a package file </param>
|
||||
/// <param name="wildcards">The wildcards to apply to match the path with.</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<T> GetMatches<T>(IEnumerable<T> source, Func<T, string> getPath, IEnumerable<string> wildcards)
|
||||
{
|
||||
var filters = wildcards.Select(WildcardToRegex);
|
||||
return source.Where(item =>
|
||||
{
|
||||
string path = getPath(item);
|
||||
return filters.Any(f => f.IsMatch(path));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes files from the source that match any wildcard.
|
||||
/// </summary>
|
||||
public static void FilterPackageFiles<T>(ICollection<T> source, Func<T, string> getPath, IEnumerable<string> wildcards)
|
||||
{
|
||||
var matchedFiles = new HashSet<T>(GetMatches(source, getPath, wildcards));
|
||||
var itemsToRemove = source.Where(matchedFiles.Contains).ToArray();
|
||||
foreach (var item in itemsToRemove)
|
||||
{
|
||||
source.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static string NormalizeWildcardForExcludedFiles(string basePath, string wildcard)
|
||||
{
|
||||
if (wildcard.StartsWith("**", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Allow any path to match the first '**' segment, see issue 2891 for more details.
|
||||
return wildcard;
|
||||
}
|
||||
basePath = NormalizeBasePath(basePath, ref wildcard);
|
||||
return Path.Combine(basePath, wildcard);
|
||||
}
|
||||
|
||||
private static Regex WildcardToRegex(string wildcard)
|
||||
{
|
||||
var pattern = Regex.Escape(wildcard);
|
||||
if (Path.DirectorySeparatorChar == '/')
|
||||
{
|
||||
// regex wildcard adjustments for *nix-style file systems
|
||||
pattern = pattern
|
||||
.Replace(@"\*\*/", ".*") //For recursive wildcards /**/, include the current directory.
|
||||
.Replace(@"\*\*", ".*") // For recursive wildcards that don't end in a slash e.g. **.txt would be treated as a .txt file at any depth
|
||||
.Replace(@"\*", @"[^/]*(/)?") // For non recursive searches, limit it any character that is not a directory separator
|
||||
.Replace(@"\?", "."); // ? translates to a single any character
|
||||
}
|
||||
else
|
||||
{
|
||||
// regex wildcard adjustments for Windows-style file systems
|
||||
pattern = pattern
|
||||
.Replace("/", @"\\") // On Windows, / is treated the same as \.
|
||||
.Replace(@"\*\*\\", ".*") //For recursive wildcards \**\, include the current directory.
|
||||
.Replace(@"\*\*", ".*") // For recursive wildcards that don't end in a slash e.g. **.txt would be treated as a .txt file at any depth
|
||||
.Replace(@"\*", @"[^\\]*(\\)?") // For non recursive searches, limit it any character that is not a directory separator
|
||||
.Replace(@"\?", "."); // ? translates to a single any character
|
||||
}
|
||||
|
||||
return new Regex('^' + pattern + '$', RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
|
||||
}
|
||||
|
||||
internal static IEnumerable<PhysicalPackageFile> ResolveSearchPattern(string basePath, string searchPath, string targetPath, bool includeEmptyDirectories)
|
||||
{
|
||||
string normalizedBasePath;
|
||||
IEnumerable<SearchPathResult> searchResults = PerformWildcardSearchInternal(basePath, searchPath, includeEmptyDirectories, out normalizedBasePath);
|
||||
|
||||
return searchResults.Select(result =>
|
||||
result.IsFile
|
||||
? new PhysicalPackageFile
|
||||
{
|
||||
SourcePath = result.Path,
|
||||
TargetPath = ResolvePackagePath(normalizedBasePath, searchPath, result.Path, targetPath)
|
||||
}
|
||||
: new EmptyFrameworkFolderFile(ResolvePackagePath(normalizedBasePath, searchPath, result.Path, targetPath))
|
||||
{
|
||||
SourcePath = result.Path
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> PerformWildcardSearch(string basePath, string searchPath)
|
||||
{
|
||||
string normalizedBasePath;
|
||||
var searchResults = PerformWildcardSearchInternal(basePath, searchPath, includeEmptyDirectories: false, normalizedBasePath: out normalizedBasePath);
|
||||
return searchResults.Select(s => s.Path);
|
||||
}
|
||||
|
||||
private static IEnumerable<SearchPathResult> PerformWildcardSearchInternal(string basePath, string searchPath, bool includeEmptyDirectories, out string normalizedBasePath)
|
||||
{
|
||||
if (!searchPath.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase)
|
||||
&& Path.DirectorySeparatorChar != '/')
|
||||
{
|
||||
//If the system's DirectorySeparatorChar is '/' we're probably dealing with Mac or *nix
|
||||
// In any case, if '/' is the separator, we don't want to trim off the first char ever
|
||||
// since it would completely change the meaning of the path
|
||||
// eg: /var/somedir/ is not at all the same as var/somedir (relative vs absolute)
|
||||
|
||||
// If we aren't dealing with network paths, trim the leading slash.
|
||||
searchPath = searchPath.TrimStart(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
bool searchDirectory = false;
|
||||
|
||||
// If the searchPath ends with \ or /, we treat searchPath as a directory,
|
||||
// and will include everything under it, recursively
|
||||
if (IsDirectoryPath(searchPath))
|
||||
{
|
||||
searchPath = searchPath + "**" + Path.DirectorySeparatorChar + "*";
|
||||
searchDirectory = true;
|
||||
}
|
||||
|
||||
basePath = NormalizeBasePath(basePath, ref searchPath);
|
||||
normalizedBasePath = GetPathToEnumerateFrom(basePath, searchPath);
|
||||
|
||||
// Append the basePath to searchPattern and get the search regex. We need to do this because the search regex is matched from line start.
|
||||
Regex searchRegex = WildcardToRegex(Path.Combine(basePath, searchPath));
|
||||
|
||||
// This is a hack to prevent enumerating over the entire directory tree if the only wildcard characters are the ones in the file name.
|
||||
// If the path portion of the search path does not contain any wildcard characters only iterate over the TopDirectory.
|
||||
SearchOption searchOption = SearchOption.AllDirectories;
|
||||
// (a) Path is not recursive search
|
||||
bool isRecursiveSearch = searchPath.IndexOf("**", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
// (b) Path does not have any wildcards.
|
||||
bool isWildcardPath = Path.GetDirectoryName(searchPath).Contains('*');
|
||||
if (!isRecursiveSearch && !isWildcardPath)
|
||||
{
|
||||
searchOption = SearchOption.TopDirectoryOnly;
|
||||
}
|
||||
|
||||
// Starting from the base path, enumerate over all files and match it using the wildcard expression provided by the user.
|
||||
// Note: We use Directory.GetFiles() instead of Directory.EnumerateFiles() here to support Mono
|
||||
var matchedFiles = from file in Directory.GetFiles(normalizedBasePath, "*.*", searchOption)
|
||||
where searchRegex.IsMatch(file)
|
||||
select new SearchPathResult(file, isFile: true);
|
||||
|
||||
if (!includeEmptyDirectories)
|
||||
{
|
||||
return matchedFiles;
|
||||
}
|
||||
|
||||
// retrieve empty directories
|
||||
// Note: We use Directory.GetDirectories() instead of Directory.EnumerateDirectories() here to support Mono
|
||||
var matchedDirectories = from directory in Directory.GetDirectories(normalizedBasePath, "*.*", searchOption)
|
||||
where searchRegex.IsMatch(directory) && IsEmptyDirectory(directory)
|
||||
select new SearchPathResult(directory, isFile: false);
|
||||
|
||||
if (searchDirectory && IsEmptyDirectory(normalizedBasePath))
|
||||
{
|
||||
matchedDirectories = matchedDirectories.Concat(new [] { new SearchPathResult(normalizedBasePath, isFile: false) });
|
||||
}
|
||||
|
||||
return matchedFiles.Concat(matchedDirectories);
|
||||
}
|
||||
|
||||
internal static string GetPathToEnumerateFrom(string basePath, string searchPath)
|
||||
{
|
||||
string basePathToEnumerate;
|
||||
int wildcardIndex = searchPath.IndexOf('*');
|
||||
if (wildcardIndex == -1)
|
||||
{
|
||||
// For paths without wildcard, we could either have base relative paths (such as lib\foo.dll) or paths outside the base path
|
||||
// (such as basePath: C:\packages and searchPath: D:\packages\foo.dll)
|
||||
// In this case, Path.Combine would pick up the right root to enumerate from.
|
||||
var searchRoot = Path.GetDirectoryName(searchPath);
|
||||
basePathToEnumerate = Path.Combine(basePath, searchRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not, find the first directory separator and use the path to the left of it as the base path to enumerate from.
|
||||
int directorySeparatoryIndex = searchPath.LastIndexOf(Path.DirectorySeparatorChar, wildcardIndex);
|
||||
if (directorySeparatoryIndex == -1)
|
||||
{
|
||||
// We're looking at a path like "NuGet*.dll", NuGet*\bin\release\*.dll
|
||||
// In this case, the basePath would continue to be the path to begin enumeration from.
|
||||
basePathToEnumerate = basePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
string nonWildcardPortion = searchPath.Substring(0, directorySeparatoryIndex);
|
||||
basePathToEnumerate = Path.Combine(basePath, nonWildcardPortion);
|
||||
}
|
||||
}
|
||||
return basePathToEnumerate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determins the path of the file inside a package.
|
||||
/// For recursive wildcard paths, we preserve the path portion beginning with the wildcard.
|
||||
/// For non-recursive wildcard paths, we use the file name from the actual file path on disk.
|
||||
/// </summary>
|
||||
internal static string ResolvePackagePath(string searchDirectory, string searchPattern, string fullPath, string targetPath)
|
||||
{
|
||||
string packagePath;
|
||||
bool isDirectorySearch = IsDirectoryPath(searchPattern);
|
||||
bool isWildcardSearch = IsWildcardSearch(searchPattern);
|
||||
bool isRecursiveWildcardSearch = isWildcardSearch && searchPattern.IndexOf("**", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
if ((isRecursiveWildcardSearch || isDirectorySearch) && fullPath.StartsWith(searchDirectory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// The search pattern is recursive. Preserve the non-wildcard portion of the path.
|
||||
// e.g. Search: X:\foo\**\*.cs results in SearchDirectory: X:\foo and a file path of X:\foo\bar\biz\boz.cs
|
||||
// Truncating X:\foo\ would result in the package path.
|
||||
packagePath = fullPath.Substring(searchDirectory.Length).TrimStart(Path.DirectorySeparatorChar);
|
||||
}
|
||||
else if (!isWildcardSearch && Path.GetExtension(searchPattern).Equals(Path.GetExtension(targetPath), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// If the search does not contain wild cards, and the target path shares the same extension, copy it
|
||||
// e.g. <file src="ie\css\style.css" target="Content\css\ie.css" /> --> Content\css\ie.css
|
||||
return targetPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
packagePath = Path.GetFileName(fullPath);
|
||||
}
|
||||
return Path.Combine(targetPath ?? String.Empty, packagePath);
|
||||
}
|
||||
|
||||
private static readonly string OneDotSlash = "." + Path.DirectorySeparatorChar;
|
||||
private static readonly string TwoDotSlash = ".." + Path.DirectorySeparatorChar;
|
||||
|
||||
internal static string NormalizeBasePath(string basePath, ref string searchPath)
|
||||
{
|
||||
// If no base path is provided, use the current directory.
|
||||
basePath = String.IsNullOrEmpty(basePath) ? OneDotSlash : basePath;
|
||||
|
||||
// If the search path is relative, transfer the ..\ portion to the base path.
|
||||
// This needs to be done because the base path determines the root for our enumeration.
|
||||
while (searchPath.StartsWith(TwoDotSlash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
basePath = Path.Combine(basePath, TwoDotSlash);
|
||||
searchPath = searchPath.Substring(TwoDotSlash.Length);
|
||||
}
|
||||
|
||||
return Path.GetFullPath(basePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path contains any wildcard characters.
|
||||
/// </summary>
|
||||
internal static bool IsWildcardSearch(string filter)
|
||||
{
|
||||
return filter.IndexOf('*') != -1;
|
||||
}
|
||||
|
||||
internal static bool IsDirectoryPath(string path)
|
||||
{
|
||||
return path != null && path.Length > 1 &&
|
||||
(path[path.Length - 1] == Path.DirectorySeparatorChar ||
|
||||
path[path.Length - 1] == Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
|
||||
private static bool IsEmptyDirectory(string directory)
|
||||
{
|
||||
return !Directory.EnumerateFileSystemEntries(directory).Any();
|
||||
}
|
||||
|
||||
private struct SearchPathResult
|
||||
{
|
||||
private readonly string _path;
|
||||
private readonly bool _isFile;
|
||||
|
||||
public string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isFile;
|
||||
}
|
||||
}
|
||||
|
||||
public SearchPathResult(string path, bool isFile)
|
||||
{
|
||||
_path = path;
|
||||
_isFile = isFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue