File path with "$(TopDir)\**\packages.config" - msbuild

This sounds really basic but couldn't find any info on internet about it.I am working with msbuild and inside a .proj file I found the following line
<ItemGroup>
<PackageFiles Include="$(TopDir)\**\packages.config" />
</ItemGroup>
I know that ".\" means current directory and "..\" previous one, but what about "**\"?

That 'double star' (i.e. **) in msbuild is used with items.
It means get all sub-directories. In your example code, it can be read as: Under the top directory, get all packages.config files in all sub-directories.
The latest docs are at Microsoft here:
https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-select-the-files-to-build

This has nothing to do with paths in CMD or DOS. This is how MSBuild's wildcards work. You can read about them here. Basically, ** matches a partial path, so in your case the items include all packages.config from any subfolders of $(TopDir). (And $(TopDir) would be specified in some <PropertyGroup> elsewhere.)

Related

How do I actually use the LinkerBindInputPaths PropertyGroup in a WiX Installer script

I've got a WiX installer script that references files in a folder at the root of my Visual Studio Solution folder. In my .wixproj, I've tried setting the Properties | Tool Setting | Linker to
-b $(SolutionDir)\WixSource
and get an error because within the middle of the $SolutionDir variable is a folder with a space. So, I tried
-b "$(SolutionDir)\WixSource"
and get an error complaining about using quotes around the name and that I should put a double backslash at, apparently, the end of the folder with spaces in it. --can't do that because it is inside the $(SolutionDir) macro.
So, following a suggestion on SO, I looked toward trying to use the LinkerBindInputPaths PropertyGroup, but the documentation is severely lacking on actual usage of this property.
I'm running WiX 3.7, and the following isn't allowed...
While Googling, I was told to that it needs to go inside the <Package> element like:
<Package ...>
<PropertyGroup>
<LinkerBindInputPaths ???/>
</PropertyGroup>
</Package>
The WiX documentation # http://wix.sourceforge.net/manual-wix3/msbuild_task_reference_light.htm says
Specifies a binder path that the linker should use to locate all files. This is equivalent to the -b switch in light.exe.
Named BindPaths are created by prefixing the 2-or-more-character bucket name followed by an equal sign ("=") to the supplied path.
With no examples found from googling, I can't decipher what a '2-or-more-character bucket name' is, or if there are any rules around it.
I was hoping to be able to specify something like $(var.SolutionDir)\WixSource, but need some help identifying how to solve this problem...
LinkerBindInputPaths is an item group, not a property group. It goes in your .wixproj MSBuild project file. For example:
<ItemGroup>
<BindInputPaths Include="$(MSBuildProjectDirectory)" />
<BindInputPaths Include="$(WIX_ROOT)src" />
<BindInputPaths Include="$(WIX_ROOT)bin" />
<BindInputPaths Include="$(WIX_ROOT)" />
</ItemGroup>

MSBuild - Project-specific targets for solution does not work

