Browse Source

Added link options

* Added various link library and linker options inputs
* Added various option to arg conversion functions for MSVC and
  GCC-style compilers
* Remove quotes around MSVC command inputs (untested)
* Updated existing tests and bootstrap/runtime .cake files to use new
  link options. This removes the need to use compile-time code on
  Linux to build Cakelisp itself
* Made missing ProcessCommandInput error more helpful
* Hide "reference has not been resolved" errors, which can be noisy
  and useless
build-system-unification
Macoy Madson 5 months ago
parent
commit
9ba3018f47
17 changed files with 385 additions and 94 deletions
  1. +4
    -16
      Bootstrap.cake
  2. +2
    -2
      Bootstrap_MSVC.cake
  3. +19
    -0
      BuildAndRunTests.sh
  4. +2
    -1
      CrossCompile_Windows.cake
  5. +36
    -13
      doc/Cakelisp.org
  6. +9
    -9
      doc/Roadmap.org
  7. +13
    -13
      runtime/HotReloading.cake
  8. +6
    -9
      runtime/HotReloadingCodeModifier.cake
  9. +89
    -0
      src/Build.cpp
  10. +10
    -0
      src/Build.hpp
  11. +2
    -1
      src/Evaluator.cpp
  12. +13
    -4
      src/Generators.cpp
  13. +113
    -24
      src/ModuleManager.cpp
  14. +9
    -1
      src/ModuleManager.hpp
  15. +39
    -1
      src/RunProcess.cpp
  16. +8
    -0
      src/RunProcessEnums.hpp
  17. +11
    -0
      test/LinkOptions.cake

+ 4
- 16
Bootstrap.cake View File

@ -22,22 +22,10 @@
(add-build-options "-DUNIX")
(defun-comptime cakelisp-link-hook (manager (& ModuleManager)
linkCommand (& ProcessCommand)
linkTimeInputs (* ProcessCommandInput) numLinkTimeInputs int
&return bool)
(Log "Cakelisp: Adding link arguments\n")
;; Dynamic loading
(on-call (field linkCommand arguments) push_back
(array ProcessCommandArgumentType_String
"-ldl"))
;; Expose Cakelisp symbols for compile-time function symbol resolution
(on-call (field linkCommand arguments) push_back
(array ProcessCommandArgumentType_String
"-Wl,--export-dynamic"))
(return true))
(add-compile-time-hook pre-link cakelisp-link-hook)
;; Cakelisp dynamically loads compile-time code
(add-library-dependency "dl")
;; Compile-time code can call much of Cakelisp. This flag exposes Cakelisp to dynamic libraries
(add-linker-options "--export-dynamic")
;; Use separate build configuration in case other things build files from src/
(add-build-config-label "Bootstrap")

+ 2
- 2
Bootstrap_MSVC.cake View File

@ -33,12 +33,12 @@
(set-cakelisp-option compile-time-compile-arguments
"/nologo" "/DEBUG:FASTLINK" "/MDd" "/c" 'source-input 'object-output
'cakelisp-headers-include)
;; "-fPIC"
;; cl.exe for linker also works
(set-cakelisp-option build-time-linker "link.exe")
(set-cakelisp-option build-time-link-arguments
"/nologo" "/DEBUG:FASTLINK" 'executable-output 'object-input)
'additional-options "/nologo" "/DEBUG:FASTLINK" 'executable-output 'object-input
'library-search-dirs 'libraries 'library-runtime-search-dirs 'linker-arguments)
;; Use separate build configuration in case other things build files from src/
(add-build-config-label "Bootstrap_Windows")

+ 19
- 0
BuildAndRunTests.sh View File

@ -7,15 +7,23 @@
# See https://clang.llvm.org/docs/UsersManual.html#precompiled-headers
# clang -g -fPIC -x c++-header src/Evaluator.hpp -o src/Evaluator.hpp.pch
echo "\nCode Modification\n"
./bin/cakelisp --execute \
test/CodeModification.cake || exit $?
echo "\nBuild options\n"
./bin/cakelisp \
test/BuildOptions.cake || exit $?
echo "\nExecute\n"
./bin/cakelisp --execute \
test/Execute.cake || exit $?
echo "\nHot reloadable library\n"
./bin/cakelisp \
runtime/Config_Linux.cake runtime/HotReloadingCodeModifier.cake runtime/TextAdventure.cake || exit $?
@ -23,14 +31,25 @@
# ./bin/cakelisp \
# runtime/Config_Windows.cake runtime/HotLoader.cake || exit $?
echo "\nHot loader\n"
./bin/cakelisp \
runtime/Config_Linux.cake runtime/HotLoader.cake || exit $?
echo "\nCompile-time defines\n"
./bin/cakelisp --execute \
test/Defines.cake || exit $?
# ./bin/cakelisp CrossCompile_Windows.cake || exit $?
echo "\nMulti-line strings\n"
./bin/cakelisp --execute test/MultiLineStrings.cake || exit $?
echo "\nBuild helpers\n"
./bin/cakelisp --execute --verbose-processes test/BuildHelpers.cake || exit $?
# echo "\nLink options\n"
# ./bin/cakelisp --verbose-processes test/LinkOptions.cake || exit $?

