Browse Source

Made progress on compile-time code execution

* Add Build.cpp to handle platform-specific argument conversion (and
more, once I organize)
* Build cakelisp in debug
* Add declspec and CAKELISP_EXPORTING define to control it (that's an
  annoying thing to have to do for Windows only)
* WIP Dynamic loading. I'm still not 100% clear about how to load
symbols from Cakelisp in the DLL, so the code is still messy
* Platform-specific arguments hooked up for compile-time code building
* Added SimpleMacros.cake for a minimal test-case
* Added minimum declspec for SimpleMacros.cake
* Removed the use of __FILE__ in case they contain backslashes
* Make sure all paths are forward slashes so backslash paths don't get
written by e.g. tokenize-push generator
* Copy .lib file to reside next to .exe. This is not entirely
necessary; it should be an option
* Read subprocess output every 50 milliseconds. This is necessary
because unlike Linux, Windows processes will wait for the parent
process to read from stdout once the pipe is full
windows-support
Macoy Madson 6 months ago
parent
commit
1e2fb67942
19 changed files with 355 additions and 110 deletions
  1. +1
    -0
      Bootstrap.cake
  2. +4
    -1
      Bootstrap_MSVC.cake
  3. +4
    -6
      Build.bat
  4. +1
    -0
      Build.sh
  5. +1
    -0
      CrossCompile_Windows.cake
  6. +2
    -2
      ReadMe.org
  7. +1
    -0
      doc/Cakelisp.org
  8. +54
    -0
      src/Build.cpp
  9. +13
    -0
      src/Build.hpp
  10. +66
    -4
      src/DynamicLoader.cpp
  11. +21
    -13
      src/Evaluator.cpp
  12. +9
    -0
      src/Exporting.hpp
  13. +11
    -8
      src/GeneratorHelpers.hpp
  14. +15
    -3
      src/Generators.cpp
  15. +1
    -0
      src/Jamfile
  16. +87
    -26
      src/ModuleManager.cpp
  17. +50
    -45
      src/RunProcess.cpp
  18. +3
    -2
      src/Tokenizer.hpp
  19. +11
    -0
      test/SimpleMacros.cake

+ 1
- 0
Bootstrap.cake View File

@ -17,6 +17,7 @@
"DynamicLoader.cpp"
"ModuleManager.cpp"
"Logging.cpp"
"Build.cpp"
"Main.cpp")
(add-build-options "-DUNIX")


+ 4
- 1
Bootstrap_MSVC.cake View File