I have a solution that has multiple projects in it, including a web application. I want MSBuild to execute "WebPublish" target against the web application project and "default target" for all other projects in the solution.
This MSDN article says that I can do it specifying the command line
msbuild SlnFolders.sln /t:NotInSlnfolder:Rebuild;NewFolder\InSolutionFolder:Clean
But I never could make it work - MSBuild return an error, something like "NotInSlnFolder:Rebuild" target does not exist. It does not matter what target to specify, Build, Rebuild or Clean - it does not work in any case.
How can I achieve my goal of specifying project-specific targets for a solution?
The MSDN documentation does not work. Or have I missed something?
NOTE: This workaround is not officially supported by Microsoft, so there is no guarantee that it will work forever.
Short Answer
In folder with the SLN file, create the file before.{YourSolution}.sln.targets, with the following content: (Replace what in curly brackets to whatever you need.)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="{MyCompany_MyProduct_WebApp:WebPublish}">
<MSBuild
Condition="'%(ProjectReference.Identity)' == '{$(SolutionDir)MyCompany.MyProduct.WebApp\MyCompany.MyProduct.WebApp.csproj}'"
Projects="#(ProjectReference)"
Targets="{WebPublish}"
BuildInParallel="True"
ToolsVersion="4.0"
Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"
SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)" />
</Target>
</Project>
After that you can execute the command line:
msbuild {YourSolution}.sln /t:{MyCompany_MyProduct_WebApp:WebPublish}
Long Answer
If you add environment variable MSBUILDEMITSOLUTION, setting its value to 1, MSBuild will not delete temporary files generated for the solution and projects.
This will allow you to find {YourSolution}.sln.metaproj and {YourSolution}.sln.metaproj.tmp files generated in the solution folder, which are just standard MSBuild project files.
For MSBuild 3.5, the generated file is {YourSolution}.sln.cache and is retained regardless of environment variables. Analyzing those files, you will understand low-level details of the process and to see the customization opportunities available.
After executing MSBuild with some project-specific target in the .Metaproj file you will find out that the list of project-specific targets is hardcoded and only standard targets are supported (Build, Rebuild, Clean, Compile, Publish; note: Publish and WebPublish are not the same). MSBuild 3.5 only generates Clean, Rebuild and Publish targets as well as a target with just the project's name that means "Build".
You also can see that NotInSlnfolder:Rebuild is just a name of an autogenerated target. In reality MSBuild does not parse it and does not care about project names and location. Also note that the autogenerated target names specify the project name with solution folders hierarchy if it's in one, e.g. SolFolder\SolSubfolder\ProjectName:Publish.
One more critically important thing you will find: The MSBuild Target Name does not support dots. All dots in project names are replaced with underscores. For example, for a project named MyCompany.MyProduct.Components you will have to specify in the command line:
/t:MyCompany_MyProduct_Components:Rebuild
That's why even standard project-specific target Build didn't work - my project name contained dots.
Analyzing file {YourSolution}.sln.metaproj.tmp, you will find out that at runtime it tries to import targets from file named before.{YourSolution}.sln.targets and after.{YourSolution}.sln.targets, if those files exist. This has a key to the workaround for this MSBuild limitation/bug.
You can open your solution file in text editor and check whether following line is exist or not if not then you can add
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> inside the <Project> tag.
Hope this help you.

MSBuild Extension Pack Zip the folders and subfolders

I have to Zip my folders and subfolders Using MSbuild, I was looking at the MSBuild Extension pack, and tried this
<ItemGroup>
<ZipFiles Include="\Test\Web\**\*.*" >
<Group>Release</Group>
</ZipFiles>
</ItemGroup>
<MSBuild.ExtensionPack.Compression.Zip TaskAction="Create" CompressFiles="#(ZipFiles)" ZipFileName="$(WorkingDir)%(ZipFiles.Group).zip"/>
When I do this it just keep adding all the files to root, instead of adding it into the specific subfolder within the zip file.
I am missing something, can anyone help here please.
You need to provide a RemoveRoot property, this property sets the root to remove from the compress files path. (More info)
<ItemGroup>
<ZipFiles Include="\Test\Web\**\*.*" >
<Group>Release</Group>
</ZipFiles>
</ItemGroup>
<MSBuild.ExtensionPack.Compression.Zip
TaskAction="Create"
CompressFiles="#(ZipFiles)"
ZipFileName="$(WorkingDir)%(ZipFiles.Group).zip"
RemoveRoot="\Test\Web"/>
I believe you need to have a value for the RemoveRoot property.
Since, nobody answered early, I went ahead and used 7 Zip, Command line utility, to do that.

How can I get MIDL to search additional include directories for qualified paths

