Rather than building to the default folder structure, which is like
Solution.sln
Project1
bin <- project 1 output
obj <- project 1 intermediate output
Project2
bin <- project 2 output
obj <- project 2 intermediate output
I instead want to build it like
Solution.sln
bin <- project 1 AND 2 output
obj
Project1 <- project 1 intermediate output
Project2 <- project 2 intermediate output
I can do
msbuild "/p:OutputPath=../bin" "/p:IntermediateOutputPath=../obj/" Test123.sln
However, using "/p:IntermediateOutputPath=../obj/$(ProjectName)/" does not work. Instead of creating a folder for each project, it creates one folder literally called $(ProjectName) (I've read that most, but not all of these macros are actually a Visual Studio thing, rather than MSBuild magic).
How can I use a project-specific value (such as ProjectName) in a property value (such as IntermediateOutputPath) when building?
(Some background information:
Having one bin folder on the solution level saves unnecessary copying of output files, which quickly amass over 100 MB in large solutions. Furthermore, it keeps the source folders clean so they can be read-only.
I still want separate obj folders though, because who knows what goes in there - might be the same file names for different projects.)
You can override the CustomAfterMicrosoftCommonTargets property of the Microsoft.Common.targets file. It's allowing injecting custom targets into projects and performs some actions.
The entry point of the build process is Make.targets:
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"; DefaultTargets="EntryMSBuild">
<ItemGroup>
<Project Include="**\*.csproj"/>
</ItemGroup>
<Target Name="EntryMSBuild">
<Message Text="-----Entry-----" Importance="high"/>
<Message Text=" Rebuild " Importance="high"/>
<Message Text="-----Entry-----" Importance="high"/>
<MSBuild Projects="#(Project)" Targets="rebuild" Properties="CustomBeforeMicrosoftCommonTargets=$(MSBuildThisFileDirectory)Setting.targets"/>
</Target>
</Project>
In Setting.targets defined IntermediateOutputPath and OutputPath for each projects:
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<IntermediateOutputPath>..\Global\obj\$(MSBuildProjectName)</IntermediateOutputPath>
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\Global\bin\</OutputPath>
</PropertyGroup>
</Project>
IntermediateOutputPath - depend on $(MSBuildProjectName);
OutputPath - common for all projects.
As a result, you get the desired structure:
Solution.sln
bin <- project 1 AND 2 output
obj
Project1 <- project 1 intermediate output
Project2 <- project 2 intermediate output
Tested on solution with two projects under .net46 and .netstandard2.0. MSBuild 15.6.82.30579
You don’t need store custom targets under C:\Program Files[(x86)]\microsoft visual studio\2017\xxx\msbuild\15.0 or any predefined path. You override CustomBeforeMicrosoftCommonTargets property that already defined in Microsoft.Common.targets and injected in the project by default.
From Microsoft.Common.targets comment:
This file defines the steps in the standard build process for .NET
projects. It contains all the steps that are common among the
different .NET languages, such as Visual Basic, and Visual C#.
Related
I am learning how to make a static library. I started with windows and Visual Studio.
The directory structure looks like this:
- MyLibraryProject
- include
- MyLibraryProject
- MyLibraryHeader.h
- src
- MyLibrarySource.cpp
- build
- MyLibraryProject.vcxproj
- MyDependentProject
- main.cpp
- MyDependentProject.vcxproj
MyLibraryProject.vcxproj has the following settings:
Setting
Value
Configuration type
Static library (.lib)
Additional Include Directories
$(MSBuildThisFileDirectory)../include/MyLibraryProject
MyDependentProject.vcxproj has no special settings, except I added MyLibraryProject onto refferences, the image features actual names I used:
If I use relative paths in main.cpp, I can build the project - the static linking works just fine and it runs:
#include "../MyLibraryProject/include/MyLibraryProject/MyLibraryHeader.h"
However, I want to include the headers like this:
// fatal error C1083: Cannot open include file: 'MyLibraryProject/MyLibraryHeader.h': No such file or directory
#include <MyLibraryProject/MyLibraryHeader.h>
And that just does not work. I also tried to use property sheet but couldn't get that to work either. I've been searching the internet, but generally found claims that if you add a reference, both headers and static libs will work.
Here's the full repository, if you're willing to take a look. Or ask in the comments if there's information missing.
Project references do not provide the dependent project with any information about headers. The most flexible way to do this instead (in Visual Studio) are property sheets. I created a file MyLibraryProject/build/MyLibraryProjectDependency.props:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
And I added it to MyDependentProject.vcxproj in Property explorer in Visual Studio. This solved the issues and headers are now seen on the path I want them.
I have a pubxml that is used in the VSBuild#1 task and the build works fine except I dont get my .pdb files. How can I make sure the .pdb files are also included in my release?
Project release Config
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Staging2|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>default</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
Pubxml file
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<PublishProvider>FileSystem</PublishProvider>
<LastUsedBuildConfiguration>Staging2</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>False</LaunchSiteAfterPublish>
<ExcludeApp_Data>False</ExcludeApp_Data>
<publishUrl>Publish</publishUrl>
<DeleteExistingFiles>True</DeleteExistingFiles>
<TargetFramework>net472</TargetFramework>
<DebugSymbols>True</DebugSymbols>
<DebugType>Full</DebugType>
</PropertyGroup>
</Project>
YAML build task
task: VSBuild#1
inputs:
solution: '**\MyApps\Main.csproj'
msbuildArgs: '/t:build /p:DeployOnBuild=true /p:PublishProfile=publish.pubxml /p:OutputPath=$(build.artifactStagingDirectory)\MainPublish\'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
Based on what I have looked up, if you can set the /Build=full property it will be fine. Well my question is if thats the case, where do I add it?
The MSBuild arguments equivalent of the <DebugType>Full</DebugType> part of the project file is: /p:DebugType=full.
So, if you change your msbuildArgs section of the VSBuild task by adding /p:DebugType=full, this should do the trick.
In order to keep the repository clean and separated from output and build files we change corresponding paths in the project file.
For Net Framework project specifying IntermediateOutputPath redirects the obj directory to the corresponding folder.
For Net Core project (3.0) using this property is not sufficient. Whereas Debug, Release folders are indeed redirected, the obj folder is still created and it contains some file - such as
project.assets.json, .csproj.nuget.cache, .csproj.nuget.dgspec.json,.csproj.nuget.g.props, .csproj.nuget.g.targets .
Using BaseIntermediateOutputPath - doesnt help either.
Just wonder if someone can suggest how to move the whole obj directory?
Thanks
The Solution suggested by Martin works fine for Net Core projects
<Project>
<PropertyGroup>
<BuildDIrectory>C:\Temp\Build\$(Configuration)</BuildDIrectory>
<RelativePath>some arelative path which depends on location of corresponding project withing the solution</RelativePath>
<BaseIntermediateOutputPath>$(BuildDIrectory)\obj\$(RelativePath)\$(AssemblyName)\</BaseIntermediateOutputPath>
<OutputPath>$(BuildDIrectory)\out\$(RelativePath)\$(AssemblyName)\</OutputPath>
<DocumentationFile>$(BuildDIrectory)\Documentation\$(RelativePath)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
...
</Project>
BaseIntermediateOutputPath works as well, but it needs to be set very early in order to take effect.
The easiest way would be to add it to a Directory.Build.props file:
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)..\shared-obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
If you want to specify it directly in the csproj file, you cannot use the <Project Sdk=" notation since the property needs to be set before parts of the SDK are applied. However it works when using explicit SDK imports and correct ordering:
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>..\shared-obj\myprojA\</BaseIntermediateOutputPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
I'm trying to create property file with include path to use in all my c++ project.
Here is repository structure.
/
/Libs
/Libs2
A.h
B.h
/Sln1
Sln1.sln
Proj1.vcxproj
Sln2.sln
Proj2.vcxproj
Props.props
I want use property file (Props.props) to add the following include path to both projects ( C:\\Libs;C:\\Libs\Libs2).
Currently I have macro in my property file:
Name Value
ProjRoot C:\<path to rep root>
And I use it in include string: $(ProjRoot)\Libs;$(ProjRoot)\Libs\Libs2
The problem with this solution is hardcoded absolute path in macro value. If my repository will be cloned on another drive I will have to change it manually.
Can I use path relative to property file in macro value?
I.e.:
Name Value
ProjRoot ./
Where ./ will resolve to path of Props.props file in all projects which will use this property file.
I cannot use $(SolutionDir) and $(ProjectDir) because there are may solutions and projects in different nesting level so path relative to them would not work.
Thank you.
Do do this one should manually edit props file and include the following:
<PropSheetPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))</PropSheetPath>
This will create property PropSheetPath with property file folder.
Found the answer here:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/2817cae7-3a71-4701-839a-9bf47af7c498/property-sheets-macro-to-reference-location-of-property-sheet?forum=vcgeneral
Just to improve previous answer... Here how it looks as a full example (I'm using a bunch of small property sheets to add third party libraries in a modular way). This is an example for adding paths to include folders for C++ compiler and library folders for linker to add CEGUI library into project (debug version, I use separate prop sheet for Release).
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<PropSheetPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))</PropSheetPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Language)'=='C++'">
<CAExcludePath>$(PropsheetPath)..\..\install\windows\Debug\include\cegui-0;$(CAExcludePath)</CAExcludePath>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>CEGUI_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(PropsheetPath)..\..\install\windows\Debug\include\cegui-0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(PropsheetPath)..\..\install\windows\Debug\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>DbgHelp.lib;CEGUIBase-0_Static_d.lib;CEGUICommonDialogs-0_Static_d.lib;CEGUICoreWindowRendererSet_Static_d.lib;CEGUIExpatParser_Static_d.lib;CEGUIOpenGLRenderer-0_Static_d.lib;CEGUITGAImageCodec_Static_d.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
I have a mixed mode C++ project producing a managed dll assembly exporting some CLR classes (call it Managed.dll). This project is using a native dll, (call it Native.dll).
When I reference the Managed.dll from another project producing Client.exe, everything works as expected, except than I need to manually copy the Native.dll in the same folder as Client.exe.
If there a way to convince VS to copy locally (in the bin folder of Client.exe) not only Managed.dll but Native.dll as well?
I have tried to include Native.dll as a dependency assembly in the manifest but this didn't help.
Edit
Managed.dll is going to be a redistributable assembly. It will be installed in a folder in "C:\Program Files.....". When a developer using Visual Studio adds a reference to Managed.dll, Native.dll should be also copied in the \bin folder of his project.
There are several ways to tell the VS to copy dlls to the destination folder:
1.Add the dll as a resource of the project. And tell the VS to copy it if the dll is newer
2.Add a new project that reference to the dll project, and set the OutDir to the folder you want. This project does nothing but copy the dll.
3.Use a PostBuildEvent in vcxproj file
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
</ClCompile>
<Link>
</Link>
<PostBuildEvent>
<Command>
echo off
mkdir "$(ProjectDir)..\..\bin\$(Configuration)\"
copy "$(OutDir)xxx.dll" "$(ProjectDir)..\..\lib\$(Configuration)\"
echo on
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
4.Use a PreBuildEvent in vcxproj file
5.Use CustomBuild in vcxproj file
<ItemGroup>
<CustomBuild Include="..\..\xxx.dll">
<FileType>Document</FileType>
<Command>
call mkdir "$(OutDir)" 2>nul &
copy /Y "..\..\xxx.dll" "$(OutDir)xxx.dll"
</Command>
<Message>Copying xxx.dll to $(OutDir)\xxx.dll</Message>
<Outputs>$(OutDir)\xxx.dll</Outputs>
</CustomBuild>
</ItemGroup>
6.Use a makefile and copy dll in makefile. and use nmake to build
7.Write a bat file that do the copy job, and invoke the bat file as in 3-6
8.Use script such as python, which can also download the dll from internet. And invoke the py file as in 3-6.
9.Other build tools can help too, such as gradle
10.Make it a NuGet plugin
11.Sometimes I just write a bat, and execute the bat manually.
Update 01 (Self extract dll example):
1.Add you native dll as resource of managed dll
2.Add this init() method
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DllSelfExtract
{
public class SelfExtract
{
public static void Init()
{
String managedDllPath = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
String nativeDllPath = managedDllPath.Replace("file:///", "").Replace("DllSelfExtract.DLL", "TestDll.dll");
if(!File.Exists(nativeDllPath))
{
Stream dllIn = Assembly.GetExecutingAssembly().GetManifestResourceStream("DllSelfExtract.TestDll.dll");
if (dllIn == null) return;
using (Stream outFile = File.Create(nativeDllPath))
{
const int sz = 4096;
byte[] buf = new byte[sz];
while (true)
{
int nRead = dllIn.Read(buf, 0, sz);
if (nRead < 1)
break;
outFile.Write(buf, 0, nRead);
}
}
}
//LoadLibrary Here
}
}
}
3.In project that use your managed dll, invoke init() method first
SelfExtract.Init();
Update 02 (NuGet example):
1.Create a new NuGet project
2.Place the managed assemblies in the /lib directory
3.Place the non-managed shared libraries and related files in the /build subdirectory and rename all non-managed *.dll to *.dl_
4.Add a custom .targets file in the /build subdirectory with something like the following contents :
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="NativeBinary" />
</ItemGroup>
<ItemGroup>
<NativeBinary Include="$(MSBuildThisFileDirectory)*">
<TargetPath></TargetPath>
</NativeBinary>
</ItemGroup>
<PropertyGroup>
<PrepareForRunDependsOn>
$(PrepareForRunDependsOn);
CopyNativeBinaries
</PrepareForRunDependsOn>
</PropertyGroup>
<Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
<Copy SourceFiles="#(NativeBinary)"
DestinationFiles="#(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
Condition="'%(Extension)'=='.dl_'">
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
</Copy>
<Copy SourceFiles="#(NativeBinary)"
DestinationFiles="#(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
Condition="'%(Extension)'!='.dl_'">
<Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
</Copy>
</Target>
</Project>
5.Add build rule for build folder in Package.nuspec
<files>
<file src="lib\" target="lib" />
<file src="tools\" target="tools" />
<file src="content\" target="content" />
<file src="build\" target="build" />
</files>
6.Build the package
7.In your other C# project just add this NuGet package.
Using /ASSEMBLYLINKRESOURCE option in linker properties seems to be the simplest solution. It makes Visual Studio consider the native dll as a part of the assembly. Also, according to documentation provided by Microsoft, allows the native dll to be installed in the Global Assembly Cache
To set this linker option in a Visual C++ project:
Right click on the project name and select Properties
Select the Linker folder
In the Input property page find the Assembly Link Resource option
Write the file name of the native assembly e.g. MyNative.dll
You will need a Post Build event to copy the native dll to the output folder.
Referencing the managed assembly from any other Visual Project, forces the native dll to be copied together with the managed assembly in the /bin folder.