Visual Studio 2022 ignores BaseIntermediateOutputPath property specified in project files - visual-studio

I have a solution containing a large number of projects.
To separate binaries from the sources, I changed BaseOutputPath to "..\bin" and BaseIntermediateOutputPath property in all projects to "..\obj".
It worked for the BaseOutputPath - all binaries are in the common directory.
For the intermediate path it didn't work at all. It broke the projects, because the obj directory is still created in the project folders, but now the files are not excluded from the project and appear as commitable for Git.
What am I doing wrong? Is it normal behavior? I don't want obj directories created in project directories. How can I achieve that behavior?
For now I just removed the property from projects. At least the obj folders are not shown as project folders.

You need to use MSBuild macros such as $(SolutionDir) or $(MSBuildThisFileDirectory) and similar, if you do not want relative paths to start from your project file.
For example, you could set "BaseIntermediateOutputPath" to $(SolutionDir)\obj or better still to $(SolutionDir)\obj-$(Configuration). This way you will separate Debug and Release builds, which, if you don't, will get you into a lot of trouble.
Here is what I use in a particular "Directory.Build.props". Note the <OutDir> entry. This will change the output directory for all projects under this directory to the stage64\lib directory.
<PropertyGroup>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<OutDir>$(SolutionDir)..\stage64\lib\</OutDir>
</PropertyGroup>

Related

How do I configure Visual Studio 2022 to build projects on another drive?

I have a system with a 1 TB SATA-connected SSD as the system disk and a 256 GB M.2 SSD as an auxiliary data disk/scratch drive. I would like to configure Visual Studio 2022 to create all project build directories (but not the projects themselves) inside a folder on this scratch drive (F:\build). From what I can tell, CMake-based projects can achieve this by creating a global CMakeSettings.json template; however, I haven't found anything for MSBuild-based projects. Is it possible to configure the MSBuild defaults to do this?
A folder tree of what I'm trying to do would look a little like this:
F:\
|- foo
|- bar
|- build
|- Project1
|- Project2
MSBuild is not a build script generator like CMake. When you create a project file with Visual Studio or the dotnet tool, the project itself is an MSBuild script. The project file should be source controlled. It is not a 'scratch' file.
Generally, MSBuild projects use an 'intermediate' directory and an 'output' directory. By default, the intermediate directory is obj\ and the output directory is bin\. These defaults can be changed by changing the BaseIntermediateOutputPath and BaseOutputPath properties. (See List of common properties and parameters.)
You can set or change properties globally by using a Directory.Build.props file. (See Customize your build.) The Directory.Build.props file is imported very early which is important because there are numerous properties defined based on the BaseIntermediateOutputPath and BaseOutputPath properties.
You might create a Directory.Build.props file like the following:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetPathOfFileAbove('$(MSBuildThisFile)', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<Acme-Drive Condition="'$(Acme-Drive)' == ''">F:</Acme-Drive>
<Acme-BuildDir Condition="'$(Acme-BuildDir)' == ''">$(Acme-Drive)\build\</Acme-BuildDir>
<BaseOutputPath Condition="'$(BaseOutputPath)' == ''">$(Acme-BuildDir)$(MSBuildProjectName)\bin\</BaseOutputPath>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)' == ''">$(Acme-BuildDir)$(MSBuildProjectName)\obj\</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
Some notes on this MSBuild code:
MSBuild will search up the directory structure and will load the first Directory.Build.props file found. The Import will search for and if present will load the next file. It's a good practice to always add the Import when creating a Directory.Build.props file (or a Directory.Build.targets file). The chain of imports will continue to work if Directory.Build.* files are added or removed in the directory tree.
Acme-Drive and Acme-BuildDir are custom properties. 'Acme-' is used as a prefix. The prefix can be anything that is appropriate for your organization or product. The prefix both provides a lower chance of a property name collision and indicates this is a custom property.
The Condition="'$(Acme-Drive)' == ''" tests if the property is unset. The value F: is only set if the Acme-Drive property doesn't already have a value. Properties can be redefined and overridden from other files and from the command line. For example, passing /p:"Acme-Drive=z:" on the MSBuild command line would switch the drive for that one run.
The value of the $(MSBuildProjectName) property is the name of the current project.
If
Your local working directory for the source code is C:\repos\MyProduct.
The code above is saved as C:\repos\MyProduct\Directory.Build.props.
You have project files:
C:\repos\MyProduct\project1\Project1.csproj
C:\repos\MyProduct\project1\Project2.csproj
then
Both projects will use the same Directory.Build.props file.
For Project1, $(BaseOutputPath) will be F:\build\project1\bin.
For Project2, $(BaseOutputPath) will be F:\build\project2\bin.

