in src/NuGet.Core/NuGet.Build.Tasks.Pack/PackTaskLogic.cs [630:761]
private IEnumerable<ContentMetadata> GetContentMetadata(IMSBuildItem packageFile, string sourcePath,
PackArgs packArgs, string[] contentTargetFolders)
{
var targetPaths = contentTargetFolders
.Select(PathUtility.EnsureTrailingSlash)
.ToList();
var isPackagePathSpecified = packageFile.Properties.Contains("PackagePath");
// if user specified a PackagePath, then use that. Look for any ** which are indicated by the RecrusiveDir metadata in msbuild.
if (isPackagePathSpecified)
{
// The rule here is that if the PackagePath is an empty string, then we add the file to the root of the package.
// Instead if it is a ';' delimited string, then the user needs to specify a '\' to indicate that the file should go to the root of the package.
var packagePathString = packageFile.GetProperty("PackagePath");
targetPaths = packagePathString == null
? new string[] { string.Empty }.ToList()
: MSBuildStringUtility.Split(packagePathString)
.Distinct()
.ToList();
var recursiveDir = packageFile.GetProperty("RecursiveDir");
// The below NuGetRecursiveDir workaround needs to be done due to msbuild bug https://github.com/Microsoft/msbuild/issues/3121
recursiveDir = string.IsNullOrEmpty(recursiveDir) ? packageFile.GetProperty("NuGetRecursiveDir") : recursiveDir;
if (!string.IsNullOrEmpty(recursiveDir))
{
var newTargetPaths = new List<string>();
var fileName = Path.GetFileName(sourcePath);
foreach (var targetPath in targetPaths)
{
newTargetPaths.Add(PathUtility.GetStringComparerBasedOnOS().
Compare(Path.GetExtension(fileName),
Path.GetExtension(targetPath)) == 0
&& !string.IsNullOrEmpty(Path.GetExtension(fileName))
? targetPath
: Path.Combine(targetPath, recursiveDir));
}
targetPaths = newTargetPaths;
}
}
var buildActionString = packageFile.GetProperty("BuildAction");
var buildAction = BuildAction.Parse(string.IsNullOrEmpty(buildActionString) ? "None" : buildActionString);
// TODO: Do the work to get the right language of the project, tracked via https://github.com/NuGet/Home/issues/4100
var language = buildAction.Equals(BuildAction.Compile) ? "cs" : "any";
var setOfTargetPaths = new HashSet<string>(targetPaths, PathUtility.GetStringComparerBasedOnOS());
// If package path wasn't specified, then we expand the "contentFiles" value we
// got from ContentTargetFolders and expand it to contentFiles/any/<TFM>/
if (!isPackagePathSpecified)
{
if (setOfTargetPaths.Remove("contentFiles" + Path.DirectorySeparatorChar)
|| setOfTargetPaths.Remove("contentFiles"))
{
foreach (var framework in packArgs.PackTargetArgs.TargetFrameworks)
{
setOfTargetPaths.Add(PathUtility.EnsureTrailingSlash(
Path.Combine("contentFiles", language, framework.GetShortFolderName()
)));
}
}
}
// this if condition means there is no package path provided, file is within the project directory
// and the target path should preserve this relative directory structure.
// This case would be something like :
// <Content Include= "folderA\folderB\abc.txt">
// Since the package path wasn't specified, we will add this to the package paths obtained via ContentTargetFolders and preserve
// relative directory structure
if (!isPackagePathSpecified &&
sourcePath.StartsWith(packArgs.CurrentDirectory, StringComparison.CurrentCultureIgnoreCase) &&
!Path.GetFileName(sourcePath)
.Equals(packageFile.GetProperty(IdentityProperty), StringComparison.CurrentCultureIgnoreCase))
{
var newTargetPaths = new List<string>();
var identity = packageFile.GetProperty(IdentityProperty);
// Identity can be a rooted absolute path too, in which case find the path relative to the current directory
if (Path.IsPathRooted(identity))
{
identity = PathUtility.GetRelativePath(PathUtility.EnsureTrailingSlash(packArgs.CurrentDirectory), identity);
identity = Path.GetDirectoryName(identity);
}
// If identity is not a rooted path, then it is a relative path to the project directory
else if (identity.EndsWith(Path.GetFileName(sourcePath), StringComparison.CurrentCultureIgnoreCase))
{
identity = Path.GetDirectoryName(identity);
}
foreach (var targetPath in setOfTargetPaths)
{
var newTargetPath = Path.Combine(targetPath, identity);
// We need to do this because evaluated identity in the above line of code can be an empty string
// in the case when the original identity string was the absolute path to a file in project directory, and is in
// the same directory as the csproj file.
newTargetPath = PathUtility.EnsureTrailingSlash(newTargetPath);
newTargetPaths.Add(newTargetPath);
}
setOfTargetPaths = new HashSet<string>(newTargetPaths, PathUtility.GetStringComparerBasedOnOS());
}
// we take the final set of evaluated target paths and append the file name to it if not
// already done. we check whether the extension of the target path is the same as the extension
// of the source path and add the filename accordingly.
var totalSetOfTargetPaths = new List<string>();
foreach (var targetPath in setOfTargetPaths)
{
var currentPath = targetPath;
var fileName = Path.GetFileName(sourcePath);
if (string.IsNullOrEmpty(Path.GetExtension(fileName)) ||
!Path.GetExtension(fileName)
.Equals(Path.GetExtension(targetPath), StringComparison.OrdinalIgnoreCase))
{
currentPath = Path.Combine(targetPath, fileName);
}
totalSetOfTargetPaths.Add(currentPath);
}
return totalSetOfTargetPaths.Select(target => new ContentMetadata()
{
BuildAction = buildAction.Value,
Source = sourcePath,
Target = target,
CopyToOutput = packageFile.GetProperty("PackageCopyToOutput"),
Flatten = packageFile.GetProperty("PackageFlatten")
});
}