@ -17,9 +17,12 @@
"DynamicLoader.cpp"
"ModuleManager.cpp"
"Logging.cpp"
"Build.cpp"
"Main.cpp")
(add-build-options "/DWINDOWS" "/EHsc")
(add-build-options "/DWINDOWS" "/DCAKELISP_EXPORTING" "/EHsc"
;;; DEBUG ONLY!
"/Zi" "/FS" "/Fd:bin\cakelisp.pdb" "/DEBUG:FASTLINK")
(set-cakelisp-option build-time-compiler "cl.exe")
(set-cakelisp-option build-time-compile-arguments


+ 4
- 6
Build.bat View File

@ -38,8 +38,10 @@ CL.exe src/Tokenizer.cpp ^
src/DynamicLoader.cpp ^
src/ModuleManager.cpp ^
src/Logging.cpp ^
src/Build.cpp ^
src/Main.cpp ^
/EHsc /MP /DWINDOWS /Fe"bin\cakelisp_bootstrap" /Zi /Fd"bin\cakelisp_bootstrap.pdb" /DEBUG:FASTLINK
/EHsc /MP /DWINDOWS /DCAKELISP_EXPORTING ^
/Fe"bin\cakelisp_bootstrap" /Zi /Fd"bin\cakelisp_bootstrap.pdb" /DEBUG:FASTLINK
echo %ERRORLEVEL%
@if %ERRORLEVEL% == 0 (
@ -53,11 +55,7 @@ CL.exe src/Tokenizer.cpp ^
)
:bootstrapBuild
"bin\cakelisp_bootstrap.exe" --verbose-processes --verbose-build-reasons --verbose-phases Bootstrap_MSVC.cake
rem Left off: Remove space between /Fo and output, -Isrc isn't going to work either
rem Figure out why CL isn't being found by CreateProcess
rem Can I list all the variables/paths being set in Cakelisp, to see if vcvars is applying, or if CreateProcess isn't passing them?
rem CL /c src/Tokenizer.cpp /Fo"cakelisp_cache/Bootstrap_Windows/Tokenizer.cpp.o" -Isrc /DWINDOWS /EHsc
"bin\cakelisp_bootstrap.exe" Bootstrap_MSVC.cake
@if %ERRORLEVEL% == 0 (
echo Success! Use bin\cakelisp.exe to build your programs
goto success


+ 1
- 0
Build.sh View File

@ -28,6 +28,7 @@ else
src/DynamicLoader.cpp \
src/ModuleManager.cpp \
src/Logging.cpp \
src/Build.cpp \
src/Main.cpp \
-DUNIX || exit $?
# Need -ldl for dynamic loading, --export-dynamic to let compile-time functions resolve to


+ 1
- 0
CrossCompile_Windows.cake View File

@ -17,6 +17,7 @@
"DynamicLoader.cpp"
"ModuleManager.cpp"
"Logging.cpp"
"Build.cpp"
"Main.cpp")
(add-build-options "-DWINDOWS")


+ 2
- 2
ReadMe.org View File

@ -134,9 +134,9 @@ You can refer to an existing project in ~cakelisp/VisualStudio~. The steps to cr
- Expand ~Configuration Properties -> C/C++ -> Preprocessor~
- Double click or ~<Edit...>~ the ~Preprocessor Definitions~ field and add the following to the beginning:
#+BEGIN_SRC sh
_CRT_SECURE_NO_WARNINGS;WINDOWS;
CAKELISP_EXPORTING;_CRT_SECURE_NO_WARNINGS;WINDOWS;
#+END_SRC
The first definition is going to be removed eventually; it makes MSVC more lenient with some errors Cakelisp has. The second definition ensures you build with Cakelisp's Windows-specific code enabled
~CAKELISP_EXPORTING~ indicates Cakelisp should export its symbols to DLLs. The ~CRC~ definition is going to be removed eventually; it makes MSVC more lenient with some errors Cakelisp has. The ~WINDOWS~ definition ensures you build with Cakelisp's Windows-specific code enabled
- Hit ~F5~ or go to ~Debug -> Start Debugging~. Visual Studio will build Cakelisp, and if it succeeds, launch Cakelisp. If you have no ~Command Arguments~ set, you should see the Cakelisp help output in a command window.
This project is for building Cakelisp itself; you don't need to make any new projects for your projects written in Cakelisp (in ~.cake~ files). Change the ~Debugging -> Command Arguments~ setting to build different Cakelisp files, or change the ~Working Directory~ to build a different Cakelisp project (e.g. one in a separate repository).


+ 1
- 0
doc/Cakelisp.org View File

@ -420,6 +420,7 @@ If ~global~ is specified instead, all modules and build dependencies would inclu
"DynamicLoader.cpp"
"ModuleManager.cpp"
"Logging.cpp"
"Build.cpp"
"Main.cpp")
#+END_SRC


+ 54
- 0
src/Build.cpp View File

@ -0,0 +1,54 @@
#include "Build.hpp"
#include "Utilities.hpp"
#ifdef WINDOWS
const char* compilerObjectExtension = "obj";
const char* linkerDynamicLibraryPrefix = "";
const char* linkerDynamicLibraryExtension = "dll";
const char* defaultExecutableName = "output.exe";
#else
const char* compilerObjectExtension = "o";
const char* linkerDynamicLibraryPrefix = "lib";
const char* linkerDynamicLibraryExtension = "so";
const char* defaultExecutableName = "a.out";
#endif
void makeIncludeArgument(char* buffer, int bufferSize, const char* searchDir)
{
// TODO: Make this a setting rather than a define
#ifdef WINDOWS
SafeSnprinf(buffer, bufferSize, "/I \"%s\"", searchDir);
#else
SafeSnprinf(buffer, bufferSize, "-I%s", searchDir);
#endif
}
void makeObjectOutputArgument(char* buffer, int bufferSize, const char* objectName)
{
// TODO: Make this a setting rather than a define
#ifdef WINDOWS
SafeSnprinf(buffer, bufferSize, "/Fo\"%s\"", objectName);
#else
SafeSnprinf(buffer, bufferSize, "%s", objectName);
#endif
}
void makeDynamicLibraryOutputArgument(char* buffer, int bufferSize, const char* libraryName,
const char* buildExecutable)
{
if (_stricmp(buildExecutable, "cl.exe") == 0)
{
Log("error: attempting to build dynamic library using cl.exe. You must use link.exe "
"instead\n");
SafeSnprinf(buffer, bufferSize, "%s", libraryName);
}
else if (_stricmp(buildExecutable, "link.exe") == 0)
{
SafeSnprinf(buffer, bufferSize, "/OUT:\"%s\"", libraryName);
}
else
{
SafeSnprinf(buffer, bufferSize, "%s", libraryName);
}
}

+ 13
- 0
src/Build.hpp View File

@ -0,0 +1,13 @@
#pragma once
extern const char* compilerObjectExtension;
extern const char* linkerDynamicLibraryPrefix;
extern const char* linkerDynamicLibraryExtension;
extern const char* defaultExecutableName;
void makeIncludeArgument(char* buffer, int bufferSize, const char* searchDir);
// On Windows, extra formatting is required to output objects
void makeObjectOutputArgument(char* buffer, int bufferSize, const char* objectName);
void makeDynamicLibraryOutputArgument(char* buffer, int bufferSize, const char* libraryName,
const char* buildExecutable);