How to avoid Visual Studio outputs assemblies in Target Framework Name directories?

We have some netstandard2.0 assemblies. When compiling they are generated within the directory .\bin\Debug\netstandard2.0 (netstandard2.0 dir name is the TFN the Target Framework Name). However only .\bin\Debug is specified in Project Properties. And we'd like to generate those assemblies within .\bin\Debug to get all our assemblies with the same directory.
We developed a Post-Build event to copy back our assemblies and their json/pdb files within .\bin\Debug and then delete the dir .\bin\Debug\netstandard2.0. However this is not a good solution because the FastUpToDate tool used during incremental compilation does'nt find .\bin\Debug\netstandard2.0\ProjectName.pdb and thus our netstandard2.0 projects are always rebuilt even when they are up-to-date.
Set AppendTargetFrameworkToOutputPath to false in your projects.
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>

Relative paths with MSBuild project vs solution

I have a number of projects which are joined into a solution. Every project has it's own directory structure, and csproj files are located on diferrent level of folder structure.
Every csproj has OutputPath property specified. OutputPath - is a relative path and it varies from project to project in such a way so all projects have the same output dir.
It is work OK if I build a separate project. But everything changes if I try to build solution file. In this case every project output folder differs (depends on a number of '..\' in that project's OutputPath).
I do know, that before some moment all was working fine. Nobody changed build.cmd neither any sln or csproj files. But now I have situation described above.
So my question is - what affects how relative path is evaluated? I mean how can I force relative OutputPath to be evaluated starting from folder where csproj file of that particular project is located. Not from folder where .sln file is.
Let's assume I have following directory structure:
dir1
a.sln
dir2
a.csproj
dir21
dir3
b.csproj
a.csproj has output path set to '../../_bin' which is just above dir1 if counted from a.csproj folder
b.csproj has output path set to '../../../_bin' which is same - just about dir1 if counted from b.csproj
a.sln contains both - a.csproj and b.csproj.
When I run msbuild I get a project build to 'dir1/../../_bin' and b project to 'dir1/../../../_bin' - both relative paths of projects files are counted from solution file location, not project files.
Well, I was able to find out what was causing this. That was custom .targets file, which was inferring SolutionDir property at the start of any msbuild.
I did find out that by using MSBuild Explorer. The tool proved to be very useful in my case - I was not aware of third party .target files on my system.
From Msbuild Import Element description
Relative paths in imported projects are interpreted relative to the
directory of the importing 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.
All MSBuild reserved properties that relate to the project file, for example, MSBuildProjectDirectory
and MSBuildProjectFile, that are referenced in an imported project are
assigned values based on the importing project file.
If you add more details or few samples to your question - it will be easier to understand exact problem.
Edit:
Okay, lets try to pinpoint that mystery. First of all - OutputPath could be affected by Environment variables.
2nd - during build sln file transformed into msbuild project file format and stored in temp file. You can get that temporary file if you execute in cmd "set msbuildemitsolution=1" and then trigger build via command line. There you can check that file and see how your individual projects called. But I suppose you will see multiple .csproj /> entries. And global msbuild properties inherited by that calls.
So I suspect if everything was fine before some point and no changes were made - you are missing OutputPath environment variable or some other variable that contributed to construction of OutputPath.
BTW - I think if you want to fix your issue with forcing relative dir - you also can use $(MSBuildProjectDirectory). This is one of msbuild reserved properties (from here), but this will require yo adjust your OutputPath in each csproj file. What i, personally, prefer to avoid, because it could affect some other targets and introduce subtle issues.

