Repeated RemoveContent using msbuild - msbuild

I have a directory structure that looks like the below. I don't know ahead of time how many PARENT folders there will be, but I do know that each one will contain exactly one folder named DATA. The name of the DATA folder does not change, but the PARENT folders could be called anything.
I would like to delete all the contents of the DATA folders, but leave the DATA folders themselves in places, using msbuild.
I know that you can use the msbuild Community Tasks/extension RemoveContent task to remove all subdirectories and files from a folder without deleting the folder itself, but I can't figure out how to select all the DATA folders and call RemoveContent on each.
TOP
- PARENT1
- FOLDER1
- FOLDER2
- DATA
- SUBFOLDER
- SUBSUBFOLDER1
- <files>
- SUBSUBFOLDER2
- <files>
- SUBFOLDER
- SUBSUBFOLDER1
- <files>
- SUBSUBFOLDER2
- <files>
- PARENT2
- FOLDER1
- DATA
- SUBFOLDER
- SUBSUBFOLDER1
- <files>
- SUBSUBFOLDER2
- <files>
- SUBFOLDER
- SUBSUBFOLDER1
- <files>
- SUBSUBFOLDER2
- <files>
- PARENT3
- FOLDERABC
- DATA
- SUBFOLDER
- SUBSUBFOLDER1
- <files>
- SUBSUBFOLDER2
- <files>
- SUBFOLDER
- SUBSUBFOLDER1
- <files>
- SUBSUBFOLDER2
- <files>
- ...

You can list the directories using a property function using System.IO.GetDirectories since that allows listing direstories using wildcards easily, then batch over the list using the RemoveContent task. Should be something like this:
<Target Name="EraseEverythingUnderAllDataDirs">
<ItemGroup>
<Dirs Include="$([System.IO.Directory]::GetDirectories(`path\toTOP`,`DATA`,System.IO.SearchOption.AllDirectories))" />
</ItemGroup>
<MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent"
Path="%(Dirs.Identity)"/>
</Target>
I'd suggest first checking if this is what you want, using the Message task instead of RemoveContent. No kidding, I once started RemoveContent on c:\ by accident and was not amused with the result.

Related

csproj include (recursive) folders based on a list (batching)

I want to add some files recursively to a single project that are located in folders inside my solution root.
Right now, I found a working solution to add those files by specifying each folder manually:
<ItemGroup>
<Content Include="..\Folder1\**\*">
<Link>Folder1\%(RecursiveDir)\%(Filename)%(Extension)</Link>
</Content>
<Content Include="..\Folder2\**\*">
<Link>Folder2\%(RecursiveDir)\%(Filename)%(Extension)</Link>
</Content>
<Content Include="..\Folder3\**\*">
<Link>Folder3\%(RecursiveDir)\%(Filename)%(Extension)</Link>
</Content>
</ItemGroup>
As those can be a handfull folders and they can be different depending on my solution, I want to have an easy list to define the folder names.
Something like this:
<PropertyGroup>
<DefinedResourceFolders>Folder1;Folder2;Folder3</DefinedResourceFolders>
</PropertyGroup>
This would also allow me to input this as a property directly when calling msbuild to extend the files that are going to the build output, maybe.
I tried to look into the MSBuild Batching documentation and several other online sources, but I could not figure it out. Most batching examples I found were working with Targets, not Includes into the solution items.
Is this possible? How would I define the <ItemGroup> for the content then?
P.S.: I don't care about wildcard issues with .csproj files when new files are added, etc. This will either be a single "Resources" project only containing those displayed files, or I am using my external .props file that I am importing into each .csproj file anyway.
Suppose you want to include all files in folders root/Folder1, root/Folder2 and root/Folder3 recursively.
This is how the builds (both VS and CLI) do what you want. However, VS will not show the files as part of the project.
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="__AddBatchedContent;$(InitialTargets)">
<!-- the usual properties -->
<!-- You could of course define the folder array directly in an Item,
but this is what you wanted :-) -->
<PropertyGroup>
<Lookups>Folder1;Folder2;Folder3</Lookups>
</PropertyGroup>
<ItemGroup>
<LookupDir Include="$(Lookups)" />
</ItemGroup>
<Target Name="__AddBatchedContent">
<ItemGroup>
<!-- save the original source folder of the globbed file name in custom
metadata "Folder" so we can use it later as its output base folder -->
<__BatchedFiles Include="..\%(LookupDir.Identity)\**\*" Folder="%(LookupDir.Identity)" />
<Content Include="#(__BatchedFiles)" Link="%(Folder)\%(RecursiveDir)\%(Filename)%(Extension)" CopyToOutputDirectory="Always" />
</ItemGroup>
</Target>
</Project>
Note that if you want to put this in a .targets file to use this technique in multiple projects, you should replace the ..\ with $(MSBuildThisFileDirectory)..\ so that the path is relative to the (known) location of the .targets file instead of the (unknown) location of the importing project.
Thanks to #Ilya Kozhevnikov for the basis of this answer.
You are not the only one who would like this to be simpler :-): https://github.com/dotnet/msbuild/issues/3274

