diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-06-21 17:39:32 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-06-21 17:39:32 -0400 |
| commit | 714b0881974965e8fcfbc57b452ef648290d14a1 (patch) | |
| tree | f283fb216113a9517686acf49ca436512500b4cb | |
| parent | 64eec046f5a77ebad9564a935c4fad8df08ea6eb (diff) | |
Parsing CPP Compiler output (#994)
* Added extractLine line parsing to StringUtil. Use for matching lines instead of calcLines. calcLines uses extractLine to extract lines.
Fixed problems found in output of some tests- due to how a how final line is handled. Now a final line has a \r or \n\r combination, but nothing else after it, it is considered the last line (not the line after it).
* Use StringUtil::extractLine in slang-generate.
* Improved comment on extractLine
* Remove test code from StringUtil::extractLine
* Made StringUtil::extractLine act as if line terminators are 'separators'.
Added unit-test-string.cpp - to check behavior.
* Adding LineParser - not entirely necessary, but slightly easier to use.
* Hack to output start of tests.
* WIP parsing CPPCompiler output.
* Make extractLine return a bool.
* First attempt at Visual Studio output parsing.
* Add handling for checking error returning from CPPCompiler.
* First pass parsing output of Gcc/Clang.
* Split out VisualStudioCompilerUtil and GCCCompilerUtil.
Simplified parsing of versions.
* Simplify CPPCompiler::Output interface.
* Fix problem with cpp-compiler on linux targets.
* Add shared library link error.
* Improving GCC/Clang parsing output.
* Make cpp compiler parsing function able to return a SlangResult.
* Handling for 'info' on clang
* Add expected result for c-compile-shared-library-error.c
* * Add flags such that link errors on shared libraries are supported.
* Added StringUtil::join
* Turn off the link shared library unfound symbol option on MacOS because it causes an error (and it's not needed on that target).
* Add natvis inclusion back to visual studio projects.
* Display message to try and determine crash problem on travisbuild.
* Fix bug in handling continuations for clang.
Disabled output of exception text.
* WIP: See what clang is outputting that is parsing incorrectly on travis.
* More handling for travis clang parsing issue.
* Restore natvis to core.vcxproj
* Fix visual studio project such that it still as natvis.
22 files changed, 1310 insertions, 368 deletions
diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj index dd2f3c144..9238cf058 100644 --- a/source/core/core.vcxproj +++ b/source/core/core.vcxproj @@ -180,6 +180,7 @@ <ClInclude Include="slang-dictionary.h" /> <ClInclude Include="slang-exception.h" /> <ClInclude Include="slang-free-list.h" /> + <ClInclude Include="slang-gcc-compiler-util.h" /> <ClInclude Include="slang-hash.h" /> <ClInclude Include="slang-io.h" /> <ClInclude Include="slang-list.h" /> @@ -203,6 +204,7 @@ <ClInclude Include="slang-token-reader.h" /> <ClInclude Include="slang-type-traits.h" /> <ClInclude Include="slang-uint-set.h" /> + <ClInclude Include="slang-visual-studio-compiler-util.h" /> <ClInclude Include="slang-writer.h" /> <ClInclude Include="windows\slang-win-visual-studio-util.h" /> </ItemGroup> @@ -210,6 +212,7 @@ <ClCompile Include="slang-byte-encode-util.cpp" /> <ClCompile Include="slang-cpp-compiler.cpp" /> <ClCompile Include="slang-free-list.cpp" /> + <ClCompile Include="slang-gcc-compiler-util.cpp" /> <ClCompile Include="slang-io.cpp" /> <ClCompile Include="slang-memory-arena.cpp" /> <ClCompile Include="slang-object-scope-manager.cpp" /> @@ -226,6 +229,7 @@ <ClCompile Include="slang-text-io.cpp" /> <ClCompile Include="slang-token-reader.cpp" /> <ClCompile Include="slang-uint-set.cpp" /> + <ClCompile Include="slang-visual-studio-compiler-util.cpp" /> <ClCompile Include="slang-writer.cpp" /> <ClCompile Include="windows\slang-win-process-util.cpp" /> <ClCompile Include="windows\slang-win-visual-studio-util.cpp" /> diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters index 8d7add8ea..3b3e429ba 100644 --- a/source/core/core.vcxproj.filters +++ b/source/core/core.vcxproj.filters @@ -39,6 +39,9 @@ <ClInclude Include="slang-free-list.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-gcc-compiler-util.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="slang-hash.h"> <Filter>Header Files</Filter> </ClInclude> @@ -108,6 +111,9 @@ <ClInclude Include="slang-uint-set.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-visual-studio-compiler-util.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="slang-writer.h"> <Filter>Header Files</Filter> </ClInclude> @@ -125,6 +131,9 @@ <ClCompile Include="slang-free-list.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-gcc-compiler-util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="slang-io.cpp"> <Filter>Source Files</Filter> </ClCompile> @@ -173,6 +182,9 @@ <ClCompile Include="slang-uint-set.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-visual-studio-compiler-util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="slang-writer.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/source/core/slang-cpp-compiler.cpp b/source/core/slang-cpp-compiler.cpp index 18cb9cdae..0ed0a1ba8 100644 --- a/source/core/slang-cpp-compiler.cpp +++ b/source/core/slang-cpp-compiler.cpp @@ -8,374 +8,167 @@ #include "slang-io.h" #include "slang-shared-library.h" +// if Visual Studio import the visual studio platform specific header #if SLANG_VC # include "windows/slang-win-visual-studio-util.h" #endif +#include "slang-visual-studio-compiler-util.h" +#include "slang-gcc-compiler-util.h" + namespace Slang { -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! GenericCPPCompiler !!!!!!!!!!!!!!!!!!!!!!*/ +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompiler::OutputMessage !!!!!!!!!!!!!!!!!!!!!!*/ -SlangResult GenericCPPCompiler::compile(const CompileOptions& options, ExecuteResult& outResult) +/* static */UnownedStringSlice CPPCompiler::OutputMessage::getTypeText(OutputMessage::Type type) { - // Copy the command line options - CommandLine cmdLine(m_cmdLine); - - // Append command line args to the end of cmdLine using the target specific function for the specified options - m_func(options, cmdLine); - -#if 0 - // Test - { - String line = ProcessUtil::getCommandLineString(cmdLine); - printf("%s", line.getBuffer()); - } -#endif - - SlangResult res = ProcessUtil::execute(cmdLine, outResult); - -#if 0 + typedef OutputMessage::Type Type; + switch (type) { - printf("stdout=\"%s\"\nstderr=\"%s\"\nret=%d\n", outResult.standardOutput.getBuffer(), outResult.standardError.getBuffer(), int(outResult.resultCode)); + default: return UnownedStringSlice::fromLiteral("Unknown"); + case Type::Info: return UnownedStringSlice::fromLiteral("Info"); + case Type::Warning: return UnownedStringSlice::fromLiteral("Warning"); + case Type::Error: return UnownedStringSlice::fromLiteral("Error"); } -#endif - - return res; } -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompilerUtil !!!!!!!!!!!!!!!!!!!!!!*/ -static bool _isDigit(char c) -{ - return c >= '0' && c <= '9'; -} +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompiler::Output !!!!!!!!!!!!!!!!!!!!!!*/ -static bool _isWhiteSpace(char c) +Index CPPCompiler::Output::getCountByType(OutputMessage::Type type) const { - return c == ' ' || c == '\t' || c == '\n' || c == '\r'; -} - -/* static */SlangResult CPPCompilerUtil::parseGCCFamilyVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, CPPCompiler::Desc& outDesc) -{ - List<UnownedStringSlice> lines; - StringUtil::calcLines(text, lines); - - for (auto line : lines) + Index count = 0; + for (const auto& msg : messages) { - // TODO(JS): Ugh - having to turn into a string to do this test isn't great. - if (String(line).startsWith(prefix)) - { - UnownedStringSlice versionSlice(line.begin() + prefix.size(), line.end()); - - List<Int> digits; - - const char* cur = versionSlice.begin(); - const char* end = versionSlice.end(); - - // Consume white space - while (cur < end && _isWhiteSpace(*cur)) cur++; - - // Version is in format 0.0.0 - while (true) - { - Int value = 0; - const char* start = cur; - while (cur < end && _isDigit(*cur)) - { - value = value * 10 + (*cur - '0'); - cur++; - } - - if (cur <= start) - { - break; - } - - digits.add(value); - - if (cur < end && *cur == '.') - { - cur++; - } - } - - if (digits.getCount() < 2) - { - return SLANG_FAIL; - } - - outDesc.majorVersion = digits[0]; - outDesc.minorVersion = digits[1]; - return SLANG_OK; - } + count += Index(msg.type == type); } - - return SLANG_FAIL; + return count; } -SlangResult CPPCompilerUtil::calcGCCFamilyVersion(const String& exeName, CPPCompiler::Desc& outDesc) +Int CPPCompiler::Output::countByStage(OutputMessage::Stage stage, Index counts[Int(OutputMessage::Type::CountOf)]) const { - CommandLine cmdLine; - cmdLine.setExecutableFilename(exeName); - cmdLine.addArg("-v"); - - ExecuteResult exeRes; - SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes)); - - const UnownedStringSlice prefixes[] = - { - UnownedStringSlice::fromLiteral("clang version"), - UnownedStringSlice::fromLiteral("gcc version"), - UnownedStringSlice::fromLiteral("Apple LLVM version"), - }; - const CPPCompiler::Type types[] = + Int count = 0; + ::memset(counts, 0, sizeof(Index) * Int(OutputMessage::Type::CountOf)); + for (const auto& msg : messages) { - CPPCompiler::Type::Clang, - CPPCompiler::Type::GCC, - CPPCompiler::Type::Clang, - }; - - SLANG_COMPILE_TIME_ASSERT(SLANG_COUNT_OF(prefixes) == SLANG_COUNT_OF(types)); - - for (Index i = 0; i < SLANG_COUNT_OF(prefixes); ++i) - { - // Set the type - outDesc.type = types[i]; - // Extract the version - if (SLANG_SUCCEEDED(parseGCCFamilyVersion(exeRes.standardError.getUnownedSlice(), prefixes[i], outDesc))) + if (msg.stage == stage) { - return SLANG_OK; + count++; + counts[Index(msg.type)]++; } } - return SLANG_FAIL; + return count++; } -/* static */void CPPCompilerUtil::calcVisualStudioArgs(const CompileOptions& options, CommandLine& cmdLine) +static void _appendCounts(const Index counts[Int(CPPCompiler::OutputMessage::Type::CountOf)], StringBuilder& out) { - // https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2019 - - cmdLine.addArg("/nologo"); - // Generate complete debugging information - cmdLine.addArg("/Zi"); - // Display full path of source files in diagnostics - cmdLine.addArg("/FC"); + typedef CPPCompiler::OutputMessage::Type Type; - if (options.flags & CompileOptions::Flag::EnableExceptionHandling) + for (Index i = 0; i < Int(Type::CountOf); i++) { - if (options.sourceType == SourceType::CPP) + if (counts[i] > 0) { - // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019 - // Assumes c functions cannot throw - cmdLine.addArg("/EHsc"); + out << CPPCompiler::OutputMessage::getTypeText(Type(i)) << "(" << counts[i] << ") "; } } +} - switch (options.optimizationLevel) - { - case OptimizationLevel::Debug: - { - // No optimization - cmdLine.addArg("/Od"); - - cmdLine.addArg("/MDd"); - break; - } - case OptimizationLevel::Normal: - { - cmdLine.addArg("/O2"); - // Multithreaded DLL - cmdLine.addArg("/MD"); - break; - } - default: break; - } - - // /Fd - followed by name of the pdb file - if (options.debugInfoType != DebugInfoType::None) - { - cmdLine.addPrefixPathArg("/Fd", options.modulePath, ".pdb"); - } - - switch (options.targetType) +static void _appendSimplified(const Index counts[Int(CPPCompiler::OutputMessage::Type::CountOf)], StringBuilder& out) +{ + typedef CPPCompiler::OutputMessage::Type Type; + for (Index i = 0; i < Int(Type::CountOf); i++) { - case TargetType::SharedLibrary: + if (counts[i] > 0) { - // Create dynamic link library - if (options.optimizationLevel == OptimizationLevel::Debug) - { - cmdLine.addArg("/LDd"); - } - else - { - cmdLine.addArg("/LD"); - } - - cmdLine.addPrefixPathArg("/Fe", options.modulePath, ".dll"); - break; + out << CPPCompiler::OutputMessage::getTypeText(Type(i)) << " "; } - case TargetType::Executable: - { - cmdLine.addPrefixPathArg("/Fe", options.modulePath, ".exe"); - break; - } - default: break; } +} - // Object file specify it's location - needed if we are out - cmdLine.addPrefixPathArg("/Fo", options.modulePath, ".obj"); - - // Add defines - for (const auto& define : options.defines) +void CPPCompiler::Output::appendSummary(StringBuilder& out) const +{ + Index counts[Int(OutputMessage::Type::CountOf)]; + if (countByStage(OutputMessage::Stage::Compile, counts) > 0) { - StringBuilder builder; - builder << define.nameWithSig; - if (define.value.getLength()) - { - builder << "=" << define.value; - } - - cmdLine.addArg(builder); + out << "Compile: "; + _appendCounts(counts, out); + out << "\n"; } - - // Add includes - for (const auto& include : options.includePaths) + if (countByStage(OutputMessage::Stage::Link, counts) > 0) { - cmdLine.addArg("/I"); - cmdLine.addArg(include); + out << "Link: "; + _appendCounts(counts, out); + out << "\n"; } +} - // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019 - // /Eha - Specifies the model of exception handling. (a, s, c, r are options) - - // Files to compile - for (const auto& sourceFile : options.sourceFiles) +void CPPCompiler::Output::appendSimplifiedSummary(StringBuilder& out) const +{ + Index counts[Int(OutputMessage::Type::CountOf)]; + if (countByStage(OutputMessage::Stage::Compile, counts) > 0) { - cmdLine.addArg(sourceFile); + out << "Compile: "; + _appendSimplified(counts, out); + out << "\n"; } - - // Link options (parameters past /link go to linker) - cmdLine.addArg("/link"); - - for (const auto& libPath : options.libraryPaths) + if (countByStage(OutputMessage::Stage::Link, counts) > 0) { - // Note that any escaping of the path is handled in the ProcessUtil:: - cmdLine.addPrefixPathArg("/LIBPATH:", libPath); + out << "Link: "; + _appendSimplified(counts, out); + out << "\n"; } } -/* static */void CPPCompilerUtil::calcGCCFamilyArgs(const CompileOptions& options, CommandLine& cmdLine) +void CPPCompiler::Output::removeByType(OutputMessage::Type type) { - cmdLine.addArg("-fvisibility=hidden"); - // Use shared libraries - //cmdLine.addArg("-shared"); - - switch (options.optimizationLevel) + Index count = messages.getCount(); + for (Index i = 0; i < count; ++i) { - case OptimizationLevel::Debug: - { - // No optimization - cmdLine.addArg("-O0"); - break; - } - case OptimizationLevel::Normal: + if (messages[i].type == type) { - cmdLine.addArg("-Os"); - break; + messages.removeAt(i); + i--; + count--; } - default: break; } +} - if (options.debugInfoType != DebugInfoType::None) - { - cmdLine.addArg("-g"); - } - - switch (options.targetType) - { - case TargetType::SharedLibrary: - { - // Shared library - cmdLine.addArg("-shared"); - // Position independent - cmdLine.addArg("-fPIC"); - - String sharedLibraryPath = SharedLibrary::calcPlatformPath(options.modulePath.getUnownedSlice()); - - cmdLine.addArg("-o"); - cmdLine.addArg(sharedLibraryPath); - break; - } - case TargetType::Executable: - { - cmdLine.addArg("-o"); - - StringBuilder builder; - builder << options.modulePath; - builder << ProcessUtil::getExecutableSuffix(); +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! GenericCPPCompiler !!!!!!!!!!!!!!!!!!!!!!*/ - cmdLine.addArg(options.modulePath); - break; - } - case TargetType::Object: - { - // Don't link, just produce object file - cmdLine.addArg("-c"); - break; - } - default: break; - } +SlangResult GenericCPPCompiler::compile(const CompileOptions& options, Output& outOutput) +{ + outOutput.reset(); - // Add defines - for (const auto& define : options.defines) - { - StringBuilder builder; - builder << define.nameWithSig; - if (define.value.getLength()) - { - builder << "=" << define.value; - } + // Copy the command line options + CommandLine cmdLine(m_cmdLine); - cmdLine.addArg(builder); - } + // Append command line args to the end of cmdLine using the target specific function for the specified options + m_calcArgsFunc(options, cmdLine); - // Add includes - for (const auto& include : options.includePaths) - { - cmdLine.addArg("-I"); - cmdLine.addArg(include); - } + ExecuteResult exeRes; - // Link options - if (0) +#if 0 + // Test { - StringBuilder linkOptions; - linkOptions << "Wl,"; - cmdLine.addArg(linkOptions); + String line = ProcessUtil::getCommandLineString(cmdLine); + printf("%s", line.getBuffer()); } +#endif - // Files to compile - for (const auto& sourceFile : options.sourceFiles) - { - cmdLine.addArg(sourceFile); - } + SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes)); - for (const auto& libPath : options.libraryPaths) +#if 0 { - // Note that any escaping of the path is handled in the ProcessUtil:: - cmdLine.addArg("-L"); - cmdLine.addArg(libPath); - cmdLine.addArg("-F"); - cmdLine.addArg(libPath); + printf("stdout=\"%s\"\nstderr=\"%s\"\nret=%d\n", exeRes.standardOutput.getBuffer(), exeRes.standardError.getBuffer(), int(exeRes.resultCode)); } +#endif - if (options.sourceType == SourceType::CPP) - { - // Make STD libs available - cmdLine.addArg("-lstdc++"); - } + return m_parseOutputFunc(exeRes, outOutput); } +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompilerUtil !!!!!!!!!!!!!!!!!!!!!!*/ + static CPPCompiler::Desc _calcCompiledWithDesc() { CPPCompiler::Desc desc = {}; @@ -383,19 +176,19 @@ static CPPCompiler::Desc _calcCompiledWithDesc() #if SLANG_VC desc = WinVisualStudioUtil::getDesc(WinVisualStudioUtil::getCompiledVersion()); #elif SLANG_CLANG - desc.type = CPPCompiler::Type::Clang; + desc.type = CPPCompiler::CompilerType::Clang; desc.majorVersion = Int(__clang_major__); desc.minorVersion = Int(__clang_minor__); #elif SLANG_SNC - desc.type = CPPCompiler::Type::SNC; + desc.type = CPPCompiler::CompilerType::SNC; #elif SLANG_GHS - desc.type = CPPCompiler::Type::GHS; + desc.type = CPPCompiler::CompilerType::GHS; #elif SLANG_GCC - desc.type = CPPCompiler::Type::GCC; + desc.type = CPPCompiler::CompilerType::GCC; desc.majorVersion = Int(__GNUC__); desc.minorVersion = Int(__GNUC_MINOR__); #else - desc.type = CPPCompiler::Type::Unknown; + desc.type = CPPCompiler::CompilerType::Unknown; #endif return desc; @@ -418,7 +211,7 @@ const CPPCompiler::Desc& CPPCompilerUtil::getCompiledWithDesc() { Int bestIndex = -1; - const CPPCompiler::Type type = desc.type; + const CPPCompiler::CompilerType type = desc.type; Int maxVersionValue = 0; Int minVersionDiff = 0x7fffffff; @@ -488,10 +281,10 @@ const CPPCompiler::Desc& CPPCompilerUtil::getCompiledWithDesc() } // If we are gcc, we can try clang and vice versa - if (desc.type == CPPCompiler::Type::GCC || desc.type == CPPCompiler::Type::Clang) + if (desc.type == CPPCompiler::CompilerType::GCC || desc.type == CPPCompiler::CompilerType::Clang) { CPPCompiler::Desc compatible = desc; - compatible.type = (compatible.type == CPPCompiler::Type::Clang) ? CPPCompiler::Type::GCC : CPPCompiler::Type::Clang; + compatible.type = (compatible.type == CPPCompiler::CompilerType::Clang) ? CPPCompiler::CompilerType::GCC : CPPCompiler::CompilerType::Clang; compiler = findCompiler(compilers, MatchType::MinGreaterEqual, compatible); if (compiler) @@ -513,9 +306,9 @@ const CPPCompiler::Desc& CPPCompilerUtil::getCompiledWithDesc() static void _addGCCFamilyCompiler(const String& exeName, CPPCompilerSet* compilerSet) { CPPCompiler::Desc desc; - if (SLANG_SUCCEEDED(CPPCompilerUtil::calcGCCFamilyVersion(exeName, desc))) + if (SLANG_SUCCEEDED(GCCCompilerUtil::calcVersion(exeName, desc))) { - RefPtr<CPPCompiler> compiler(new GenericCPPCompiler(desc, exeName, &CPPCompilerUtil::calcGCCFamilyArgs)); + RefPtr<CPPCompiler> compiler(new GenericCPPCompiler(desc, exeName, &GCCCompilerUtil::calcArgs, &GCCCompilerUtil::parseOutput)); compilerSet->addCompiler(compiler); } } diff --git a/source/core/slang-cpp-compiler.h b/source/core/slang-cpp-compiler.h index 83c18bca2..60b534556 100644 --- a/source/core/slang-cpp-compiler.h +++ b/source/core/slang-cpp-compiler.h @@ -13,7 +13,7 @@ class CPPCompiler: public RefObject { public: typedef RefObject Super; - enum class Type + enum class CompilerType { Unknown, VisualStudio, @@ -41,9 +41,9 @@ public: Int getVersionValue() const { return majorVersion * 100 + minorVersion; } /// Ctor - Desc(Type inType = Type::Unknown, Int inMajorVersion = 0, Int inMinorVersion = 0):type(inType), majorVersion(inMajorVersion), minorVersion(inMinorVersion) {} + Desc(CompilerType inType = CompilerType::Unknown, Int inMajorVersion = 0, Int inMinorVersion = 0):type(inType), majorVersion(inMajorVersion), minorVersion(inMinorVersion) {} - Type type; ///< The type of the compiler + CompilerType type; ///< The type of the compiler Int majorVersion; ///< Major version (interpretation is type specific) Int minorVersion; ///< Minor version }; @@ -101,10 +101,68 @@ public: List<String> libraryPaths; }; + struct OutputMessage + { + enum class Type + { + Unknown, + Info, + Warning, + Error, + CountOf, + }; + enum class Stage + { + Compile, + Link, + }; + + void reset() + { + type = Type::Unknown; + stage = Stage::Compile; + fileLine = 0; + } + static UnownedStringSlice getTypeText(OutputMessage::Type type); + + + Type type; ///< The type of error + Stage stage; ///< The stage the error came from + String text; ///< The text of the error + String code; ///< The compiler specific error code + String filePath; ///< The path the error originated from + Int fileLine; ///< The line number the error came from + }; + + struct Output + { + /// Reset to an initial empty state + void reset() { messages.clear(); result = SLANG_OK; } + + /// Get the number of messages by type + Index getCountByType(OutputMessage::Type type) const; + /// True if there are any messages of the type + bool has(OutputMessage::Type type) const { return getCountByType(type) > 0; } + + /// Stores in outCounts, the amount of messages for the stage of each type + Int countByStage(OutputMessage::Stage stage, Index outCounts[Int(OutputMessage::Type::CountOf)]) const; + + /// Append a summary to out + void appendSummary(StringBuilder& out) const; + /// Appends a summary that just identifies if there is an error of a type (not a count) + void appendSimplifiedSummary(StringBuilder& out) const; + + /// Remove all messages of the type + void removeByType(OutputMessage::Type type); + + SlangResult result; + List<OutputMessage> messages; + }; + /// Get the desc of this compiler const Desc& getDesc() const { return m_desc; } /// Compile using the specified options. The result is in resOut - virtual SlangResult compile(const CompileOptions& options, ExecuteResult& outResult) = 0; + virtual SlangResult compile(const CompileOptions& options, Output& outOutput) = 0; protected: @@ -121,23 +179,27 @@ public: typedef CPPCompiler Super; typedef void(*CalcArgsFunc)(const CPPCompiler::CompileOptions& options, CommandLine& cmdLine); + typedef SlangResult(*ParseOutputFunc)(const ExecuteResult& exeResult, Output& output); - virtual SlangResult compile(const CompileOptions& options, ExecuteResult& outResult) SLANG_OVERRIDE; + virtual SlangResult compile(const CompileOptions& options, Output& outOutput) SLANG_OVERRIDE; - GenericCPPCompiler(const Desc& desc, const String& exeName, CalcArgsFunc func) : + GenericCPPCompiler(const Desc& desc, const String& exeName, CalcArgsFunc calcArgsFunc, ParseOutputFunc parseOutputFunc) : Super(desc), - m_func(func) + m_calcArgsFunc(calcArgsFunc), + m_parseOutputFunc(parseOutputFunc) { m_cmdLine.setExecutableFilename(exeName); } - GenericCPPCompiler(const Desc& desc, const CommandLine& cmdLine, CalcArgsFunc func) : + GenericCPPCompiler(const Desc& desc, const CommandLine& cmdLine, CalcArgsFunc calcArgsFunc, ParseOutputFunc parseOutputFunc) : Super(desc), m_cmdLine(cmdLine), - m_func(func) + m_calcArgsFunc(calcArgsFunc), + m_parseOutputFunc(parseOutputFunc) {} - CalcArgsFunc m_func; + CalcArgsFunc m_calcArgsFunc; + ParseOutputFunc m_parseOutputFunc; CommandLine m_cmdLine; }; @@ -188,18 +250,6 @@ struct CPPCompilerUtil Newest, }; - /// Extracts version number into desc from text (assumes gcc/clang -v layout with a line with version) - static SlangResult parseGCCFamilyVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, CPPCompiler::Desc& outDesc); - - /// Runs the exeName, and extracts the version info into outDesc - static SlangResult calcGCCFamilyVersion(const String& exeName, CPPCompiler::Desc& outDesc); - - /// Calculate gcc family compilers (including clang) cmdLine arguments from options - static void calcGCCFamilyArgs(const CompileOptions& options, CommandLine& cmdLine); - - /// Calculate Visual Studio family compilers cmdLine arguments from options - static void calcVisualStudioArgs(const CompileOptions& options, CommandLine& cmdLine); - /// Find a compiler static CPPCompiler* findCompiler(const CPPCompilerSet* set, MatchType matchType, const CPPCompiler::Desc& desc); static CPPCompiler* findCompiler(const List<CPPCompiler*>& compilers, MatchType matchType, const CPPCompiler::Desc& desc); diff --git a/source/core/slang-gcc-compiler-util.cpp b/source/core/slang-gcc-compiler-util.cpp new file mode 100644 index 000000000..9f22526a1 --- /dev/null +++ b/source/core/slang-gcc-compiler-util.cpp @@ -0,0 +1,460 @@ +// slang-gcc-compiler-util.cpp +#include "slang-gcc-compiler-util.h" + +#include "slang-common.h" +#include "../../slang-com-helper.h" +#include "slang-string-util.h" + +#include "slang-io.h" +#include "slang-shared-library.h" + +namespace Slang +{ + +/* static */SlangResult GCCCompilerUtil::parseVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, CPPCompiler::Desc& outDesc) +{ + List<UnownedStringSlice> lines; + StringUtil::calcLines(text, lines); + + for (auto line : lines) + { + if (line.startsWith(prefix)) + { + const UnownedStringSlice remainingSlice = UnownedStringSlice(line.begin() + prefix.size(), line.end()).trim(); + const Index versionEndIndex = remainingSlice.indexOf(' '); + if (versionEndIndex < 0) + { + return SLANG_FAIL; + } + + const UnownedStringSlice versionSlice(remainingSlice.begin(), remainingSlice.begin() + versionEndIndex); + + // Version is in format 0.0.0 + List<UnownedStringSlice> split; + StringUtil::split(versionSlice, '.', split); + List<Int> digits; + + for (auto v : split) + { + Int version; + SLANG_RETURN_ON_FAIL(StringUtil::parseInt(v, version)); + digits.add(version); + } + + if (digits.getCount() < 2) + { + return SLANG_FAIL; + } + + outDesc.majorVersion = digits[0]; + outDesc.minorVersion = digits[1]; + return SLANG_OK; + } + } + + return SLANG_FAIL; +} + +SlangResult GCCCompilerUtil::calcVersion(const String& exeName, CPPCompiler::Desc& outDesc) +{ + CommandLine cmdLine; + cmdLine.setExecutableFilename(exeName); + cmdLine.addArg("-v"); + + ExecuteResult exeRes; + SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes)); + + const UnownedStringSlice prefixes[] = + { + UnownedStringSlice::fromLiteral("clang version"), + UnownedStringSlice::fromLiteral("gcc version"), + UnownedStringSlice::fromLiteral("Apple LLVM version"), + }; + const CPPCompiler::CompilerType types[] = + { + CPPCompiler::CompilerType::Clang, + CPPCompiler::CompilerType::GCC, + CPPCompiler::CompilerType::Clang, + }; + + SLANG_COMPILE_TIME_ASSERT(SLANG_COUNT_OF(prefixes) == SLANG_COUNT_OF(types)); + + for (Index i = 0; i < SLANG_COUNT_OF(prefixes); ++i) + { + // Set the type + outDesc.type = types[i]; + // Extract the version + if (SLANG_SUCCEEDED(parseVersion(exeRes.standardError.getUnownedSlice(), prefixes[i], outDesc))) + { + return SLANG_OK; + } + } + return SLANG_FAIL; +} + +static SlangResult _parseErrorType(const UnownedStringSlice& in, CPPCompiler::OutputMessage::Type& outType) +{ + typedef CPPCompiler::OutputMessage::Type Type; + + if (in == "error" || in == "fatal error") + { + outType = Type::Error; + } + else if (in == "warning") + { + outType = Type::Warning; + } + else if (in == "info" || in == "note") + { + outType = Type::Info; + } + else + { + return SLANG_FAIL; + } + return SLANG_OK; +} + +namespace { // anonymous + +enum class LineParseResult +{ + Single, ///< It's a single line + Start, ///< Line was the start of a message + Continuation, ///< Not totally clear, add to previous line if nothing else hit + Ignore, ///< Ignore the line +}; + +} // anonymous + +static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParseResult& outLineParseResult, CPPCompiler::OutputMessage& outMsg) +{ + typedef CPPCompiler::OutputMessage OutputMessage; + typedef OutputMessage::Type Type; + + // Set to default case + outLineParseResult = LineParseResult::Ignore; + + /* example error output from different scenarios */ + + /* + tests/cpp-compiler/c-compile-error.c: In function ‘int main(int, char**)’: + tests/cpp-compiler/c-compile-error.c:8:13: error: ‘b’ was not declared in this scope + int a = b + c; + ^ + tests/cpp-compiler/c-compile-error.c:8:17: error: ‘c’ was not declared in this scope + int a = b + c; + ^ + */ + + /* /tmp/ccS0JCWe.o:c-compile-link-error.c:(.rdata$.refptr.thing[.refptr.thing]+0x0): undefined reference to `thing' + collect2: error: ld returned 1 exit status*/ + + /* + clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated] + Undefined symbols for architecture x86_64: + "_thing", referenced from: + _main in c-compile-link-error-a83ace.o + ld: symbol(s) not found for architecture x86_64 + clang: error: linker command failed with exit code 1 (use -v to see invocation) */ + + /* /tmp/c-compile-link-error-ccf151.o: In function `main': + c-compile-link-error.c:(.text+0x19): undefined reference to `thing' + clang: error: linker command failed with exit code 1 (use -v to see invocation) + */ + + /* /tmp/c-compile-link-error-301c8c.o: In function `main': + /home/travis/build/shader-slang/slang/tests/cpp-compiler/c-compile-link-error.c:10: undefined reference to `thing' + clang-7: error: linker command failed with exit code 1 (use -v to see invocation)*/ + + outMsg.stage = OutputMessage::Stage::Compile; + + List<UnownedStringSlice> split; + StringUtil::split(line, ':', split); + + if (split.getCount() == 2) + { + const auto split0 = split[0].trim(); + if (split0 == UnownedStringSlice::fromLiteral("ld")) + { + // We'll ignore for now + outMsg.stage = OutputMessage::Stage::Link; + outMsg.type = Type::Info; + outMsg.text = split[1].trim(); + outLineParseResult = LineParseResult::Start; + return SLANG_OK; + } + outLineParseResult = LineParseResult::Ignore; + return SLANG_OK; + } + else if (split.getCount() == 3) + { + const auto split0 = split[0].trim(); + const auto split1 = split[1].trim(); + const auto text = split[2].trim(); + + // Check for special handling for clang + if (split0.startsWith(UnownedStringSlice::fromLiteral("clang"))) + { + // Extract the type + SLANG_RETURN_ON_FAIL(_parseErrorType(split[1].trim(), outMsg.type)); + + if (text.startsWith("linker command failed")) + { + outMsg.stage = OutputMessage::Stage::Link; + } + + outMsg.text = text; + outLineParseResult = LineParseResult::Start; + return SLANG_OK; + } + else if (split1.startsWith("(.text")) + { + // This is a little weak... but looks like it's a link error + outMsg.filePath = split[0]; + outMsg.type = Type::Error; + outMsg.stage = OutputMessage::Stage::Link; + outMsg.text = text; + outLineParseResult = LineParseResult::Single; + return SLANG_OK; + } + else if (text.startsWith("ld returned")) + { + outMsg.stage = CPPCompiler::OutputMessage::Stage::Link; + SLANG_RETURN_ON_FAIL(_parseErrorType(split[1].trim(), outMsg.type)); + outMsg.text = line; + outLineParseResult = LineParseResult::Single; + return SLANG_OK; + } + else if (text == "") + { + // This is probably a prelude line, we'll just ignore it + outLineParseResult = LineParseResult::Ignore; + return SLANG_OK; + } + } + else if (split.getCount() == 4) + { + // Probably a link error, give the source line + String ext = Path::getFileExt(split[0]); + + // Maybe a bit fragile -> but probably okay for now + if (ext != "o" && ext != "obj") + { + outLineParseResult = LineParseResult::Ignore; + return SLANG_OK; + } + else + { + outMsg.filePath = split[1]; + outMsg.fileLine = 0; + outMsg.type = OutputMessage::Type::Error; + outMsg.stage = OutputMessage::Stage::Link; + outMsg.text = split[3]; + + outLineParseResult = LineParseResult::Start; + return SLANG_OK; + } + } + else if (split.getCount() == 5) + { + // Probably a regular error line + SLANG_RETURN_ON_FAIL(_parseErrorType(split[3].trim(), outMsg.type)); + + outMsg.filePath = split[0]; + SLANG_RETURN_ON_FAIL(StringUtil::parseInt(split[1], outMsg.fileLine)); + outMsg.text = split[4]; + + outLineParseResult = LineParseResult::Start; + return SLANG_OK; + } + + // Assume it's a continuation + outLineParseResult = LineParseResult::Continuation; + return SLANG_OK; +} + +/* static */SlangResult GCCCompilerUtil::parseOutput(const ExecuteResult& exeRes, CPPCompiler::Output& outOutput) +{ + LineParseResult prevLineResult = LineParseResult::Ignore; + + outOutput.reset(); + + for (auto line : LineParser(exeRes.standardError.getUnownedSlice())) + { + CPPCompiler::OutputMessage msg; + LineParseResult lineRes; + + SLANG_RETURN_ON_FAIL(_parseGCCFamilyLine(line, lineRes, msg)); + + switch (lineRes) + { + case LineParseResult::Start: + { + // It's start of a new message + outOutput.messages.add(msg); + prevLineResult = LineParseResult::Start; + break; + } + case LineParseResult::Single: + { + // It's a single message, without anything following + outOutput.messages.add(msg); + prevLineResult = LineParseResult::Ignore; + break; + } + case LineParseResult::Continuation: + { + if (prevLineResult == LineParseResult::Start || prevLineResult == LineParseResult::Continuation) + { + if (outOutput.messages.getCount() > 0) + { + // We are now in a continuation, add to the last + auto& text = outOutput.messages.getLast().text; + text.append("\n"); + text.append(line); + } + prevLineResult = LineParseResult::Continuation; + } + break; + } + case LineParseResult::Ignore: + { + prevLineResult = lineRes; + break; + } + default: return SLANG_FAIL; + } + } + + if (outOutput.has(CPPCompiler::OutputMessage::Type::Error) || exeRes.resultCode != 0) + { + outOutput.result = SLANG_FAIL; + } + + return SLANG_OK; +} + +/* static */void GCCCompilerUtil::calcArgs(const CompileOptions& options, CommandLine& cmdLine) +{ + cmdLine.addArg("-fvisibility=hidden"); + // Use shared libraries + //cmdLine.addArg("-shared"); + + switch (options.optimizationLevel) + { + case OptimizationLevel::Debug: + { + // No optimization + cmdLine.addArg("-O0"); + break; + } + case OptimizationLevel::Normal: + { + cmdLine.addArg("-Os"); + break; + } + default: break; + } + + if (options.debugInfoType != DebugInfoType::None) + { + cmdLine.addArg("-g"); + } + + switch (options.targetType) + { + case TargetType::SharedLibrary: + { + // Shared library + cmdLine.addArg("-shared"); + // Position independent + cmdLine.addArg("-fPIC"); + + String sharedLibraryPath = SharedLibrary::calcPlatformPath(options.modulePath.getUnownedSlice()); + + cmdLine.addArg("-o"); + cmdLine.addArg(sharedLibraryPath); + break; + } + case TargetType::Executable: + { + cmdLine.addArg("-o"); + + StringBuilder builder; + builder << options.modulePath; + builder << ProcessUtil::getExecutableSuffix(); + + cmdLine.addArg(options.modulePath); + break; + } + case TargetType::Object: + { + // Don't link, just produce object file + cmdLine.addArg("-c"); + break; + } + default: break; + } + + // Add defines + for (const auto& define : options.defines) + { + StringBuilder builder; + builder << define.nameWithSig; + if (define.value.getLength()) + { + builder << "=" << define.value; + } + + cmdLine.addArg(builder); + } + + // Add includes + for (const auto& include : options.includePaths) + { + cmdLine.addArg("-I"); + cmdLine.addArg(include); + } + + // Link options + if (0) // && options.targetType != TargetType::Object) + { + //linkOptions << "-Wl,"; + //cmdLine.addArg(linkOptions); + } + + if (options.targetType == TargetType::SharedLibrary) + { +#if !SLANG_APPLE_FAMILY + // On MacOS, this linker option is not supported. That's ok though in + // so far as on MacOS it does report any unfound symbols without the option. + + // Linker flag to report any undefined symbols as a link error + cmdLine.addArg("-Wl,--no-undefined"); +#endif + } + + // Files to compile + for (const auto& sourceFile : options.sourceFiles) + { + cmdLine.addArg(sourceFile); + } + + for (const auto& libPath : options.libraryPaths) + { + // Note that any escaping of the path is handled in the ProcessUtil:: + cmdLine.addArg("-L"); + cmdLine.addArg(libPath); + cmdLine.addArg("-F"); + cmdLine.addArg(libPath); + } + + if (options.sourceType == SourceType::CPP) + { + // Make STD libs available + cmdLine.addArg("-lstdc++"); + } +} + +} diff --git a/source/core/slang-gcc-compiler-util.h b/source/core/slang-gcc-compiler-util.h new file mode 100644 index 000000000..29070fc61 --- /dev/null +++ b/source/core/slang-gcc-compiler-util.h @@ -0,0 +1,33 @@ +#ifndef SLANG_GCC_COMPILER_UTIL_H +#define SLANG_GCC_COMPILER_UTIL_H + +#include "slang-cpp-compiler.h" + +namespace Slang +{ + +/* Utility for processing input and output of gcc-like compilers, including clang */ +struct GCCCompilerUtil +{ + typedef CPPCompiler::CompileOptions CompileOptions; + typedef CPPCompiler::OptimizationLevel OptimizationLevel; + typedef CPPCompiler::TargetType TargetType; + typedef CPPCompiler::DebugInfoType DebugInfoType; + typedef CPPCompiler::SourceType SourceType; + + /// Extracts version number into desc from text (assumes gcc/clang -v layout with a line with version) + static SlangResult parseVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, CPPCompiler::Desc& outDesc); + + /// Runs the exeName, and extracts the version info into outDesc + static SlangResult calcVersion(const String& exeName, CPPCompiler::Desc& outDesc); + + /// Calculate gcc family compilers (including clang) cmdLine arguments from options + static void calcArgs(const CompileOptions& options, CommandLine& cmdLine); + + /// Parse ExecuteResult into Output + static SlangResult parseOutput(const ExecuteResult& exeRes, CPPCompiler::Output& outOutput); +}; + +} + +#endif diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index 95caf8319..4295a2c52 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -39,6 +39,45 @@ static const Guid IID_ISlangBlob = SLANG_UUID_ISlangBlob; } } +/* static */void StringUtil::join(const List<String>& values, char separator, StringBuilder& out) +{ + join(values, UnownedStringSlice(&separator, 1), out); +} + +/* static */void StringUtil::join(const List<String>& values, const UnownedStringSlice& separator, StringBuilder& out) +{ + const Index count = values.getCount(); + if (count <= 0) + { + return; + } + out.append(values[0]); + for (Index i = 1; i < count; i++) + { + out.append(separator); + out.append(values[i]); + } +} + +/* static */void StringUtil::join(const UnownedStringSlice* values, Index valueCount, char separator, StringBuilder& out) +{ + join(values, valueCount, UnownedStringSlice(&separator, 1), out); +} + +/* static */void StringUtil::join(const UnownedStringSlice* values, Index valueCount, const UnownedStringSlice& separator, StringBuilder& out) +{ + if (valueCount <= 0) + { + return; + } + out.append(values[0]); + for (Index i = 1; i < valueCount; i++) + { + out.append(separator); + out.append(values[i]); + } +} + /* static */int StringUtil::indexOfInSplit(const UnownedStringSlice& in, char splitChar, const UnownedStringSlice& find) { const char* start = in.begin(); @@ -285,4 +324,45 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string) } } +SLANG_FORCE_INLINE static bool _isDigit(char c) +{ + return (c >= '0' && c <= '9'); +} + +/* static */SlangResult StringUtil::parseInt(const UnownedStringSlice& in, Int& outValue) +{ + const char* cur = in.begin(); + const char* end = in.end(); + + bool negate = false; + if (cur < end && *cur == '-') + { + negate = true; + cur++; + } + + // We need at least one digit + if (cur >= end || !_isDigit(*cur)) + { + return SLANG_FAIL; + } + + Int value = *cur++ - '0'; + // Do the remaining digits + for (; cur < end; ++cur) + { + const char c = *cur; + if (!_isDigit(c)) + { + return SLANG_FAIL; + } + value = value * 10 + (c - '0'); + } + + value = negate ? -value : value; + + outValue = value; + return SLANG_OK; +} + } // namespace Slang diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index 74ec75dc6..77b7ca2a3 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -41,6 +41,13 @@ struct StringUtil /// Slices contents will directly address into in, so contents will only stay valid as long as in does. static void split(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& slicesOut); + /// Append the joining of in items, separated by 'separator' onto out + static void join(const List<String>& in, char separator, StringBuilder& out); + static void join(const List<String>& in, const UnownedStringSlice& separator, StringBuilder& out); + + static void join(const UnownedStringSlice* values, Index valueCount, char separator, StringBuilder& out); + static void join(const UnownedStringSlice* values, Index valueCount, const UnownedStringSlice& separator, StringBuilder& out); + /// Equivalent to doing a split and then finding the index of 'find' on the array /// Returns -1 if not found static int indexOfInSplit(const UnownedStringSlice& in, char splitChar, const UnownedStringSlice& find); @@ -94,6 +101,9 @@ struct StringUtil /// Equal if the lines are equal (in effect a way to ignore differences in line breaks) static bool areLinesEqual(const UnownedStringSlice& a, const UnownedStringSlice& b); + + /// Convert in to int. Returns SLANG_FAIL on error + static SlangResult parseInt(const UnownedStringSlice& in, Int& outValue); }; /* A helper class that allows parsing of lines from text with iteration. Uses StringUtil::extractLine for the actual underlying implementation. */ diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp index 54703def1..8b2a35b3f 100644 --- a/source/core/slang-string.cpp +++ b/source/core/slang-string.cpp @@ -7,9 +7,15 @@ namespace Slang SLANG_RETURN_NEVER void signalUnexpectedError(char const* message) { + // Can be useful to uncomment during debug when problem is on CI + // printf("Unexpected: %s\n", message); throw InternalError(message); } + SLANG_FORCE_INLINE static bool _isWhiteSpace(char c) + { + return c == ' ' || c == '\t'; + } // OSString @@ -100,6 +106,17 @@ namespace Slang return endsWith(UnownedTerminatedStringSlice(str)); } + + UnownedStringSlice UnownedStringSlice::trim() const + { + const char* start = m_begin; + const char* end = m_end; + + while (start < end && _isWhiteSpace(*start)) start++; + while (end > start && _isWhiteSpace(end[-1])) end--; + return UnownedStringSlice(start, end); + } + // StringSlice diff --git a/source/core/slang-string.h b/source/core/slang-string.h index 560d137db..cb2731cee 100644 --- a/source/core/slang-string.h +++ b/source/core/slang-string.h @@ -98,10 +98,22 @@ namespace Slang return Index(m_end - m_begin); } - int indexOf(char c) const + Index indexOf(char c) const { - const int size = int(m_end - m_begin); - for (int i = 0; i < size; ++i) + const Index size = int(m_end - m_begin); + for (Index i = 0; i < size; ++i) + { + if (m_begin[i] == c) + { + return i; + } + } + return -1; + } + Index lastIndexOf(char c) const + { + const Index size = Index(m_end - m_begin); + for (Index i = size - 1; i >= 0; --i) { if (m_begin[i] == c) { @@ -156,6 +168,9 @@ namespace Slang bool endsWith(UnownedStringSlice const& other) const; bool endsWith(char const* str) const; + + UnownedStringSlice trim() const; + int GetHashCode() const { return Slang::GetHashCode(m_begin, size_t(m_end - m_begin)); diff --git a/source/core/slang-visual-studio-compiler-util.cpp b/source/core/slang-visual-studio-compiler-util.cpp new file mode 100644 index 000000000..20d655e78 --- /dev/null +++ b/source/core/slang-visual-studio-compiler-util.cpp @@ -0,0 +1,300 @@ +// slang-visual-studio-compiler-util.cpp +#include "slang-visual-studio-compiler-util.h" + +#include "slang-common.h" +#include "../../slang-com-helper.h" +#include "slang-string-util.h" + +#include "slang-io.h" + +namespace Slang +{ + +/* static */void VisualStudioCompilerUtil::calcArgs(const CompileOptions& options, CommandLine& cmdLine) +{ + // https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2019 + + cmdLine.addArg("/nologo"); + // Generate complete debugging information + cmdLine.addArg("/Zi"); + // Display full path of source files in diagnostics + cmdLine.addArg("/FC"); + + if (options.flags & CompileOptions::Flag::EnableExceptionHandling) + { + if (options.sourceType == SourceType::CPP) + { + // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019 + // Assumes c functions cannot throw + cmdLine.addArg("/EHsc"); + } + } + + switch (options.optimizationLevel) + { + case OptimizationLevel::Debug: + { + // No optimization + cmdLine.addArg("/Od"); + + cmdLine.addArg("/MDd"); + break; + } + case OptimizationLevel::Normal: + { + cmdLine.addArg("/O2"); + // Multithreaded DLL + cmdLine.addArg("/MD"); + break; + } + default: break; + } + + // /Fd - followed by name of the pdb file + if (options.debugInfoType != DebugInfoType::None) + { + cmdLine.addPrefixPathArg("/Fd", options.modulePath, ".pdb"); + } + + switch (options.targetType) + { + case TargetType::SharedLibrary: + { + // Create dynamic link library + if (options.optimizationLevel == OptimizationLevel::Debug) + { + cmdLine.addArg("/LDd"); + } + else + { + cmdLine.addArg("/LD"); + } + + cmdLine.addPrefixPathArg("/Fe", options.modulePath, ".dll"); + break; + } + case TargetType::Executable: + { + cmdLine.addPrefixPathArg("/Fe", options.modulePath, ".exe"); + break; + } + default: break; + } + + // Object file specify it's location - needed if we are out + cmdLine.addPrefixPathArg("/Fo", options.modulePath, ".obj"); + + // Add defines + for (const auto& define : options.defines) + { + StringBuilder builder; + builder << define.nameWithSig; + if (define.value.getLength()) + { + builder << "=" << define.value; + } + + cmdLine.addArg(builder); + } + + // Add includes + for (const auto& include : options.includePaths) + { + cmdLine.addArg("/I"); + cmdLine.addArg(include); + } + + // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019 + // /Eha - Specifies the model of exception handling. (a, s, c, r are options) + + // Files to compile + for (const auto& sourceFile : options.sourceFiles) + { + cmdLine.addArg(sourceFile); + } + + // Link options (parameters past /link go to linker) + cmdLine.addArg("/link"); + + for (const auto& libPath : options.libraryPaths) + { + // Note that any escaping of the path is handled in the ProcessUtil:: + cmdLine.addPrefixPathArg("/LIBPATH:", libPath); + } +} + +static SlangResult _parseErrorType(const UnownedStringSlice& in, CPPCompiler::OutputMessage::Type& outType) +{ + typedef CPPCompiler::OutputMessage::Type Type; + + if (in == "error" || in == "fatal error") + { + outType = Type::Error; + } + else if (in == "warning") + { + outType = Type::Warning; + } + else if (in == "info") + { + outType = Type::Info; + } + else + { + return SLANG_FAIL; + } + return SLANG_OK; +} + +static SlangResult _parseVisualStudioLine(const UnownedStringSlice& line, CPPCompiler::OutputMessage& outMsg) +{ + typedef CPPCompiler::OutputMessage OutputMessage; + + UnownedStringSlice linkPrefix = UnownedStringSlice::fromLiteral("LINK :"); + if (line.startsWith(linkPrefix)) + { + outMsg.stage = OutputMessage::Stage::Link; + outMsg.type = OutputMessage::Type::Info; + + outMsg.text = UnownedStringSlice(line.begin() + linkPrefix.size(), line.end()); + + return SLANG_OK; + } + + outMsg.stage = OutputMessage::Stage::Compile; + + const char*const start = line.begin(); + const char*const end = line.end(); + + UnownedStringSlice postPath; + // Handle the path and line no + { + const char* cur = start; + + // We have to assume it is a path up to the first : that isn't part of a drive specification + + if ((end - cur > 2) && Path::isDriveSpecification(UnownedStringSlice(start, start + 2))) + { + // Skip drive spec + cur += 2; + } + + // Find the first colon after this + Index colonIndex = UnownedStringSlice(cur, end).indexOf(':'); + if (colonIndex < 0) + { + return SLANG_FAIL; + } + + // Looks like we have a line number + if (cur[colonIndex - 1] == ')') + { + const char* lineNoEnd = cur + colonIndex - 1; + const char* lineNoStart = lineNoEnd; + while (lineNoStart > start && *lineNoStart != '(') + { + lineNoStart--; + } + // Check this appears plausible + if (*lineNoStart != '(' || *lineNoEnd != ')') + { + return SLANG_FAIL; + } + Int numDigits = 0; + Int lineNo = 0; + for (const char* digitCur = lineNoStart + 1; digitCur < lineNoEnd; ++digitCur) + { + char c = *digitCur; + if (c >= '0' && c <= '9') + { + lineNo = lineNo * 10 + (c - '0'); + numDigits++; + } + else + { + return SLANG_FAIL; + } + } + if (numDigits == 0) + { + return SLANG_FAIL; + } + + outMsg.filePath = UnownedStringSlice(start, lineNoStart); + outMsg.fileLine = lineNo; + } + else + { + outMsg.filePath = UnownedStringSlice(start, cur + colonIndex); + outMsg.fileLine = 0; + } + + // Save the remaining text in 'postPath' + postPath = UnownedStringSlice(cur + colonIndex + 1, end); + } + + // Split up the error section + UnownedStringSlice postError; + { + // tests/cpp-compiler/c-compile-link-error.exe : fatal error LNK1120: 1 unresolved externals + + const Index errorColonIndex = postPath.indexOf(':'); + if (errorColonIndex < 0) + { + return SLANG_FAIL; + } + + const UnownedStringSlice errorSection = UnownedStringSlice(postPath.begin(), postPath.begin() + errorColonIndex); + Index errorCodeIndex = errorSection.lastIndexOf(' '); + if (errorCodeIndex < 0) + { + return SLANG_FAIL; + } + + // Extract the code + outMsg.code = UnownedStringSlice(errorSection.begin() + errorCodeIndex + 1, errorSection.end()); + if (outMsg.code.startsWith(UnownedStringSlice::fromLiteral("LNK"))) + { + outMsg.stage = OutputMessage::Stage::Link; + } + + // Extract the bit before the code + SLANG_RETURN_ON_FAIL(_parseErrorType(UnownedStringSlice(errorSection.begin(), errorSection.begin() + errorCodeIndex).trim(), outMsg.type)); + + // Link codes start with LNK prefix + postError = UnownedStringSlice(postPath.begin() + errorColonIndex + 1, end); + } + + outMsg.text = postError; + + return SLANG_OK; +} + +/* static */SlangResult VisualStudioCompilerUtil::parseOutput(const ExecuteResult& exeRes, CPPCompiler::Output& outOutput) +{ + outOutput.reset(); + + for (auto line : LineParser(exeRes.standardOutput.getUnownedSlice())) + { +#if 0 + fwrite(line.begin(), 1, line.size(), stdout); + fprintf(stdout, "\n"); +#endif + + OutputMessage msg; + if (SLANG_SUCCEEDED(_parseVisualStudioLine(line, msg))) + { + outOutput.messages.add(msg); + } + } + + // if it has a compilation error.. set on output + if (outOutput.has(OutputMessage::Type::Error)) + { + outOutput.result = SLANG_FAIL; + } + + return SLANG_OK; +} + +} diff --git a/source/core/slang-visual-studio-compiler-util.h b/source/core/slang-visual-studio-compiler-util.h new file mode 100644 index 000000000..86845b5af --- /dev/null +++ b/source/core/slang-visual-studio-compiler-util.h @@ -0,0 +1,26 @@ +#ifndef SLANG_VISUAL_STUDIO_COMPILER_UTIL_H +#define SLANG_VISUAL_STUDIO_COMPILER_UTIL_H + +#include "slang-cpp-compiler.h" + +namespace Slang +{ + +struct VisualStudioCompilerUtil +{ + typedef CPPCompiler::CompileOptions CompileOptions; + typedef CPPCompiler::OptimizationLevel OptimizationLevel; + typedef CPPCompiler::TargetType TargetType; + typedef CPPCompiler::DebugInfoType DebugInfoType; + typedef CPPCompiler::SourceType SourceType; + typedef CPPCompiler::OutputMessage OutputMessage; + + /// Calculate Visual Studio family compilers cmdLine arguments from options + static void calcArgs(const CompileOptions& options, CommandLine& cmdLine); + /// Parse Visual Studio exeRes into CPPCompiler::Output + static SlangResult parseOutput(const ExecuteResult& exeRes, CPPCompiler::Output& outOutput); +}; + +} + +#endif diff --git a/source/core/windows/slang-win-visual-studio-util.cpp b/source/core/windows/slang-win-visual-studio-util.cpp index 81d77c710..7e683ee55 100644 --- a/source/core/windows/slang-win-visual-studio-util.cpp +++ b/source/core/windows/slang-win-visual-studio-util.cpp @@ -4,6 +4,8 @@ #include "../slang-process-util.h" #include "../slang-string-util.h" +#include "../slang-visual-studio-compiler-util.h" + #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # define NOMINMAX @@ -291,7 +293,7 @@ static SlangResult _find(int versionIndex, WinVisualStudioUtil::VersionPath& out CommandLine cmdLine; calcExecuteCompilerArgs(versionPath, cmdLine); - RefPtr<GenericCPPCompiler> compiler = new GenericCPPCompiler(desc, cmdLine, &CPPCompilerUtil::calcVisualStudioArgs); + RefPtr<GenericCPPCompiler> compiler = new GenericCPPCompiler(desc, cmdLine, &VisualStudioCompilerUtil::calcArgs, &VisualStudioCompilerUtil::parseOutput); set->addCompiler(compiler); } } diff --git a/source/core/windows/slang-win-visual-studio-util.h b/source/core/windows/slang-win-visual-studio-util.h index 76e1cc710..34be8473d 100644 --- a/source/core/windows/slang-win-visual-studio-util.h +++ b/source/core/windows/slang-win-visual-studio-util.h @@ -51,14 +51,11 @@ struct WinVisualStudioUtil /// Convert a version number into a string static void append(Version version, StringBuilder& outBuilder); - /// Calculate the command line args - static void calcArgs(const CPPCompiler::CompileOptions& options, CommandLine& cmdLine); - /// Get version as desc static CPPCompiler::Desc getDesc(Version version) { CPPCompiler::Desc desc; - desc.type = CPPCompiler::Type::VisualStudio; + desc.type = CPPCompiler::CompilerType::VisualStudio; desc.majorVersion = Int(version) / 10; desc.minorVersion = Int(version) % 10; return desc; diff --git a/tests/cpp-compiler/c-compile-error.c b/tests/cpp-compiler/c-compile-error.c new file mode 100644 index 000000000..ae36dd9c5 --- /dev/null +++ b/tests/cpp-compiler/c-compile-error.c @@ -0,0 +1,12 @@ +//TEST(smoke):CPP_COMPILER_EXECUTE: + +#include <stdlib.h> +#include <stdio.h> + +int main(int argc, char** argv) +{ + int a = b + c; + + printf("Hello World!\n"); + return 0; +} diff --git a/tests/cpp-compiler/c-compile-error.c.expected b/tests/cpp-compiler/c-compile-error.c.expected new file mode 100644 index 000000000..09646b013 --- /dev/null +++ b/tests/cpp-compiler/c-compile-error.c.expected @@ -0,0 +1 @@ +Compile: Error diff --git a/tests/cpp-compiler/c-compile-link-error.c b/tests/cpp-compiler/c-compile-link-error.c new file mode 100644 index 000000000..08b93a38b --- /dev/null +++ b/tests/cpp-compiler/c-compile-link-error.c @@ -0,0 +1,12 @@ +//TEST(smoke):CPP_COMPILER_EXECUTE: + +#include <stdlib.h> +#include <stdio.h> + +extern int thing; + +int main(int argc, char** argv) +{ + printf("Hello World %d!\n", thing); + return 0; +} diff --git a/tests/cpp-compiler/c-compile-link-error.c.expected b/tests/cpp-compiler/c-compile-link-error.c.expected new file mode 100644 index 000000000..593b17b75 --- /dev/null +++ b/tests/cpp-compiler/c-compile-link-error.c.expected @@ -0,0 +1 @@ +Link: Error diff --git a/tests/cpp-compiler/c-compile-shared-library-link-error.c b/tests/cpp-compiler/c-compile-shared-library-link-error.c new file mode 100644 index 000000000..1339a3b52 --- /dev/null +++ b/tests/cpp-compiler/c-compile-shared-library-link-error.c @@ -0,0 +1,27 @@ +//TEST(smoke):CPP_COMPILER_SHARED_LIBRARY: + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#if defined(_MSC_VER) +# define DLL_EXPORT __declspec(dllexport) +#else +//# define DLL_EXPORT +# define DLL_EXPORT __attribute__ ((dllexport)) __attribute__((__visibility__("default"))) +#endif + +#ifdef __cplusplus +#define EXTERN_C extern "C" +#else +#define EXTERN_C +#endif + +extern int symbolNotFound; + +EXTERN_C DLL_EXPORT int test(int intValue, const char* textValue, char* outTextValue) +{ + strcpy(outTextValue, textValue); + return intValue + symbolNotFound; +} + diff --git a/tests/cpp-compiler/c-compile-shared-library-link-error.c.expected b/tests/cpp-compiler/c-compile-shared-library-link-error.c.expected new file mode 100644 index 000000000..593b17b75 --- /dev/null +++ b/tests/cpp-compiler/c-compile-shared-library-link-error.c.expected @@ -0,0 +1 @@ +Link: Error diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index 6eae194e6..0543dd067 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -1086,6 +1086,20 @@ String getExpectedOutput(String const& outputStem) return expectedOutput; } +static String _calcSummary(const CPPCompiler::Output& inOutput) +{ + CPPCompiler::Output output(inOutput); + + // We only want to analyse errors for now + output.removeByType(CPPCompiler::OutputMessage::Type::Info); + output.removeByType(CPPCompiler::OutputMessage::Type::Warning); + + StringBuilder builder; + + output.appendSimplifiedSummary(builder); + return builder; +} + static TestResult runCPPCompilerExecute(TestContext* context, TestInput& input) { CPPCompilerSet* compilerSet = context->getCPPCompilerSet(); @@ -1131,15 +1145,23 @@ static TestResult runCPPCompilerExecute(TestContext* context, TestInput& input) options.sourceFiles.add(filePath); options.modulePath = modulePath; - ExecuteResult exeRes; - - if (SLANG_FAILED(compiler->compile(options, exeRes))) + CPPCompiler::Output output; + if (SLANG_FAILED(compiler->compile(options, output))) { return TestResult::Fail; } - // Execute the binary and see what we get + String actualOutput; + + // If the actual compilation failed, then the output will be + if (SLANG_FAILED(output.result)) + { + actualOutput = _calcSummary(output); + } + else { + // Execute the binary and see what we get + CommandLine cmdLine; StringBuilder exePath; @@ -1154,9 +1176,14 @@ static TestResult runCPPCompilerExecute(TestContext* context, TestInput& input) } // Write the output, and compare to expected - String actualOutput = getOutput(exeRes); - Slang::File::writeAllText(actualOutputPath, actualOutput); + actualOutput = getOutput(exeRes); + } + + // Write the output + Slang::File::writeAllText(actualOutputPath, actualOutput); + // Check that they are the same + { // Read the expected String expectedOutput; try @@ -1226,43 +1253,74 @@ static TestResult runCPPCompilerSharedLibrary(TestContext* context, TestInput& i options.includePaths.add("."); - ExecuteResult exeRes; - - if (SLANG_FAILED(compiler->compile(options, exeRes))) + CPPCompiler::Output output; + if (SLANG_FAILED(compiler->compile(options, output))) { return TestResult::Fail; } - SharedLibrary::Handle handle; - if (SLANG_FAILED(SharedLibrary::loadWithPlatformPath(sharedLibraryPath.getBuffer(), handle))) + if (SLANG_FAILED(output.result)) { - return TestResult::Fail; - } - - const int inValue = 10; - const char inBuffer[] = "Hello World!"; + // Compilation failed + String actualOutput = _calcSummary(output); - char buffer[128] = ""; - int value = 0; + // Write the output + Slang::File::writeAllText(actualOutputPath, actualOutput); - typedef int (*TestFunc)(int intValue, const char* textValue, char* outTextValue); + // Check that they are the same + { + // Read the expected + String expectedOutput; + try + { + String expectedOutputPath = outputStem + ".expected"; + expectedOutput = Slang::File::readAllText(expectedOutputPath); + } + catch (Slang::IOException) + { + } - // We could capture output if we passed in a ISlangWriter - but for that to work we'd need a - TestFunc testFunc = (TestFunc)SharedLibrary::findFuncByName(handle, "test"); - if (testFunc) - { - value = testFunc(inValue, inBuffer, buffer); + // Compare if they are the same + if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice())) + { + context->reporter->dumpOutputDifference(expectedOutput, actualOutput); + return TestResult::Fail; + } + } } else { - printf("Unable to access 'test' function\n"); - } + SharedLibrary::Handle handle; + if (SLANG_FAILED(SharedLibrary::loadWithPlatformPath(sharedLibraryPath.getBuffer(), handle))) + { + return TestResult::Fail; + } - SharedLibrary::unload(handle); + const int inValue = 10; + const char inBuffer[] = "Hello World!"; - if (!(inValue == value && strcmp(inBuffer, buffer) == 0)) - { - return TestResult::Fail; + char buffer[128] = ""; + int value = 0; + + typedef int (*TestFunc)(int intValue, const char* textValue, char* outTextValue); + + // We could capture output if we passed in a ISlangWriter - but for that to work we'd need a + TestFunc testFunc = (TestFunc)SharedLibrary::findFuncByName(handle, "test"); + if (testFunc) + { + value = testFunc(inValue, inBuffer, buffer); + } + else + { + printf("Unable to access 'test' function\n"); + } + + SharedLibrary::unload(handle); + + if (!(inValue == value && strcmp(inBuffer, buffer) == 0)) + { + return TestResult::Fail; + } } return TestResult::Pass; diff --git a/tools/slang-test/unit-test-string.cpp b/tools/slang-test/unit-test-string.cpp index 5811a4a64..d585132c8 100644 --- a/tools/slang-test/unit-test-string.cpp +++ b/tools/slang-test/unit-test-string.cpp @@ -72,6 +72,37 @@ static void stringUnitTest() SLANG_CHECK(_checkLineParser(UnownedStringSlice::fromLiteral("\n"))); SLANG_CHECK(_checkLineParser(UnownedStringSlice::fromLiteral(""))); } + { + Int value; + SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("-10"), value)) && value == -10); + SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("0"), value)) && value == 0); + SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("-0"), value)) && value == 0); + + SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("13824"), value)) && value == 13824); + SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("-13824"), value)) && value == -13824); + } + + { + UnownedStringSlice values[] = { UnownedStringSlice("hello"), UnownedStringSlice("world"), UnownedStringSlice("!") }; + + StringBuilder builder; + builder.Clear(); + StringUtil::join(values, 0, ',', builder); + SLANG_CHECK(builder == ""); + + builder.Clear(); + StringUtil::join(values, 1, ',', builder); + SLANG_CHECK(builder == "hello"); + + + builder.Clear(); + StringUtil::join(values, 2, ',', builder); + SLANG_CHECK(builder == "hello,world"); + + builder.Clear(); + StringUtil::join(values, 3, UnownedStringSlice("ab"), builder); + SLANG_CHECK(builder == "helloabworldab!"); + } } SLANG_UNIT_TEST("String", stringUnitTest); |
