We have many standard .vcxproj files that we use MSBuild to build. For certain properties and targets we need a .vcxproj.user file to override the properties that are specific to the build in progress.
In order to use the same build process in the development environment as the build machine, we'd like to specify the path to the .vcxproj.user as a different directory to the .vcxproj file, so the build process can generate the file it needs in a separate directory without disturbing the user's own settings.
I've found very little documentation on where msbuild looks for the .user file, or if it is even possible to specify the path?
Path *.user file for cpp projects is specified in C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\Microsoft.Cpp.Default.props
<MSBuildAllProjects Condition="Exists('$(MSBuildProjectFullPath).user')">$(MSBuildAllProjects);$(MSBuildProjectFullPath).user</MSBuildAllProjects>
It is intended to specify custom user settings (for example you can specify app to run in debug) and couldn't be changed.
To change build process you can use CustomBeforeMicrosoftCommonTargets. You can create your own *.targets file which will imports special properties/targets, change build process to make some preprocessing actions and so on. I've wrote an example for AfterTargets here. All you need is to implement custom build logic and change registration to BeforeTargets.
Related
I am trying to package the binaries of an external application in my installer. This external application is managed by another department. There can be multiple versions installed and in multiple locations (ie Program Files and Program Files (x86)). There is a batch file that can find the latest version installed.
What I am trying to do is to dynamically define a preprocessor that matches the location of this external app.
Here is how I proceed.
In the Pre-build Event Command Line of the wix project, I call a batch file.
This batch file finds the path to the external app (ie C:\Program Files (x86)\Foo company\Bar program v3.4) and saves it in an environment variable (%EXTERNAL_APP_PATH%).
Then I call heat with: call "%WIX%bin\heat.exe" dir "%EXTERNAL_APP_PATH%" -cg ExternalAppBinaryFiles -dr INSTALLBINDIR -sreg -srd -var var.ExternalAppBinariesSourceDir -ag -sfrag -out "heat_generated.wxs"
Then I generate a custom file heat_var_ExternalAppBinariesSourceDir.wxs whose content looks like the following:
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define ExternalAppBinariesSourceDir = "C:\Program Files (x86)\Foo company\Bar program v3.4" ?>
</Wix>
When building the project, I get the following error: heat_generated.wxs(6,0): error CNDL0150: Undefined preprocessor variable '$(var.ExternalAppBinariesSourceDir)'.
I was expecting that file heat_var_ExternalAppBinariesSourceDir.wxs which define this preprocessor variable would be sufficient for the heat generated file. Mostly because I do include heat_var_ExternalAppBinariesSourceDir.wxs in my project files.
My understanding is that heat generated files only resolves preprocessor variables from "global preprocessor variables" defined in the project properties (under Build section, label "Define preprocessor variables").
If I define this preprocessor variable as ExternalAppBinariesSourceDir=C:\Program Files (x86)\Foo company\Bar program v3.4 in the project properties, the solution builds with no error.
However, I do have to hardcode the path which is the opposite of "dynamically find the path of my application".
A possible solution would be to add a custom include file (*.wxi) in the generated heat file. Is there a way to force my heat generated file to include another file? This way I could generate a custom include file with the preprocessor variable defined.
Maybe I am not using heat as intended. If I omit the -var var.ExternalAppBinariesSourceDir part in the command line, the Source attribute of <File> elements are prefixed with SourceDir\.
The result is a build fail with the following error: error LGHT0103: The system cannot find the file 'SourceDir\myapp.exe'.
Is there a way to have absolute paths for Source attributes in a heat generated file?
I would like to keep the process as lite as possible for people that build the installer. For example, double-click the solution file in File Explorer, the solution opens in Visual Studio, right-click the Wix setup project and select Build.
I have looked at many other wix example. I have not found people that have the same use case as mine. Most use cases hardcode a preprocessor with a relative path to the binaries from the project files. Some suggest to change the arguments to candle.exe/light.exe, but like I said, I build from Visual Studio and does not call light.exe or candle.exe directly.
I suspect that I should be able to specify "search paths" for resolving paths that are prefixed with SourceDir\. Looking at my project properties in Visual Studio, there is a "paths" section, but it is all grayed out. Again, I will need to be able to specify dynamic search paths. The problem is still the same. Also search paths might be problematic if multiple directories have the same filename. This is often problematic when packaging a dll with a standard name.
I am using Visual Studio 2017 and Wix Toolset 3.11.
If you want to use an environment variable as the base for your files, you have a few options. Both MSBuild and WiX provide ways to access environment variables directly:
In MSBuild, environment variables are straight-up available as properties. So you could use $(EXTERNAL_APP_PATH) in your .wixproj and get the path. In particular, you could use it in a DefineConstants property in your .wixproj like:
<PropertyGroup>
<DefineConstants>
$(DefineConstants);
ExternalAppBinariesSourceDir=$(EXTERNAL_APP_PATH)
</DefineConstants>
</PropertyGroup>
In WiX, you can directly reference environment variables in the preprocessor using $(env.EXTERNAL_APP_PATH). That will accomplish the same as the above but without using $(var.ExternalAppBinariesSourceDir) as an intermediary.
The option I would choose is a combination of the above two. I'd use BindPaths (a WiX feature designed to allow you to specify where your files are found) along with the MSBuild support for environment variables by adding the following item to your .wixproj:
<ItemGroup>
<BindInputPath Include="$(EXTERNAL_APP_PATH)" />
</ItemGroup>
Then all the file sources rooted in SourceDir\ will automatically search the list of BindInputPaths from your .wixproj to be found.
The latter option is the most powerful and flexible. But any of the above should get you what you want... assuming you want to use the environment variable. Modifying the above to use an MSBuild property from the command line (or other options) should not be hard either.
When using the new SDK style csproj format, it is possible to use a <targetframeworks> element for a semicolon-separated list of target frameworks to build the project for.
This results in one subfolder per target framework in the build output folder:
However, when passing the OutDir property on the msbuild command line, it does NOT create the subfolders, and the built assemblies are all placed in the same folder.
Command line that works, but doesn't allow output location:
msbuild projectfile.csproj
Command line that selects output directory, but places built assemblies in same folder (effectively overwriting the target framework assemblies that are built first):
msbuild projectfile.csproj /p:OutDir=buildtemp
Is there a way to place the build output in a non-default folder while still retaining the targetframwork subfolders?
The property that is now used is OutputPath, however setting it from the CLI makes it a global property and overrules the automatic appending of the output path. The workaround is to make an intermediate property that is global and consume it from the project.
Add this to your project file inside a PropertyGroup:
<OutputPath>$(BaseOutputPath)</OutputPath>
Then you can set this property globally when calling the build command:
martin.ullrich#martins-imac:~/tmp$ dotnet build /p:BaseOutputPath=bin/foo
tmp -> /Users/martin.ullrich/tmp/bin/foo/netstandard1.4/tmp.dll
tmp -> /Users/martin.ullrich/tmp/bin/foo/netstandard1.6/tmp.dll
The problem here is that the SDK tries to change the OutputPath property based on TargetFramework and AppendTargetFrameworkToOutputPath. But if the value is specified via CLI, the project logic cannot overwrite it.
Also note that BaseOutputPath is actually used in the SDK defaults (defaulted to bin\), but it will try to append the configuration name by default..
After using TeamCity, my build process is Visual Studio (sln) -> NuGet Pack. Everything builds fine and the artifact is created. But the artifact has every single file, include the .vbproj, .vb files, the classes folder which only holds .vb files.
Is there a way to turn a setting on to create the artifact which doesn't have all the .vb files etc due to them being compiled in the .dll?
For anyone wondering, I just excluded the .vb files in the nuspec
Have you considered using Octopack to package the nuget. It is smart enough to include all the required files into the package and will not require a custom nuspec file that you would need to source control and manage for any future changes.
IMO custom nuspec file should be used only if you have a very specific requirement such as if the target directory is different.
(mark as content and copy always for the files that you want to include)
I have a solution file (MySolution.sln) with a single project in it (MyProject.vcxproj). I would like to execute a custom target (MyCustomTarget) on my project through the solution. It would look something like this:
msbuild MySolution.sln /t:MyCustomTarget
When I execute the command, I'll get an error message:
MySolution.sln.metaproj : error MSB4057: The target "MyCustomTarget" does not exist in the project. [MySolution.sln]
You can replace MyCustomTarget with any standard targets from Microsoft.Cpp.Win32.targets (e.g.: ClCompile, Link) or any other target of your choice you include from .targets files in MyProject.vcxproj. None of them would work.
When the environment variable msbuildemitsolution is set to 1, I can inspect the generated MySolution.sln.metaproj file. At the bottom 4 targets are specified: Build, Rebuild, Clean, and Publish. Using these targets instead of MyCustomTarget, the project builds ok. Also, if I specify the project file instead of the solution file, it builds too:
msbuild MyProject.vcxproj /t:MyCustomTarget
But using this format, I will lose the OutDir property, manually have to set the Configuration and Platform, so I just lose the benefits of having a solution file.
Is there any way I can use my custom target with the solution file I originally intended?
As far as I understand the problem is that msbuild generates this intermediate project file (mysolution.sln.metproj) but that will won have the imports from MyProject.vcxproj, including the .targets files. No wonder MyCustomTarget is not recognized.
My current workaround is using the project file with msbuild and trying not to miss anything from the solution file:
msbuild MyProject.vcxproj /t:MyCustomTarget /p:Configuration=MyConfig;Platform=MyPlatform;OutDir=MySolution\Platform_MyConfig\
But this is not a proper solution, inflexible, prone to error and does not automatically adapt changes in the solution file.
MSBuild 15 now raises custom targets automatically into the solution metaproj so your initial approach of running the target directly on the solution is now supposed to work.
I think you already answered your question. The answer is NO. There is no target called "MyCustomTarget" inside the .sln.metaproj file, so MSBuild gives you that error message.
Now, to resolve your problem with passing extra parameters on command line. Passing platform and configuration won't be required, if you set defaults in your .vcxproj file. Add this somewhere in your project file, before any of the standard target files are imported:
<Platform Condition="'$(Platform)'==''">MyPlatform</Platform>
<Configuration Condition="'$(Configuration)'==''">MyConfiguration</Configuration>
Configuring OutDir, which is shared across all projects in solution can be done like this. I will assume your solution is structured so that .sln file is in root folder, and all projects are in sub-folders (arbitrarily deep) under the root, or in the same folder as the solution. If this is not the case, you will have to tweak the code a little to adjust to your situation.
Right after you defined Platform and Configuration in your project, add this property group:
<PropertyGroup>
<RootFolder>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory),MySolutionName.sln))</RootFolder>
<OutDir Condition="'$(OutDir)'==''">$(RootFolder)\$(Platform)_$(Configuration)</OutDir>
</PropertyGroup>
The code above follows your convention of setting OutDir to MySolution\MyPlatform_MyConfiguration.
The downside of all this approach is that you have to manually modify all projects in your solution. However it will give you lots of flexibility in the future. For example, any common settings shared across all projects, could be extracted into single .props file that you can <Import> into every project, so changes to configuration could be done in one place.
In order to use the custom target that exists in your project file while building using the solution file, use the following format:
msbuild MySolution.sln /t:MyProject:MyCustomTarget
Note that if the project is in a sub folder (solution folder) you need to add the folder name:
msbuild MySolution.sln /t:src\MyProject:MyCustomTarget
and if the project name contains spaces or dots they are replaced with underscores.
I see that you can use the /fileLogger and /fileloggerparameters command line arguments in msbuild to specify things like the location of the log file. Is there any way to specify this information in the Project or PropertyGroup section of the project file? I have all my other project properties imported via an include file. I really don't want to have to one set of properties in an include file and then another set that is specified on the command line.
As far as I'm aware just VC projects has ability log build into separate files. But you have to build it through devenv :-(
and you don't have control over other logging parameters.
Microsoft.Cpp.Default.props
<ItemDefinitionGroup>
<BuildLog>
<Path>$(IntDir)\$(MSBuildProjectName).log</Path>
</BuildLog>
</ItemDefinitionGroup>
Other ugly way is to execute build of each project via
<Exec Command="msbuild.exe project /fl /flp...." />
I guess you want to avid it.
I'm think it could be possible to create custom distributed file logger to do this but I sill don't have it working properly.