What is incremental clean in msbuild and when is it triggered?

I am debugging a bug in my build process that happens occasionally but I can't directly reproduce it. I'm using msbuild with teamcity.
I have a dependency hierarchy like this:
Some.Interop.dll
Dependency-> SharedDllABC.dll
SomeService.exe
Depenendcy-> Some.Interop
Usually the final service exectuable gets in its release directory:
Some.Interop
SharedDllABC.Dll
ServiceExectuable.exe
However I can see in our msbuild logs that sometimes the tertiary dependency gets deleted during an Incremental Clean after everything is built resulting in:
Some.Interop
ServiceExectuable.exe
You can see it here in the msbuild log:
[src\SomeService\SomeService.csproj] _TimeStampAfterCompile
[12:32:43]: [src\SomeService\SomeService.csproj] Compile
// some other targets
[12:32:43]: [src\SomeService\SomeService.csproj] _CopyFilesMarkedCopyLocal
[12:32:43]: [_CopyFilesMarkedCopyLocal] Copy
[12:32:43]: [Copy] Copying file from "C:Projects\trunk\src\Some.Interop\bin\Release\Some.Interop.dll" to "bin\Release\Some.Interop.dll".
// some other targets
[src\Project\SomeService\SomeService.csproj] IncrementalClean
[18:54:42]: [IncrementalClean] Delete
[18:54:42]: [Delete] Deleting file "C:\Projects\trunk\src\Project\SomeService\bin\Release\SharedDllABC.dll".
[18:54:42]: [Delete] Deleting file "C:\Projects\trunk\src\Project\SomeServiceService\bin\Release\SharedDllABC.pdb".
[18:54:42]: [src\Project\SomeService\SomeService.csproj] CoreBuild
[18:54:42]: [src\Project\SomeService\SomeService.csproj] AfterBuild
[18:54:42]: [src\Project\SomeService\SomeService.csproj] Build
This is my direct msbuild output, I just changed the project names/dll names to match my example. By the time this Incremental Clean has occurred the SomeService.csproj has already been built. You can see that its not getting copied. However in other msbuild logs it does properly get copied and then the incremental clean doesn't delete it.
I think incrementeal clean from this post is supposed to clean dll's that were created from previous builds, but that doesn't explain how this dll didn't get built when most of the time it does. In visual studio this always works as well.
I guess I just want to know what exactly is Incremental clean, what causes it to kick in, and maybe what things I should look for when debugging a situation like this (assembly versions, timestamps, etc?)
Try the following:
Add:
<Target Name="IncrementalClean" />
to a .targets file that's included in all projects.
From --> https://github.com/Microsoft/msbuild/issues/1054
#Kebabbi recommends a good fix by editing a csproj file. As of MSBuild 15, there is a simple way to make this apply to all CSPROJ files, instead of editing each csproj file.
https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2017
Directory.Build.props and Directory.Build.targets
Prior to MSBuild version 15, if you wanted to provide a new, custom property to projects in your solution, you had to manually add a reference to that property to every project file in the solution. Or, you had to define the property in a .props file and then explicitly import the .props file in every project in the solution, among other things.
However, now you can add a new property to every project in one step by defining it in a single file called Directory.Build.props in the root folder that contains your source. When MSBuild runs, Microsoft.Common.props searches your directory structure for the Directory.Build.props file (and Microsoft.Common.targets looks for Directory.Build.targets). If it finds one, it imports the property. Directory.Build.props is a user-defined file that provides customizations to projects under a directory.
Create a file Directory.Build.props, and place it adjacent to the SLN file.
<Project>
<Target
Name="ForceAssignProjectConfigurationBeforeSplitProjectReferencesByFileExistence_KLUDGE"
BeforeTargets="_SplitProjectReferencesByFileExistence"
DependsOnTargets="AssignProjectConfiguration" />
</Project>
This could be caused by a bug in MsBuild: https://github.com/Microsoft/msbuild/issues/1054.
A fix is proposed in the comments: https://github.com/Microsoft/msbuild/issues/1054#issuecomment-406438561
When MsBuild determines which items to copy from referenced projects, it should do this recursively but does not properly do this.
As a workaround the following can be added to each csproj.
<Target
Name="ForceAssignProjectConfigurationBeforeSplitProjectReferencesByFileExistence_KLUDGE"
BeforeTargets="_SplitProjectReferencesByFileExistence"
DependsOnTargets="AssignProjectConfiguration"
/>
I just spent a few days trying to figure this out with a similar pattern. In our case it was nuget files that were being removed from the output folder.
NugetPackage (that drops files in x86/x64 subfolders in output folder)
LibraryA.dll
Dependency-> NugetPackage
LibraryB.dll
Dependency-> LibraryA.dll
In our case, we have a number of solution files that are built as part of an msbuild script in a certain order.
The problem was that LibraryB.csproj was included in two solution files.
Solution1 builds and output files are all present.
Solution2 builds and sees that LibraryB.dll is present and up to date, so for some reason triggers the IncrementalClean that removes the NugetPackage files from the output folder.
Once I removed the LibraryB.csproj from solution 2, the problem is solved and the files are present in the output folder.

