xcopy with MsBuild - msbuild

I have a really simple build script that looks like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Bundle">
<ItemGroup>
<BuildArtifacts Include="..\_buildartifacts" />
<Application Include="..\_application" />
</ItemGroup>
<Target Name="Clean">
<RemoveDir Directories="#(BuildArtifacts)" />
<RemoveDir Directories="#(Application)" />
</Target>
<Target Name="Init" DependsOnTargets="Clean">
<MakeDir Directories="#(BuildArtifacts)" />
<MakeDir Directories="#(Application)" />
</Target>
<Target Name="Bundle" DependsOnTargets="Compile">
<Exec Command="xcopy.exe %(BuildArtifacts.FullPath) %(Application.FullPath) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" WorkingDirectory="C:\Windows\" />
</Target>
The problem is the Bundle target, only the %(BuildArtifacts.FullPath) gets extracted, %(BuildArtifacts.FullPath) is ignored when the scripts executes.
The command looks like this when executing:
xcopy.exe C:\#Code\blaj_buildartifacts /e /EXCLUDE:C:\#Code\blaj\files_to_ignore_when_bundling.txt" exited with code 4
As you can see, the destination path is not there, if I hard code the paths or just the destination path it all works. Any suggestion on what I am doing wrong here?
Update
I managed to solve the problem, I removed the last part WorkingDirectory="C:\Windows\"
And changed the script into this:
<Exec Command="xcopy.exe #(BuildArtifacts) #(Application) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" />
and now it's working :)

I managed to solve this. I've updated the question with the solution.
I removed the last part WorkingDirectory="C:\Windows\" And changed the script into this:
<Exec Command="xcopy.exe #(BuildArtifacts) #(Application) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" />
and now it's working :)

You need to execute xcopy twice. You are trying to use task batching for two different item arrays in the same invocation, it doesn't work like that. Try this:
<Target Name="Bundle" DependsOnTargets="Compile">
<Exec Command="xcopy.exe %(BuildArtifacts.FullPath) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" WorkingDirectory="C:\Windows\" />
<Exec Command="xcopy.exe %(Application.FullPath) /e /EXCLUDE:$(MSBuildProjectDirectory)\files_to_ignore_when_bundling.txt" WorkingDirectory="C:\Windows\" />
</Target>

Related

How to specify target dir during Copy Task in MSBuild

I have existing MSBuild code that I got help with but I need further help.
<!--?xml version="1.0" encoding="utf-8"?-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Demo">
<ItemGroup>
<Source1 Include="C:\temp\path01\**\*.txt" /> <!-- \%2A.txt -->
<Target1 Include="C:\temp\path02" />
</ItemGroup>
<Target Name="Demo" BeforeTargets="Build;Rebuild">
<!--<Exec Command="xcopy.exe /C /S /F /R /Y /I /D #(Source1) #(Target1)" />-->
<Copy SourceFiles="#(Source1)" DestinationFiles="#(Source1->'#(Target1)\path02\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true" />
<Message Text="== END ===============================" />
</Target>
</Project>
But seems like #(Target1) is not working. It actually creates the directory called #(Target1) instead of using Item value. See the result below.
Creating directory "#(Target1)\path02".
Copying file from "C:\temp\path01\0001.txt" to "#(Target1)\path02\0001.txt".
Copying file from "C:\temp\path01\0002.txt" to "#(Target1)\path02\0002.txt".
Creating directory "#(Target1)\path02\sub".
Copying file from "C:\temp\path01\sub\sub-0003.txt" to "#(Target1)\path02\sub\sub-0003.txt".
Copying file from "C:\temp\path01\sub\sub-0004.txt" to "#(Target1)\path02\sub\sub-0004.txt".
== END ===============================
I would like to avoid hard coding the destination dir if possible.
TIA.

Transform a delimited string to array or ItemGroup

I'm trying to create a MSBuild target to post-process assemblies through a custom executable (e.g. convert.exe).
The target receives a semi-colon ; separated list of assemblies as Input and I would like to batch Exec.
<Target Name="_CollectAssemblies" DependsOnTargets="ResolveReferences">
<ItemGroup>
<_Assemblies Include="#(ReferencePath);#(CopyLocalFiles);#(ResolvedDependencyFiles);#(ReferenceDependencyPaths);$(TargetPath)" />
</ItemGroup>
</Target>
<Target Name="_ConvertFiles" DependsOnTargets="_CollectAssemblies"
Inputs="#(_Assemblies)">
<Exec Command="echo #(_Assemblies)" />
<!--<Exec Command="$(MSBuildThisFileDirectory)convert.exe #(_Assemblies)" />-->
</Target>
The Exec command outputs, echo Assembly1.dll;Assembly2.dll;Assembly3.dll;.
How do I transform the Input so that I can process each assembly individually?
e.g.
echo Assembly1.dll
echo Assembly2.dll
echo Assembly3.dll
So far I have tried:
<Target Name="_ConvertFiles" DependsOnTargets="_CollectAssemblies"
Inputs="#(_Assemblies)">
<ItemGroup>
<_SplitAssemblies Include="$(_Assemblies.Split(';'))" />
</ItemGroup>
<Exec Command="echo %(_SplitAssemblies.Identity)" />
</Target>
Answering my own question. MSBuild already had a way of batching the items (as I thought!).
<Target Name="_ConvertFiles" DependsOnTargets="_CollectAssemblies"
Inputs="#(_Assemblies)">
<ItemGroup>
<_ConvertAssemblies Include="#(_Assemblies)"/>
</ItemGroup>
<Exec Command="$(MSBuildThisFileDirectory)convert.exe %(_ConvertAssemblies.Identity)" />
<Error Text="Stop" />
</Target>