How to extract NuGet contentFiles to a specific directory?

What I've tried so far:
I've got the following .nuspec file, defining my package (simplified for better readability):
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
.
.
.
<dependencies/>
<contentFiles>
<files include="any\any\config\*.*" buildAction="None" copyToOutput="true" flatten="false"/>
</contentFiles>
</metadata>
<files>
<file src="bin\Assembly.dll" target="lib\net461" />
<file src="bin\Assembly.pdb" target="lib\net461" />
<file src="Config\cf.xml" target="contentFiles\any\any\config"/>
<file src="Config\cf.txt" target="contentFiles\any\any\config"/>
</files>
</package>
As you can see, it contains a compiled Assembly along with it's debug symbols file as well as two content Files in a sub directory.
This results in the following compilation output, where the Assembly.dll is extracted to the output directory as well as the config sub directory (with the two cf.* files in it):
Question:
What I want to do is to move the sub directory config one step up in the directory tree, so it sits next to the output directory - basically maintaining the structure of the file input in the .nuspec file (assuming bin is the output directory of my .csproj):
How can I tell the NuGet package the exact location where I want the contentFiles to be extracted to?
You can achieve this by creating a MSBuild target that moves your config folder one level above the project build output.
Add a .targets file with the following content to your NuGet's build\net461 folder:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Sets up items for target MoveConfigFiles, so that they can be used as Inputs and Outputs. -->
<Target Name="PrepareMoveConfigFiles" AfterTargets="Build">
<!-- ItemGroup wrapped in Target to delay evaluation of contained Items -->
<ItemGroup>
<!-- Contains source locations of files to be moved -->
<ConfigSourceFiles Include="$(OutputPath)\config\**\*.*" />
<!-- Contains target locations of files to be moved -->
<ConfigTargetFiles Include="#(ConfigSourceFiles->'%(FullPath)'->Replace('\config\', '\..\config\'))" />
</ItemGroup>
</Target>
<!-- Moves files from $(OutputPath)\config to $(OutputPath)\..\config,
then deletes $(OutputPath)\config. -->
<Target Name="MoveConfigFiles" AfterTargets="PrepareMoveConfigAndSqlFiles" Inputs="#(ConfigSourceFiles)" Outputs="#(ConfigTargetFiles)">
<Move SourceFiles="#(ConfigSourceFiles)" DestinationFiles="#(ConfigTargetFiles)" />
<RemoveDir Directories="$(OutputPath)\config" />
</Target>
</Project>
The target PrepareMoveConfigFiles is executed after the Build target (which guarantees the NuGet's contents exist in build output), and sets up items used as Inputs and Outputs of the next MoveConfigFiles target. (The items are set inside this target to ensure they are evaluated after the Build target has completed. If the item definitions would be placed below the Project level, they would be evaluated earlier and thus would potentially be empty because the files of the content folder have not yet been deployed.)
The ConfigSourceFiles item just contains the source files, and ConfigTargetFiles takes the paths of all ConfigSourceFiles and replaces \config\ with \..\config\, which leads to the desired target locations.
The MoveConfigFiles target then uses the Move and RemoveDir tasks to accomplish the movement of files and deletion of the original config folder.
It is a weird question...
You have several things to do a nuspec file;
The main are "the nuget packages".
Your nuspec line:
<file src="bin\Assembly.dll" target="lib\net461" />
<file src="bin\Assembly.pdb" target="lib\net461" />
I don't understand Assembly.pdb to distribute it, but is not important.
The goal of this lines are distribute yout dlls in your projects.
When you compile your solution (in your solution dir) it search for this dlls in same root application directory or else check if are register on system (gac mainly).
On the other hand other kind of lines are the "content" into nuget
<file src="Config\cf.xml" target="contentFiles\any\any\config"/>
<file src="Config\cf.txt" target="contentFiles\any\any\config"/>
This is additional content but maybe it may not will be used in the project.
If you want a route system you can use content, but you could have problems using the dlls.
So, I suppose that maybe you would this:
<file src="bin\Assembly.dll" target="contentFiles\any\any\bin" />
<file src="bin\Assembly.pdb" target="contentFiles\any\any\bin" />
<file src="Config\cf.xml" target="contentFiles\any\any\config"/>
<file src="Config\cf.txt" target="contentFiles\any\any\config"/>
And other nuspec example (I would not do this, but, it is possible to do it):
<files>
<file src="content\bin\youtBinayFile" target="content\bin\youtBinayFile" />
<file src="content\Config\YourConfigFile" target="content\Config\YourConfigFile" />
<file src="readme.txt" target="readme.txt" />
</files>
I hope I've helped

Ant build to operate only on subdirectories

I'm trying to create an Ant build that will run a target in every subfolder in a folder. I need this one in particular to only run at the subfolder level, because if I run it from the top folder and tel it to include subfolders, it scrambles the results.
I put the following together from suggestions I've seen, and it seems close but it's not working. A couple of points about it:
When I run this, I get "C:\Developer\SVN\trunk\DITA\xxx_conversion\test\${subdir} does not exist." (A subdirectory does exist, I have ...\test\testsub\xxx_xxx.dita)
The target named "script" works perfectly by itself. If I point it to a folder, it transforms what's in it and gives me the result I need.
In the foreach task if I change to just <dirset dir="."> as a test then the build succeeds, but it basically just runs the "script" target as is.
In the target named "script", if I change "*.dita" to "**\*.dita" it then includes the top level folder in its operation so the result is not what I need, which is just to take each .dita file in each subfolder and wrap it in a folder of the same name as the dita file:
Starting state: testsub\xxx_xxx.dita
Desired result: testsub\xxx_xxx\xxx_xxx.dita
Scrambled result: testsub\xxx_xxx\testsub\xxx_xxx.dita
If anyone can tell me what's wrong with this (possibly many things) that would be great:
<project name="move_and_wrap_dita_topics" default="script" basedir="C:\Developer\SVN\trunk\DITA\xxx_conversion\test">
<taskdef resource="net/sf/antcontrib/antlib.xml">
</taskdef>
<foreach target="script" param="worksheet" inheritall="true">
<path>
<dirset dir="${subDir}">
<include name="*"/>
</dirset>
</path>
</foreach>
<target name="script">
<copy todir="C:\Developer\SVN\trunk\DITA\xxx_conversion\cleaned" verbose="true">
<fileset dir="C:\Developer\SVN\trunk\DITA\xxx_conversion\test">
<include name="*.dita"/>
</fileset>
<scriptmapper language="javascript">
self.addMappedName(source.replace(source.split('.')[0], source.split('.')[0] + "/" + source.split('.')[0]));
</scriptmapper>
</copy>
</target>
</project>
As an alternative, if there's some way to just write the "include name" in the "script" task so that it skips the top folder completely and only rewrites starting at the subfolders, that would be even simpler. I've tried <include name="/*.dita"/>and all sorts of other variations but no luck.
Starting state: testsub\xxx_xxx.dita
Desired result: testsub\xxx_xxx\xxx_xxx.dita
This should be doable with a single regex mapper, you don't need the foreach at all:
<copy todir="C:\Developer\SVN\trunk\DITA\xxx_conversion\cleaned" verbose="true">
<fileset dir="C:\Developer\SVN\trunk\DITA\xxx_conversion\test"
includes="*/*.dita" />
<!-- use handledirsep to treat \ as if it were / in the source file name -->
<regexpmapper handledirsep="true" from="^(.*)/([^/]*)\.dita$"
to="\1/\2/\2.dita" />
</copy>
The includes="*/*.dita" will match .dita files that are one level below the base directory of the fileset, and the regex will convert testsub/xxx.dita in the source to testsub/xxx/xxx.dita in the destination. If you want to find files that are one-or-more levels deep then use */**/*.dita instead.

Use File Input To Copy Specific Folders With MSBuild

I have a working directory with
folder1, folder2, folder3, folder4, .. folder10
I have a text file with project (folder) names in it:
file content:
folder1
folder2
folder3
I want to read the lines from the file, and then copy only those folders to a new folder while maintaining directory structure.
mynewfolder{ folder1, folder2, folder3 }
There will be more than one file for input. The resulting working directory (I'll call it parent) would change like this:
parent before msbuild:
parent{ folder1, folder2, .., folder10, mybuild.xml}
parent after msbuild:
parent{ folder1, folder2, .., folder10, mybuild.xml,
mynewfolder{folder1, folder2, folder3},
myothernewfolder{folder5, folder7, folder9} }
The closest I've been able to get is to copy into mynewfolder all the contents of folder1, folder2, and folder3 (so, structure was lost)
I've tried to use FindInList, and I've tried using the transform method, and I also tried to adjust everything to match this example:
MSBuild - Comparing ItemGroups metadata
But I haven't had much luck.
I've only had about 5 hours experience with MSBuild so I'm a bit lost at this point. Thanks in advance for any help you can offer!
Is this what you want?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<Target Name="Sample">
<ItemGroup>
<_File Include="fileWithDirectorieNames.txt" />
</ItemGroup>
<PropertyGroup>
<_DestDir>d:\copy\</_DestDir>
</PropertyGroup>
<ReadLinesFromFile File="#(_File)" >
<Output TaskParameter="Lines" ItemName="ItemsFromFile"/>
</ReadLinesFromFile>
<ItemGroup>
<_files2Copy Include="%(ItemsFromFile.Identity)\**\*" >
<lastDir>%(ItemsFromFile.Filename)%(ItemFromFile.Extension)\</lastDir>
</_files2Copy>
</ItemGroup>
<Copy SourceFiles="#(_files2Copy)"
DestinationFiles="#(_files2Copy->'$(_DestDir)%(lastDir)%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>

Email reports from cruisecontrol?

Is there a way to have cruisecontrol to email test result in through the config file? C:\Program Files\cruisecontrol\test-output\index.html
This is my current publish setting in the config.xml:
<publishers>
<currentbuildstatuspublisher file="${log.dir}/status.txt"/>
<htmlemail
buildresultsurl="http://${build.server}/buildresults/${project.name}"
subjectprefix="[CruiseControl] "
xsldir="${cruise.install.dir}/webapps/cruisecontrol/xsl"
css="${cruise.install.dir}/webapps/cruisecontrol/css/cruisecontrol.css"
logdir="${log.dir}"
mailhost="mailhost" defaultsuffix="#myemail.com" reportsuccess="always"
returnaddress="${return.email}"
>
<always address="${team.email}"/>
</htmlemail>
</publishers>
you should be able to include it using the fileMerge task, something like this:
<publishers>
<merge>
<files>
<file>C:\Project\test-output\emailable-report.html</file>
<file><!-- path --></file>
</files>
</merge>
</publishers>
Edit: Assuming the file is XML based...