Update: Just over six months after opening a support call to Microsoft it has been rejected, they claim it is not a bug (since the documentation doesn't explicitly say that the behaviour seen isn't the correct one). They rejected the DCR saying that since they haven't heard any complaints in the last 10 years this is obviously not a common use case.
This is a call to arms, if you have run into the same issue, please open a support call with Microsoft so they understand that it should be fixed. I know that at least someone has run into the same problem becasue I found this comment in Chrome's source code:
# Building .idl files.
# This is a total mess. MIDL needs to be run from $OPEN_DIR because it's too
# stupid to apply its include paths to a relative path like "ui/ie/bla.idl"
# (it only looks in the current dir). So we have to jump through hoops to fix
# up our relative include paths and output files.
Original question:
I have the following file structure:
C:\first\Foo.idl
C:\second\Bar.idl
Where Bar.idl contains the following line:
import "first/Foo.idl";
How can I get midl to compile Bar.idl when compiling from C:\second?
If I imported Foo.idl directly (without specifying first/) then specifying first as an additional include directory would be enough (midl /I c:\first Bar.idl) and it would find Foo.idl
Alternately if I compiled from C:\ (midl second\Bar.idl) that would be OK too.
The problem is that when compiling from within C:\second with the command line midl /I C:\ Bar.idl, I get the following compilation error:
c1 : fatal error C1083: Cannot open source file: 'first\Foo.idl': No such file or directory
It looks like midl is willing to search relative paths only if they are relative to the current directory and not to one of the specified additional include directories and uses the additional include directories only for unqualified file names, this behaviour is specific to the import keyword, when using include the results are as expected.
I would like to be able to add two different additional include directories so that if I have the file on my local machine midl will take that version, otherwise it will take the file from the server (so chdiring to the root folder is not an option).
Is there a way to get around this?
Its the end of 2020 and MIDL 3.0 is out. However, the problem described by the OP still persists. But if you're using Visual Studio there is a straight forward way to deal with that issue.
If you're adding an .idl file to a project the following MSBuild code gets generated in the project file to which the .idl file is added:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
</ItemGroup>
If you add a second file in another folder and reference the first one this will generate another entry:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
<Midl Include="Folder2\YourSecondCustomFile.idl" />
</ItemGroup>
The problem is that if you compile that code the MIDL compiler will not be aware of any additional include directories. Hence, if you add
#include "YourCustomFile.idl"
at the beginning of YourSecondCustomFile.idl the MIDL compiler will not search Folder1 for any .idl files to be included and compilation will fail.
However, by adding AdditionalIncludeDirectories MSBuild item metadata you can influence which folders are passed to the MIDL compiler as additional include directories.
So, to instruct the MIDL compiler to search Folder1 for include files when compiling YourSecondCustomFile.idl modify the MSBuild code as follows:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
<Midl Include="Folder2\YourSecondCustomFile.idl">
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)Folder1</AdditionalIncludeDirectories>
</Midl>
</ItemGroup>
ProjectDir is an MSBuild property that points to the directory containing the current project (at least in C++ projects it does). The ; is used to separate different directories. Each of those directories will be passed to the MIDL compiler as a separate directory to search for include files.
This should resolve the compilation problem. I don't think the AdditionalIncludeDirectories item metadata can be added using Visual Studio's user interface so you should edit the Visual Studio project directly in a text editor.
Note that the item metadata is valid per item, i.e. for each individual MIDL file. So, you have to add AdditionalIncludeDirectories to every MIDL file that references other MIDL file.
If you need the same content in multiple AdditionalIncludeDirectories you could define a property somewhere else in the project file like this:
<PropertyGroup>
<AdditionalMidlIncludeDirectories>$(ProjectDir);$(ProjectDir)Folder1;$(ProjectDir)Folder2</AdditionalMidlIncludeDirectories>
</PropertyGroup>
And then you use that property everwhere. You could add the same AdditionalIncludeDirectories statement to every single MIDL which would ensure that the same include directories would be used for every MIDL compiler call:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl">
<AdditionalIncludeDirectories>$(AdditionalMidlIncludeDirectories)</AdditionalIncludeDirectories>
</Midl>
<Midl Include="Folder2\YourSecondCustomFile.idl">
<AdditionalIncludeDirectories>$(AdditionalMidlIncludeDirectories)</AdditionalIncludeDirectories>
</Midl>
</ItemGroup>
Edit:
As mentioned in the comment below the code can be further simplified by applying MSBuild's ItemDefinitionGroup. An ItemDefinitionGroup is used to add metadata to MSBuild items which means that the AdditionalIncludeDirectories metadata can be automatically added to each and every Midl element. The ItemDefinitionGroup is defined as follows:
<ItemDefinitionGroup>
<Midl>
<AdditionalIncludeDirectories>
$(ProjectDir);
$(ProjectDir)Folder1;
$(ProjectDir)Folder2
</AdditionalIncludeDirectories>
</Midl>
</ItemDefinitionGroup>
This simplifies the Midl ItemGroup as follows:
<ItemGroup>
<Midl Include="Folder1\YourCustomFile.idl" />
<Midl Include="Folder2\YourSecondCustomFile.idl" />
</ItemGroup>
As you note, while this is dumb, Microsoft Support have confirmed this is not a bug. The following are possible workarounds.
1. Use the /I switch. A lot.
Use the /I switch to specify both c:\first and c:\second, and specify import "Foo.idl" instead of a relative path.
If the command line becomes too long specify a response file.
2. Use symbolic links
Use symbolic links or junctions to the include directories to reference them all into a single hierarchy under a known directory. Then you can use paths relative to that directory.
A pre-build step might be used to maintain the symbolic links.
MKLINK.exe can create junctions or symbolic links.
3. Use an additional build step
Create an additional build step which copies the required files to known locations, then inport them from there.
This is a call to arms...
12 years, 6 months later, I'm here with my pitchfork, ready to go.
In the meantime, for an easier fix, you can go to Project Properties -> MIDL -> General -> Additional Include Directories. Setting that to "$(MSBuildProjectDirectory)\YourSubFolder" works just fine.