I want to run a task for al files and exclude those who did not change

I have two buildtargets to check my code quality.
I run the following buildtargets every time i compile. This takes up too much time and i would like them to only check the files that did change.
In other words i want to filter files that did not change from the ItemGroup CppCheckFiles / LinterFiles.
<Target Name="CppCheck">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
</Target>
<Target Name="SPLint">
<ItemGroup>
<LinterFiles Include="*main.c" />
<LinterFiles Include="Source/*/*.c" />
<LinterFiles Include="Source/*/*.h" />
</ItemGroup>
<Message Text="$(Configuration) starting." Importance="High" />
<Exec Command="splintCaller %(LinterFiles.FullPath)" />
</Target>
I know that the regular build process does this and i wonder if i have to go so fas as to write my own task.
hmm.. this sounds interesting. I can't help you. But it would be nice if the cppcheck wiki or manual had some small example project that did this.
Some people use cppcheck in commit hooks. I've tried it with GIT myself (I added a linux shell script). And there is a TortoiseSVN plugin you can try (http://sourceforge.net/apps/phpbb/cppcheck/viewtopic.php?f=3&t=443).
The solution is incremental Build. Where MSBuild compares Timestamps to exclude complete Buildtargets if nothing changed.
The following target creates a timesstamp for each file and skippes those files that did not change.
cppcheck.exe returns -1 if an error was detected and the timestamp is not written.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CppCheck" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<CppCheckFiles Include="*main.c" />
<CppCheckFiles Include="Source/*/*.c" />
</ItemGroup>
<Target Name="CppCheck"
Inputs="#(CppCheckFiles)"
Outputs="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp">
<Exec Command="C:\Cppcheck\cppcheck.exe %(CppCheckFiles.FullPath) --enable=style --template="{file}({line}): error:{severity}-{id}: {message}"" />
<MakeDir Directories="CCPCheck"/>
<Touch Files="CCPCheck\%(CppCheckFiles.Filename)%(CppCheckFiles.Extension).stamp" AlwaysCreate = "true" />
</Target>
</Project>

How to delete all file and folders with msbuild

How can I delete all files and folders from a given path?
I tried this, but I'm unable to select the directories.
<Target Name="CleanSource" Condition="$(path)!=''">
<Message Text="path=$(path)"/>
<ItemGroup>
<fileToDelete Include="$(path)\**\*.*" />
<directoryToDelete Include="$(path)\**\" /><!these doest not select any directory at all-->
</ItemGroup>
<Message Text="file to delete:#(fileToDelete)"/>
<Message Text="directory to delete:#(directoryToDelete)"/>
<Delete Files="#(fileToDelete)" />
<Message Text="file effectively deleted:#(DeletedFiles)"/>
<RemoveDir Directories="#(directoryToDelete)" />
<Message Text="Directory effectively deleted:#(RemovedDirectories)"/>
</Target>
The RemoveDir task removes the specified directories and all of its files and subdirectories. You don't have to remove the files and subdirectories first. Just pass the directory name to RemoveDir.
<ItemGroup>
<DirsToClean Include="work" />
</ItemGroup>
<Target Name="CleanWork">
<RemoveDir Directories="#(DirsToClean)" />
</Target>
While there are ways to construct this using just MSBuild, I'd highly recommend the MSBuild Extension pack.
http://msbuildextensionpack.codeplex.com/ [has been moved]
GitHub: MSBuildExtensionPack
Using the pack, you get a RemoveContent task that does exactly what you are needing. Once you install, you'd just do something like:
<MSBuild.ExtensionPack.FileSystem.Folder
TaskAction="RemoveContent" Path="$(PathtoEmpty)"/>
I'm arriving to this conversation a little late, but I found the easiest way to accomplish this was to use the Exec task to execute the batch command given by lain in response to a similar question (with minor edits by yours truly):
<Exec Command="FOR /D %%p IN ("$(path)*.*") DO rmdir "%%p" /s /q" />
Finally I did use powershell wich is much more fast:
<exec>
<executable>powershell.exe</executable>
<buildArgs><![CDATA[-command "& {if( [System.IO.Directory]::Exists($pwd) ){dir $pwd | ri -recurse
-force}}"]]></buildArgs>
</exec>

How do you update an environment variable from inside an Exec task?

I am trying to pipe the output from a command to an Environment variable thus:
<Exec Command="for /f "tokens=*" %%i in ('svn info') do SET SVNINFO=%%i" />
and then use SVNINFO as a property in MSBuild.
While the command line counterpart:
for /f "tokens=*" %i in ('svn info') do SET SVNINFO=%i
works, the change in the value of the Environment variable when called from the Exec does not persist. (I am not able to obtain its value as a property.) Am I missing something here? Is there any better way to achieve this?
Starting with .NET 4.5 you can capture output of the Exec task using its ConsoleOutput parameter by setting ConsoleToMsBuild="true" (documentation). For example, the following target captures %TIME% value into the Time MSBuild property:
<Project>
<Target Name="Build">
<Exec Command="echo %TIME%" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="Time" />
</Exec>
<Message Text="Current time is $(Time)" />
</Target>
</Project>
Maybe using the Exec Task Output is a better way:
<Project DefaultTargets="DefaultTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Exe">
<Exec Command="echo %PATH%">
<Output TaskParameter="Outputs" PropertyName="ExecOutput" />
</Exec>
</Target>
<Target Name="DefaultTarget" DependsOnTargets="Exe">
<Message Text="Result from Exec is $(ExecOutput)" />
</Target>
</Project>