+ 66
- 4
src/DynamicLoader.cpp View File

@ -5,6 +5,9 @@
#include <string>
#include <unordered_map>
#include "Utilities.hpp"
#include "FileUtilities.hpp"
#ifdef UNIX
#include <dlfcn.h>
#elif WINDOWS
@ -22,6 +25,27 @@ struct DynamicLibrary
typedef std::unordered_map<std::string, DynamicLibrary> DynamicLibraryMap;
static DynamicLibraryMap dynamicLibraries;
#ifdef WINDOWS
void makeBacklashFilename(char* buffer, int bufferSize, const char* filename)
{
char* bufferWrite = buffer;
for (const char* currentChar = filename; *currentChar; ++currentChar)
{
if (*currentChar == '/')
*bufferWrite = '\\';
else
*bufferWrite = *currentChar;
++bufferWrite;
if (bufferWrite - buffer >= bufferSize)
{
Log("error: could not make safe filename: buffer too small\n");
break;
}
}
}
#endif
DynamicLibHandle loadDynamicLibrary(const char* libraryPath)
{
void* libHandle = nullptr;
@ -44,8 +68,43 @@ DynamicLibHandle loadDynamicLibrary(const char* libraryPath)
}
#elif WINDOWS
// TODO: Any way to get errors if this fails?
libHandle = LoadLibrary(libraryPath);
// TODO Clean this up! Only the cakelispBin is necessary I think (need to double check that)
// TODO Clear added dirs after? (RemoveDllDirectory())
const char* absoluteLibPath =
makeAbsolutePath_Allocated(/*fromDirectory=*/nullptr, libraryPath);
char convertedPath[MAX_PATH_LENGTH] = {0};
// TODO Remove, redundant with makeAbsolutePath_Allocated()
makeBacklashFilename(convertedPath, sizeof(convertedPath), absoluteLibPath);
char dllDirectory[MAX_PATH_LENGTH] = {0};
getDirectoryFromPath(convertedPath, dllDirectory, sizeof(dllDirectory));
{
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, dllDirectory, -1, NULL, 0);
wchar_t* wstrDllDirectory = new wchar_t[wchars_num];
MultiByteToWideChar(CP_UTF8, 0, dllDirectory, -1, wstrDllDirectory, wchars_num);
AddDllDirectory(wstrDllDirectory);
delete[] wstrDllDirectory;
}
// When loading cakelisp.lib, it will actually need to find cakelisp.exe for the symbols
{
const char* cakelispBinDirectory =
makeAbsolutePath_Allocated(/*fromDirectory=*/nullptr, "bin");
int wchars_num = MultiByteToWideChar(CP_UTF8, 0, cakelispBinDirectory, -1, NULL, 0);
wchar_t* wstrDllDirectory = new wchar_t[wchars_num];
MultiByteToWideChar(CP_UTF8, 0, cakelispBinDirectory, -1, wstrDllDirectory, wchars_num);
AddDllDirectory(wstrDllDirectory);
free((void*)cakelispBinDirectory);
delete[] wstrDllDirectory;
}
libHandle = LoadLibraryEx(convertedPath, nullptr,
LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
if (!libHandle)
{
fprintf(stderr, "DynamicLoader Error: Failed to load %s with code %d\n", convertedPath,
GetLastError());
free((void*)absoluteLibPath);
return nullptr;
}
free((void*)absoluteLibPath);
#endif
dynamicLibraries[libraryPath] = {libHandle};
@ -80,9 +139,12 @@ void* getSymbolFromDynamicLibrary(DynamicLibHandle library, const char* symbolNa
return symbol;
#elif WINDOWS
// TODO: Any way to get errors if this fails?
// Sergey: GetLastError
void* procedure = (void*)GetProcAddress((HINSTANCE)library, symbolName);
if (!procedure)
{
fprintf(stderr, "DynamicLoader Error:\n%d\n", GetLastError());
return nullptr;
}
return procedure;
#else
return nullptr;


+ 21
- 13
src/Evaluator.cpp View File

@ -1,5 +1,6 @@
#include "Evaluator.hpp"
#include "Build.hpp"
#include "Converters.hpp"
#include "DynamicLoader.hpp"
#include "FileUtilities.hpp"
@ -836,13 +837,14 @@ int BuildExecuteCompileTimeFunctions(EvaluatorEnvironment& environment,
buildObject.artifactsName.c_str());
char buildObjectName[MAX_PATH_LENGTH] = {0};
PrintfBuffer(buildObjectName, "%s/%s.o", cakelispWorkingDir,
buildObject.artifactsName.c_str());
PrintfBuffer(buildObjectName, "%s/%s.%s", cakelispWorkingDir,
buildObject.artifactsName.c_str(), compilerObjectExtension);
buildObject.buildObjectName = buildObjectName;
char dynamicLibraryOut[MAX_PATH_LENGTH] = {0};
PrintfBuffer(dynamicLibraryOut, "%s/lib%s.so", cakelispWorkingDir,
buildObject.artifactsName.c_str());
PrintfBuffer(dynamicLibraryOut, "%s/%s%s.%s", cakelispWorkingDir,
linkerDynamicLibraryPrefix, buildObject.artifactsName.c_str(),
linkerDynamicLibraryExtension);
buildObject.dynamicLibraryPath = dynamicLibraryOut;
if (canUseCachedFile(environment, sourceOutputName, buildObject.dynamicLibraryPath.c_str()))
@ -857,17 +859,18 @@ int BuildExecuteCompileTimeFunctions(EvaluatorEnvironment& environment,
char headerInclude[MAX_PATH_LENGTH] = {0};
if (environment.cakelispSrcDir.empty())
{
PrintBuffer(headerInclude, "-Isrc/");
}
makeIncludeArgument(headerInclude, sizeof(headerInclude), "src/");
else
{
PrintfBuffer(headerInclude, "-I%s", environment.cakelispSrcDir.c_str());
}
makeIncludeArgument(headerInclude, sizeof(headerInclude),
environment.cakelispSrcDir.c_str());
char buildObjectArgument[MAX_PATH_LENGTH] = {0};
makeObjectOutputArgument(buildObjectArgument, sizeof(buildObjectArgument),
buildObjectName);
ProcessCommandInput compileTimeInputs[] = {
{ProcessCommandArgumentType_SourceInput, {sourceOutputName}},
{ProcessCommandArgumentType_ObjectOutput, {buildObjectName}},
{ProcessCommandArgumentType_ObjectOutput, {buildObjectArgument}},
{ProcessCommandArgumentType_CakelispHeadersInclude, {headerInclude}}};
const char** buildArguments = MakeProcessArgumentsFromCommand(
environment.compileTimeBuildCommand, compileTimeInputs, ArraySize(compileTimeInputs));
@ -933,9 +936,14 @@ int BuildExecuteCompileTimeFunctions(EvaluatorEnvironment& environment,
if (logging.buildProcess)
Logf("Compiled %s successfully\n", buildObject.definition->name.c_str());
char dynamicLibraryOutArgument[MAX_PATH_LENGTH] = {0};
makeDynamicLibraryOutputArgument(
dynamicLibraryOutArgument, sizeof(dynamicLibraryOutArgument),
buildObject.dynamicLibraryPath.c_str(), environment.compileTimeLinkCommand.fileToExecute.c_str());
ProcessCommandInput linkTimeInputs[] = {
{ProcessCommandArgumentType_DynamicLibraryOutput,
{buildObject.dynamicLibraryPath.c_str()}},
{dynamicLibraryOutArgument}},
{ProcessCommandArgumentType_ObjectInput, {buildObject.buildObjectName.c_str()}}};
const char** linkArgumentList = MakeProcessArgumentsFromCommand(
environment.compileTimeLinkCommand, linkTimeInputs, ArraySize(linkTimeInputs));
@ -1494,7 +1502,7 @@ void environmentDestroyInvalidateTokens(EvaluatorEnvironment& environment)
if (expectedSignature.empty())
{
if (!tokenizeLinePrintError(g_environmentCompileTimeVariableDestroySignature,
__FILE__, __LINE__, expectedSignature))
"Evaluator.cpp", __LINE__, expectedSignature))
{
Log("error: failed to tokenize "
"g_environmentCompileTimeVariableDestroySignature! Internal code "


+ 9
- 0
src/Exporting.hpp View File

@ -0,0 +1,9 @@
#ifdef WINDOWS
#ifdef CAKELISP_EXPORTING
#define CAKELISP_API __declspec(dllexport)
#else
#define CAKELISP_API __declspec(dllimport)
#endif
#else
#define CAKELISP_API
#endif

+ 11
- 8
src/GeneratorHelpers.hpp View File

@ -4,8 +4,9 @@
#include <string>
#include "EvaluatorEnums.hpp"
#include "TokenEnums.hpp"
#include "Exporting.hpp"
#include "GeneratorHelpersEnums.hpp"
#include "TokenEnums.hpp"
struct Token;
struct EvaluatorContext;
@ -15,13 +16,14 @@ struct StringOutput;
struct ObjectDefinition;
void StripInvocation(int& startTokenIndex, int& endTokenIndex);
int FindCloseParenTokenIndex(const std::vector<Token>& tokens, int startTokenIndex);
CAKELISP_API int FindCloseParenTokenIndex(const std::vector<Token>& tokens, int startTokenIndex);
bool ExpectEvaluatorScope(const char* generatorName, const Token& token,
const EvaluatorContext& context, EvaluatorScope expectedScope);
bool IsForbiddenEvaluatorScope(const char* generatorName, const Token& token,
const EvaluatorContext& context, EvaluatorScope forbiddenScope);
bool ExpectTokenType(const char* generatorName, const Token& token, TokenType expectedType);
CAKELISP_API bool ExpectTokenType(const char* generatorName, const Token& token,
TokenType expectedType);
// Errors and returns false if out of invocation (or at closing paren)
bool ExpectInInvocation(const char* message, const std::vector<Token>& tokens, int indexToCheck,
int endInvocationIndex);
@ -37,14 +39,15 @@ bool isSpecialSymbol(const Token& token);
// Returns -1 if argument is not within range
int getArgument(const std::vector<Token>& tokens, int startTokenIndex, int desiredArgumentIndex,
int endTokenIndex);
int getExpectedArgument(const char* message, const std::vector<Token>& tokens, int startTokenIndex,
int desiredArgumentIndex, int endTokenIndex);
CAKELISP_API int getExpectedArgument(const char* message, const std::vector<Token>& tokens,
int startTokenIndex, int desiredArgumentIndex,
int endTokenIndex);
// Expects startTokenIndex to be the invocation. The name of the invocation is included in the count
// Note: Body arguments will not work properly with this
int getNumArguments(const std::vector<Token>& tokens, int startTokenIndex, int endTokenIndex);
// Like getNumArguments, includes invocation
bool ExpectNumArguments(const std::vector<Token>& tokens, int startTokenIndex, int endTokenIndex,
int numExpectedArguments);
CAKELISP_API bool ExpectNumArguments(const std::vector<Token>& tokens, int startTokenIndex,
int endTokenIndex, int numExpectedArguments);
bool isLastArgument(const std::vector<Token>& tokens, int startTokenIndex, int endTokenIndex);
// There are no more arguments once this returns endArrayTokenIndex
int getNextArgument(const std::vector<Token>& tokens, int currentTokenIndex,
@ -73,7 +76,7 @@ void MakeUniqueSymbolName(EvaluatorEnvironment& environment, const char* prefix,
void MakeContextUniqueSymbolName(EvaluatorEnvironment& environment, const EvaluatorContext& context,
const char* prefix, Token* tokenToChange);
void PushBackTokenExpression(std::vector<Token>& output, const Token* startToken);
CAKELISP_API void PushBackTokenExpression(std::vector<Token>& output, const Token* startToken);
void addModifierToStringOutput(StringOutput& operation, StringOutputModifierFlags flag);


+ 15
- 3
src/Generators.cpp View File

@ -329,7 +329,7 @@ bool AddCompileTimeHookGenerator(EvaluatorEnvironment& environment, const Evalua
static std::vector<Token> expectedSignature;
if (expectedSignature.empty())
{
if (!tokenizeLinePrintError(g_modulePreBuildHookSignature, __FILE__, __LINE__,
if (!tokenizeLinePrintError(g_modulePreBuildHookSignature, "Generators.cpp", __LINE__,
expectedSignature))
return false;
}
@ -355,7 +355,7 @@ bool AddCompileTimeHookGenerator(EvaluatorEnvironment& environment, const Evalua
static std::vector<Token> expectedSignature;
if (expectedSignature.empty())
{
if (!tokenizeLinePrintError(g_environmentPreLinkHookSignature, __FILE__,
if (!tokenizeLinePrintError(g_environmentPreLinkHookSignature, "Generators.cpp",
__LINE__, expectedSignature))
return false;
}
@ -382,7 +382,7 @@ bool AddCompileTimeHookGenerator(EvaluatorEnvironment& environment, const Evalua
if (expectedSignature.empty())
{
if (!tokenizeLinePrintError(g_environmentPostReferencesResolvedHookSignature,
__FILE__, __LINE__, expectedSignature))
"Generators.cpp", __LINE__, expectedSignature))
return false;
}
@ -1776,6 +1776,12 @@ bool DefMacroGenerator(EvaluatorEnvironment& environment, const EvaluatorContext
// Macros will be found without headers thanks to dynamic linking
// bool isModuleLocal = tokens[startTokenIndex + 1].contents.compare("defmacro-local") == 0;
// TODO Only do this when using MSVC
#ifdef WINDOWS
addStringOutput(compTimeOutput->source, "__declspec(dllexport)", StringOutMod_SpaceAfter,
&tokens[startTokenIndex]);
#endif
// Macros must return success or failure
addStringOutput(compTimeOutput->source, "bool", StringOutMod_SpaceAfter,
&tokens[startTokenIndex]);
@ -1869,6 +1875,12 @@ bool DefGeneratorGenerator(EvaluatorEnvironment& environment, const EvaluatorCon
// Generators will be found without headers thanks to dynamic linking
// bool isModuleLocal = tokens[startTokenIndex + 1].contents.compare("defgenerator-local") == 0;
// TODO Only do this when using MSVC
#ifdef WINDOWS
addStringOutput(compTimeOutput->source, "__declspec(dllexport)", StringOutMod_SpaceAfter,
&tokens[startTokenIndex]);
#endif
// Generators must return success or failure
addStringOutput(compTimeOutput->source, "bool", StringOutMod_SpaceAfter,
&tokens[startTokenIndex]);


+ 1
- 0
src/Jamfile View File

@ -16,6 +16,7 @@ RunProcess.cpp
OutputPreambles.cpp
DynamicLoader.cpp
ModuleManager.cpp
Build.cpp
Logging.cpp
;


+ 87
- 26
src/ModuleManager.cpp View File

@ -3,6 +3,7 @@
#include <string.h>
#include <cstring>
#include "Build.hpp"
#include "Converters.hpp"
#include "DynamicLoader.hpp"
#include "Evaluator.hpp"
@ -17,14 +18,6 @@
#include "Utilities.hpp"
#include "Writer.hpp"
#ifdef WINDOWS
const char* compilerObjectExtension = "obj";
const char* defaultExecutableName = "output.exe";
#else
const char* compilerObjectExtension = "o";
const char* defaultExecutableName = "a.out";
#endif
// The ' symbols tell the signature validator that the actual contents of those symbols can be
// user-defined (just like C letting you specify arguments without names)
const char* g_modulePreBuildHookSignature =
@ -86,20 +79,28 @@ void moduleManagerInitialize(ModuleManager& manager)
{ProcessCommandArgumentType_String, "/nologo"},
// Not 100% sure what the right default for this is
{ProcessCommandArgumentType_String, "/EHsc"},
{ProcessCommandArgumentType_String, "/c"},
// Need this to properly add declspec for importing symbols
{ProcessCommandArgumentType_String, "/DWINDOWS"},
// TODO Fix
{ProcessCommandArgumentType_String, "/DEBUG"},
{ProcessCommandArgumentType_String, "/Zi"},
{ProcessCommandArgumentType_String, "/Fp\"cakelisp_cache\\comptime_my_print.pdb\""},
{ProcessCommandArgumentType_String, "/c"},
{ProcessCommandArgumentType_SourceInput, EmptyString},
{ProcessCommandArgumentType_ObjectOutput, EmptyString},
{ProcessCommandArgumentType_CakelispHeadersInclude, EmptyString}};
// TODO: Dynamic linking support
// {ProcessCommandArgumentType_String, "-fPIC"}};
// };
manager.environment.compileTimeLinkCommand.fileToExecute = "link.exe";
manager.environment.compileTimeLinkCommand.arguments = {
// TODO: Dynamic linking support
// {ProcessCommandArgumentType_String, "-shared"},
// {ProcessCommandArgumentType_String, "-o"},
{ProcessCommandArgumentType_String, "/nologo"},
{ProcessCommandArgumentType_String, "/DLL"},
{ProcessCommandArgumentType_DynamicLibraryOutput, EmptyString},
{ProcessCommandArgumentType_ObjectInput, EmptyString}};
{ProcessCommandArgumentType_ObjectInput, EmptyString},
// On Windows, .exes create .lib files for exports
{ProcessCommandArgumentType_String, "/LIBPATH:\"bin\""},
{ProcessCommandArgumentType_String, "cakelisp.lib"}};
manager.environment.buildTimeBuildCommand.fileToExecute = "cl.exe";
manager.environment.buildTimeBuildCommand.arguments = {
@ -177,6 +178,25 @@ void moduleManagerDestroy(ModuleManager& manager)
closeAllDynamicLibraries();
}
void makeSafeFilename(char* buffer, int bufferSize, const char* filename)
{
char* bufferWrite = buffer;
for (const char* currentChar = filename; *currentChar; ++currentChar)
{
if (*currentChar == '\\')
*bufferWrite = '/';
else
*bufferWrite = *currentChar;
++bufferWrite;
if (bufferWrite - buffer >= bufferSize)
{
Log("error: could not make safe filename: buffer too small\n");
break;
}
}
}
bool moduleLoadTokenizeValidate(const char* filename, const std::vector<Token>** tokensOut)
{
*tokensOut = nullptr;
@ -297,7 +317,10 @@ bool moduleManagerAddEvaluateFile(ModuleManager& manager, const char* filename,
char resolvedPath[MAX_PATH_LENGTH] = {0};
makeAbsoluteOrRelativeToWorkingDir(filename, resolvedPath, ArraySize(resolvedPath));
const char* normalizedFilename = _strdup(resolvedPath);
char safePathBuffer[MAX_PATH_LENGTH] = {0};
makeSafeFilename(safePathBuffer, sizeof(safePathBuffer), resolvedPath);
const char* normalizedFilename = _strdup(safePathBuffer);
// Enabling this makes all file:line messages really long. For now, I'll keep it as relative to
// current working directory of this executable.
// const char* normalizedFilename = makeAbsolutePath_Allocated(".", filename);
@ -667,16 +690,6 @@ void builtObjectsFree(std::vector<BuiltObject*>& objects)
objects.clear();
}
void makeIncludeArgument(char* buffer, int bufferSize, const char* searchDir)
{
// TODO: Make this a setting rather than a define
#ifdef WINDOWS
SafeSnprinf(buffer, bufferSize, "/I \"%s\"", searchDir);
#else
SafeSnprinf(buffer, bufferSize, "-I%s", searchDir);
#endif
}
void copyModuleBuildOptionsToBuiltObject(Module* module, ProcessCommand* buildCommandOverride,
BuiltObject* object)
{
@ -702,6 +715,32 @@ void getExecutableOutputName(ModuleManager& manager, std::string& finalOutputNam
finalOutputNameOut = defaultExecutableName;
}
// TODO: Safer version
bool changeExtension(char* buffer, const char* newExtension)
{
int bufferLength = strlen(buffer);
char* expectExtensionStart = nullptr;
for (char* currentChar = buffer + (bufferLength - 1); *currentChar && currentChar > buffer;
--currentChar)
{
if (*currentChar == '.')
{
expectExtensionStart = currentChar;
break;
}
}
if (!expectExtensionStart)
return false;
char* extensionWrite = expectExtensionStart + 1;
for (const char* extensionChar = newExtension; *extensionChar; ++extensionChar)
{
*extensionWrite = *extensionChar;
++extensionWrite;
}
return true;
}
// Copy cachedOutputExecutable to finalOutputNameOut, adding executable permissions
// TODO: There's no easy way to know whether this exe is the current build configuration's
// output exe, so copy it every time
@ -717,6 +756,27 @@ bool copyExecutableToFinalOutput(ModuleManager& manager, const std::string& cach
return false;
}
// TODO: Consider a better place for this
#ifdef WINDOWS
char executableLib[MAX_PATH_LENGTH] = {0};
SafeSnprinf(executableLib, sizeof(executableLib), "%s", cachedOutputExecutable.c_str());
bool modifiedExtension = changeExtension(executableLib, "lib");
if (modifiedExtension && fileExists(executableLib))
{
char finalOutputLib[MAX_PATH_LENGTH] = {0};
SafeSnprinf(finalOutputLib, sizeof(finalOutputLib), "%s", finalOutputName.c_str());
modifiedExtension = changeExtension(finalOutputLib, "lib");
if (modifiedExtension && !copyBinaryFileTo(executableLib, finalOutputLib))
{
Log("error: failed to copy executable lib from cache\n");
return false;
}
}
#endif
addExecutablePermission(finalOutputName.c_str());
return true;
}
@ -908,7 +968,8 @@ bool moduleManagerBuild(ModuleManager& manager, std::vector<std::string>& builtO
if (_stricmp(buildCommand.fileToExecute.c_str(), "CL.exe") == 0)
{
char msvcObjectOutput[MAX_PATH_LENGTH] = {0};
PrintfBuffer(msvcObjectOutput, "/Fo\"%s\"", object->filename.c_str());
makeObjectOutputArgument(msvcObjectOutput, sizeof(msvcObjectOutput),
object->filename.c_str());
objectOutputOverride = msvcObjectOutput;
objectOutput = &objectOutputOverride;
}


+ 50
- 45
src/RunProcess.cpp View File

@ -38,7 +38,7 @@ struct Subprocess
int pipeReadFileDescriptor;
#elif WINDOWS
PROCESS_INFORMATION* processInfo;
HANDLE hChildStd_IN_Wr;
// HANDLE hChildStd_IN_Wr; // Not used
HANDLE hChildStd_OUT_Rd;
#endif
std::string command;
@ -62,12 +62,6 @@ void subprocessReceiveStdOut(const char* processOutputBuffer)
Logf("%s", processOutputBuffer);
}
// TODO: Make separate pipe for std err?
// void subprocessReceiveStdErr(const char* processOutputBuffer)
// {
// Logf("%s", processOutputBuffer);
// }
// Does not work
#ifdef WINDOWS
void LogLastError()
@ -434,11 +428,11 @@ int runProcess(const RunProcessArguments& arguments, int* statusOut)
// If they are not explicitly closed, there is no way to recognize that the child process ended
CloseHandle(hChildStd_OUT_Wr);
CloseHandle(hChildStd_IN_Rd);
CloseHandle(hChildStd_IN_Wr);
Subprocess newProcess = {0};
newProcess.statusOut = statusOut;
newProcess.processInfo = processInfo;
newProcess.hChildStd_IN_Wr = hChildStd_IN_Wr;
newProcess.hChildStd_OUT_Rd = hChildStd_OUT_Rd;
newProcess.command = commandLineString;
s_subprocesses.push_back(std::move(newProcess));
@ -450,6 +444,42 @@ int runProcess(const RunProcessArguments& arguments, int* statusOut)
return 1;
}
#ifdef WINDOWS
void readProcessPipe(Subprocess& process, SubprocessOnOutputFunc onOutput)
{
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
char buffer[4096] = {0};
bool encounteredError = false;
while (true)
{
DWORD bytesRead = 0;
DWORD bytesWritten = 0;
bool success = ReadFile(process.hChildStd_OUT_Rd, buffer, sizeof(buffer), &bytesRead, NULL);
if (!success || bytesRead == 0)
{
encounteredError = !success;
break;
}
success = WriteFile(hParentStdOut, buffer, bytesRead, &bytesWritten, NULL);
if (!success)
{
encounteredError = true;
break;
}
if (bytesRead <= sizeof(buffer))
onOutput(buffer);
}
// This seems to give a lot of false-positives
// if (encounteredError)
// Log("warning: encountered read or write error while receiving sub-process "
// "output\n");
}
#endif
// This function prints all the output for each process in one contiguous block, so that outputs
// between two processes aren't mangled together terribly
void waitForAllProcessesClosed(SubprocessOnOutputFunc onOutput)
@ -457,10 +487,6 @@ void waitForAllProcessesClosed(SubprocessOnOutputFunc onOutput)
if (s_subprocesses.empty())
return;
#ifdef WINDOWS
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
#endif
for (Subprocess& process : s_subprocesses)
{
#ifdef UNIX
@ -484,39 +510,19 @@ void waitForAllProcessesClosed(SubprocessOnOutputFunc onOutput)
if (*process.statusOut != 0)
Logf("%s\n", process.command.c_str());
#elif WINDOWS
// Wait until child process exits.
WaitForSingleObject(process.processInfo->hProcess, INFINITE);
// Read all output
{
DWORD bytesRead, bytesWritten;
char buffer[4096] = {0};
bool encounteredError = false;
while (true)
{
bool success =
ReadFile(process.hChildStd_OUT_Rd, buffer, sizeof(buffer), &bytesRead, NULL);
if (!success || bytesRead == 0)
{
encounteredError = !success;
break;
}
success = WriteFile(hParentStdOut, buffer, bytesRead, &bytesWritten, NULL);
if (!success)
{
encounteredError = true;
break;
}
onOutput(buffer);
}
// We cannot wait indefinitely because the process eventually waits for us to read from the
// output pipe (e.g. its buffer gets full). pollProcessTimeMilliseconds may need to be
// tweaked for better performance; if the buffer is full, the subprocess will wait for as
// long as pollProcessTimeMilliseconds - time taken to fill buffer. Very low wait times will
// mean Cakelisp unnecessarily taking up cycles, so it's a tradeoff.
const int pollProcessTimeMilliseconds = 50;
while (WAIT_TIMEOUT ==
WaitForSingleObject(process.processInfo->hProcess, pollProcessTimeMilliseconds))
readProcessPipe(process, onOutput);
// This seems to give a lot of false-positives
// if (encounteredError)
// Log("warning: encountered read or write error while receiving sub-process "
// "output\n");
}
// If the wait was ended but wasn't a timeout, we still need to read out
readProcessPipe(process, onOutput);
DWORD exitCode = 0;
if (!GetExitCodeProcess(process.processInfo->hProcess, &exitCode))
@ -531,10 +537,9 @@ void waitForAllProcessesClosed(SubprocessOnOutputFunc onOutput)
*process.statusOut = exitCode;
// Close process, thread, and stdin/out handles.
// Close process, thread, and stdout handles.
CloseHandle(process.processInfo->hProcess);
CloseHandle(process.processInfo->hThread);
CloseHandle(process.hChildStd_IN_Wr);
CloseHandle(process.hChildStd_OUT_Rd);
#endif
}


+ 3
- 2
src/Tokenizer.hpp View File

@ -3,6 +3,7 @@
#include <vector>
#include <string>
#include "Exporting.hpp"
#include "TokenEnums.hpp"
const char* tokenTypeToString(TokenType type);
@ -31,8 +32,8 @@ void destroyToken(Token* token);
const char* tokenizeLine(const char* inputLine, const char* source, unsigned int lineNumber,
std::vector<Token>& tokensOut);
// Invocations of this are generated by TokenizePushGenerator()
bool tokenizeLinePrintError(const char* inputLine, const char* source, unsigned int lineNumber,
std::vector<Token>& tokensOut);
CAKELISP_API bool tokenizeLinePrintError(const char* inputLine, const char* source,
unsigned int lineNumber, std::vector<Token>& tokensOut);
bool validateParentheses(const std::vector<Token>& tokens);


+ 11
- 0
test/SimpleMacros.cake View File

@ -0,0 +1,11 @@
(c-import "<stdio.h>")
(defmacro my-print (message string)
(printf "Compile-time print!\n")
(tokenize-push output (printf "%s\n" (token-splice message)))
(return true))
(defun main(&return int)
(printf "Hello, world! From Cakelisp!\n")
(my-print "Printed thanks to a macro!")
(return 0))

Loading…
Cancel
Save