Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/Windows/VCToolChain.cs
Wes Hunt 0255802490 Enable /Bt+ and /time+ in VC local builds when UEBuildWindows.bLogDetailedCompilerTimingInfo is true.
#codereview:mike.fricker

[CL 2528520 by Wes Hunt in Main branch]
2015-04-28 10:53:30 -04:00

1733 lines
63 KiB
C#

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;
using Microsoft.Win32;
using System.Text;
namespace UnrealBuildTool
{
public class VCToolChain : UEToolChain
{
public override void RegisterToolChain()
{
// Register this tool chain for both Win64 and Win32
Log.TraceVerbose(" Registered for {0}", CPPTargetPlatform.Win64.ToString());
UEToolChain.RegisterPlatformToolChain(CPPTargetPlatform.Win64, this);
Log.TraceVerbose(" Registered for {0}", CPPTargetPlatform.Win32.ToString());
UEToolChain.RegisterPlatformToolChain(CPPTargetPlatform.Win32, this);
}
static void AddDefinition( StringBuilder String, string Definition )
{
// Split the definition into name and value
int ValueIdx = Definition.IndexOf('=');
if(ValueIdx == -1)
{
AddDefinition(String, Definition, null);
}
else
{
AddDefinition(String, Definition.Substring(0, ValueIdx), Definition.Substring(ValueIdx + 1));
}
}
static void AddDefinition( StringBuilder String, string Variable, string Value )
{
// If the value has a space in it and isn't wrapped in quotes, do that now
if( Value != null && !Value.StartsWith( "\"" ) && ( Value.Contains( " " ) || Value.Contains( "$" ) ) )
{
Value = "\"" + Value + "\"";
}
if( WindowsPlatform.bUseVCCompilerArgs )
{
if( Value != null )
{
String.Append( " /D" + Variable + "=" + Value );
}
else
{
String.Append( " /D" + Variable );
}
}
else
{
if( Value != null )
{
String.Append( " -D " + Variable + "=" + Value );
}
else
{
String.Append( " -D " + Variable );
}
}
}
static void AddIncludePath( StringBuilder String, string IncludePath )
{
// If the value has a space in it and isn't wrapped in quotes, do that now
if( !IncludePath.StartsWith( "\"" ) && ( IncludePath.Contains( " " ) || IncludePath.Contains( "$" ) ) )
{
IncludePath = "\"" + IncludePath + "\"";
}
if( WindowsPlatform.bUseVCCompilerArgs )
{
String.Append( " /I " + IncludePath );
}
else
{
String.Append( " -I" + IncludePath );
}
}
static void AppendCLArguments_Global(CPPEnvironment CompileEnvironment, VCEnvironment EnvVars, StringBuilder Arguments)
{
// NOTE: Uncommenting this line will print includes as they are encountered by the preprocessor. This can help with diagnosing include order problems.
if( WindowsPlatform.bCompileWithClang && !WindowsPlatform.bUseVCCompilerArgs )
{
// Arguments.Append( " -H" );
}
else
{
// Arguments.Append( " /showIncludes" );
}
if( WindowsPlatform.bCompileWithClang )
{
// Arguments.Append( " -###" ); // @todo clang: Print Clang command-lines (instead of outputting compile results!)
if( !WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append( " -std=c++11" );
Arguments.Append( " -fdiagnostics-format=msvc" );
Arguments.Append( " -Xclang -relaxed-aliasing -Xclang --dependent-lib=msvcrt -Xclang --dependent-lib=oldnames -gline-tables-only -ffunction-sections" );
}
// @todo clang: We're impersonating the Visual C++ compiler by setting MSC_VER and _MSC_FULL_VER to values that MSVC would set
string VersionString;
string FullVersionString;
switch( WindowsPlatform.Compiler )
{
case WindowsCompiler.VisualStudio2012:
VersionString = "17.0";
FullVersionString = "1700";
break;
case WindowsCompiler.VisualStudio2013:
VersionString = "18.0";
FullVersionString = "1800";
break;
default:
throw new BuildException( "Unexpected value for WindowsPlatform.Compiler: " + WindowsPlatform.Compiler.ToString() );
}
Arguments.Append(" -fms-compatibility-version=" + VersionString);
AddDefinition( Arguments, "_MSC_FULL_VER", FullVersionString + "00000" );
}
// @todo clang: Clang on Windows doesn't respect "#pragma warning (error: ####)", and we're not passing "/WX", so warnings are not
// treated as errors when compiling on Windows using Clang right now.
if( BuildConfiguration.bEnableCodeAnalysis )
{
Arguments.Append(" /analyze");
// Don't cause analyze warnings to be errors
Arguments.Append(" /analyze:WX-");
// Report functions that use a LOT of stack space. You can lower this value if you
// want more aggressive checking for functions that use a lot of stack memory.
Arguments.Append(" /analyze:stacksize81940");
// Don't bother generating code, only analyze code (may report fewer warnings though.)
//Arguments.Append(" /analyze:only");
}
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Prevents the compiler from displaying its logo for each invocation.
Arguments.Append(" /nologo");
// Enable intrinsic functions.
Arguments.Append(" /Oi"); // @todo clang: No Clang equivalent to this?
}
if( WindowsPlatform.bCompileWithClang )
{
// Tell the Clang compiler whether we want to generate 32-bit code or 64-bit code
if( CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64 )
{
Arguments.Append( " --target=x86_64-pc-windows-msvc" );
}
else
{
Arguments.Append( " --target=x86-pc-windows-msvc" );
}
}
// Compile into an .obj file, and skip linking.
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /c");
}
else
{
Arguments.Append(" -c");
}
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Separate functions for linker.
Arguments.Append(" /Gy");
// Allow 800% of the default memory allocation limit.
Arguments.Append(" /Zm800");
// Disable "The file contains a character that cannot be represented in the current code page" warning for non-US windows.
Arguments.Append(" /wd4819");
}
if( BuildConfiguration.bUseSharedPCHs )
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
// @todo SharedPCH: Disable warning about PCH defines not matching .cpp defines. We "cheat" these defines a little
// bit to make shared PCHs work. But it's totally safe. Trust us.
Arguments.Append(" /wd4651");
// @todo SharedPCH: Disable warning about redefining *API macros. The PCH header is compiled with various DLLIMPORTs, but
// when a module that uses that PCH header *IS* one of those imports, that module is compiled with EXPORTS, so the macro
// is redefined on the command-line. We need to clobber those defines to make shared PCHs work properly!
Arguments.Append(" /wd4005");
}
}
// If compiling as a DLL, set the relevant defines
if (CompileEnvironment.Config.bIsBuildingDLL)
{
AddDefinition( Arguments, "_WINDLL" );
}
// When targeting Windows XP with Visual Studio 2012+, we need to tell the compiler to use the older Windows SDK that works
// with Windows XP (http://blogs.msdn.com/b/vcblog/archive/2012/10/08/10357555.aspx)
if (WindowsPlatform.IsWindowsXPSupported())
{
AddDefinition( Arguments, "_USING_V110_SDK71_");
}
// Handle Common Language Runtime support (C++/CLI)
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
Arguments.Append(" /clr");
// Don't use default lib path, we override it explicitly to use the 4.0 reference assemblies.
Arguments.Append(" /clr:nostdlib");
}
//
// Debug
//
if (CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug)
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Disable compiler optimization.
Arguments.Append(" /Od");
// Favor code size (especially useful for embedded platforms).
Arguments.Append(" /Os");
// Allow inline method expansion unless E&C support is requested
if( !BuildConfiguration.bSupportEditAndContinue )
{
Arguments.Append(" /Ob2");
}
if ((CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64))
{
// Runtime stack checks are not allowed when compiling for CLR
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
Arguments.Append(" /RTCs");
}
}
}
}
//
// Development and LTCG
//
else
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
if(CompileEnvironment.Config.OptimizeCode == ModuleRules.CodeOptimization.InShippingBuildsOnly && CompileEnvironment.Config.Target.Configuration != CPPTargetConfiguration.Shipping)
{
// Disable compiler optimization.
Arguments.Append(" /Od");
// Favor code size (especially useful for embedded platforms).
Arguments.Append(" /Os");
}
else
{
// Maximum optimizations if desired.
if( CompileEnvironment.Config.OptimizeCode >= ModuleRules.CodeOptimization.InNonDebugBuilds )
{
Arguments.Append(" /Ox");
}
// Favor code speed.
Arguments.Append(" /Ot");
// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if ( BuildConfiguration.bOmitFramePointers == false
&& ((CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)))
{
Arguments.Append(" /Oy-");
}
}
// Allow inline method expansion
Arguments.Append(" /Ob2");
//
// LTCG
//
if (CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
if( BuildConfiguration.bAllowLTCG )
{
// Enable link-time code generation.
Arguments.Append(" /GL");
}
}
}
else
{
if(CompileEnvironment.Config.OptimizeCode != ModuleRules.CodeOptimization.InShippingBuildsOnly || CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Maximum optimizations if desired.
if( CompileEnvironment.Config.OptimizeCode >= ModuleRules.CodeOptimization.InNonDebugBuilds )
{
Arguments.Append( " -O3");
}
}
}
}
//
// PC
//
if ((CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64))
{
// SSE options are not allowed when using CLR compilation or the 64 bit toolchain
// (both enable SSE2 automatically)
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled &&
CompileEnvironment.Config.Target.Platform != CPPTargetPlatform.Win64)
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Allow the compiler to generate SSE2 instructions.
Arguments.Append(" /arch:SSE2");
}
}
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Append(" /errorReport:prompt");
}
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
// Enable C++ exceptions when building with the editor or when building UHT.
if (!WindowsPlatform.bCompileWithClang && // @todo clang: C++ exceptions are not supported with Clang on Windows yet
(CompileEnvironment.Config.bEnableExceptions || UEBuildConfiguration.bBuildEditor || UEBuildConfiguration.bForceEnableExceptions))
{
// Enable C++ exception handling, but not C exceptions.
Arguments.Append(" /EHsc");
}
else
{
// This is required to disable exception handling in VC platform headers.
CompileEnvironment.Config.Definitions.Add("_HAS_EXCEPTIONS=0");
if( !WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append( " -fno-exceptions" );
}
}
}
else
{
// For C++/CLI all exceptions must be left enabled
Arguments.Append(" /EHa");
}
}
else if (CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.HTML5)
{
Arguments.Append(" /EHsc");
}
// If enabled, create debug information.
if (CompileEnvironment.Config.bCreateDebugInfo)
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Store debug info in .pdb files.
// @todo clang: PDB files are emited from Clang but do not fully work with Visual Studio yet (breakpoints won't hit due to "symbol read error")
if( BuildConfiguration.bUsePDBFiles )
{
// Create debug info suitable for E&C if wanted.
if( BuildConfiguration.bSupportEditAndContinue &&
// We only need to do this in debug as that's the only configuration that supports E&C.
CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug)
{
Arguments.Append(" /ZI");
}
// Regular PDB debug information.
else
{
Arguments.Append(" /Zi");
}
// We need to add this so VS won't lock the PDB file and prevent synchronous updates. This forces serialization through MSPDBSRV.exe.
// See http://msdn.microsoft.com/en-us/library/dn502518.aspx for deeper discussion of /FS switch.
if (BuildConfiguration.bUseIncrementalLinking && WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2013)
{
Arguments.Append(" /FS");
}
}
// Store C7-format debug info in the .obj files, which is faster.
else
{
Arguments.Append(" /Z7");
}
}
}
// Specify the appropriate runtime library based on the platform and config.
if( CompileEnvironment.Config.bUseStaticCRT )
{
if( CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug && BuildConfiguration.bDebugBuildsActuallyUseDebugCRT )
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /MTd");
}
else
{
AddDefinition( Arguments, "_MT" );
AddDefinition( Arguments, "_DEBUG" );
}
}
else
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /MT");
}
else
{
AddDefinition( Arguments, "_MT" );
}
}
}
else
{
if( CompileEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Debug && BuildConfiguration.bDebugBuildsActuallyUseDebugCRT )
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /MDd");
}
else
{
AddDefinition( Arguments, "_MT" );
AddDefinition( Arguments, "_DEBUG" );
AddDefinition( Arguments, "_DLL" );
}
}
else
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /MD");
}
else
{
AddDefinition( Arguments, "_MT" );
AddDefinition( Arguments, "_DLL" );
}
}
}
if (WindowsPlatform.bUseVCCompilerArgs && !BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// @todo clang: Not supported in clang-cl yet
if( !WindowsPlatform.bCompileWithClang )
{
// Allow large object files to avoid hitting the 2^16 section limit when running with -StressTestUnity.
Arguments.Append(" /bigobj");
// Relaxes floating point precision semantics to allow more optimization.
Arguments.Append(" /fp:fast");
}
if (CompileEnvironment.Config.OptimizeCode >= ModuleRules.CodeOptimization.InNonDebugBuilds)
{
// Allow optimized code to be debugged more easily. This makes PDBs a bit larger, but doesn't noticeably affect
// compile times. The executable code is not affected at all by this switch, only the debugging information.
if (EnvVars.CLExeVersion >= new Version("18.0.30723"))
{
// VC2013 Update 3 has a new flag for doing this
Arguments.Append(" /Zo");
}
else
{
Arguments.Append(" /d2Zi+");
}
}
}
//
// Options skipped for UnrealCodeAnalyzer
//
if (!BuildConfiguration.bRunUnrealCodeAnalyzer) // Disabled when compiling UnrealCodeAnalyzer as it breaks compilation (some structs in clang/llvm headers require 8-byte alignment in 32-bit compilation)
{
if (CompileEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
// Pack struct members on 8-byte boundaries.
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /Zp8");
}
else
{
Arguments.Append(" -fpack-struct=8" );
}
}
else
{
// Pack struct members on 4-byte boundaries.
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /Zp4");
}
else
{
Arguments.Append(" -fpack-struct=4" );
}
}
}
//
// Options required by UnrealCodeAnalyzer
//
if (BuildConfiguration.bRunUnrealCodeAnalyzer)
{
AddDefinition( Arguments, "UNREAL_CODE_ANALYZER");
}
}
static void AppendCLArguments_CPP( CPPEnvironment CompileEnvironment, StringBuilder Arguments )
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Explicitly compile the file as C++.
Arguments.Append(" /TP");
}
else
{
if( CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create )
{
// Tell Clang to generate a PCH header
Arguments.Append(" -x c++-header");
}
else
{
Arguments.Append(" -x c++" );
}
}
if (!CompileEnvironment.Config.bEnableBufferSecurityChecks)
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
// This will disable buffer security checks (which are enabled by default) that the MS compiler adds around arrays on the stack,
// Which can add some performance overhead, especially in performance intensive code
// Only disable this if you know what you are doing, because it will be disabled for the entire module!
Arguments.Append(" /GS-");
}
}
// C++/CLI requires that RTTI is left enabled
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLRDisabled)
{
if (CompileEnvironment.Config.bUseRTTI)
{
// Enable C++ RTTI.
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /GR");
}
else
{
Arguments.Append(" -Xclang -frtti-data" );
}
}
else
{
// Disable C++ RTTI.
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append(" /GR-");
}
else
{
Arguments.Append(" -Xclang -fno-rtti-data" );
}
}
}
// Set warning level.
if( WindowsPlatform.bUseVCCompilerArgs )
{
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// Restrictive during regular compilation.
Arguments.Append(" /W4");
}
else
{
// If we had /W4 with clang on windows we would be flooded with warnings. This will be fixed incrementally.
Arguments.Append(" /W0");
}
}
else
{
Arguments.Append(" -Wall");
}
if( WindowsPlatform.bCompileWithClang )
{
// Disable specific warnings that cause problems with Clang
// NOTE: These must appear after we set the MSVC warning level
// @todo clang: Ideally we want as few warnings disabled as possible
//
// Allow Microsoft-specific syntax to slide, even though it may be non-standard. Needed for Windows headers.
Arguments.Append(" -Wno-microsoft");
// @todo clang: Hack due to how we have our 'DummyPCH' wrappers setup when using unity builds. This warning should not be disabled!!
Arguments.Append(" -Wno-msvc-include");
// @todo clang: Kind of a shame to turn these off. We'd like to catch unused variables, but it is tricky with how our assertion macros work.
Arguments.Append(" -Wno-inconsistent-missing-override");
Arguments.Append(" -Wno-unused-variable");
Arguments.Append(" -Wno-unused-local-typedefs");
Arguments.Append(" -Wno-unused-function");
Arguments.Append(" -Wno-unused-private-field");
Arguments.Append(" -Wno-unused-value");
Arguments.Append(" -Wno-inline-new-delete"); // @todo clang: We declare operator new as inline. Clang doesn't seem to like that.
Arguments.Append(" -Wno-implicit-exception-spec-mismatch");
// Sometimes we compare 'this' pointers against nullptr, which Clang warns about by default
Arguments.Append(" -Wno-undefined-bool-conversion" );
// @todo clang: Disabled warnings were copied from MacToolChain for the most part
Arguments.Append(" -Wno-deprecated-declarations");
Arguments.Append(" -Wno-deprecated-writable-strings");
Arguments.Append(" -Wno-deprecated-register");
Arguments.Append(" -Wno-switch-enum");
Arguments.Append(" -Wno-logical-op-parentheses"); // needed for external headers we shan't change
Arguments.Append(" -Wno-null-arithmetic"); // needed for external headers we shan't change
Arguments.Append(" -Wno-deprecated-declarations"); // needed for wxWidgets
Arguments.Append(" -Wno-return-type-c-linkage"); // needed for PhysX
Arguments.Append(" -Wno-ignored-attributes"); // needed for nvtesslib
Arguments.Append(" -Wno-uninitialized");
Arguments.Append(" -Wno-tautological-compare");
Arguments.Append(" -Wno-switch");
Arguments.Append(" -Wno-invalid-offsetof"); // needed to suppress warnings about using offsetof on non-POD types.
}
}
static void AppendCLArguments_C(StringBuilder Arguments)
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
Arguments.Append( " -x c");
}
else
{
// Explicitly compile the file as C.
Arguments.Append(" /TC");
}
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Level 0 warnings. Needed for external C projects that produce warnings at higher warning levels.
Arguments.Append(" /W0");
}
}
static void AppendLinkArguments(LinkEnvironment LinkEnvironment, StringBuilder Arguments)
{
if( WindowsPlatform.bCompileWithClang && WindowsPlatform.bAllowClangLinker )
{
// This tells LLD to run in "Windows emulation" mode, meaning that it will accept MSVC Link arguments
Arguments.Append( " -flavor link" );
// @todo clang: The following static libraries aren't linking correctly with Clang:
// tbbmalloc.lib, zlib_64.lib, libpng_64.lib, freetype2412MT.lib, IlmImf.lib
// LLD: Assertion failed: result.size() == 1, file ..\tools\lld\lib\ReaderWriter\FileArchive.cpp, line 71
//
// Only omit frame pointers on the PC (which is implied by /Ox) if wanted.
if ( !BuildConfiguration.bOmitFramePointers )
{
Arguments.Append(" --disable-fp-elim");
}
}
// Don't create a side-by-side manifest file for the executable.
Arguments.Append(" /MANIFEST:NO");
// Prevents the linker from displaying its logo for each invocation.
Arguments.Append(" /NOLOGO");
if (LinkEnvironment.Config.bCreateDebugInfo)
{
// Output debug info for the linked executable.
Arguments.Append(" /DEBUG");
}
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Append(" /errorReport:prompt");
//
// PC
//
if ((LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32) ||
(LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64))
{
// Set machine type/ architecture to be 64 bit.
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
Arguments.Append(" /MACHINE:x64");
}
// 32 bit executable/ target.
else
{
Arguments.Append(" /MACHINE:x86");
}
{
if (LinkEnvironment.Config.bIsBuildingConsoleApplication)
{
Arguments.Append(" /SUBSYSTEM:CONSOLE");
}
else
{
Arguments.Append(" /SUBSYSTEM:WINDOWS");
}
// When targeting Windows XP in Visual Studio 2012+, we need to tell the linker we are going to support execution
// on that older platform. The compiler defaults to version 6.0+. We'll modify the SUBSYSTEM parameter here.
if (WindowsPlatform.IsWindowsXPSupported())
{
Arguments.Append(LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64 ? ",5.02" : ",5.01");
}
}
if (LinkEnvironment.Config.bIsBuildingConsoleApplication && !LinkEnvironment.Config.bIsBuildingDLL && !String.IsNullOrEmpty( LinkEnvironment.Config.WindowsEntryPointOverride ) )
{
// Use overridden entry point
Arguments.Append(" /ENTRY:" + LinkEnvironment.Config.WindowsEntryPointOverride);
}
// Allow the OS to load the EXE at different base addresses than its preferred base address.
Arguments.Append(" /FIXED:No");
// Option is only relevant with 32 bit toolchain.
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32)
{
// Disables the 2GB address space limit on 64-bit Windows and 32-bit Windows with /3GB specified in boot.ini
Arguments.Append(" /LARGEADDRESSAWARE");
}
// Explicitly declare that the executable is compatible with Data Execution Prevention.
Arguments.Append(" /NXCOMPAT");
// Set the default stack size.
Arguments.Append(" /STACK:5000000");
// E&C can't use /SAFESEH. Also, /SAFESEH isn't compatible with 64-bit linking
if( !BuildConfiguration.bSupportEditAndContinue &&
LinkEnvironment.Config.Target.Platform != CPPTargetPlatform.Win64)
{
// Generates a table of Safe Exception Handlers. Documentation isn't clear whether they actually mean
// Structured Exception Handlers.
Arguments.Append(" /SAFESEH");
}
// Allow delay-loaded DLLs to be explicitly unloaded.
Arguments.Append(" /DELAY:UNLOAD");
if (LinkEnvironment.Config.bIsBuildingDLL)
{
Arguments.Append(" /DLL");
}
if (LinkEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
// DLLs built with managed code aren't allowed to have entry points as they will try to initialize
// complex static variables. Managed code isn't allowed to run during DLLMain, we can't allow
// these variables to be initialized here.
if (LinkEnvironment.Config.bIsBuildingDLL)
{
// NOTE: This appears to only be needed if we want to get call stacks for debugging exit crashes related to the above
// Result += " /NOENTRY /NODEFAULTLIB:nochkclr.obj";
}
}
}
// Don't embed the full PDB path; we want to be able to move binaries elsewhere. They will always be side by side.
Arguments.Append(" /PDBALTPATH:%_PDB%");
//
// Shipping & LTCG
//
if( BuildConfiguration.bAllowLTCG &&
LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Use link-time code generation.
Arguments.Append(" /LTCG");
// This is where we add in the PGO-Lite linkorder.txt if we are using PGO-Lite
//Result += " /ORDER:@linkorder.txt";
//Result += " /VERBOSE";
}
//
// Shipping binary
//
if (LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Generate an EXE checksum.
Arguments.Append(" /RELEASE");
// Eliminate unreferenced symbols.
Arguments.Append(" /OPT:REF");
// Remove redundant COMDATs.
Arguments.Append(" /OPT:ICF");
}
//
// Regular development binary.
//
else
{
// Keep symbols that are unreferenced.
Arguments.Append(" /OPT:NOREF");
// Disable identical COMDAT folding.
Arguments.Append(" /OPT:NOICF");
}
// Enable incremental linking if wanted.
// NOTE: Don't bother using incremental linking for C++/CLI projects, as that's not supported and the option
// will silently be ignored anyway
if (BuildConfiguration.bUseIncrementalLinking && LinkEnvironment.Config.CLRMode != CPPCLRMode.CLREnabled)
{
Arguments.Append(" /INCREMENTAL");
}
else
{
Arguments.Append(" /INCREMENTAL:NO");
}
// Disable
//LINK : warning LNK4199: /DELAYLOAD:nvtt_64.dll ignored; no imports found from nvtt_64.dll
// type warning as we leverage the DelayLoad option to put third-party DLLs into a
// non-standard location. This requires the module(s) that use said DLL to ensure that it
// is loaded prior to using it.
Arguments.Append(" /ignore:4199");
// Suppress warnings about missing PDB files for statically linked libraries. We often don't want to distribute
// PDB files for these libraries.
Arguments.Append(" /ignore:4099"); // warning LNK4099: PDB '<file>' was not found with '<file>'
}
static void AppendLibArguments(LinkEnvironment LinkEnvironment, StringBuilder Arguments)
{
// Prevents the linker from displaying its logo for each invocation.
Arguments.Append(" /NOLOGO");
// Prompt the user before reporting internal errors to Microsoft.
Arguments.Append(" /errorReport:prompt");
//
// PC
//
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win32 || LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
// Set machine type/ architecture to be 64 bit.
if (LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
Arguments.Append(" /MACHINE:x64");
}
// 32 bit executable/ target.
else
{
Arguments.Append(" /MACHINE:x86");
}
{
if (LinkEnvironment.Config.bIsBuildingConsoleApplication)
{
Arguments.Append(" /SUBSYSTEM:CONSOLE");
}
else
{
Arguments.Append(" /SUBSYSTEM:WINDOWS");
}
// When targeting Windows XP in Visual Studio 2012+, we need to tell the linker we are going to support execution
// on that older platform. The compiler defaults to version 6.0+. We'll modify the SUBSYSTEM parameter here.
if (WindowsPlatform.IsWindowsXPSupported())
{
Arguments.Append(LinkEnvironment.Config.Target.Platform == CPPTargetPlatform.Win64 ? ",5.02" : ",5.01");
}
}
}
//
// Shipping & LTCG
//
if (LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping)
{
// Use link-time code generation.
Arguments.Append(" /LTCG");
}
}
public override CPPOutput CompileCPPFiles(UEBuildTarget Target, CPPEnvironment CompileEnvironment, List<FileItem> SourceFiles, string ModuleName)
{
var EnvVars = VCEnvironment.SetEnvironment(CompileEnvironment.Config.Target.Platform);
StringBuilder SharedArguments = new StringBuilder();
AppendCLArguments_Global(CompileEnvironment, EnvVars, SharedArguments);
// Add include paths to the argument list.
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.IncludePaths)
{
AddIncludePath( SharedArguments, IncludePath );
}
}
else
{
foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.IncludePaths)
{
AddIncludePath( SharedArguments, System.IO.Path.GetFullPath(IncludePath) );
}
}
foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths)
{
if( WindowsPlatform.bCompileWithClang )
{
// @todo Clang: Clang uses a special command-line syntax for system headers. This is used for two reasons. The first is that Clang will automatically
// suppress compiler warnings in headers found in these directories, such as the DirectX SDK headers. The other reason this is important is in the case
// where there the same header include path is passed as both a regular include path and a system include path (extracted from INCLUDE environment). In
// this case Clang will ignore any earlier occurrence of the include path, preventing a system header include path from overriding a different system
// include path set later on by a module. NOTE: When passing "-Xclang", these options will always appear at the end of the command-line string, meaning
// they will be forced to appear *after* all environment-variable-extracted includes. This is technically okay though.
if( WindowsPlatform.bUseVCCompilerArgs )
{
SharedArguments.AppendFormat(" -Xclang -internal-isystem -Xclang \"{0}\"", IncludePath);
}
else
{
SharedArguments.AppendFormat(" -isystem \"{0}\"", IncludePath);
}
}
else
{
AddIncludePath( SharedArguments, IncludePath );
}
}
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
// Add .NET framework assembly paths. This is needed so that C++/CLI projects
// can reference assemblies with #using, without having to hard code a path in the
// .cpp file to the assembly's location.
foreach (string AssemblyPath in CompileEnvironment.Config.SystemDotNetAssemblyPaths)
{
SharedArguments.AppendFormat(" /AI \"{0}\"", AssemblyPath);
}
// Add explicit .NET framework assembly references
foreach (string AssemblyName in CompileEnvironment.Config.FrameworkAssemblyDependencies)
{
SharedArguments.AppendFormat( " /FU \"{0}\"", AssemblyName );
}
// Add private assembly references
foreach( PrivateAssemblyInfo CurAssemblyInfo in CompileEnvironment.PrivateAssemblyDependencies )
{
SharedArguments.AppendFormat( " /FU \"{0}\"", CurAssemblyInfo.FileItem.AbsolutePath );
}
}
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
// Force MSVC
SharedArguments.Append(" /Bt+");
}
// Add preprocessor definitions to the argument list.
foreach (string Definition in CompileEnvironment.Config.Definitions)
{
// Escape all quotation marks so that they get properly passed with the command line.
var DefinitionArgument = Definition.Contains("\"") ? Definition.Replace("\"", "\\\"") : Definition;
AddDefinition(SharedArguments, DefinitionArgument);
}
var BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.Platform);
// Create a compile action for each source file.
CPPOutput Result = new CPPOutput();
foreach (FileItem SourceFile in SourceFiles)
{
Action CompileAction = new Action(ActionType.Compile);
CompileAction.CommandDescription = "Compile";
// ensure compiler timings are captured when we execute the action.
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
CompileAction.bPrintDebugInfo = true;
}
StringBuilder FileArguments = new StringBuilder();
bool bIsPlainCFile = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant() == ".C";
// Add the C++ source file and its included files to the prerequisite item list.
AddPrerequisiteSourceFile( Target, BuildPlatform, CompileEnvironment, SourceFile, CompileAction.PrerequisiteItems );
// If this is a CLR file then make sure our dependent assemblies are added as prerequisites
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
foreach( PrivateAssemblyInfo CurPrivateAssemblyDependency in CompileEnvironment.PrivateAssemblyDependencies )
{
CompileAction.PrerequisiteItems.Add( CurPrivateAssemblyDependency.FileItem );
}
}
if( WindowsPlatform.bCompileWithClang )
{
CompileAction.OutputEventHandler = new DataReceivedEventHandler( ClangCompilerOutputFormatter );
}
bool bEmitsObjectFile = true;
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
// Generate a CPP File that just includes the precompiled header.
string PCHCPPFilename = "PCH." + Path.GetFileName(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename) + ".cpp";
string PCHCPPPath = Path.Combine(CompileEnvironment.Config.OutputDirectory, PCHCPPFilename);
FileItem PCHCPPFile = FileItem.CreateIntermediateTextFile(
PCHCPPPath,
string.Format("#include \"{0}\"\r\n", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename)
);
// Make sure the original source directory the PCH header file existed in is added as an include
// path -- it might be a private PCH header and we need to make sure that its found!
string OriginalPCHHeaderDirectory = Path.GetDirectoryName( SourceFile.AbsolutePath );
AddIncludePath( FileArguments, OriginalPCHHeaderDirectory );
var PrecompiledFileExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.Win64].GetBinaryExtension(UEBuildBinaryType.PrecompiledHeader);
// Add the precompiled header file to the produced items list.
FileItem PrecompiledHeaderFile = FileItem.GetItemByPath(
Path.Combine(
CompileEnvironment.Config.OutputDirectory,
Path.GetFileName(SourceFile.AbsolutePath) + PrecompiledFileExtension
)
);
CompileAction.ProducedItems.Add(PrecompiledHeaderFile);
Result.PrecompiledHeaderFile = PrecompiledHeaderFile;
if( WindowsPlatform.bUseVCCompilerArgs )
{
// Add the parameters needed to compile the precompiled header file to the command-line.
FileArguments.AppendFormat(" /Yc\"{0}\"", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename);
FileArguments.AppendFormat(" /Fp\"{0}\"", PrecompiledHeaderFile.AbsolutePath);
// If we're creating a PCH that will be used to compile source files for a library, we need
// the compiled modules to retain a reference to PCH's module, so that debugging information
// will be included in the library. This is also required to avoid linker warning "LNK4206"
// when linking an application that uses this library.
if (CompileEnvironment.Config.bIsBuildingLibrary)
{
// NOTE: The symbol name we use here is arbitrary, and all that matters is that it is
// unique per PCH module used in our library
string FakeUniquePCHSymbolName = Path.GetFileNameWithoutExtension(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename);
FileArguments.AppendFormat(" /Yl{0}", FakeUniquePCHSymbolName);
}
}
else
{
FileArguments.Append( " -Xclang -emit-pch" );
FileArguments.AppendFormat(" -o\"{0}\"", PrecompiledHeaderFile.AbsolutePath);
// Clang PCH generation doesn't create an .obj file to link in, unlike MSVC
bEmitsObjectFile = false;
}
FileArguments.AppendFormat(" \"{0}\"", PCHCPPFile.AbsolutePath);
CompileAction.StatusDescription = PCHCPPFilename;
}
else
{
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
CompileAction.bIsUsingPCH = true;
CompileAction.PrerequisiteItems.Add(CompileEnvironment.PrecompiledHeaderFile);
if( WindowsPlatform.bCompileWithClang )
{
// NOTE: With Clang, PCH headers are ALWAYS forcibly included!
// NOTE: This needs to be before the other include paths to ensure Clang uses it instead of the source header file.
if( WindowsPlatform.bUseVCCompilerArgs )
{
FileArguments.AppendFormat(" /FI\"{0}\"", Path.ChangeExtension(CompileEnvironment.PrecompiledHeaderFile.AbsolutePath, null));
}
else
{
// FileArguments.Append( " -Xclang -fno-validate-pch" ); // @todo clang: Interesting option for faster performance
FileArguments.AppendFormat(" -include-pch \"{0}\"", CompileEnvironment.PrecompiledHeaderFile.AbsolutePath);
}
}
else
{
FileArguments.AppendFormat(" /Yu\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode);
FileArguments.AppendFormat(" /Fp\"{0}\"", CompileEnvironment.PrecompiledHeaderFile.AbsolutePath);
// Is it unsafe to always force inclusion? Clang is doing it, and .generated.cpp files
// won't work otherwise, because they're not located in the context of the module,
// so they can't access the module's PCH without an absolute path.
//if( CompileEnvironment.Config.bForceIncludePrecompiledHeader )
{
// Force include the precompiled header file. This is needed because we may have selected a
// precompiled header that is different than the first direct include in the C++ source file, but
// we still need to make sure that our precompiled header is the first thing included!
FileArguments.AppendFormat( " /FI\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode);
}
}
}
// UnrealCodeAnalyzer requires compiled file name to be first argument.
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// Add the source file path to the command-line.
FileArguments.AppendFormat(" \"{0}\"", SourceFile.AbsolutePath);
}
CompileAction.StatusDescription = Path.GetFileName( SourceFile.AbsolutePath );
}
if( bEmitsObjectFile )
{
var ObjectFileExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.Win64].GetBinaryExtension(UEBuildBinaryType.Object);
// Add the object file to the produced item list.
FileItem ObjectFile = FileItem.GetItemByPath(
Path.Combine(
CompileEnvironment.Config.OutputDirectory,
Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension
)
);
CompileAction.ProducedItems.Add(ObjectFile);
Result.ObjectFiles.Add(ObjectFile);
// UnrealCodeAnalyzer requires specific position of output file.
if (!BuildConfiguration.bRunUnrealCodeAnalyzer)
{
if( WindowsPlatform.bUseVCCompilerArgs )
{
FileArguments.AppendFormat(" /Fo\"{0}\"", ObjectFile.AbsolutePath);
}
else
{
FileArguments.AppendFormat(" -o\"{0}\"", ObjectFile.AbsolutePath);
}
}
}
// Create PDB files if we were configured to do that.
//
// Also, when debug info is off and XGE is enabled, force PDBs, otherwise it will try to share
// a PDB file, which causes PCH creation to be serial rather than parallel (when debug info is disabled)
// --> See https://udn.epicgames.com/lists/showpost.php?id=50619&list=unprog3
if (BuildConfiguration.bUsePDBFiles ||
(BuildConfiguration.bAllowXGE && !CompileEnvironment.Config.bCreateDebugInfo))
{
string PDBFileName;
bool bActionProducesPDB = false;
// All files using the same PCH are required to share the same PDB that was used when compiling the PCH
if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include)
{
PDBFileName = "PCH." + Path.GetFileName(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename);
}
// Files creating a PCH use a PDB per file.
else if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create)
{
PDBFileName = "PCH." + Path.GetFileName(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename);
bActionProducesPDB = true;
}
// Ungrouped C++ files use a PDB per file.
else if( !bIsPlainCFile )
{
PDBFileName = Path.GetFileName( SourceFile.AbsolutePath );
bActionProducesPDB = true;
}
// Group all plain C files that doesn't use PCH into the same PDB
else
{
PDBFileName = "MiscPlainC";
}
{
// Specify the PDB file that the compiler should write to.
FileItem PDBFile = FileItem.GetItemByPath(
Path.Combine(
CompileEnvironment.Config.OutputDirectory,
PDBFileName + ".pdb"
)
);
FileArguments.AppendFormat(" /Fd\"{0}\"", PDBFile.AbsolutePath);
// Only use the PDB as an output file if we want PDBs and this particular action is
// the one that produces the PDB (as opposed to no debug info, where the above code
// is needed, but not the output PDB, or when multiple files share a single PDB, so
// only the action that generates it should count it as output directly)
if( BuildConfiguration.bUsePDBFiles && bActionProducesPDB )
{
CompileAction.ProducedItems.Add(PDBFile);
Result.DebugDataFiles.Add(PDBFile);
}
}
}
// Add C or C++ specific compiler arguments.
if (bIsPlainCFile)
{
AppendCLArguments_C(FileArguments);
}
else
{
AppendCLArguments_CPP( CompileEnvironment, FileArguments );
}
CompileAction.WorkingDirectory = Path.GetFullPath(".");
if (BuildConfiguration.bRunUnrealCodeAnalyzer)
{
CompileAction.CommandPath = System.IO.Path.Combine(CompileAction.WorkingDirectory, @"..", @"Binaries", @"Win32", @"NotForLicensees", @"UnrealCodeAnalyzer.exe");
}
else
{
CompileAction.CommandPath = EnvVars.CompilerPath;
}
string UnrealCodeAnalyzerArguments = "";
if (BuildConfiguration.bRunUnrealCodeAnalyzer)
{
var ObjectFileExtension = @".includes";
FileItem ObjectFile = FileItem.GetItemByPath(Path.Combine(CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension));
var ClangPath = System.IO.Path.Combine(CompileAction.WorkingDirectory, @"ThirdParty", @"llvm", @"3.5.0", @"bin", @"vs2013", @"x86", @"release", @"clang++.exe");
UnrealCodeAnalyzerArguments = @"-CreateIncludeFiles " + SourceFile.AbsolutePath + @" -OutputFile=""" + ObjectFile.AbsolutePath + @""" -- " + ClangPath + @" --driver-mode=cl ";
}
CompileAction.CommandArguments = UnrealCodeAnalyzerArguments + SharedArguments.ToString() + FileArguments.ToString() + CompileEnvironment.Config.AdditionalArguments;
if( CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create )
{
Log.TraceVerbose("Creating PCH: " + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename);
Log.TraceVerbose(" Command: " + CompileAction.CommandArguments);
}
else
{
Log.TraceVerbose(" Compiling: " + CompileAction.StatusDescription);
Log.TraceVerbose(" Command: " + CompileAction.CommandArguments);
}
if( WindowsPlatform.bCompileWithClang || BuildConfiguration.bRunUnrealCodeAnalyzer)
{
// Clang doesn't print the file names by default, so we'll do it ourselves
CompileAction.bShouldOutputStatusDescription = true;
}
else
{
// VC++ always outputs the source file name being compiled, so we don't need to emit this ourselves
CompileAction.bShouldOutputStatusDescription = false;
}
// Don't farm out creation of precompiled headers as it is the critical path task.
CompileAction.bCanExecuteRemotely =
CompileEnvironment.Config.PrecompiledHeaderAction != PrecompiledHeaderAction.Create ||
BuildConfiguration.bAllowRemotelyCompiledPCHs
;
// @todo: XGE has problems remote compiling C++/CLI files that use .NET Framework 4.0
if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled)
{
CompileAction.bCanExecuteRemotely = false;
}
}
return Result;
}
public override CPPOutput CompileRCFiles(UEBuildTarget Target, CPPEnvironment Environment, List<FileItem> RCFiles)
{
var EnvVars = VCEnvironment.SetEnvironment(Environment.Config.Target.Platform);
CPPOutput Result = new CPPOutput();
var BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(Environment.Config.Target.Platform);
foreach (FileItem RCFile in RCFiles)
{
Action CompileAction = new Action(ActionType.Compile);
CompileAction.CommandDescription = "Resource";
CompileAction.WorkingDirectory = Path.GetFullPath(".");
CompileAction.CommandPath = EnvVars.ResourceCompilerPath;
CompileAction.StatusDescription = Path.GetFileName(RCFile.AbsolutePath);
// Resource tool can run remotely if possible
CompileAction.bCanExecuteRemotely = true;
var Arguments = new StringBuilder();
if( WindowsPlatform.bCompileWithClang )
{
CompileAction.OutputEventHandler = new DataReceivedEventHandler( ClangCompilerOutputFormatter );
}
// Suppress header spew
Arguments.Append( " /nologo" );
// If we're compiling for 64-bit Windows, also add the _WIN64 definition to the resource
// compiler so that we can switch on that in the .rc file using #ifdef.
if (Environment.Config.Target.Platform == CPPTargetPlatform.Win64)
{
AddDefinition( Arguments, "_WIN64" );
}
// When targeting Windows XP with Visual Studio 2012+, we need to tell the compiler to use the older Windows SDK that works
// with Windows XP (http://blogs.msdn.com/b/vcblog/archive/2012/10/08/10357555.aspx)
if (WindowsPlatform.IsWindowsXPSupported())
{
AddDefinition( Arguments, "_USING_V110_SDK71_" );
}
// Language
Arguments.AppendFormat( " /l 0x409" );
// Include paths.
foreach (string IncludePath in Environment.Config.CPPIncludeInfo.IncludePaths)
{
AddIncludePath( Arguments, IncludePath );
}
// System include paths.
foreach( var SystemIncludePath in Environment.Config.CPPIncludeInfo.SystemIncludePaths )
{
AddIncludePath( Arguments, SystemIncludePath );
}
// Preprocessor definitions.
foreach (string Definition in Environment.Config.Definitions)
{
AddDefinition( Arguments, Definition);
}
// Add the RES file to the produced item list.
FileItem CompiledResourceFile = FileItem.GetItemByPath(
Path.Combine(
Environment.Config.OutputDirectory,
Path.GetFileName(RCFile.AbsolutePath) + ".res"
)
);
CompileAction.ProducedItems.Add(CompiledResourceFile);
Arguments.AppendFormat(" /fo \"{0}\"", CompiledResourceFile.AbsolutePath);
Result.ObjectFiles.Add(CompiledResourceFile);
// Add the RC file as a prerequisite of the action.
Arguments.AppendFormat(" \"{0}\"", RCFile.AbsolutePath);
CompileAction.CommandArguments = Arguments.ToString();
// Add the C++ source file and its included files to the prerequisite item list.
AddPrerequisiteSourceFile( Target, BuildPlatform, Environment, RCFile, CompileAction.PrerequisiteItems );
}
return Result;
}
public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly)
{
var EnvVars = VCEnvironment.SetEnvironment(LinkEnvironment.Config.Target.Platform);
if (LinkEnvironment.Config.bIsBuildingDotNetAssembly)
{
return FileItem.GetItemByPath(LinkEnvironment.Config.OutputFilePath);
}
bool bIsBuildingLibrary = LinkEnvironment.Config.bIsBuildingLibrary || bBuildImportLibraryOnly;
bool bIncludeDependentLibrariesInLibrary = bIsBuildingLibrary && LinkEnvironment.Config.bIncludeDependentLibrariesInLibrary;
// Get link arguments.
StringBuilder Arguments = new StringBuilder();
if(bIsBuildingLibrary)
{
AppendLibArguments(LinkEnvironment, Arguments);
}
else
{
AppendLinkArguments(LinkEnvironment, Arguments);
}
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
Arguments.Append(" /time+");
}
// If we're only building an import library, add the '/DEF' option that tells the LIB utility
// to simply create a .LIB file and .EXP file, and don't bother validating imports
if (bBuildImportLibraryOnly)
{
Arguments.Append(" /DEF");
// Ensure that the import library references the correct filename for the linked binary.
Arguments.AppendFormat(" /NAME:\"{0}\"", Path.GetFileName(LinkEnvironment.Config.OutputFilePath));
}
// Add delay loaded DLLs.
if (!bIsBuildingLibrary)
{
// Delay-load these DLLs.
foreach (string DelayLoadDLL in LinkEnvironment.Config.DelayLoadDLLs)
{
Arguments.AppendFormat(" /DELAYLOAD:\"{0}\"", DelayLoadDLL);
}
}
// @todo UE4 DLL: Why do I need LIBPATHs to build only export libraries with /DEF? (tbbmalloc.lib)
if (!LinkEnvironment.Config.bIsBuildingLibrary || (LinkEnvironment.Config.bIsBuildingLibrary && bIncludeDependentLibrariesInLibrary))
{
// Add the library paths to the argument list.
foreach (string LibraryPath in LinkEnvironment.Config.LibraryPaths)
{
Arguments.AppendFormat(" /LIBPATH:\"{0}\"", LibraryPath);
}
// Add the excluded default libraries to the argument list.
foreach (string ExcludedLibrary in LinkEnvironment.Config.ExcludedLibraries)
{
Arguments.AppendFormat(" /NODEFAULTLIB:\"{0}\"", ExcludedLibrary);
}
}
// For targets that are cross-referenced, we don't want to write a LIB file during the link step as that
// file will clobber the import library we went out of our way to generate during an earlier step. This
// file is not needed for our builds, but there is no way to prevent MSVC from generating it when
// linking targets that have exports. We don't want this to clobber our LIB file and invalidate the
// existing timstamp, so instead we simply emit it with a different name
string ImportLibraryFilePath = Path.Combine( LinkEnvironment.Config.IntermediateDirectory,
Path.GetFileNameWithoutExtension(LinkEnvironment.Config.OutputFilePath) + ".lib" );
if (LinkEnvironment.Config.bIsCrossReferenced && !bBuildImportLibraryOnly)
{
ImportLibraryFilePath += ".suppressed";
}
FileItem OutputFile;
if (bBuildImportLibraryOnly)
{
OutputFile = FileItem.GetItemByPath(ImportLibraryFilePath);
}
else
{
OutputFile = FileItem.GetItemByPath(LinkEnvironment.Config.OutputFilePath);
OutputFile.bNeedsHotReloadNumbersDLLCleanUp = LinkEnvironment.Config.bIsBuildingDLL;
}
List<FileItem> ProducedItems = new List<FileItem>();
ProducedItems.Add(OutputFile);
List<FileItem> PrerequisiteItems = new List<FileItem>();
// Add the input files to a response file, and pass the response file on the command-line.
List<string> InputFileNames = new List<string>();
foreach (FileItem InputFile in LinkEnvironment.InputFiles)
{
InputFileNames.Add(string.Format("\"{0}\"", InputFile.AbsolutePath));
PrerequisiteItems.Add(InputFile);
}
if (!bBuildImportLibraryOnly)
{
// Add input libraries as prerequisites, too!
foreach (FileItem InputLibrary in LinkEnvironment.InputLibraries)
{
InputFileNames.Add(string.Format("\"{0}\"", InputLibrary.AbsolutePath));
PrerequisiteItems.Add(InputLibrary);
}
}
if (!bIsBuildingLibrary || (LinkEnvironment.Config.bIsBuildingLibrary && bIncludeDependentLibrariesInLibrary))
{
foreach (string AdditionalLibrary in LinkEnvironment.Config.AdditionalLibraries)
{
InputFileNames.Add(string.Format("\"{0}\"", AdditionalLibrary));
// If the library file name has a relative path attached (rather than relying on additional
// lib directories), then we'll add it to our prerequisites list. This will allow UBT to detect
// when the binary needs to be relinked because a dependent external library has changed.
//if( !String.IsNullOrEmpty( Path.GetDirectoryName( AdditionalLibrary ) ) )
{
PrerequisiteItems.Add(FileItem.GetItemByPath(AdditionalLibrary));
}
}
}
// Create a response file for the linker
string ResponseFileName = GetResponseFileName( LinkEnvironment, OutputFile );
// Never create response files when we are only generating IntelliSense data
if( !ProjectFileGenerator.bGenerateProjectFiles )
{
ResponseFile.Create( ResponseFileName, InputFileNames );
}
Arguments.AppendFormat(" @\"{0}\"", ResponseFileName);
// Add the output file to the command-line.
Arguments.AppendFormat(" /OUT:\"{0}\"", OutputFile.AbsolutePath);
if (bBuildImportLibraryOnly || (LinkEnvironment.Config.bHasExports && !bIsBuildingLibrary))
{
// An export file is written to the output directory implicitly; add it to the produced items list.
string ExportFilePath = Path.ChangeExtension(ImportLibraryFilePath, ".exp");
FileItem ExportFile = FileItem.GetItemByPath(ExportFilePath);
ProducedItems.Add(ExportFile);
}
if (!bIsBuildingLibrary)
{
// There is anything to export
if (LinkEnvironment.Config.bHasExports
// Shipping monolithic builds don't need exports
&& (!((LinkEnvironment.Config.Target.Configuration == CPPTargetConfiguration.Shipping) && (LinkEnvironment.bShouldCompileMonolithic != false))))
{
// Write the import library to the output directory for nFringe support.
FileItem ImportLibraryFile = FileItem.GetItemByPath(ImportLibraryFilePath);
Arguments.AppendFormat(" /IMPLIB:\"{0}\"", ImportLibraryFilePath);
ProducedItems.Add(ImportLibraryFile);
}
if (LinkEnvironment.Config.bCreateDebugInfo)
{
// Write the PDB file to the output directory.
{
string PDBFilePath = Path.Combine(LinkEnvironment.Config.OutputDirectory, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".pdb");
FileItem PDBFile = FileItem.GetItemByPath(PDBFilePath);
Arguments.AppendFormat(" /PDB:\"{0}\"", PDBFilePath);
ProducedItems.Add(PDBFile);
}
// Write the MAP file to the output directory.
#if false
if (true)
{
string MAPFilePath = Path.Combine(LinkEnvironment.Config.OutputDirectory, Path.GetFileNameWithoutExtension(OutputFile.AbsolutePath) + ".map");
FileItem MAPFile = FileItem.GetItemByPath(MAPFilePath);
Arguments.AppendFormat(" /MAP:\"{0}\"", MAPFilePath);
LinkAction.ProducedItems.Add(MAPFile);
}
#endif
}
// Add the additional arguments specified by the environment.
Arguments.Append(LinkEnvironment.Config.AdditionalArguments);
}
// Create an action that invokes the linker.
Action LinkAction = new Action(ActionType.Link);
LinkAction.CommandDescription = "Link";
LinkAction.WorkingDirectory = Path.GetFullPath(".");
LinkAction.CommandPath = bIsBuildingLibrary ? EnvVars.LibraryLinkerPath : EnvVars.LinkerPath;
LinkAction.CommandArguments = Arguments.ToString();
LinkAction.ProducedItems .AddRange(ProducedItems);
LinkAction.PrerequisiteItems.AddRange(PrerequisiteItems);
LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
// ensure compiler timings are captured when we execute the action.
if (!WindowsPlatform.bCompileWithClang && WindowsPlatform.bLogDetailedCompilerTimingInfo)
{
LinkAction.bPrintDebugInfo = true;
}
if( WindowsPlatform.bCompileWithClang )
{
LinkAction.OutputEventHandler = new DataReceivedEventHandler( ClangCompilerOutputFormatter );
}
// Tell the action that we're building an import library here and it should conditionally be
// ignored as a prerequisite for other actions
LinkAction.bProducesImportLibrary = bBuildImportLibraryOnly || LinkEnvironment.Config.bIsBuildingDLL;
// Allow remote linking. Especially in modular builds with many small DLL files, this is almost always very efficient
LinkAction.bCanExecuteRemotely = true;
Log.TraceVerbose( " Linking: " + LinkAction.StatusDescription );
Log.TraceVerbose( " Command: " + LinkAction.CommandArguments );
return OutputFile;
}
public override void CompileCSharpProject(CSharpEnvironment CompileEnvironment, string ProjectFileName, string DestinationFile)
{
// Initialize environment variables required for spawned tools.
var EnvVars = VCEnvironment.SetEnvironment(CompileEnvironment.EnvironmentTargetPlatform);
var BuildProjectAction = new Action(ActionType.BuildProject);
// Specify the source file (prerequisite) for the action
var ProjectFileItem = FileItem.GetExistingItemByPath(ProjectFileName);
if (ProjectFileItem == null)
{
throw new BuildException("Expected C# project file {0} to exist.", ProjectFileName);
}
// Add the project and the files contained to the prerequisites.
BuildProjectAction.PrerequisiteItems.Add(ProjectFileItem);
var ProjectFile = new VCSharpProjectFile( Utils.MakePathRelativeTo( ProjectFileName, ProjectFileGenerator.MasterProjectRelativePath ) );
var ProjectPreReqs = ProjectFile.GetCSharpDependencies();
var ProjectFolder = Path.GetDirectoryName(ProjectFileName);
foreach( string ProjectPreReqRelativePath in ProjectPreReqs )
{
string ProjectPreReqAbsolutePath = Path.Combine( ProjectFolder, ProjectPreReqRelativePath );
var ProjectPreReqFileItem = FileItem.GetExistingItemByPath(ProjectPreReqAbsolutePath);
if( ProjectPreReqFileItem == null )
{
throw new BuildException("Expected C# dependency {0} to exist.", ProjectPreReqAbsolutePath);
}
BuildProjectAction.PrerequisiteItems.Add(ProjectPreReqFileItem);
}
// We might be able to distribute this safely, but it doesn't take any time.
BuildProjectAction.bCanExecuteRemotely = false;
// Setup execution via MSBuild.
BuildProjectAction.WorkingDirectory = Path.GetFullPath(".");
BuildProjectAction.StatusDescription = Path.GetFileName(ProjectFileName);
BuildProjectAction.CommandPath = EnvVars.MSBuildPath;
if (CompileEnvironment.TargetConfiguration == CSharpTargetConfiguration.Debug)
{
BuildProjectAction.CommandArguments = " /target:rebuild /property:Configuration=Debug";
}
else
{
BuildProjectAction.CommandArguments = " /target:rebuild /property:Configuration=Development";
}
// Be less verbose
BuildProjectAction.CommandArguments += " /nologo /verbosity:minimal";
// Add project
BuildProjectAction.CommandArguments += String.Format(" \"{0}\"", ProjectFileItem.AbsolutePath);
// Specify the output files.
string PDBFilePath = Path.Combine( Path.GetDirectoryName(DestinationFile), Path.GetFileNameWithoutExtension(DestinationFile) + ".pdb" );
FileItem PDBFile = FileItem.GetItemByPath( PDBFilePath );
BuildProjectAction.ProducedItems.Add( FileItem.GetItemByPath(DestinationFile) );
BuildProjectAction.ProducedItems.Add( PDBFile );
}
/** Gets the default include paths for the given platform. */
public static string GetVCIncludePaths(CPPTargetPlatform Platform)
{
Debug.Assert(Platform == CPPTargetPlatform.Win32 || Platform == CPPTargetPlatform.Win64);
// Make sure we've got the environment variables set up for this target
VCEnvironment.SetEnvironment(Platform);
// Also add any include paths from the INCLUDE environment variable. MSVC is not necessarily running with an environment that
// matches what UBT extracted from the vcvars*.bat using SetEnvironmentVariablesFromBatchFile(). We'll use the variables we
// extracted to populate the project file's list of include paths
// @todo projectfiles: Should we only do this for VC++ platforms?
var IncludePaths = Environment.GetEnvironmentVariable("INCLUDE");
if (!String.IsNullOrEmpty(IncludePaths) && !IncludePaths.EndsWith(";"))
{
IncludePaths += ";";
}
return IncludePaths;
}
public override void AddFilesToReceipt(BuildReceipt Receipt, UEBuildBinary Binary)
{
if (Binary.Config.Type == UEBuildBinaryType.DynamicLinkLibrary)
{
Receipt.AddBuildProduct(Path.Combine(Binary.Config.IntermediateDirectory, Path.GetFileNameWithoutExtension(Binary.Config.OutputFilePath) + ".lib"), BuildProductType.ImportLibrary);
}
}
/** Formats compiler output from Clang so that it is clickable in Visual Studio */
protected static void ClangCompilerOutputFormatter(object sender, DataReceivedEventArgs e)
{
var Output = e.Data;
if (Output == null)
{
return;
}
// @todo clang: Convert relative includes to absolute files so they'll be clickable
Log.TraceInformation(Output);
}
public override void StripSymbols(string SourceFileName, string TargetFileName)
{
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "MSBuild", "Microsoft", "VisualStudio", "v12.0", "AppxPackage", "PDBCopy.exe");
StartInfo.Arguments = String.Format("\"{0}\" \"{1}\" -p", SourceFileName, TargetFileName);
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
Utils.RunLocalProcessAndLogOutput(StartInfo);
}
};
}