使用MSBuild生成解决方案列表时,如何将所有输出复制到另一个文件夹?
我们在解决方案和项目的大文件夹树中有大量遗留代码,其中许多我们不感兴趣,但不幸的是,目前无法移动 我们目前正在使用MSBuild编译我们感兴趣的解决方案,如下所示:使用MSBuild生成解决方案列表时,如何将所有输出复制到另一个文件夹?,msbuild,Msbuild,我们在解决方案和项目的大文件夹树中有大量遗留代码,其中许多我们不感兴趣,但不幸的是,目前无法移动 我们目前正在使用MSBuild编译我们感兴趣的解决方案,如下所示: <ItemGroup> <Solutions Include="some_path\solution1.sln" <Solutions Include="some_other_path\solution2.sln" <Solutions Include="yet_another_path\s
<ItemGroup>
<Solutions Include="some_path\solution1.sln"
<Solutions Include="some_other_path\solution2.sln"
<Solutions Include="yet_another_path\solution3.sln"
</ItemGroup>
<Target Name="Rebuild">
<MSBuild Projects=@(Solutions) Targets="Rebuild" />
</Target>
这可以很好地进行构建,但我们要做的是将各个项目bin\Release文件夹中的所有文件放到一个不同的树结构中,该树结构具有相同的相对文件夹,但具有不同的根
我们如何做到这一点而不只是寻找一个特定的文件模式,例如所有的dll或exe文件,因为我们的文件结构包含许多我们不感兴趣的文件
因此,基本上有没有一种方法可以找到我们的构建脚本正在构建的所有文件,并将它们移动到另一个保留其相对路径的位置?您是否尝试过覆盖输出路径
<MSBuild Projects="@(Solutions)" Properties="OutputPath=$(ParentDir)\Output\%(Solutions.Filename)"/>
这将把每个解决方案的输出放在$ParentDir\Output的树中。这里有两个想法:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<!--
<UsingTask AssemblyFile="$(ProgramFiles)\MSBuild\MSBuild.Community.Tasks.dll" TaskName="Version"/>
-->
<Import Project="$(MSBuildExtensionsPath32)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<PropertyGroup>
<TPath Condition="Exists('$(MSBuildExtensionsPath32)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks')">$(MSBuildExtensionsPath32)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks</TPath>
</PropertyGroup>
<Import Project="$(TPath)"/>
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="ShowVariables" />
<CallTarget Targets="ShowMeTheFiles" />
</Target>
<Target Name="ShowVariables">
<Message Text=" $_WorkingCheckout_ = '$(WorkingCheckout)' " />
<Message Text=" $_MSBuildExtensionsPath32_ = '$(MSBuildExtensionsPath32)' " />
<Message Text=" $_MSBuildProjectDirectory_ = '$(MSBuildProjectDirectory)' " />
</Target>
<Target Name="ShowMeTheFiles">
<ItemGroup>
<AllFiles Include="$(WorkingCheckout)\**\*.*" />
</ItemGroup>
<MSBuild.Community.Tasks.RegexMatch Expression="\d+" Input="%(AllFiles.FullPath)">
<Output TaskParameter="Output" ItemName="MatchedFiles" />
</MSBuild.Community.Tasks.RegexMatch>
<Message Text="rootdir + directory + filename + extension: @(MatchedFiles->'%(rootdir)%(directory)%(filename)%(extension)')"/>
<Copy
SourceFiles="@(MatchedFiles)"
DestinationFiles="@(MatchedFiles->'C:\WUTEMP\DestinationFolder\01\%(RecursiveDir)%(Filename)%(Extension)')"
/>
<MSBuild.ExtensionPack.FileSystem.FindUnder TaskAction="FindFiles" Path="$(WorkingCheckout)\" SearchPattern="F*">
<Output ItemName="AllFilesStartingWithF" TaskParameter="FoundItems"/>
</MSBuild.ExtensionPack.FileSystem.FindUnder>
<Message Text="rootdir + directory + filename + extension: @(AllFilesStartingWithF->'%(rootdir)%(directory)%(filename)%(extension)')"/>
<Copy
SourceFiles="@(AllFilesStartingWithF)"
DestinationFiles="@(AllFilesStartingWithF->'C:\WUTEMP\DestinationFolder\02\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
</Project>
但是我认为RecursiveDir在转换过程中受到了打击:这里是一个存根自定义MSBuild任务。 您可以将RunMyCustomKeeperLogic逻辑更改为您想要的任何逻辑。 我把它作为一个简化的例子,每5个文件保存一次 一旦文件返回到.msbuild世界中,您就可以复制/移动它们并对其执行任何操作
namespace GranadaCoder.Framework.CrossDomain.MSBuild.Tasks.IO
{
using System.Collections.Generic;
using Microsoft.Build.Utilities;
public abstract class FileBasedTaskBase : Task
{
/// <summary>
/// Converts the delimited source file string to and IList.
/// </summary>
/// <param name="sourceFilesString">The source files string.</param>
/// <returns></returns>
protected IList<string> ConvertSourceFileStringToList(string sourceFilesString)
{
IList<string> returnList = sourceFilesString.Split(';');
return returnList;
}
/// <summary>
/// Task Entry Point.
/// </summary>
/// <returns></returns>
public override bool Execute()
{
AbstractExecute();
return !Log.HasLoggedErrors;
}
protected abstract bool AbstractExecute();
}
}
namespace GranadaCoder.Framework.CrossDomain.MSBuild.Tasks.IO//.CustomFileKeepTask
{
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class CustomFileKeepTask : FileBasedTaskBase
{
private static readonly string ROOT_DIRECTORY = "myrootdir";
private static readonly string FULL_PATH = "myfullpath";
private static readonly string FILE_NAME = "myfilename";
private static readonly string DIRECTORY = "mydirectory";
private static readonly string EXTENSION = "myextension";
private static readonly string KEEPER = "mykeeper";
/// <summary>
/// Gets or sets the source files.
/// </summary>
/// <value>The source files.</value>
[Required]
public string SourceFiles { get; set; }
/// <summary>
/// Gets the file versions as a Task Output property.
/// </summary>
/// <value>The file versions.</value>
[Output]
public ITaskItem[] FilteredFiles
{ get; private set; }
/// <summary>
/// Task Entry Point.
/// </summary>
/// <returns></returns>
protected override bool AbstractExecute()
{
InternalExecute();
return !Log.HasLoggedErrors;
}
/// <summary>
/// Internal Execute Wrapper.
/// </summary>
private void InternalExecute()
{
IList<string> files = null;
if (String.IsNullOrEmpty(this.SourceFiles))
{
Log.LogWarning("No SourceFiles specified");
return;
}
if (!String.IsNullOrEmpty(this.SourceFiles))
{
Console.WriteLine(this.SourceFiles);
files = base.ConvertSourceFileStringToList(this.SourceFiles);
}
List<FileInfoWrapper> fiws = new List<FileInfoWrapper>();
foreach (string f in files)
{
FileInfoWrapper fiw = null;
fiw = this.DetermineExtraInformation(f);
fiws.Add(fiw);
}
fiws = RunMyCustomKeeperLogic(fiws);
ArrayList itemsAsStringArray = new ArrayList();
foreach (var fiw in fiws.Where(x => x.IsAKeeper == true))
{
IDictionary currentMetaData = new System.Collections.Hashtable();
currentMetaData.Add(ROOT_DIRECTORY, fiw.RootDirectory);
currentMetaData.Add(FULL_PATH, fiw.FullPath);
currentMetaData.Add(FILE_NAME, fiw.FileName);
currentMetaData.Add(DIRECTORY, fiw.Directory);
currentMetaData.Add(EXTENSION, fiw.Extension);
string trueOrFalse = fiw.IsAKeeper.ToString();
currentMetaData.Add(KEEPER, trueOrFalse);
itemsAsStringArray.Add(new TaskItem(trueOrFalse, currentMetaData));
}
this.FilteredFiles = (ITaskItem[])itemsAsStringArray.ToArray(typeof(ITaskItem));
}
private List<FileInfoWrapper> RunMyCustomKeeperLogic(List<FileInfoWrapper> fiws)
{
int counter = 0;
foreach (var fiw in fiws)
{
if(counter++ % 5 == 0)
{
fiw.IsAKeeper = true;
}
}
return fiws;
}
/// <summary>
/// Determines the file version.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <returns>File version or 0.0.0.0 if value cannot be determined</returns>
private FileInfoWrapper DetermineExtraInformation(string fileName)
{
FileInfoWrapper fiw = new FileInfoWrapper();
fiw.Directory = string.Empty;
fiw.Extension = string.Empty;
fiw.FileName = string.Empty;
fiw.FullPath = string.Empty;
fiw.RootDirectory = string.Empty;
fiw.IsAKeeper = false;
try
{
if (System.IO.File.Exists(fileName))
{
fiw.Extension = System.IO.Path.GetExtension(fileName);
fiw.FileName = System.IO.Path.GetFileNameWithoutExtension(fileName);
fiw.FullPath = fileName;// System.IO.Path.GetFileName(fileName);
fiw.RootDirectory = System.IO.Path.GetPathRoot(fileName);
//Take the full path and remove the root directory to mimic the DotNet default behavior of '%filename'
fiw.Directory = System.IO.Path.GetDirectoryName(fileName).Remove(0, fiw.RootDirectory.Length);
}
}
catch (Exception ex)
{
if (ex is IOException
|| ex is UnauthorizedAccessException
|| ex is PathTooLongException
|| ex is DirectoryNotFoundException
|| ex is SecurityException)
{
Log.LogWarning("Error trying to determine file version " + fileName + ". " + ex.Message);
}
else
{
Log.LogErrorFromException(ex);
throw;
}
}
return fiw;
}
/// <summary>
/// Internal wrapper class to hold file properties of interest.
/// </summary>
internal sealed class FileInfoWrapper
{
public string Directory { get; set; }
public string Extension { get; set; }
public string FileName { get; set; }
public string FullPath { get; set; }
public string RootDirectory { get; set; }
public bool IsAKeeper { get; set; }
}
}
}
下面是一个示例.msbuild或.proj或.xml文件,用于调用上述函数
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="AllTargetsWrapper" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="GranadaCoder.Framework.CrossDomain.MSBuild.dll" TaskName="CustomFileKeepTask"/>
<Target Name="AllTargetsWrapper">
<CallTarget Targets="CustomFileKeepTask1" />
<CallTarget Targets="CustomFileKeepTask2" />
</Target>
<PropertyGroup>
<WorkingCheckout>c:\Program Files\MSBuild</WorkingCheckout>
</PropertyGroup>
<ItemGroup>
<MyTask1IncludeFiles Include="$(WorkingCheckout)\**\*.*" />
</ItemGroup>
<Target Name="CustomFileKeepTask1">
<CustomFileKeepTask SourceFiles="@(MyTask1IncludeFiles)" >
<Output TaskParameter="FilteredFiles" ItemName="MyFilteredFileItemNames"/>
</CustomFileKeepTask>
<Message Text=" MyFilteredFileItemNames MetaData "/>
<Message Text=" ------------------------------- "/>
<Message Text=" "/>
<Message Text="directory: "/>
<Message Text="@(MyFilteredFileItemNames->'%(mydirectory)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="extension: "/>
<Message Text="@(MyFilteredFileItemNames->'%(myextension)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="filename: "/>
<Message Text="@(MyFilteredFileItemNames->'%(myfilename)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="fullpath: "/>
<Message Text="@(MyFilteredFileItemNames->'%(myfullpath)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="rootdir: "/>
<Message Text="@(MyFilteredFileItemNames->'%(myrootdir)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="FilteredFile: "/>
<Message Text="@(MyFilteredFileItemNames->'%(mykeeper)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="rootdir + directory + filename + extension: "/>
<Message Text="@(MyFilteredFileItemNames->'%(myrootdir)%(mydirectory)%(myfilename)%(myextension)')"/>
<Message Text=" "/>
<Message Text=" "/>
<Message Text="List of files using special characters (carriage return)"/>
<Message Text="@(MyFilteredFileItemNames->'"%(myfullpath)"' , '%0D%0A')"/>
<Message Text=" "/>
<Message Text=" "/>
</Target>
<ItemGroup>
<MyTask2IncludeFiles Include="c:\windows\notepad.exe" />
</ItemGroup>
<Target Name="CustomFileKeepTask2">
<CustomFileKeepTask SourceFiles="@(MyTask2IncludeFiles)" >
<Output TaskParameter="FilteredFiles" PropertyName="SingleFileFilteredFile"/>
</CustomFileKeepTask>
<Message Text="SingleFileFilteredFile = $(SingleFileFilteredFile) "/>
</Target>
</Project>
这很接近,但构建文件中的每个解决方案都包含多个项目,这些项目将其文件构建到不同的文件夹中。当我使用您的建议答案时,我会为每个解决方案获得一个文件夹,但这些文件夹包含为解决方案中的所有项目生成的所有文件。而不是每个项目都有一个子文件夹,这正是我所追求的。然后改用项目来构建。*。csproj不是sln。但不幸的是,在我的简化示例中,你看不到10个解决方案中有200个项目,我们试图避免将它们全部显式列出。如果我们必须列出它们,我们可能只需在VisualStudio中查看并更改所有输出路径设置,这样我们就不必担心生成顺序。据我所知,您不认为您可以对任何已知任务执行所需的操作。您需要特殊的逻辑,这意味着自定义msbuild任务。如果你从来没有写过,也没那么糟糕。让我看看是否能找到一个旧的示例。是的,如果我有时间尝试编写一个自定义任务,那么使用MSBuild执行自定义任务可能是我想要的唯一方法。我不确定是否可以使用您的确切示例,因为它似乎是选择要构建哪些文件,而不是选择要获取哪些输出文件。现在,虽然我只想通过100个左右的项目来生成可执行文件并更改其输出文件夹{sigh},但我的示例只是标记文件……。然后在任务之外,您可以将这些文件放在传统命令中。也许我不理解您的示例。对我来说,它看起来像是在标记.cs文件,而我想做的是获取其相对文件夹结构中所有构建文件的列表。如果我必须指定我不想移动的每个文件,我想我实际上最好手动更改100个项目输出。这只是我做的一个愚蠢的例子。另外请注意,MyTask1ExcludeFiles强调的是排除。。。我将试图通过编辑原文来写出逻辑。我删除了那些让你陷入困境的行。基本上,MyTask1IncludeFiles表示获取某个目录中所有文件的列表……。然后我将它们推送到自定义任务中,并用isakeer布尔标志标记其中一些文件。我的逻辑是愚蠢的,你将不得不写你自己的自定义需要。然后,一旦这些文件在自定义任务内被标记…它们的列表在任务外可用。。。让你在他们身上跑或做任何你想做的事。