How do I import the msbuildcommunitytasks project from another msbuild project with a relative file path?

Please go easy I am new to msbuild and msbuildtasks!
How can I set a property which represents a relative file path to a targets file which I want to import? I need relative references so it will work on all dev machines. But the target for import is trying to use the relative file path internally, which won't work as it is re-evaluated relative to the imported target!
Effectively I am trying to work around the documented behaviour of imported projects:
All relative paths in imported
projects are interpreted relative to
the directory of the imported project.
Therefore, if a project file is
imported into several project files in
different locations, the relative
paths in the imported project file
will be interpreted differently for
each imported project.
There was a similar question at Is it possible to use MSBuild Extension Pack without installation?. That question was how to do the same with the MSBuild Extension Pack, both of which are similar in this aspect. For the Extension Pack you have to declare the property ExtensionTasksPath,and for the Community tasks you have to declare a similar property named MSBuildCommunityTasksLib. So in your case it should look like:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildCommunityTasksLib Condition="'$(MSBuildCommunityTasksLib)' == ''">E:\Data\Development\My Code\Community\MSBuild\CommunityTasks\</MSBuildCommunityTasksLib>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksLib)MSBuild.Community.Tasks.Targets"/>
<Target Name="Demo">
<!-- Use the tasks here -->
</Target>
</Project>
Ok, I've found the answer. Essentially you have to set the property MSBuildCommunityTasksPath as a relative path back to the original containing directory.
For example, given a folder structure like this:
Root---project---Build---{My msbuild project}
|
|-Tools---MSBuildCommunityTasks---{Binaries and Targets}
Where :
{My msbuild project} is in Root\Project\Build\
{MSbuildCommunityTasks} is in Root\Project\Tools\MsBuildCommunityTasks
To get the targets project to reference its binaries via the property MSBuildCommunityTasksPath, it will find the tasks file like this:
<PropertyGroup>
<MSBuildCommunityTasksPath>..\MSBuildCommunityTasks\</MSBuildCommunityTasksPath> <!--Relative path back to yourself-->
</PropertyGroup>
Then you can import the targets file with another relative file reference :
<Import Project="..\..\Tools\MSBuildCommunityTasks\MsBuild.Community.Tasks.Targets"/>
#Sayed Ibrahim Hashimi
Talkin about MSBuild4
Just declaring the MSBuildCommunityTasksLib wont suffice cause if u check the MSBuild.Community.Tasks.Targets file the properties are declared as follows
<PropertyGroup>
<MSBuildCommunityTasksPath Condition="'$(MSBuildCommunityTasksPath)' == ''">$(MSBuildExtensionsPath)\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
<MSBuildCommunityTasksLib>$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.dll</MSBuildCommunityTasksLib>
</PropertyGroup>
So if U only over ride the MSBuildCommunityTasksLib it will again get over ridden in the MSBuild.Community.Tasks.Targets file as it is not conditional
So u HAVE TO ALSO OVERRIDE MSBuildCommunityTasksPath so that its proerty is NOT SET FROM MSBuildExtensionsPath but from ur custom path.
Correst me if I m wrong
This appears to be one answer:
http://social.msdn.microsoft.com/forums/en-US/msbuild/thread/feb782e3-72ae-4476-9011-617796f217b6
But this (if I understand it correctly) appears to be a ridiculous solution. To get the paths to work I need to change the imported project references? What would happen if I wanted to reference the imported project from third project in another folder?!?
I'm a noob at msbuild if I'm quite honest however I've just solved my own problem I had with this. I was turning one of the targets into its own project and it wasn't finding the paths for the msbuild community paths. If you look at your original project you may find something like this
<PropertyGroup>
<ExtensionTasksPath>./</ExtensionTasksPath>
<MSBuildCommunityTasksPath>./</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="MSBuildExtensionPack\MSBuild.ExtensionPack.tasks"/>
<Import Project="MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
Copy this code into your new project and it should work.
I just wanted to add, since i cannot comment (rep), that to do a path to your particular project you can use $(SolutionDir) on your property group like so:
$(SolutionDir)\My Code\Community\MSBuild\CommunityTasks\
This way its not tied down to a specific drive and can be based off of the location of the project relative to your solutions directory structure.
Also thanks for the answer above it helped me in my project with the addition above.