Files
FlaxEngine/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs

663 lines
29 KiB
C#

// Copyright (c) Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Flax.Build.Graph;
using Flax.Build.NativeCpp;
namespace Flax.Build
{
partial class Configuration
{
/// <summary>
/// Specifies the initial memory size (in MB) to use by Web app.
/// </summary>
[CommandLine("webInitialMemory", "<size_mb>", "Specifies the initial memory size (in MB) to use by Web app.")]
public static int WebInitialMemory = 32;
}
}
namespace Flax.Build.Platforms
{
/// <summary>
/// The build toolchain for Web with Emscripten.
/// </summary>
/// <seealso cref="Toolchain" />
public sealed class WebToolchain : Toolchain
{
private string _sysrootPath;
private string _compilerPath;
private Version _compilerVersion;
/// <summary>
/// Initializes a new instance of the <see cref="WebToolchain"/> class.
/// </summary>
/// <param name="platform">The platform.</param>
/// <param name="architecture">The target architecture.</param>
public WebToolchain(WebPlatform platform, TargetArchitecture architecture)
: base(platform, architecture)
{
var sdkPath = EmscriptenSdk.Instance.EmscriptenPath;
// Setup tools
_compilerPath = Path.Combine(sdkPath, "emscripten", "emcc");
var clangPath = Path.Combine(sdkPath, "bin", "clang++");
if (Platform.BuildTargetPlatform == TargetPlatform.Windows)
{
_compilerPath += ".bat";
clangPath += ".exe";
}
// Determinate compiler version
_compilerVersion = UnixToolchain.GetClangVersion(platform.Target, clangPath);
// Setup system paths
SystemIncludePaths.Add(Path.Combine(sdkPath, "lib", "clang", _compilerVersion.Major.ToString(), "include"));
SystemIncludePaths.Add(Path.Combine(sdkPath, "emscripten", "system", "include"));
SystemIncludePaths.Add(Path.Combine(sdkPath, "emscripten", "system", "lib"));
_sysrootPath = Path.Combine(sdkPath, "emscripten/cache/sysroot/include");
}
public static string GetLibName(string path)
{
var libName = Path.GetFileNameWithoutExtension(path);
if (libName.StartsWith("lib"))
libName = libName.Substring(3);
return libName;
}
/// <inheritdoc />
public override string DllExport => "__attribute__((__visibility__(\\\"default\\\")))";
/// <inheritdoc />
public override string DllImport => "";
/// <inheritdoc />
public override TargetCompiler Compiler => TargetCompiler.Clang;
/// <inheritdoc />
public override string NativeCompilerPath => _compilerPath;
/// <inheritdoc />
public override void LogInfo()
{
Log.Info("Clang version: " + _compilerVersion);
}
/// <inheritdoc />
public override void SetupEnvironment(BuildOptions options)
{
base.SetupEnvironment(options);
options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_WEB");
options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_UNIX");
options.CompileEnv.PreprocessorDefinitions.Add("__EMSCRIPTEN__");
options.CompileEnv.EnableExceptions = false;
options.CompileEnv.CpuArchitecture = CpuArchitecture.None; // TODO: try SIMD support in Emscripten
}
private void AddSharedArgs(List<string> args, BuildOptions options, bool debugInformation, bool optimization)
{
//args.Add("-pthread");
if (debugInformation)
args.Add("-g2");
else
args.Add("-g0");
if (options.CompileEnv.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode)
args.Add("-Os");
if (options.CompileEnv.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode)
args.Add("-O3");
else if (optimization && options.Configuration == TargetConfiguration.Release)
args.Add("-O3");
else if (optimization)
args.Add("-O2");
else
args.Add("-O0");
if (options.CompileEnv.RuntimeTypeInfo)
args.Add("-frtti");
else
args.Add("-fno-rtti");
if (options.CompileEnv.TreatWarningsAsErrors)
args.Add("-Wall -Werror");
if (options.CompileEnv.EnableExceptions)
args.Add("-fexceptions");
else
args.Add("-fno-exceptions");
if (options.LinkEnv.LinkTimeCodeGeneration)
args.Add("-flto");
//if (options.LinkEnv.Output == LinkerOutput.SharedLibrary)
args.Add("-fPIC");
var sanitizers = options.CompileEnv.Sanitizers;
if (sanitizers.HasFlag(Sanitizer.Address))
args.Add("-fsanitize=address");
if (sanitizers.HasFlag(Sanitizer.Undefined))
args.Add("-fsanitize=undefined");
if (sanitizers == Sanitizer.None)
args.Add("-fsanitize=null -fsanitize-minimal-runtime"); // Minimal Runtime
}
/// <inheritdoc />
public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List<string> sourceFiles, string outputPath)
{
var output = new CompileOutput();
// Setup arguments shared by all source files
var commonArgs = new List<string>();
commonArgs.AddRange(options.CompileEnv.CustomArgs);
{
commonArgs.Add("-c");
AddSharedArgs(commonArgs, options, options.CompileEnv.DebugInformation, options.CompileEnv.Optimization);
// Hack to pull SDL3 port via emcc
//if (options.CompileEnv.PreprocessorDefinitions.Contains("PLATFORM_SDL"))
// commonArgs.Add("--use-port=sdl3");
}
// Add preprocessor definitions
foreach (var definition in options.CompileEnv.PreprocessorDefinitions)
{
commonArgs.Add(string.Format("-D \"{0}\"", definition));
}
// Add include paths
foreach (var includePath in options.CompileEnv.IncludePaths)
{
if (SystemIncludePaths.Contains(includePath)) // TODO: fix SystemIncludePaths so this chack can be removed
continue; // Skip system includes as those break compilation (need to fix sys root linking for emscripten)
commonArgs.Add(string.Format("-I\"{0}\"", includePath.Replace('\\', '/')));
}
// Hack for sysroot includes
commonArgs.Add(string.Format("-I\"{0}\"", _sysrootPath.Replace('\\', '/')));
// Compile all C/C++ files
var args = new List<string>();
foreach (var sourceFile in sourceFiles)
{
var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile);
var task = graph.Add<CompileCppTask>();
// Use shared arguments
args.Clear();
args.AddRange(commonArgs);
// Language for the file
args.Add("-x");
if (sourceFile.EndsWith(".c", StringComparison.OrdinalIgnoreCase))
args.Add("c");
else
{
args.Add("c++");
// C++ version
switch (options.CompileEnv.CppVersion)
{
case CppVersion.Cpp14:
args.Add("-std=c++14");
break;
case CppVersion.Cpp17:
case CppVersion.Latest:
args.Add("-std=c++17");
break;
case CppVersion.Cpp20:
args.Add("-std=c++20");
break;
}
}
// Object File Name
var objFile = Path.Combine(outputPath, sourceFilename + ".o");
args.Add(string.Format("-o \"{0}\"", objFile.Replace('\\', '/')));
output.ObjectFiles.Add(objFile);
task.ProducedFiles.Add(objFile);
// Source File Name
args.Add("\"" + sourceFile.Replace('\\', '/') + "\"");
// Request included files to exist
var includes = IncludesCache.FindAllIncludedFiles(sourceFile);
task.PrerequisiteFiles.AddRange(includes);
// Compile
task.WorkingDirectory = options.WorkingDirectory;
task.CommandPath = _compilerPath;
task.CommandArguments = string.Join(" ", args);
task.PrerequisiteFiles.Add(sourceFile);
task.InfoMessage = Path.GetFileName(sourceFile);
task.Cost = task.PrerequisiteFiles.Count; // TODO: include source file size estimation to improve tasks sorting
}
return output;
}
public override bool CompileCSharp(ref CSharpOptions options)
{
switch (options.Action)
{
/*case CSharpOptions.ActionTypes.GetOutputFiles:
{
foreach (var inputFile in options.InputFiles)
{
string assemblyPath;
if (Configuration.AOTMode == DotNetAOTModes.MonoAOTDynamic)
assemblyPath = inputFile + Platform.SharedLibraryFileExtension;
else
assemblyPath = Path.Combine(Path.GetDirectoryName(inputFile), Platform.StaticLibraryFilePrefix + Path.GetFileName(inputFile) + Platform.StaticLibraryFileExtension);
if (Path.GetFileNameWithoutExtension(inputFile) == "System.Private.CoreLib")
{
// Use pre-compiled binaries
DotNetSdk.Instance.GetHostRuntime(TargetPlatform.Web, TargetArchitecture.x86, out var hostRuntime);
assemblyPath = Path.Combine(hostRuntime.Path, "libmonosgen-2.0.a");
}
options.OutputFiles.Add(assemblyPath);
}
return false;
}*/
case CSharpOptions.ActionTypes.GetPlatformTools:
{
string arch = Platform.BuildTargetArchitecture switch
{
TargetArchitecture.x64 => "x64",
TargetArchitecture.ARM64 => "arm64",
_ => throw new PlatformNotSupportedException(Platform.BuildTargetArchitecture.ToString()),
};
string os;
switch (Platform.BuildPlatform.Target)
{
case TargetPlatform.Windows:
os = "win";
break;
default:
throw new PlatformNotSupportedException(Platform.BuildPlatform.Target.ToString());
}
options.PlatformToolsPath = Path.Combine(DotNetSdk.SelectVersionFolder(Path.Combine(DotNetSdk.Instance.RootPath, $"packs/Microsoft.NETCore.App.Runtime.AOT.{os}-{arch}.Cross.browser-wasm")), "tools");
return false;
}
case CSharpOptions.ActionTypes.MonoLink:
{
// Setup arguments
var args = new List<string>();
//args.AddRange(options.LinkEnv.CustomArgs);
{
args.Add(string.Format("-o \"{0}\"", options.OutputFiles[0].Replace('\\', '/')));
//AddSharedArgs(args, options, false, false);
// Setup memory
var initialMemory = Configuration.WebInitialMemory;
//if (options.CompileEnv.Sanitizers.HasFlag(Sanitizer.Address))
// initialMemory = Math.Max(initialMemory, 64); // Address Sanitizer needs more memory
/*args.Add($"-sINITIAL_MEMORY={initialMemory}MB");
args.Add("-sSTACK_SIZE=4MB");
args.Add("-sALLOW_MEMORY_GROWTH=1");
// Setup file access (Game Cooker packs files with file_packager tool)
args.Add("-sFORCE_FILESYSTEM");*/
//args.Add("-sLZ4");
// https://emscripten.org/docs/compiling/Dynamic-Linking.html#dynamic-linking
// TODO: use -sMAIN_MODULE=2 and -sSIDE_MODULE=2 to strip unused code (mark public APIs with EMSCRIPTEN_KEEPALIVE)
/*if (options.LinkEnv.Output == LinkerOutput.Executable)
{
args.Add("-sMAIN_MODULE");
args.Add("-sEXPORT_ALL");
}
else*/
{
args.Add("-sSIDE_MODULE");
}
}
args.Add("-Wl,--allow-multiple-definition"); // Multiple pthread-related definitions in dotnet runtime
args.Add("-Wl,--start-group");
// Input libraries
var libraryPaths = new HashSet<string>();
var dynamicLibExt = Platform.SharedLibraryFileExtension;
var executableExt = Platform.ExecutableFileExtension;
foreach (var library in options.InputFiles)
{
/*var dir = Path.GetDirectoryName(library);
var ext = Path.GetExtension(library);
if (library.StartsWith("--use-port="))
{
// Ports (https://emscripten.org/docs/compiling/Building-Projects.html#emscripten-ports)
args.Add(library);
}
else if (string.IsNullOrEmpty(dir))
{
args.Add(string.Format("\"-l{0}\"", library));
}
else if (ext == executableExt)
{
// Skip executable
}
else if (ext == dynamicLibExt)
{
// Link against dynamic library
//task.PrerequisiteFiles.Add(library);
libraryPaths.Add(dir);
args.Add(string.Format("\"-l{0}\"", GetLibName(library)));
}
else
{
//task.PrerequisiteFiles.Add(library);
args.Add(string.Format("\"-l{0}\"", GetLibName(library)));
libraryPaths.Add(dir); // FIXME
}*/
args.Add(library.Replace(Path.DirectorySeparatorChar, '/') + Platform.StaticLibraryFileExtension);
}
// Input files (link static libraries last)
/*task.PrerequisiteFiles.AddRange(options.LinkEnv.InputFiles);
foreach (var file in options.LinkEnv.InputFiles.Where(x => !x.EndsWith(".a")).Concat(options.LinkEnv.InputFiles.Where(x => x.EndsWith(".a"))))
{
args.Add(string.Format("\"{0}\"", file.Replace('\\', '/')));
}
// Additional lib paths
libraryPaths.AddRange(options.LinkEnv.LibraryPaths);
foreach (var path in libraryPaths)
{
args.Add(string.Format("-L\"{0}\"", path.Replace('\\', '/')));
}*/
args.Add("-Wl,--end-group");
// Use a response file (it can contain any commands that you would specify on the command line)
bool useResponseFile = true;
string responseFile = null;
if (useResponseFile)
{
responseFile = Path.Combine(options.AssembliesPath, Path.GetFileName(options.OutputFiles[0]) + ".response");
//task.PrerequisiteFiles.Add(responseFile);
Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args));
args.Clear();
args.Add(string.Format("@\"{0}\"", responseFile));
}
//task.WorkingDirectory = options.WorkingDirectory;
//task.CommandPath = _compilerPath;
//task.CommandArguments = string.Join(" ", args);
int result = Utilities.Run(NativeCompilerPath, $"{string.Join(" ", args)}", null, Path.GetDirectoryName(options.OutputFiles[0]), Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput);
if (result != 0)
return true;
return false;
}
case CSharpOptions.ActionTypes.MonoCompile:
{
string binaryExtension = Platform.BuildPlatform.Target == TargetPlatform.Windows ? ".exe" : "";
var aotCompilerPath = Path.Combine(options.PlatformToolsPath, "mono-aot-cross") + binaryExtension;
//var clangPath = Path.Combine(ToolchainPath, "usr/bin/clang");
var inputFile = options.InputFiles[0];
var inputFileAsm = inputFile + ".s";
var inputFileObj = inputFile + ".o";
var outputFileDylib = options.OutputFiles[0];
var inputFileFolder = Path.GetDirectoryName(inputFile);
/*if (Path.GetFileNameWithoutExtension(inputFile) == "System.Private.CoreLib")
{
// Pre-compiled, skip
return false;
}*/
string outputFile;
{
if (Configuration.AOTMode == DotNetAOTModes.MonoAOTDynamic)
outputFile = inputFile + Platform.SharedLibraryFileExtension;
else
outputFile = Path.Combine(Path.GetDirectoryName(inputFile), Platform.StaticLibraryFilePrefix + Path.GetFileName(inputFile) + Platform.StaticLibraryFileExtension);
}
// Setup options
bool debugSymbols = options.EnableDebugSymbols;
bool useLLVM = true;
//if (useLLVM)
debugSymbols = false;
var llvmPath = "\"C:\\Program Files\\LLVM\\bin\"";//Path.Combine(EmscriptenSdk.Instance.EmscriptenPath, "bin");
var monoAotMode = "full";
if (useLLVM)
monoAotMode = "llvmonly";
//if ("mscorlib.dll" == "")
// monoAotMode = "interp";
var monoDebugMode = debugSymbols ? "soft-debug" : "nodebug";
var aotCompilerArgs = $"{(useLLVM ? "--llvm" : "")} --aot={monoAotMode},asmonly,verbose,stats,print-skipped,{monoDebugMode}{(useLLVM ? $",llvm-path={llvmPath},llvm-outfile=" + Path.GetFileName(outputFile) : "")} -O=float32";
//aotCompilerArgs += " --verbose";
//aotCompilerArgs += $" --aot-path=\"{inputFileFolder}\"";
//var aotCompilerArgs = $"{(useLLVM ? " --llvm" : "")} --aot=static,llvmonly,verbose,stats,print-skipped,llvm-path={llvmPath},{monoDebugMode}{(useLLVM ? ",llvm-outfile=" + Path.GetFileName(outputFile) : "")} -O=float32";
//var aotCompilerArgs = $"{(useLLVM ? " --llvm" : "")} --aot=static,llvmonly,verbose,stats,print-skipped,{monoDebugMode}{(useLLVM ? ",llvm-outfile=" + Path.GetFileName(outputFile) : "")} -O=float32";
//if (debugSymbols || options.EnableToolDebug)
// aotCompilerArgs = "--debug " + aotCompilerArgs;
var envVars = new Dictionary<string, string>();
envVars["MONO_PATH"] = options.ClassLibraryPath.Replace('/', Path.DirectorySeparatorChar) + Path.PathSeparator + options.AssembliesPath.Replace('/', Path.DirectorySeparatorChar);
Log.Info("MONO_PATH: " + envVars["MONO_PATH"]);
if (options.EnableToolDebug)
{
envVars["MONO_LOG_LEVEL"] = "debug";
}
//envVars["MONO_DEBUG"] = "gen-seq-points";
// Run cross-compiler compiler (outputs assembly code)
int result = Utilities.Run(aotCompilerPath, $"{aotCompilerArgs} \"{inputFile}\"", null, inputFileFolder, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars);
if (result != 0)
return true;
// Get build args for iOS
/*var clangArgs = new List<string>();
AddArgsCommon(null, clangArgs);
var clangArgsText = string.Join(" ", clangArgs);
// Build object file
result = Utilities.Run(clangPath, $"\"{inputFileAsm}\" -c -o \"{inputFileObj}\" " + clangArgsText, null, inputFileFolder, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars);
if (result != 0)
return true;
// Build dylib file
result = Utilities.Run(clangPath, $"\"{inputFileObj}\" -dynamiclib -fPIC -o \"{outputFileDylib}\" " + clangArgsText, null, inputFileFolder, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars);
if (result != 0)
return true;
// Clean intermediate results
File.Delete(inputFileAsm);
File.Delete(inputFileObj);
// Fix rpath id
result = Utilities.Run("install_name_tool", $"-id \"@rpath/{Path.GetFileName(outputFileDylib)}\" \"{outputFileDylib}\"", null, inputFileFolder, Utilities.RunOptions.ConsoleLogOutput, envVars);
if (result != 0)
return true;*/
return false;
}
}
return base.CompileCSharp(ref options);
}
#if false
/// <inheritdoc />
public override bool CompileCSharp(ref CSharpOptions options)
{
switch (options.Action)
{
case CSharpOptions.ActionTypes.MonoCompile:
{
var aotCompilerPath = Path.Combine(options.PlatformToolsPath, "mono-aot-cross.exe");
// Setup options
var monoAotMode = "full";
var monoDebugMode = options.EnableDebugSymbols ? "soft-debug" : "nodebug";
var aotCompilerArgs = $"--aot={monoAotMode},verbose,stats,print-skipped,{monoDebugMode} -O=all";
if (options.EnableDebugSymbols || options.EnableToolDebug)
aotCompilerArgs = "--debug " + aotCompilerArgs;
var envVars = new Dictionary<string, string>();
envVars["MONO_PATH"] = options.AssembliesPath + ";" + options.ClassLibraryPath;
if (options.EnableToolDebug)
{
envVars["MONO_LOG_LEVEL"] = "debug";
}
// Run cross-compiler compiler
int result = Utilities.Run(aotCompilerPath, $"{aotCompilerArgs} \"{options.InputFiles[0]}\"", null, ""/*options.PlatformToolsPath*/, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars);
return result != 0;
}
}
return base.CompileCSharp(ref options);
}
#endif
private Task CreateBinary(TaskGraph graph, BuildOptions options, string outputFilePath)
{
var task = graph.Add<LinkTask>();
// Setup arguments
var args = new List<string>();
args.AddRange(options.LinkEnv.CustomArgs);
{
args.Add(string.Format("-o \"{0}\"", outputFilePath.Replace('\\', '/')));
AddSharedArgs(args, options, options.LinkEnv.DebugInformation, options.LinkEnv.Optimization);
// Setup memory
var initialMemory = Configuration.WebInitialMemory;
if (options.CompileEnv.Sanitizers.HasFlag(Sanitizer.Address))
initialMemory = Math.Max(initialMemory, 64); // Address Sanitizer needs more memory
args.Add($"-sINITIAL_MEMORY={initialMemory}MB");
args.Add("-sSTACK_SIZE=4MB");
args.Add("-sALLOW_MEMORY_GROWTH=1");
// Setup file access (Game Cooker packs files with file_packager tool)
args.Add("-sFORCE_FILESYSTEM");
args.Add("-sLZ4");
// https://emscripten.org/docs/compiling/Dynamic-Linking.html#dynamic-linking
// TODO: use -sMAIN_MODULE=2 and -sSIDE_MODULE=2 to strip unused code (mark public APIs with EMSCRIPTEN_KEEPALIVE)
if (options.LinkEnv.Output == LinkerOutput.Executable)
{
args.Add("-sMAIN_MODULE");
args.Add("-sEXPORT_ALL");
}
else
{
args.Add("-sSIDE_MODULE");
}
}
args.Add("-Wl,--allow-multiple-definition"); // Multiple pthread-related definitions in dotnet runtime
args.Add("-Wl,--start-group");
// Input libraries
var libraryPaths = new HashSet<string>();
var dynamicLibExt = Platform.SharedLibraryFileExtension;
var executableExt = Platform.ExecutableFileExtension;
foreach (var library in options.LinkEnv.InputLibraries.Concat(options.Libraries))
{
var dir = Path.GetDirectoryName(library);
var ext = Path.GetExtension(library);
if (library.StartsWith("--use-port="))
{
// Ports (https://emscripten.org/docs/compiling/Building-Projects.html#emscripten-ports)
args.Add(library);
}
else if (string.IsNullOrEmpty(dir))
{
args.Add(string.Format("\"-l{0}\"", library));
}
else if (ext == executableExt)
{
// Skip executable
}
else if (ext == dynamicLibExt)
{
// Link against dynamic library
task.PrerequisiteFiles.Add(library);
libraryPaths.Add(dir);
args.Add(string.Format("\"-l{0}\"", GetLibName(library)));
}
else
{
task.PrerequisiteFiles.Add(library);
args.Add(string.Format("\"-l{0}\"", GetLibName(library)));
libraryPaths.Add(dir); // FIXME
}
}
// Input files (link static libraries last)
task.PrerequisiteFiles.AddRange(options.LinkEnv.InputFiles);
foreach (var file in options.LinkEnv.InputFiles.Where(x => !x.EndsWith(".a")).Concat(options.LinkEnv.InputFiles.Where(x => x.EndsWith(".a"))))
{
args.Add(string.Format("\"{0}\"", file.Replace('\\', '/')));
}
// Additional lib paths
libraryPaths.AddRange(options.LinkEnv.LibraryPaths);
foreach (var path in libraryPaths)
{
args.Add(string.Format("-L\"{0}\"", path.Replace('\\', '/')));
}
args.Add("-Wl,--end-group");
// Use a response file (it can contain any commands that you would specify on the command line)
bool useResponseFile = true;
string responseFile = null;
if (useResponseFile)
{
responseFile = Path.Combine(options.IntermediateFolder, Path.GetFileName(outputFilePath) + ".response");
task.PrerequisiteFiles.Add(responseFile);
Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args));
}
// Link
task.WorkingDirectory = options.WorkingDirectory;
task.CommandPath = _compilerPath;
task.CommandArguments = useResponseFile ? string.Format("@\"{0}\"", responseFile) : string.Join(" ", args);
task.InfoMessage = "Linking " + outputFilePath;
task.Cost = task.PrerequisiteFiles.Count;
task.ProducedFiles.Add(outputFilePath);
return task;
}
/// <inheritdoc />
public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath)
{
outputFilePath = Utilities.NormalizePath(outputFilePath);
Task linkTask;
switch (options.LinkEnv.Output)
{
case LinkerOutput.Executable:
case LinkerOutput.SharedLibrary:
linkTask = CreateBinary(graph, options, outputFilePath);
break;
case LinkerOutput.StaticLibrary:
case LinkerOutput.ImportLibrary:
default:
throw new ArgumentOutOfRangeException();
}
}
}
}