Project file with just files and no built output

How can I make a project file (VS 2008) that just has some data files in and has no built output?
I can make an empty project and add my data files to it (which get copied to the output folder
), but it produces an EmptyProject.dll after I do a build. I want just my data files in the output directory and not some empty DLL or EXE.
I want the data files to be the only thing in this project as the project will be shared in a couple of solutions.
Our application is C#. All of our normal code projects are C#.
The data files are schemas (XSD). I want these schemas to be in the output folder, but I don't want them included with an existing project. I would like a project named "Schemas" that has nothing in except the XSD files and does nothing except copy the XSD files to the output folder. I would like this in a project file so that the same schemas project can be referenced in multiple solutions.
I don't know of a way to suppress the creation of the .dll file. BUT... here's an easy workaround. In the project properties, Build Events tab, write a Post-build event command line that will delete the file. Something like:
del path\filename.dll
Expanding on Scott's answer:
Create a new project of type Empty project
In Properties->Application, change Output type to Class Library
In Properties->Build->Advanced, change Debug Info to None
In Properties->Build Events, set the Post-build event command line to del $(TargetPath)
That way, the project creates only a DLL, which gets deleted. At the same time, the "copy to output directory" settings on your data files is respected.
Possibly another way is editing the csproj file by replacing this:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
with this:
<Target Name="Build" />
<Target Name="Rebuild" />
Then builds don't create anything. It worked for me.
Same general idea should work for any xxproj file. Just replace the <Import Project...> tags with the <Target...> tags.
I'd be interested in knowing if this causes any issues or doesn't work for anyone.
What do you need a project for if you're not building it?
You can use solution folders to "store" files...
Why not just disable building this project for all configurations (use the Configuration Manager) - that way it won't build.
Great stuff. Expanding on Scott > Daniel's answer:
Safe to remove all References and Properties (AssemblyInfo.cs)
If it is a node/grunt/gulp project then you can invoke it in your Build Events > *Post-build event command line * eg: gulp build or gulp clean
Perhaps you can add removal or obj and bin output folders to your node/grunt/gulp clean scripts mitigating the need for del $(TargetPath)

Resources