+ 2
- 1
CrossCompile_Windows.cake View File

@ -51,7 +51,8 @@
(set-cakelisp-option build-time-linker "/usr/bin/x86_64-w64-mingw32-g++")
(set-cakelisp-option build-time-link-arguments
"-o" 'executable-output 'object-input)
'additional-options "-o" 'executable-output 'object-input
'library-search-dirs 'libraries 'library-runtime-search-dirs 'linker-arguments)
;; Use separate build configuration in case other things build files from src/
(add-build-config-label "CrossCompile_Windows")

+ 36
- 13
doc/Cakelisp.org View File

@ -443,6 +443,39 @@ You can specify multiple options. For example, we could set a debug build with w
These options are appended to the default or module-overridden build command.
#+BEGIN_SRC lisp
;; Cakelisp dynamically loads compile-time code
(add-library-dependency "dl")
;; Compile-time code can call much of Cakelisp. This flag exposes Cakelisp to dynamic libraries
(add-linker-options "--export-dynamic")
#+END_SRC
Before linking, we also need to add some flags to the linker.
Note that ~add-library-dependency~ will attempt to modify the given library names in a platform-independent way. For example, if you pass in ~"dl"~, here is how it would change:
| Linker | Modified |
|---------------+----------|
| ~link.exe~ | ~dl.lib~ |
| ~cl.exe~ | ~dl.lib~ |
| Anything else | ~ldl~ |
Note that on Linux, dynamic libraries are named e.g. ~libdl.so~, then requested via e.g. ~ldl~. Windows' MSVC typically names dlls simply ~dl.dll~. Cakelisp takes ~dl~ and tries to do the right thing for each platform. If it's not working, use ~add-compiler-link-options~ to provide the exact format you need, and it will not be converted.
~add-linker-options~ passes the given options to the linker itself, not the compiler which invokes the linker. For example, ~g++ -o~ will not get ~--export-dynamic~, rather, ~ld~ will get it due to ~-Wl~ automatically being prepended by ~add-linker-options~. If you want to pass arbitrary options to the compiler invoking the linker, use ~add-compiler-link-options~.
The following are also related to linker configuration:
- ~add-library-search-directory~
- ~add-library-runtime-search-directory~: Adds given strings to ~rpath~, which tells Unix systems where to look for dynamic libraries. Note that this does not work on Windows, which requires special treatment for DLL loading. Figuring out how to handle this in Cakelisp is TBD
#+BEGIN_SRC lisp
;; Use separate build configuration in case other things build files from src/
(add-build-config-label "Bootstrap")
#+END_SRC
This configuration label ensures Cakelisp itself doesn't get affected by your runtime programs. It does this by using a separate folder in the cache.
*** Procedural command modification
There may be cases when you need to do complex logic or modifications of the link command. We use a ~hook~ to give us a chance to do so.
#+BEGIN_SRC lisp
(defun-comptime cakelisp-link-hook (manager (& ModuleManager)
linkCommand (& ProcessCommand)
@ -462,22 +495,12 @@ These options are appended to the default or module-overridden build command.
(add-compile-time-hook pre-link cakelisp-link-hook)
#+END_SRC
Before linking, we need to add some flags to the linker. We use a ~hook~ to give us a chance to do so. ~(add-compile-time-hook pre-link cakelisp-link-hook)~ adds the hook, then ~cakelisp-link-hook~ is invoked before link time.
/Note:/ An easier way to specify Link arguments is on the roadmap. They will resemble ~add-build-options~.
#+BEGIN_SRC lisp
;; Use separate build configuration in case other things build files from src/
(add-build-config-label "Bootstrap")
#+END_SRC
This configuration label ensures Cakelisp itself doesn't get affected by your runtime programs. It does this by using a separate folder in the cache.
~(add-compile-time-hook pre-link cakelisp-link-hook)~ adds the hook, then ~cakelisp-link-hook~ is invoked before link time.
** Build commands
The environment comes with default commands. Build commands can be overridden to whatever process you choose, with the structure you choose. For example, the linker can be changed like so:
The environment comes with default commands (defined in ~src/ModuleManager.cpp~). Build commands can be overridden to whatever process you choose, with the structure you choose. For example, the linker can be changed like so:
#+BEGIN_SRC lisp
(set-cakelisp-option build-time-linker "/usr/bin/g++")
(set-cakelisp-option build-time-linker "g++")
(set-cakelisp-option build-time-link-arguments
"-o" 'executable-output 'object-input
"-ldl" "-lpthread" "-Wl,-rpath,.,--export-dynamic")


+ 9
- 9
doc/Roadmap.org View File

@ -58,15 +58,6 @@ For example, you could have one of your computers with spare cycles run ~cakelis
Ideally, this wouldn't require any 3rd-party dependencies. It's made trickier by the requirement that build slaves should be able to cross-compile things for the master/client architecture, which might not match the slave architecture.
* Language
** Conditionals based on build configuration
It is useful to be able to define blocks of code which are e.g. operating-system specific, e.g. something like:
#+BEGIN_SRC lisp
(defun file-exists ()
(comptime-when 'Unix (return (!= -1 (access filename F_OK))))
(comptime-when 'Windows (return (windows-code-here))))
#+END_SRC
I don't have an idea what the cleanest, easiest, and reasonably flexible solution is yet.
** Classes with member functions
While I have been focusing on a C-style implementation that doesn't emphasize member function usage, defining classses with member functions is an essential feature for interfacing with 3rd party libraries. For example, Ogre requires it for window callbacks:
#+BEGIN_SRC C++
@ -223,6 +214,15 @@ Mostly listing this because it's what I use (LSP is a bit too heavyweight for my
The following are things that were on the Roadmap that are now complete.
These are sorted with most recently completed appearing first.
** Conditionals based on build configuration
It is useful to be able to define blocks of code which are e.g. operating-system specific, e.g. something like:
#+BEGIN_SRC lisp
(defun file-exists ()
(comptime-when 'Unix (return (!= -1 (access filename F_OK))))
(comptime-when 'Windows (return (windows-code-here))))
#+END_SRC
I don't have an idea what the cleanest, easiest, and reasonably flexible solution is yet.
** External process execution
It should be easy to run external dependencies' build systems within a Cakelisp build phase. For example, I want to be able to lazily build Ogre during the ~pre-build~ hook of my game. Ogre requires CMake and ninja to build. Utilizing the ~RunProcess~ and file system code should make this straightforward.
** GitHub syntax highlighting


+ 13
- 13
runtime/HotReloading.cake View File

@ -1,3 +1,5 @@
(set-cakelisp-option use-c-linkage true)
(import "DynamicLoader.cake"
&comptime-only "Macros.cake")
(c-import "<unordered_map>" "<vector>")
@ -159,16 +161,14 @@
;;
(comptime-cond
('Unix
;; TODO: This only makes sense on a per-target basis. Instead, modules should be able to append
;; arguments to the link command only
(set-cakelisp-option build-time-linker "/usr/bin/g++")
;; This needs to link -ldl and such (depending on platform...)
(set-cakelisp-option build-time-link-arguments
;; "-shared" ;; This causes c++ initializers to fail and no linker errors. Need to only enable on lib?
"-o" 'executable-output 'object-input
;; TODO: OS dependent
;; Need --export-dynamic so the loaded library can use our symbols
"-ldl" "-lpthread" "-Wl,-rpath,.,--export-dynamic")
(add-build-options "-fPIC")))
;; Did this weird thing because comptime-cond doesn't have (not)
('No-Hot-Reload-Options) ;; Make sure to not touch environment (they only want headers)
(true
(comptime-cond
('Unix
;; dl for dynamic loading
(add-library-dependency "dl" "pthread")
(add-library-runtime-search-directory ".")
;; Make sure the thing which gets loaded can access our API
(add-linker-options "--export-dynamic")
(add-build-options "-fPIC")))))

+ 6
- 9
runtime/HotReloadingCodeModifier.cake View File

@ -31,6 +31,11 @@
;; This needs to be set before any functions which need C linkage are evaluated by Cakelisp
(set-cakelisp-option use-c-linkage true)
;; This is a hack: We want to generate HotReloading.cake.hpp, but we don't want to inherit all the
;; loader-specific options. HotReloading.cake will see this define and not add the options
;; TODO: Make a context variable for preventing environment changes during &decls-only?
(comptime-define-symbol 'No-Hot-Reload-Options)
(import &comptime-only "Macros.cake")
;; This is redefined by make-code-hot-reloadable
@ -336,15 +341,7 @@
(add-compile-time-hook post-references-resolved make-code-hot-reloadable)
(defun-comptime hot-reload-lib-link-hook (manager (& ModuleManager)
linkCommand (& ProcessCommand)
linkTimeInputs (* ProcessCommandInput)
numLinkTimeInputs int
&return bool)
(command-add-string-argument linkCommand "-shared")
(return true))
(add-compile-time-hook pre-link hot-reload-lib-link-hook)
(add-compiler-link-options "-shared")
;; TODO: Automatically make library if no main found?
(set-cakelisp-option executable-output "libGeneratedCakelisp.so")


+ 89
- 0
src/Build.cpp View File

@ -57,6 +57,95 @@ void makeDynamicLibraryOutputArgument(char* buffer, int bufferSize, const char*
}
}
void makeExecutableOutputArgument(char* buffer, int bufferSize, const char* executableName,
const char* linkExecutable)
{
// Annoying exception for MSVC not having spaces between some arguments
if (StrCompareIgnoreCase(linkExecutable, "cl.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "/Fe\"%s\"", executableName);
}
else if (StrCompareIgnoreCase(linkExecutable, "link.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "/out:\"%s\"", executableName);
}
else
{
SafeSnprintf(buffer, bufferSize, "%s", executableName);
}
}
void makeLinkLibraryArgument(char* buffer, int bufferSize, const char* libraryName,
const char* linkExecutable)
{
if (StrCompareIgnoreCase(linkExecutable, "cl.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "%s.lib", libraryName);
}
else if (StrCompareIgnoreCase(linkExecutable, "link.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "%s.lib", libraryName);
}
else
{
SafeSnprintf(buffer, bufferSize, "-l%s", libraryName);
}
}
void makeLinkLibrarySearchDirArgument(char* buffer, int bufferSize, const char* searchDir,
const char* linkExecutable)
{
if (StrCompareIgnoreCase(linkExecutable, "cl.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "/LIBPATH:%s", searchDir);
}
else if (StrCompareIgnoreCase(linkExecutable, "link.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "/LIBPATH:%s", searchDir);
}
else
{
SafeSnprintf(buffer, bufferSize, "-L%s", searchDir);
}
}
void makeLinkLibraryRuntimeSearchDirArgument(char* buffer, int bufferSize, const char* searchDir,
const char* linkExecutable)
{
if (StrCompareIgnoreCase(linkExecutable, "cl.exe") == 0)
{
// TODO: Decide how to handle DLLs on Windows. Copy to same dir? Convert to full paths?
// SafeSnprintf(buffer, bufferSize, "/LIBPATH:%s", searchDir);
Log("warning: link library runtime search directories not supported on Windows\n");
}
else if (StrCompareIgnoreCase(linkExecutable, "link.exe") == 0)
{
// SafeSnprintf(buffer, bufferSize, "/LIBPATH:%s", searchDir);
Log("warning: link library runtime search directories not supported on Windows\n");
}
else
{
SafeSnprintf(buffer, bufferSize, "-Wl,-rpath,%s", searchDir);
}
}
void makeLinkerArgument(char* buffer, int bufferSize, const char* argument,
const char* linkExecutable)
{
if (StrCompareIgnoreCase(linkExecutable, "cl.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "%s", argument);
}
else if (StrCompareIgnoreCase(linkExecutable, "link.exe") == 0)
{
SafeSnprintf(buffer, bufferSize, "%s", argument);
}
else
{
SafeSnprintf(buffer, bufferSize, "-Wl,%s", argument);
}
}
bool resolveExecutablePath(const char* fileToExecute, char* resolvedPathOut,
int resolvedPathOutSize)
{


+ 10
- 0
src/Build.hpp View File

@ -11,6 +11,16 @@ void makeIncludeArgument(char* buffer, int bufferSize, const char* searchDir);
void makeObjectOutputArgument(char* buffer, int bufferSize, const char* objectName);
void makeDynamicLibraryOutputArgument(char* buffer, int bufferSize, const char* libraryName,
const char* buildExecutable);
void makeExecutableOutputArgument(char* buffer, int bufferSize, const char* executableName,
const char* linkExecutable);
void makeLinkLibraryArgument(char* buffer, int bufferSize, const char* libraryName,
const char* linkExecutable);
void makeLinkLibrarySearchDirArgument(char* buffer, int bufferSize, const char* searchDir,
const char* linkExecutable);
void makeLinkLibraryRuntimeSearchDirArgument(char* buffer, int bufferSize, const char* searchDir,
const char* linkExecutable);
void makeLinkerArgument(char* buffer, int bufferSize, const char* argument,
const char* linkExecutable);
// On Windows, extra work is done to find the compiler and linker executables. This function handles
// looking up those environment variables to determine which executable to use


+ 2
- 1
src/Evaluator.cpp View File

@ -1489,7 +1489,8 @@ bool EvaluateResolveReferences(EvaluatorEnvironment& environment)
if (referenceStatus.guessState == GuessState_None)
{
ErrorAtToken(*referenceStatus.name, "reference has not been resolved");
if (logging.references || logging.buildProcess)
ErrorAtToken(*referenceStatus.name, "reference has not been resolved");
}
}


+ 13
- 4
src/Generators.cpp View File

@ -71,6 +71,11 @@ bool SetProcessCommandArguments(EvaluatorEnvironment& environment, const std::ve
{"'object-input", ProcessCommandArgumentType_ObjectInput},
{"'library-output", ProcessCommandArgumentType_DynamicLibraryOutput},
{"'executable-output", ProcessCommandArgumentType_ExecutableOutput},
{"'library-search-dirs", ProcessCommandArgumentType_LibrarySearchDirs},
{"'libraries", ProcessCommandArgumentType_Libraries},
{"'library-runtime-search-dirs",
ProcessCommandArgumentType_LibraryRuntimeSearchDirs},
{"'linker-arguments", ProcessCommandArgumentType_LinkerArguments},
};
bool found = false;
for (unsigned int i = 0; i < ArraySize(symbolsToCommandTypes); ++i)
@ -455,11 +460,13 @@ bool AddStringOptionsGenerator(EvaluatorEnvironment& environment, const Evaluato
};
const StringOptionList possibleDestinations[] = {
{"add-cakelisp-search-directory", &environment.searchPaths},
{"add-c-search-directory-global", &environment.cSearchDirectories},
{"add-c-search-directory-global", &environment.cSearchDirectories},
{"add-c-search-directory-module", &context.module->cSearchDirectories},
{"add-library-search-directory", &context.module->librarySearchDirectories},
{"add-library-runtime-search-directory", &context.module->libraryRuntimeSearchDirectories},
{"add-library-dependency", &context.module->libraryDependencies},
{"add-library-search-directory", &context.module->librarySearchDirectories},
{"add-library-runtime-search-directory", &context.module->libraryRuntimeSearchDirectories},
{"add-library-dependency", &context.module->libraryDependencies},
{"add-compiler-link-options", &context.module->compilerLinkOptions},
{"add-linker-options", &context.module->toLinkerOptions},
{"add-build-options", &context.module->additionalBuildOptions},
{"add-build-config-label", &environment.buildConfigurationLabels}};
@ -2913,6 +2920,8 @@ void importFundamentalGenerators(EvaluatorEnvironment& environment)
environment.generators["add-library-search-directory"] = AddStringOptionsGenerator;
environment.generators["add-library-runtime-search-directory"] = AddStringOptionsGenerator;
environment.generators["add-library-dependency"] = AddStringOptionsGenerator;
environment.generators["add-compiler-link-options"] = AddStringOptionsGenerator;
environment.generators["add-linker-options"] = AddStringOptionsGenerator;
environment.generators["add-build-config-label"] = AddBuildConfigLabelGenerator;
// Compile-time conditionals, erroring, etc.


+ 113
- 24
src/ModuleManager.cpp View File

@ -1,6 +1,7 @@
#include "ModuleManager.hpp"
#include <string.h>
#include <cstring>
#include "Build.hpp"
@ -89,7 +90,7 @@ void moduleManagerInitialize(ModuleManager& manager)
// with this matching as well (use just /MD for release) See
// https://stackoverflow.com/questions/22279052/c-passing-stdstring-by-reference-to-function-in-dll
{ProcessCommandArgumentType_String, "/MDd"},
// Debug only
// Debug only
{ProcessCommandArgumentType_String, "/DEBUG:FASTLINK"},
{ProcessCommandArgumentType_String, "/Zi"},
{ProcessCommandArgumentType_String, "/c"},
@ -103,11 +104,11 @@ void moduleManagerInitialize(ModuleManager& manager)
{ProcessCommandArgumentType_String, "/DLL"},
// On Windows, .exes create .lib files for exports. Link it here so we don't get
// unresolved externals
{ProcessCommandArgumentType_String, "/LIBPATH:\"bin\""},
{ProcessCommandArgumentType_String, "/LIBPATH:bin"},
{ProcessCommandArgumentType_String, "cakelisp.lib"},
// Debug only
{ProcessCommandArgumentType_String, "/DEBUG:FASTLINK"},
{ProcessCommandArgumentType_DynamicLibraryOutput, EmptyString},
// Debug only
{ProcessCommandArgumentType_String, "/DEBUG:FASTLINK"},
{ProcessCommandArgumentType_DynamicLibraryOutput, EmptyString},
{ProcessCommandArgumentType_ObjectInput, EmptyString}};
manager.environment.buildTimeBuildCommand.fileToExecute = "cl.exe";
@ -123,8 +124,13 @@ void moduleManagerInitialize(ModuleManager& manager)
manager.environment.buildTimeLinkCommand.fileToExecute = "link.exe";
manager.environment.buildTimeLinkCommand.arguments = {
{ProcessCommandArgumentType_String, "/nologo"},
{ProcessCommandArgumentType_AdditionalOptions, EmptyString},
{ProcessCommandArgumentType_ExecutableOutput, EmptyString},
{ProcessCommandArgumentType_ObjectInput, EmptyString}};
{ProcessCommandArgumentType_ObjectInput, EmptyString},
{ProcessCommandArgumentType_LibrarySearchDirs, EmptyString},
{ProcessCommandArgumentType_Libraries, EmptyString},
{ProcessCommandArgumentType_LibraryRuntimeSearchDirs, EmptyString},
{ProcessCommandArgumentType_LinkerArguments, EmptyString}};
#else
// G++ by default
manager.environment.compileTimeBuildCommand.fileToExecute = "g++";
@ -159,9 +165,14 @@ void moduleManagerInitialize(ModuleManager& manager)
manager.environment.buildTimeLinkCommand.fileToExecute = "g++";
manager.environment.buildTimeLinkCommand.arguments = {
{ProcessCommandArgumentType_AdditionalOptions, EmptyString},
{ProcessCommandArgumentType_String, "-o"},
{ProcessCommandArgumentType_ExecutableOutput, EmptyString},
{ProcessCommandArgumentType_ObjectInput, EmptyString}};
{ProcessCommandArgumentType_ObjectInput, EmptyString},
{ProcessCommandArgumentType_LibrarySearchDirs, EmptyString},
{ProcessCommandArgumentType_Libraries, EmptyString},
{ProcessCommandArgumentType_LibraryRuntimeSearchDirs, EmptyString},
{ProcessCommandArgumentType_LinkerArguments, EmptyString}};
#endif
}
@ -789,6 +800,12 @@ static bool commandEqualsCachedCommand(ModuleManager& manager, const char* artif
return findIt->second == newCommandCrc;
}
static void addStringIfUnique(std::vector<std::string>& output, const char* stringToAdd)
{
if (FindInContainer(output, stringToAdd) == output.end())
output.push_back(stringToAdd);
}
static bool moduleManagerReadCacheFile(ModuleManager& manager);
static void moduleManagerWriteCacheFile(ModuleManager& manager);
@ -803,6 +820,12 @@ bool moduleManagerBuild(ModuleManager& manager, std::vector<std::string>& builtO
// Pointer because the objects can't move, status codes are pointed to
std::vector<BuiltObject*> builtObjects;
std::vector<std::string> linkLibraries;
std::vector<std::string> librarySearchDirs;
std::vector<std::string> libraryRuntimeSearchDirs;
std::vector<std::string> compilerLinkOptions;
std::vector<std::string> toLinkerOptions;
for (int moduleIndex = 0; moduleIndex < numModules; ++moduleIndex)
{
Module* module = manager.modules[moduleIndex];
@ -878,6 +901,26 @@ bool moduleManagerBuild(ModuleManager& manager, std::vector<std::string>& builtO
}
}
// Add link arguments
{
struct
{
std::vector<std::string>* inputs;
std::vector<std::string>* output;
} linkArgumentsToAdd[]{
{&module->toLinkerOptions, &toLinkerOptions},
{&module->compilerLinkOptions, &compilerLinkOptions},
{&module->libraryDependencies, &linkLibraries},
{&module->librarySearchDirectories, &librarySearchDirs},
{&module->libraryRuntimeSearchDirectories, &libraryRuntimeSearchDirs}};
for (int linkArgumentSet = 0; linkArgumentSet < ArraySize(linkArgumentsToAdd);
++linkArgumentSet)
{
for (const std::string& str : *(linkArgumentsToAdd[linkArgumentSet].inputs))
addStringIfUnique(*(linkArgumentsToAdd[linkArgumentSet].output), str.c_str());
}
}
if (module->skipBuild)
continue;
@ -1132,30 +1175,76 @@ bool moduleManagerBuild(ModuleManager& manager, std::vector<std::string>& builtO
objectsToLink[i] = object->filename.c_str();
}
// Copy it so hooks can modify it
ProcessCommand linkCommand = manager.environment.buildTimeLinkCommand;
// Annoying exception for MSVC not having spaces between some arguments
std::string* executableOutput = &outputExecutableName;
std::string executableOutputOverride;
if (StrCompareIgnoreCase(linkCommand.fileToExecute.c_str(), "cl.exe") == 0)
char executableOutput[MAX_PATH_LENGTH + 5] = {0};
makeExecutableOutputArgument(executableOutput, sizeof(executableOutput),
outputExecutableName.c_str(),
linkCommand.fileToExecute.c_str());
// Various library and linker arguments need prefixes added. Do that here
std::vector<const char*> librariesArgs;
std::vector<const char*> librarySearchDirsArgs;
std::vector<const char*> libraryRuntimeSearchDirsArgs;
std::vector<const char*> convertedLinkerArgs;
std::vector<const char*> compilerLinkArgs;
struct
{
char msvcExecutableOutput[MAX_PATH_LENGTH] = {0};
PrintfBuffer(msvcExecutableOutput, "/Fe\"%s\"", outputExecutableName.c_str());
executableOutputOverride = msvcExecutableOutput;
executableOutput = &executableOutputOverride;
}
else if (StrCompareIgnoreCase(linkCommand.fileToExecute.c_str(), "link.exe") == 0)
std::vector<std::string>* stringsIn;
// Use C++ to manage our string memory, pointed to by argumentsOut
std::vector<std::string> argumentsOutMemory;
std::vector<const char*>* argumentsOut;
void (*argumentConversionFunc)(char* buffer, int bufferSize, const char* stringIn,
const char* linkExecutable);
} libraryArguments[] = {
{&linkLibraries, {}, &librariesArgs, makeLinkLibraryArgument},
{&librarySearchDirs, {}, &librarySearchDirsArgs, makeLinkLibrarySearchDirArgument},
{&libraryRuntimeSearchDirs,
{},
&libraryRuntimeSearchDirsArgs,
makeLinkLibraryRuntimeSearchDirArgument},
{&toLinkerOptions, {}, &convertedLinkerArgs, makeLinkerArgument},
// We can't know how to auto-convert these because they could be anything
{&compilerLinkOptions, {}, &compilerLinkArgs, nullptr}};
for (int inputIndex = 0; inputIndex < ArraySize(libraryArguments); ++inputIndex)
{
char msvcExecutableOutput[MAX_PATH_LENGTH] = {0};
PrintfBuffer(msvcExecutableOutput, "/out:\"%s\"", outputExecutableName.c_str());
executableOutputOverride = msvcExecutableOutput;
executableOutput = &executableOutputOverride;
int numStrings = (int)libraryArguments[inputIndex].stringsIn->size();
libraryArguments[inputIndex].argumentsOutMemory.resize(numStrings);
libraryArguments[inputIndex].argumentsOut->resize(numStrings);
int currentString = 0;
for (const std::string& stringIn : *libraryArguments[inputIndex].stringsIn)
{
if (libraryArguments[inputIndex].argumentConversionFunc)
{
// Give some extra padding for prefixes
char argumentOut[MAX_PATH_LENGTH + 15] = {0};
libraryArguments[inputIndex].argumentConversionFunc(
argumentOut, sizeof(argumentOut), stringIn.c_str(),
linkCommand.fileToExecute.c_str());
libraryArguments[inputIndex].argumentsOutMemory[currentString] = argumentOut;
}
else
libraryArguments[inputIndex].argumentsOutMemory[currentString] = stringIn;
++currentString;
}
for (int stringIndex = 0; stringIndex < numStrings; ++stringIndex)
(*libraryArguments[inputIndex].argumentsOut)[stringIndex] =
libraryArguments[inputIndex].argumentsOutMemory[stringIndex].c_str();
}
// Copy it so hooks can modify it
ProcessCommandInput linkTimeInputs[] = {
{ProcessCommandArgumentType_ExecutableOutput, {executableOutput->c_str()}},
{ProcessCommandArgumentType_ObjectInput, objectsToLink}};
{ProcessCommandArgumentType_ExecutableOutput, {executableOutput}},
{ProcessCommandArgumentType_ObjectInput, objectsToLink},
{ProcessCommandArgumentType_AdditionalOptions, compilerLinkArgs},
{ProcessCommandArgumentType_LibrarySearchDirs, librarySearchDirsArgs},
{ProcessCommandArgumentType_Libraries, librariesArgs},
{ProcessCommandArgumentType_LibraryRuntimeSearchDirs, libraryRuntimeSearchDirsArgs},
{ProcessCommandArgumentType_LinkerArguments, convertedLinkerArgs}};
// Hooks should cooperate with eachother, i.e. try to only add things
for (PreLinkHook preLinkHook : manager.environment.preLinkHooks)


+ 9
- 1
src/ModuleManager.hpp View File

@ -30,11 +30,19 @@ struct Module
// Build system
std::vector<ModuleDependency> dependencies;
std::vector<std::string> cSearchDirectories;
std::vector<std::string> additionalBuildOptions;
std::vector<std::string> librarySearchDirectories;
std::vector<std::string> libraryRuntimeSearchDirectories;
std::vector<std::string> libraryDependencies;
std::vector<std::string> additionalBuildOptions;
// compilerLinkOptions goes to e.g. G++ to set up arguments to the actual linker.
// toLinkerOptions is forwarded to e.g. ld directly, not to G++
std::vector<std::string> compilerLinkOptions;
std::vector<std::string> toLinkerOptions;
// Do not build or link this module. Useful both for compile-time only files (which error
// because they are empty files) and for files only evaluated for their declarations (e.g. if
// the definitions are going to be provided via dynamic linking)


+ 39
- 1
src/RunProcess.cpp View File

@ -482,6 +482,43 @@ void PrintProcessArguments(const char** processArguments)
Log("\n");
}
static const char* ProcessCommandArgumentTypeToString(ProcessCommandArgumentType type)
{
switch (type)
{
case ProcessCommandArgumentType_None:
return "None";
case ProcessCommandArgumentType_String:
return "String";
case ProcessCommandArgumentType_SourceInput:
return "SourceInput";
case ProcessCommandArgumentType_ObjectOutput:
return "ObjectOutput";
case ProcessCommandArgumentType_CakelispHeadersInclude:
return "CakelispHeadersInclude";
case ProcessCommandArgumentType_IncludeSearchDirs:
return "IncludeSearchDirs";
case ProcessCommandArgumentType_AdditionalOptions:
return "AdditionalOptions";
case ProcessCommandArgumentType_ObjectInput:
return "ObjectInput";
case ProcessCommandArgumentType_DynamicLibraryOutput:
return "DynamicLibraryOutput";
case ProcessCommandArgumentType_LibrarySearchDirs:
return "LibrarySearchDirs";
case ProcessCommandArgumentType_Libraries:
return "Libraries";
case ProcessCommandArgumentType_LibraryRuntimeSearchDirs:
return "LibraryRuntimeSearchDirs";
case ProcessCommandArgumentType_LinkerArguments:
return "LinkerArguments";
case ProcessCommandArgumentType_ExecutableOutput:
return "ExecutableOutput";
default:
return "Unknown";
}
}
// The array will need to be deleted, but the array members will not
const char** MakeProcessArgumentsFromCommand(const char* fileToExecute,
std::vector<ProcessCommandArgument>& arguments,
@ -510,7 +547,8 @@ const char** MakeProcessArgumentsFromCommand(const char* fileToExecute,
}
if (!found)
{
Log("error: command missing input\n");
Logf("error: command to %s missing ProcessCommandInput of type %s\n", fileToExecute,
ProcessCommandArgumentTypeToString(argument.type));
return nullptr;
}
}


+ 8
- 0
src/RunProcessEnums.hpp View File

@ -1,7 +1,10 @@
#pragma once
// Update ProcessCommandArgumentTypeToString() after modifying this enum
enum ProcessCommandArgumentType
{
ProcessCommandArgumentType_None = 0,
ProcessCommandArgumentType_String,
ProcessCommandArgumentType_SourceInput,
@ -12,5 +15,10 @@ enum ProcessCommandArgumentType
ProcessCommandArgumentType_ObjectInput,
ProcessCommandArgumentType_DynamicLibraryOutput,
ProcessCommandArgumentType_LibrarySearchDirs,
ProcessCommandArgumentType_Libraries,
ProcessCommandArgumentType_LibraryRuntimeSearchDirs,
// Pass to the linker, e.g. on clang, add -Wl,
ProcessCommandArgumentType_LinkerArguments,
ProcessCommandArgumentType_ExecutableOutput
};

+ 11
- 0
test/LinkOptions.cake View File

@ -0,0 +1,11 @@
(c-import "<stdio.h>")
(defun main(&return int)
(printf "Hello, world! From Cakelisp!\n")
(return 0))
(add-library-search-directory "test/search/me")
(add-library-runtime-search-directory "test/runtime/search/me" "me/too")
(add-library-dependency "test" "test2")
(add-library-dependency "test3")
(add-linker-options "-shared")

Loading…
Cancel
Save