From 1c961f46a49cd341e482d742ba74723d8324916d Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Fri, 29 Sep 2023 14:34:47 +0100 Subject: [PATCH] Use the system linker for linking symbols libraries outside Windows. Builtin lld is giving some weird linking errors when linking with the new LLVM version. We probably need to set some custom options. Using the system linker should be a better idea anyway, more robust and future proof. --- src/CppParser/Parser.cpp | 3 +- src/Generator/Passes/GenerateSymbolsPass.cs | 73 ++++++++-- src/Parser/LinkerOptions.cs | 154 +++++++++++++++++++- 3 files changed, 214 insertions(+), 16 deletions(-) diff --git a/src/CppParser/Parser.cpp b/src/CppParser/Parser.cpp index 048dbbf416..f784383cb7 100644 --- a/src/CppParser/Parser.cpp +++ b/src/CppParser/Parser.cpp @@ -4851,8 +4851,7 @@ ParserResult* Parser::Compile(const std::string& File) const llvm::Triple Triple = c->getTarget().getTriple(); llvm::StringRef Dir(llvm::sys::path::parent_path(File)); llvm::SmallString<1024> Object(Dir); - llvm::sys::path::append(Object, - (Triple.isOSWindows() ? "" : "lib") + Stem + ".o"); + llvm::sys::path::append(Object, Stem + ".o"); c->getFrontendOpts().OutputFile = std::string(Object); llvm::LLVMContext context; diff --git a/src/Generator/Passes/GenerateSymbolsPass.cs b/src/Generator/Passes/GenerateSymbolsPass.cs index 385771ae36..0e16bfd27c 100644 --- a/src/Generator/Passes/GenerateSymbolsPass.cs +++ b/src/Generator/Passes/GenerateSymbolsPass.cs @@ -63,19 +63,7 @@ private void GenerateSymbols() new[] { module }).SelectMany(d => d.Libraries)) linkerOptions.AddLibraries(library); - using (var result = Parser.ClangParser.Build( - Context.ParserOptions, linkerOptions, path, - Last: remainingCompilationTasks == 1)) - { - if (PrintDiagnostics(result)) - { - compiledLibraries[module] = new CompiledLibrary - { - OutputDir = Options.OutputDir, - Library = module.SymbolsLibraryName - }; - } - } + compiledLibraries[module] = Build(linkerOptions, path, module); } } @@ -83,6 +71,65 @@ private void GenerateSymbols() } } + private CompiledLibrary Build(LinkerOptions linkerOptions, string path, Module module) + { + var useBuiltinToolchain = Platform.IsWindows; + if (useBuiltinToolchain) + { + using var result = Parser.ClangParser.Build( + Context.ParserOptions, linkerOptions, path, + Last: remainingCompilationTasks == 1); + + if (!PrintDiagnostics(result)) + return null; + } + else + { + using var result = Parser.ClangParser.Compile(Context.ParserOptions, path); + if (result != null) + { + if (!PrintDiagnostics(result)) + return null; + } + + var targetPlatform = Options.Compilation.Platform.GetValueOrDefault(Platform.Host); + var linker = LinkerOptions.GetLinkerExecutableName(targetPlatform); + + linkerOptions.AddArguments("-L" + Path.GetDirectoryName(path)); + linkerOptions.SetupLibraryArguments(); + + var objectFile = Path.ChangeExtension(path, "o"); + linkerOptions.AddArguments(objectFile); + + var sharedObjectFile = LinkerOptions.GetSharedObjectName(path, targetPlatform); + linkerOptions.AddArguments("-o " + sharedObjectFile); + + var invocation = linkerOptions.GetLinkerInvocation(); + + Diagnostics.Message($"Linking library {Path.GetFileName(sharedObjectFile)}..."); + if (Options.Verbose) + Diagnostics.Message($"Invoking the linker with: {linker} {invocation}"); + + var outMessage = ProcessHelper.Run( + linker, invocation, out var errorCode, out var errorMessage); + + if (errorCode != 0) + { + Diagnostics.Error($"Linking failed with: {outMessage} {errorMessage}"); + } + else + { + Diagnostics.Message($"Linking success."); + } + } + + return new CompiledLibrary + { + OutputDir = Options.OutputDir, + Library = module.SymbolsLibraryName + }; + } + public override bool VisitClassTemplateSpecializationDecl(ClassTemplateSpecialization specialization) { if (!specialization.IsGenerated || diff --git a/src/Parser/LinkerOptions.cs b/src/Parser/LinkerOptions.cs index 6fab1a24a2..83423d74e0 100644 --- a/src/Parser/LinkerOptions.cs +++ b/src/Parser/LinkerOptions.cs @@ -1,4 +1,8 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; namespace CppSharp.Parser { @@ -70,5 +74,153 @@ public void Setup(string triple, LanguageVersion? languageVersion) break; } } + + public void SetupLibraryArguments() + { + for (uint i = 0; i < LibraryDirsCount; i++) + { + var dir = GetLibraryDirs(i); + AddArguments("-L" + dir); + } + + for (uint i = 0; i < LibrariesCount; i++) + { + var lib = GetLibraries(i); + AddArguments("-l" + lib); + } + } + + public string GetLinkerInvocation() + { + var args = new List(); + for (uint i = 0; i < ArgumentsCount; i++) + { + var arg = GetArguments(i); + args.Add(arg); + } + + return string.Join(" ", args); + } + + public static string GetSharedObjectName(string path, TargetPlatform targetPlatform) + { + var prefix = GetPlatformSharedObjectPrefix(targetPlatform); + var extension = GetPlatformSharedObjectExtension(targetPlatform); + var name = $"{prefix}{Path.GetFileNameWithoutExtension(path)}.{extension}"; + return Path.Join(Path.GetDirectoryName(path), name); + } + + public static string GetLinkerExecutableName(TargetPlatform targetPlatform) + { + // If LLD exists on the PATH, then prefer it. If not, use the host linker. + var lldLinkerExe = GetLLDLinkerExecutableName(targetPlatform); + return ExistsOnPath(lldLinkerExe) ? + lldLinkerExe : GetPlatformLinkerExecutableName(targetPlatform); + } + + public static string GetPlatformSharedObjectPrefix(TargetPlatform targetPlatform) + { + switch (targetPlatform) + { + case TargetPlatform.Windows: + return ""; + case TargetPlatform.Linux: + case TargetPlatform.Android: + case TargetPlatform.MacOS: + case TargetPlatform.iOS: + case TargetPlatform.WatchOS: + case TargetPlatform.TVOS: + case TargetPlatform.Emscripten: + return "lib"; + default: + throw new ArgumentOutOfRangeException(nameof(targetPlatform), targetPlatform, null); + } + } + + public static string GetPlatformSharedObjectExtension(TargetPlatform targetPlatform) + { + switch (targetPlatform) + { + case TargetPlatform.Windows: + return "dll"; + case TargetPlatform.Linux: + case TargetPlatform.Android: + case TargetPlatform.Emscripten: + return "so"; + case TargetPlatform.MacOS: + case TargetPlatform.iOS: + case TargetPlatform.WatchOS: + case TargetPlatform.TVOS: + return "dylib"; + default: + throw new ArgumentOutOfRangeException(nameof(targetPlatform), targetPlatform, null); + } + } + + public static string GetPlatformLinkerExecutableName(TargetPlatform targetPlatform) + { + switch (targetPlatform) + { + case TargetPlatform.Windows: + return "link.exe"; + case TargetPlatform.Linux: + return "ld"; + case TargetPlatform.Android: + case TargetPlatform.MacOS: + case TargetPlatform.iOS: + case TargetPlatform.WatchOS: + case TargetPlatform.TVOS: + return "ld"; + case TargetPlatform.Emscripten: + return GetLLDLinkerExecutableName(targetPlatform); + default: + throw new ArgumentOutOfRangeException(nameof(targetPlatform), targetPlatform, null); + } + } + + public static string GetLLDLinkerExecutableName(TargetPlatform targetPlatform) + { + switch (targetPlatform) + { + case TargetPlatform.Windows: + return "lld-link"; + case TargetPlatform.Linux: + case TargetPlatform.Android: + return "ld.lld"; + case TargetPlatform.MacOS: + case TargetPlatform.iOS: + case TargetPlatform.WatchOS: + case TargetPlatform.TVOS: + return "ld64.lld"; + case TargetPlatform.Emscripten: + return "wasm-ld"; + default: + throw new ArgumentOutOfRangeException(nameof(targetPlatform), targetPlatform, null); + } + } + + private static bool ExistsOnPath(string fileName) + { + return GetFullPath(fileName) != null; + } + + private static string GetFullPath(string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + if (File.Exists(fileName)) + return Path.GetFullPath(fileName); + + var environmentVariablePath = Environment.GetEnvironmentVariable("PATH"); + if (environmentVariablePath == null) + throw new NullReferenceException(nameof(environmentVariablePath)); + + foreach (var path in environmentVariablePath.Split(Path.PathSeparator)) + { + var fullPath = Path.Combine(path, fileName); + if (File.Exists(fullPath)) + return fullPath; + } + return null; + } } }