26#include <unordered_map>
67 return amatch[1].str().empty()
68 || (!bmatch[1].str().empty() && amatch[1].str() < bmatch[1].str());
81 std::transform(
s.begin(),
s.end(),
s.begin(),
82 [](
unsigned char c) { return (c >=
'A' && c <=
'Z') ? c | 0x20 : c; });
88 std::transform(
s.begin(),
s.end(),
s.begin(),
89 [](
unsigned char c) { return (c >=
'a' && c <=
'z') ? c & 0xdf : c; });
95 struct :
public std::streambuf
97 int overflow(
int c)
override {
return c; }
106 std::cerr <<
"Internal error. Please create bugreport at https://bugreports.qt.io "
107 "using 'Build tools: Other component.'"
113 return std::filesystem::path(std::filesystem::weakly_canonical(
path).generic_string());
122 struct CommandLineOption
124 CommandLineOption(T *_value,
bool _isOptional =
false)
125 :
value(_value), isOptional(_isOptional)
138 const std::string &
moduleName()
const {
return m_moduleName; }
140 const std::string &
sourceDir()
const {
return m_sourceDir; }
142 const std::string &
binaryDir()
const {
return m_binaryDir; }
144 const std::string &
includeDir()
const {
return m_includeDir; }
154 const std::string &
stagingDir()
const {
return m_stagingDir; }
158 const std::set<std::string> &
knownModules()
const {
return m_knownModules; }
168 const std::set<std::string> &
headers()
const {
return m_headers; }
182 bool debug()
const {
return m_debug; }
184 bool copy()
const {
return m_copy; }
194 std::cout <<
"Usage: syncqt -sourceDir <dir> -binaryDir <dir> -module <module name>"
195 " -includeDir <dir> -privateIncludeDir <dir> -qpaIncludeDir <dir> -rhiIncludeDir <dir>"
196 " -stagingDir <dir> <-headers <header list>|-all> [-debug]"
197 " [-versionScript <path>] [-qpaHeadersFilter <regex>] [-rhiHeadersFilter <regex>]"
198 " [-framework [-frameworkIncludeDir <dir>]]"
199 " [-knownModules <module1> <module2>... <moduleN>]"
200 " [-nonQt] [-internal] [-copy]\n"
202 "Mandatory arguments:\n"
203 " -module Module name.\n"
204 " -headers List of header files.\n"
205 " -all In 'all' mode syncqt scans source\n"
206 " directory for public qt headers and\n"
207 " artifacts not considering CMake source\n"
208 " tree. The main use cases are the \n"
209 " generating of documentation and creating\n"
210 " API review changes.\n"
211 " -sourceDir Module source directory.\n"
212 " -binaryDir Module build directory.\n"
213 " -includeDir Module include directory where the\n"
214 " generated header files will be located.\n"
215 " -privateIncludeDir Module include directory for the\n"
216 " generated private header files.\n"
217 " -qpaIncludeDir Module include directory for the \n"
218 " generated QPA header files.\n"
219 " -rhiIncludeDir Module include directory for the \n"
220 " generated RHI header files.\n"
221 " -stagingDir Temporary staging directory to collect\n"
222 " artifacts that need to be installed.\n"
223 " -knownModules list of known modules. syncqt uses the\n"
224 " list to check the #include macros\n"
226 "Optional arguments:\n"
227 " -internal Indicates that the module is internal.\n"
228 " -nonQt Indicates that the module is not a Qt\n"
230 " -privateHeadersFilter Regex that filters private header files\n"
231 " from the list of 'headers'.\n"
232 " -qpaHeadersFilter Regex that filters qpa header files from.\n"
233 " the list of 'headers'.\n"
234 " -rhiHeadersFilter Regex that filters rhi header files from.\n"
235 " the list of 'headers'.\n"
236 " -publicNamespaceFilter Symbols that are in the specified\n"
238 " are treated as public symbols.\n"
239 " -versionScript Generate linker version script by\n"
241 " -debug Enable debug output.\n"
242 " -framework Indicates that module is framework.\n"
243 " -frameworkIncludeDir The directory to store the framework\n"
245 " E.g. QtCore.framework/Versions/A/Headers\n"
246 " -copy Copy header files instead of creating\n"
248 " -minimal Do not create CaMeL case headers for the\n"
249 " public C++ symbols.\n"
250 " -showonly Show actions, but not perform them.\n"
251 " -warningsAreErrors Treat all warnings as errors.\n"
252 " -help Print this help.\n";
257 [[nodiscard]]
bool checkRequiredArguments(
const std::unordered_map<std::string, T> &
arguments)
263 std::cerr <<
"Missing argument: " <<
argument.first << std::endl;
270 [[nodiscard]]
bool parseArguments(
int argc,
char *argv[])
272 std::string qpaHeadersFilter;
273 std::string rhiHeadersFilter;
274 std::string privateHeadersFilter;
275 std::string publicNamespaceFilter;
276 static std::unordered_map<std::string, CommandLineOption<std::string>> stringArgumentMap = {
277 {
"-module", { &m_moduleName } },
278 {
"-sourceDir", { &m_sourceDir } },
279 {
"-binaryDir", { &m_binaryDir } },
280 {
"-privateHeadersFilter", { &privateHeadersFilter,
true } },
281 {
"-qpaHeadersFilter", { &qpaHeadersFilter,
true } },
282 {
"-rhiHeadersFilter", { &rhiHeadersFilter,
true } },
283 {
"-includeDir", { &m_includeDir } },
284 {
"-privateIncludeDir", { &m_privateIncludeDir } },
285 {
"-qpaIncludeDir", { &m_qpaIncludeDir } },
286 {
"-rhiIncludeDir", { &m_rhiIncludeDir } },
287 {
"-stagingDir", { &m_stagingDir,
true } },
288 {
"-versionScript", { &m_versionScriptFile,
true } },
289 {
"-frameworkIncludeDir", { &m_frameworkIncludeDir,
true } },
290 {
"-publicNamespaceFilter", { &publicNamespaceFilter,
true } },
293 static const std::unordered_map<std::string, CommandLineOption<std::set<std::string>>>
295 {
"-headers", { &m_headers,
true } },
296 {
"-generatedHeaders", { &m_generatedHeaders,
true } },
297 {
"-knownModules", { &m_knownModules,
true } },
300 static const std::unordered_map<std::string, CommandLineOption<bool>> boolArgumentMap = {
301 {
"-nonQt", { &m_isNonQtModule,
true } }, {
"-debug", { &m_debug,
true } },
302 {
"-help", { &m_printHelpOnly,
true } }, {
"-framework", { &m_isFramework,
true } },
303 {
"-internal", { &m_isInternal,
true } }, {
"-all", { &m_scanAllMode,
true } },
304 {
"-copy", { &m_copy,
true } }, {
"-minimal", { &m_minimal,
true } },
305 {
"-showonly", { &m_showOnly,
true } }, {
"-showOnly", { &m_showOnly,
true } },
306 {
"-warningsAreErrors", { &m_warningsAreErrors,
true } }
310 std::set<std::string> *currentListValue =
nullptr;
312 auto parseArgument = [&
currentValue, ¤tListValue](
const std::string &
arg) ->
bool {
315 currentListValue =
nullptr;
318 if (
it != stringArgumentMap.
end()) {
319 if (
it->second.value ==
nullptr) {
330 if (
it != boolArgumentMap.
end()) {
331 if (
it->second.value ==
nullptr) {
335 *(
it->second.value) =
true;
342 if (
it != listArgumentMap.
end()) {
343 if (
it->second.value ==
nullptr) {
347 currentListValue =
it->second.value;
348 currentListValue->
insert(
"");
353 std::cerr <<
"Unknown argument: " <<
arg << std::endl;
360 }
else if (currentListValue !=
nullptr) {
361 currentListValue->insert(
arg);
363 std::cerr <<
"Unknown argument: " <<
arg << std::endl;
369 for (
int i = 1;
i < argc; ++
i) {
370 std::string
arg(argv[
i]);
375 std::ifstream ifs(
arg.substr(1), std::ifstream::in);
376 if (!ifs.is_open()) {
377 std::cerr <<
"Unable to open rsp file: " <<
arg[0] << std::endl;
380 std::string argFromFile;
381 while (std::getline(ifs, argFromFile)) {
382 if (argFromFile.empty())
384 if (!parseArgument(argFromFile))
390 if (!parseArgument(
arg))
397 if (!qpaHeadersFilter.empty())
398 m_qpaHeadersRegex = std::regex(qpaHeadersFilter);
400 if (!rhiHeadersFilter.empty())
401 m_rhiHeadersRegex = std::regex(rhiHeadersFilter);
403 if (!privateHeadersFilter.empty())
404 m_privateHeadersRegex = std::regex(privateHeadersFilter);
406 if (!publicNamespaceFilter.empty())
407 m_publicNamespaceRegex = std::regex(publicNamespaceFilter);
409 if (m_headers.empty() && !m_scanAllMode) {
410 std::cerr <<
"You need to specify either -headers or -all option." << std::endl;
414 if (!m_headers.empty() && m_scanAllMode) {
415 std::cerr <<
"Both -headers and -all are specified. Need to choose only one"
416 "operational mode." << std::endl;
420 for (
const auto &
argument : listArgumentMap)
424 ret &= checkRequiredArguments(stringArgumentMap);
425 ret &= checkRequiredArguments(listArgumentMap);
433 void normilizePaths()
435 static std::array<std::string *, 9>
paths = {
436 &m_sourceDir, &m_binaryDir, &m_includeDir, &m_privateIncludeDir,
437 &m_qpaIncludeDir, &m_rhiIncludeDir, &m_stagingDir,
438 &m_versionScriptFile, &m_frameworkIncludeDir
446 std::string m_moduleName;
447 std::string m_sourceDir;
448 std::string m_binaryDir;
449 std::string m_includeDir;
450 std::string m_privateIncludeDir;
451 std::string m_qpaIncludeDir;
452 std::string m_rhiIncludeDir;
453 std::string m_stagingDir;
454 std::string m_versionScriptFile;
455 std::string m_frameworkIncludeDir;
456 std::set<std::string> m_knownModules;
457 std::set<std::string> m_headers;
458 std::set<std::string> m_generatedHeaders;
459 bool m_scanAllMode =
false;
461 bool m_isFramework =
false;
462 bool m_isNonQtModule =
false;
463 bool m_isInternal =
false;
464 bool m_printHelpOnly =
false;
465 bool m_debug =
false;
466 bool m_minimal =
false;
467 bool m_showOnly =
false;
468 bool m_warningsAreErrors =
false;
469 std::regex m_qpaHeadersRegex;
470 std::regex m_rhiHeadersRegex;
471 std::regex m_privateHeadersRegex;
472 std::regex m_publicNamespaceRegex;
479 class SymbolDescriptor
489 void update(
const std::string &
file, SourceType
type)
498 const std::string &
file()
const {
return m_file; }
501 SourceType m_type = MaxSourceType;
504 using SymbolContainer = std::unordered_map<std::string, SymbolDescriptor>;
508 std::vector<std::string> versionScriptContent;
509 std::string requireConfig;
510 bool masterInclude =
true;
515 std::map<std::string , std::string ,
517 m_masterHeaderContents;
519 std::unordered_map<std::string ,
522 std::vector<std::string> m_versionScriptContents;
523 std::set<std::string> m_producedHeaders;
524 std::vector<std::string> m_headerCheckExceptions;
525 SymbolContainer m_symbols;
526 std::ostream &scannerDebug()
const
528 if (m_commandLineArgs->
debug())
533 enum { Active, Stopped, IgnoreNext, Ignore } m_versionScriptGeneratorState = Active;
535 std::filesystem::path m_outputRootName;
536 std::filesystem::path m_currentFile;
537 std::string m_currentFilename;
538 std::string m_currentFileString;
539 size_t m_currentFileLineNumber = 0;
540 bool m_currentFileInSourceDir =
false;
542 enum FileType { PublicHeader = 0, PrivateHeader = 1, QpaHeader = 2, ExportHeader = 4, RhiHeader = 8 };
543 unsigned int m_currentFileType = PublicHeader;
546 std::string_view m_warningMessagePreamble;
550 : m_commandLineArgs(commandLineArgs),
553 std::filesystem::weakly_canonical(m_commandLineArgs->includeDir()).root_name()),
562 [[nodiscard]] std::filesystem::path
makeHeaderAbsolute(
const std::string &filename)
const;
571 m_versionScriptGeneratorState =
578 for (
auto const &
entry :
579 std::filesystem::recursive_directory_iterator(m_commandLineArgs->
sourceDir())) {
581 const bool isRegularFile =
entry.is_regular_file();
583 const bool isDocFileHeuristicFlag =
585 const bool shouldProcessHeader =
586 isRegularFile && isHeaderFlag && !isDocFileHeuristicFlag;
587 const std::string filePath =
entry.path().generic_string();
589 if (shouldProcessHeader) {
590 scannerDebug() <<
"Processing header: " << filePath << std::endl;
595 <<
"Skipping processing header: " << filePath
596 <<
" isRegularFile: " << isRegularFile
597 <<
" isHeaderFlag: " << isHeaderFlag
598 <<
" isDocFileHeuristicFlag: " << isDocFileHeuristicFlag
605 std::set<std::string> rspHeaders;
606 const auto &headers = m_commandLineArgs->
headers();
609 scannerDebug() <<
"Processing header: " <<
header << std::endl;
614 for (
const auto &
header : rspHeaders) {
615 scannerDebug() <<
"Processing header: " <<
header << std::endl;
622 if (m_commandLineArgs->
minimal())
627 const std::string &filename =
it->second.file();
628 if (!filename.empty()) {
630 m_commandLineArgs->
includeDir() +
'/' +
it->first, filename)) {
631 m_producedHeaders.
insert(
it->first);
641 std::string versionHeaderFilename(moduleNameLower +
"version.h");
642 std::string versionHeaderCamel(m_commandLineArgs->
moduleName() +
"Version");
643 std::string versionFile = m_commandLineArgs->
includeDir() +
'/' + versionHeaderFilename;
646 FileStamp originalStamp = std::filesystem::last_write_time(versionFile, ec);
648 originalStamp = FileStamp::clock::now();
652 m_commandLineArgs->
includeDir() +
'/' + versionHeaderCamel,
653 versionHeaderFilename, originalStamp)) {
656 m_masterHeaderContents[versionHeaderFilename] = {};
657 m_producedHeaders.insert(versionHeaderFilename);
658 m_producedHeaders.insert(versionHeaderCamel);
703 bool skipCleanup =
false)
706 if (!std::filesystem::exists(outputDirectory)) {
707 std::filesystem::create_directories(outputDirectory);
708 }
else if (!skipCleanup) {
709 for (
const auto &
entry :
710 std::filesystem::recursive_directory_iterator(outputDirectory)) {
711 if (m_producedHeaders.find(
entry.path().filename().generic_string())
712 == m_producedHeaders.end()) {
713 std::filesystem::remove(
entry.path());
718 for (
const auto &
header : m_producedHeaders) {
720 std::filesystem::path
dst(outputDirectory +
'/' +
header);
730 static const std::regex ExportsHeaderRegex(
"^q(.*)exports(_p)?\\.h$");
732 m_currentFile = headerFile;
733 m_currentFileLineNumber = 0;
734 m_currentFilename = m_currentFile.filename().generic_string();
735 m_currentFileType = PublicHeader;
736 m_currentFileString = m_currentFile.generic_string();
737 m_currentFileInSourceDir = m_currentFileString.find(m_commandLineArgs->
sourceDir()) == 0;
740 m_currentFileType = PrivateHeader;
743 m_currentFileType = QpaHeader | PrivateHeader;
746 m_currentFileType = RhiHeader | PrivateHeader;
748 if (std::regex_match(m_currentFilename, ExportsHeaderRegex))
749 m_currentFileType |= ExportHeader;
755 static const std::regex ThirdPartyFolderRegex(
".+/3rdparty/.+");
758 static const std::regex ConfigHeaderRegex(
"^(q|.+-)config(_p)?\\.h");
763 if (!m_currentFileInSourceDir
764 && m_currentFileString.find(m_commandLineArgs->
binaryDir()) != 0) {
765 scannerDebug() <<
"Header file: " << headerFile
766 <<
" is outside the sync directories. Skipping." << std::endl;
767 m_headerCheckExceptions.push_back(m_currentFileString);
772 if (m_currentFilename.empty()) {
773 std::cerr <<
"Header file name of " << m_currentFileString <<
"is empty" << std::endl;
778 FileStamp originalStamp = std::filesystem::last_write_time(headerFile, ec);
780 originalStamp = FileStamp::clock::now();
783 bool isPrivate = m_currentFileType & PrivateHeader;
784 bool isQpa = m_currentFileType & QpaHeader;
785 bool isRhi = m_currentFileType & RhiHeader;
786 bool isExport = m_currentFileType & ExportHeader;
788 <<
"processHeader:start: " << headerFile
789 <<
" m_currentFilename: " << m_currentFilename
790 <<
" isPrivate: " << isPrivate
791 <<
" isQpa: " << isQpa
792 <<
" isRhi: " << isRhi
797 std::string outputDir = m_commandLineArgs->
includeDir();
805 if (!std::filesystem::exists(outputDir))
806 std::filesystem::create_directories(outputDir);
808 bool headerFileExists = std::filesystem::exists(headerFile);
810 std::filesystem::path headerFileRootName =
811 std::filesystem::weakly_canonical(headerFile, ec).root_name();
812 std::string aliasedFilepath = !ec && headerFileRootName == m_outputRootName
813 ? std::filesystem::relative(headerFile, outputDir).generic_string()
814 : headerFile.generic_string();
817 std::string aliasPath = outputDir +
'/' + m_currentFilename;
822 if (m_commandLineArgs->
copy() && headerFileExists) {
832 if (m_commandLineArgs->
minimal())
840 if (!headerFileExists) {
841 scannerDebug() <<
"Header file: " << headerFile
842 <<
" doesn't exist, but is added to syncqt scanning. Skipping.";
847 bool is3rdParty = std::regex_match(m_currentFileString, ThirdPartyFolderRegex);
849 if (!std::regex_match(m_currentFilename, ConfigHeaderRegex)) {
853 if (m_commandLineArgs->
isNonQtModule() || is3rdParty || isQpa || isRhi
854 || !m_currentFileInSourceDir || isGenerated) {
872 ParsingResult parsingResult;
873 parsingResult.masterInclude = m_currentFileInSourceDir && !isExport && !is3rdParty
874 && !isQpa && !isRhi && !isPrivate && !isGenerated;
875 if (!
parseHeader(headerFile, parsingResult, skipChecks)) {
876 scannerDebug() <<
"parseHeader failed: " << headerFile << std::endl;
882 && !parsingResult.versionScriptContent.empty()) {
883 m_versionScriptContents.insert(m_versionScriptContents.end(),
884 parsingResult.versionScriptContent.begin(),
885 parsingResult.versionScriptContent.end());
890 bool willBeInModuleMasterHeader =
false;
891 if (!isQpa && !isRhi && !isPrivate) {
892 if (m_currentFilename.find(
'_') == std::string::npos
893 && parsingResult.masterInclude) {
894 m_masterHeaderContents[m_currentFilename] = parsingResult.requireConfig;
895 willBeInModuleMasterHeader =
true;
900 <<
"processHeader:end: " << headerFile
901 <<
" is3rdParty: " << is3rdParty
902 <<
" isGenerated: " << isGenerated
903 <<
" m_currentFileInSourceDir: " << m_currentFileInSourceDir
904 <<
" willBeInModuleMasterHeader: " << willBeInModuleMasterHeader
906 }
else if (m_currentFilename ==
"qconfig.h") {
917 static const std::regex VersionScriptSymbolRegex(
918 "^(?:struct|class)(?:\\s+Q_\\w*_EXPORT)?\\s+([\\w:]+)[^;]*(;$)?");
921 static const std::regex VersionScriptNamespaceRegex(
922 "^namespace\\s+Q_\\w+_EXPORT\\s+([\\w:]+).*");
925 static const std::regex TrailingColonRegex(
"([\\w]+):$");
927 switch (m_versionScriptGeneratorState) {
929 scannerDebug() <<
"line ignored: " <<
buffer << std::endl;
930 m_versionScriptGeneratorState = Active;
935 m_versionScriptGeneratorState = Ignore;
947 symbol =
match[1].str();
948 else if (std::regex_match(
buffer,
match, VersionScriptNamespaceRegex))
949 symbol =
match[1].str();
951 if (std::regex_match(symbol,
match, TrailingColonRegex))
952 symbol =
match[1].str();
955 if (!symbol.empty() && symbol[symbol.size() - 1] !=
';') {
956 std::string relPath = m_currentFileInSourceDir
957 ? std::filesystem::relative(m_currentFile, m_commandLineArgs->
sourceDir())
959 : std::filesystem::relative(m_currentFile, m_commandLineArgs->
binaryDir())
962 std::string versionStringRecord =
" *";
965 while (endPos != std::string::npos) {
966 endPos = symbol.find(
"::", startPos);
967 size_t length = endPos != std::string::npos ? (endPos - startPos)
968 : (symbol.size() - startPos);
970 std::string symbolPart = symbol.substr(startPos,
length);
971 versionStringRecord += std::to_string(symbolPart.size());
972 versionStringRecord += symbolPart;
974 startPos = endPos + 2;
976 versionStringRecord +=
"*;";
978 versionStringRecord +=
980 versionStringRecord +=
" # ";
981 versionStringRecord += relPath;
982 versionStringRecord +=
":";
983 versionStringRecord += std::to_string(m_currentFileLineNumber);
984 versionStringRecord +=
"\n";
985 result.versionScriptContent.push_back(versionStringRecord);
993 [[nodiscard]]
bool parseHeader(
const std::filesystem::path &headerFile,
995 unsigned int skipChecks)
998 std::cout << headerFile <<
" [" << m_commandLineArgs->
moduleName() <<
"]" << std::endl;
1000 static const std::regex MacroRegex(
"^\\s*#.*");
1025 static const std::regex SkipHeaderCheckRegex(
"^#\\s*pragma qt_sync_skip_header_check$");
1026 static const std::regex StopProcessingRegex(
"^#\\s*pragma qt_sync_stop_processing$");
1027 static const std::regex SuspendProcessingRegex(
"^#\\s*pragma qt_sync_suspend_processing$");
1028 static const std::regex ResumeProcessingRegex(
"^#\\s*pragma qt_sync_resume_processing$");
1029 static const std::regex ExplixitClassPragmaRegex(
"^#\\s*pragma qt_class\\(([^\\)]+)\\)$");
1030 static const std::regex DeprecatesPragmaRegex(
"^#\\s*pragma qt_deprecates\\(([^\\)]+)\\)$");
1031 static const std::regex NoMasterIncludePragmaRegex(
"^#\\s*pragma qt_no_master_include$");
1035 static const std::string_view WeMeantItString(
"We mean it.");
1038 static const std::regex BeginNamespaceRegex(
"^QT_BEGIN_NAMESPACE(_[A-Z_]+)?$");
1039 static const std::regex EndNamespaceRegex(
"^QT_END_NAMESPACE(_[A-Z_]+)?$");
1045 static const std::regex IncludeRegex(
"^#\\s*include\\s*[<\"](.+)[>\"]");
1048 static const std::regex NamespaceRegex(
"\\s*namespace ([^ ]*)\\s+");
1052 static const std::regex DeclareIteratorRegex(
"^ *Q_DECLARE_\\w*ITERATOR\\((\\w+)\\);?$");
1057 static const std::regex RequireConfigRegex(
"^ *QT_REQUIRE_CONFIG\\((\\w+)\\);?$");
1065 static const std::regex ElfVersionTagRegex(
".*ELFVERSION:(stop|ignore-next|ignore).*");
1067 std::ifstream
input(headerFile, std::ifstream::in);
1068 if (!
input.is_open()) {
1069 std::cerr <<
"Unable to open " << headerFile << std::endl;
1073 bool hasQtBeginNamespace =
false;
1074 std::string qtBeginNamespace;
1075 std::string qtEndNamespace;
1076 bool hasWeMeantIt =
false;
1077 bool isSuspended =
false;
1078 bool isMultiLineComment =
false;
1079 std::size_t bracesDepth = 0;
1080 std::size_t namespaceCount = 0;
1081 std::string namespaceString;
1087 std::string tmpLine;
1088 std::size_t linesProcessed = 0;
1092 while (std::getline(
input, tmpLine)) {
1093 ++m_currentFileLineNumber;
1105 if (
line[
i] ==
'\r')
1107 if (bracesDepth == namespaceCount) {
1108 if (
line[
i] ==
'/') {
1110 if (
line[
i + 1] ==
'*') {
1111 isMultiLineComment =
true;
1113 }
else if (
line[
i + 1] ==
'/') {
1115 &&
line.find(WeMeantItString) != std::string::npos) {
1116 hasWeMeantIt =
true;
1119 if (m_versionScriptGeneratorState != Stopped
1120 && std::regex_match(
line,
match, ElfVersionTagRegex)) {
1122 m_versionScriptGeneratorState = Ignore;
1123 else if (
match[1].
str() ==
"ignore-next")
1124 m_versionScriptGeneratorState = IgnoreNext;
1126 m_versionScriptGeneratorState = Stopped;
1133 isMultiLineComment =
false;
1138 if (isMultiLineComment) {
1140 line.find(WeMeantItString) != std::string::npos) {
1141 hasWeMeantIt =
true;
1147 if (
line[
i] ==
'{') {
1148 if (std::regex_match(
buffer,
match, NamespaceRegex)) {
1150 namespaceString +=
"::";
1151 namespaceString +=
match[1].str();
1155 }
else if (
line[
i] ==
'}') {
1156 if (namespaceCount > 0 && bracesDepth == namespaceCount) {
1157 namespaceString.resize(namespaceString.rfind(
"::"));
1161 }
else if (bracesDepth == namespaceCount) {
1167 scannerDebug() << m_currentFilename <<
": " <<
buffer << std::endl;
1169 if (m_currentFileType & PrivateHeader) {
1179 (m_currentFileType & PrivateHeader) || (m_currentFileType & QpaHeader) || (m_currentFileType & RhiHeader);
1182 if (std::regex_match(
buffer, MacroRegex)) {
1183 if (std::regex_match(
buffer, SkipHeaderCheckRegex)) {
1186 }
else if (std::regex_match(
buffer, StopProcessingRegex)) {
1188 m_headerCheckExceptions.push_back(m_currentFileString);
1190 }
else if (std::regex_match(
buffer, SuspendProcessingRegex)) {
1192 }
else if (std::regex_match(
buffer, ResumeProcessingRegex)) {
1193 isSuspended =
false;
1194 }
else if (std::regex_match(
buffer,
match, ExplixitClassPragmaRegex)) {
1197 SymbolDescriptor::Pragma);
1201 }
else if (std::regex_match(
buffer, NoMasterIncludePragmaRegex)) {
1202 result.masterInclude =
false;
1203 }
else if (std::regex_match(
buffer,
match, DeprecatesPragmaRegex)) {
1204 m_deprecatedHeaders[
match[1].str()] =
1205 m_commandLineArgs->
moduleName() +
'/' + m_currentFilename;
1206 }
else if (std::regex_match(
buffer,
match, IncludeRegex) && !isSuspended) {
1208 std::string includedHeader =
match[1].str();
1212 .generic_string())) {
1215 <<
":" << m_currentFileLineNumber
1216 <<
" includes private header " << includedHeader << std::endl;
1218 for (
const auto &module : m_commandLineArgs->
knownModules()) {
1219 std::string suggestedHeader =
"Qt" + module +
'/' + includedHeader;
1220 if (std::filesystem::exists(m_commandLineArgs->
includeDir() +
"/../"
1221 + suggestedHeader)) {
1223 std::cerr << m_warningMessagePreamble << m_currentFileString
1224 <<
":" << m_currentFileLineNumber
1225 <<
" includes " << includedHeader
1226 <<
" when it should include "
1227 << suggestedHeader << std::endl;
1244 if (namespaceCount == 0
1245 || std::regex_match(namespaceString,
1248 SymbolDescriptor::Declaration);
1251 }
else if (std::regex_match(
buffer,
match, DeclareIteratorRegex)) {
1252 std::string iteratorSymbol =
match[1].str() +
"Iterator";
1254 SymbolDescriptor::Declaration);
1256 m_currentFilename, SymbolDescriptor::Declaration);
1258 }
else if (std::regex_match(
buffer,
match, RequireConfigRegex)) {
1267 if (std::regex_match(
buffer,
match, BeginNamespaceRegex)) {
1268 qtBeginNamespace =
match[1].str();
1269 hasQtBeginNamespace =
true;
1270 }
else if (std::regex_match(
buffer,
match, EndNamespaceRegex)) {
1271 qtEndNamespace =
match[1].str();
1278 if (hasQtBeginNamespace) {
1279 if (qtBeginNamespace != qtEndNamespace) {
1281 std::cerr << m_warningMessagePreamble << m_currentFileString
1282 <<
" the begin namespace macro QT_BEGIN_NAMESPACE" << qtBeginNamespace
1283 <<
" doesn't match the end namespace macro QT_END_NAMESPACE"
1284 << qtEndNamespace << std::endl;
1288 std::cerr << m_warningMessagePreamble << m_currentFileString
1289 <<
" does not include QT_BEGIN_NAMESPACE" << std::endl;
1295 std::cerr << m_warningMessagePreamble << m_currentFileString
1296 <<
" does not have the \"We mean it.\" warning"
1300 scannerDebug() <<
"linesTotal: " << m_currentFileLineNumber
1301 <<
" linesProcessed: " << linesProcessed << std::endl;
1304 m_headerCheckExceptions.push_back(m_currentFileString);
1307 return !(faults & m_criticalChecks);
1313 scannerDebug() <<
"checkLineForSymbols: " <<
line << std::endl;
1321 static const std::regex ClassRegex(
1322 "^ *(template *<.*> *)?(class|struct) +([^ <>]* "
1323 "+)?((?!Q_DECL_FINAL|final|sealed)[^<\\s\\:]+) ?(<[^>\\:]*> "
1324 "?)?\\s*(?:Q_DECL_FINAL|final|sealed)?\\s*((,|:)\\s*(public|protected|private)? "
1329 static const std::regex FunctionPointerRegex(
1330 "^ *typedef *.*\\(\\*(Q[^\\)]+)\\)\\(.*\\); *");
1334 static const std::regex TypedefRegex(
"^ *typedef\\s+(.*)\\s+(Q\\w+); *$");
1338 static const std::regex QtClassRegex(
"^Q\\w+$");
1341 if (std::regex_match(
line,
match, FunctionPointerRegex)) {
1342 symbol =
match[1].str();
1343 }
else if (std::regex_match(
line,
match, TypedefRegex)) {
1344 symbol =
match[2].str();
1345 }
else if (std::regex_match(
line,
match, ClassRegex)) {
1346 symbol =
match[4].str();
1347 if (!std::regex_match(symbol, QtClassRegex))
1352 return !symbol.empty();
1357 return std::regex_match(headerFileName, m_commandLineArgs->
qpaHeadersRegex());
1362 return std::regex_match(headerFileName, m_commandLineArgs->
rhiHeadersRegex());
1372 static const std::string pchSuffix(
"_pch.h");
1373 return headerFilename.find(pchSuffix, headerFilename.size() - pchSuffix.size())
1374 != std::string::npos;
1379 return path.extension().string() ==
".h";
1384 return headerFilePath.find(
"/doc/") != std::string::npos;
1394 const std::string &aliasedFilePath);
1397 const std::string &outputFilePath,
const std::string &aliasedFilePath,
1398 const FileStamp &originalStamp = FileStamp::clock::now());
1404 if (m_masterHeaderContents.empty())
1411 std::stringstream
buffer;
1412 buffer <<
"#ifndef QT_" << moduleUpper <<
"_MODULE_H\n"
1413 <<
"#define QT_" << moduleUpper <<
"_MODULE_H\n"
1414 <<
"#include <" << m_commandLineArgs->
moduleName() <<
"/"
1415 << m_commandLineArgs->
moduleName() <<
"Depends>\n";
1416 for (
const auto &headerContents : m_masterHeaderContents) {
1417 if (!headerContents.second.empty()) {
1418 buffer <<
"#if QT_CONFIG(" << headerContents.second <<
")\n"
1419 <<
"#include \"" << headerContents.first <<
"\"\n"
1422 buffer <<
"#include \"" << headerContents.first <<
"\"\n";
1427 m_producedHeaders.insert(m_commandLineArgs->
moduleName());
1435 std::stringstream
buffer;
1436 buffer <<
"/* This file was generated by syncqt. */\n"
1437 <<
"#ifndef QT_" << moduleNameUpper <<
"_VERSION_H\n"
1438 <<
"#define QT_" << moduleNameUpper <<
"_VERSION_H\n\n"
1439 <<
"#define " << moduleNameUpper <<
"_VERSION_STR \"" << QT_VERSION_STR <<
"\"\n\n"
1440 <<
"#define " << moduleNameUpper <<
"_VERSION "
1441 <<
"0x0" << QT_VERSION_MAJOR <<
"0" << QT_VERSION_MINOR <<
"0" << QT_VERSION_PATCH
1443 <<
"#endif // QT_" << moduleNameUpper <<
"_VERSION_H\n";
1450 static std::regex cIdentifierSymbolsRegex(
"[^a-zA-Z0-9_]");
1451 static std::string guard_base =
"DEPRECATED_HEADER_" + m_commandLineArgs->
moduleName();
1452 for (
auto it = m_deprecatedHeaders.
begin();
it != m_deprecatedHeaders.
end(); ++
it) {
1453 std::string &replacement =
it->second;
1454 std::string qualifiedHeaderName =
1455 std::regex_replace(
it->first, cIdentifierSymbolsRegex,
"_");
1456 std::string guard = guard_base +
"_" + qualifiedHeaderName;
1457 std::string warningText =
"Header <" + m_commandLineArgs->
moduleName() +
"/" +
it->first
1458 +
"> is deprecated. Please include <" + replacement +
"> instead.";
1459 std::stringstream
buffer;
1460 buffer <<
"#ifndef " << guard <<
"\n"
1461 <<
"#define " << guard <<
"\n"
1462 <<
"#if defined(__GNUC__)\n"
1463 <<
"# warning " << warningText <<
"\n"
1464 <<
"#elif defined(_MSC_VER)\n"
1465 <<
"# pragma message (\"" << warningText <<
"\")\n"
1467 <<
"#include <" << replacement <<
">\n"
1470 m_producedHeaders.insert(
it->first);
1477 std::stringstream
buffer;
1478 for (
const auto &
header : m_headerCheckExceptions)
1482 +
"_header_check_exceptions",
1488 std::stringstream
buffer;
1489 for (
const auto &content : m_versionScriptContents)
1494 bool updateOrCopy(
const std::filesystem::path &
src,
const std::filesystem::path &
dst);
1496 SymbolDescriptor::SourceType
type);
1504 SymbolDescriptor::SourceType
type)
1507 std::cout <<
" SYMBOL: " << symbol << std::endl;
1508 m_symbols[symbol].update(
file,
type);
1511[[nodiscard]] std::filesystem::path
1514 if (std::filesystem::path(filename).is_relative())
1526 std::cout <<
"Source and destination paths are same when copying " <<
src.string()
1527 <<
". Skipping." << std::endl;
1532 std::filesystem::copy(
src,
dst, std::filesystem::copy_options::update_existing, ec);
1535 std::filesystem::remove(
dst, ec);
1539 std::cerr <<
"Unable to remove file: " <<
src <<
" to " <<
dst <<
" error: ("
1540 << ec.value() <<
")" << ec.message() << std::endl;
1544 std::filesystem::copy(
src,
dst, std::filesystem::copy_options::overwrite_existing, ec);
1546 std::cerr <<
"Unable to copy file: " <<
src <<
" to " <<
dst <<
" error: ("
1547 << ec.value() <<
")" << ec.message() << std::endl;
1560 const std::string &aliasedFilePath)
1565 std::string
buffer =
"#include \"";
1566 buffer += aliasedFilePath;
1576 const std::string &aliasedFilePath,
1582 if (std::filesystem::exists({ outputFilePath })
1583 && std::filesystem::last_write_time({ outputFilePath }) >= originalStamp) {
1586 scannerDebug() <<
"Rewrite " << outputFilePath << std::endl;
1589 ofs.open(outputFilePath, std::ofstream::out | std::ofstream::trunc);
1590 if (!ofs.is_open()) {
1591 std::cerr <<
"Unable to write header file alias: " << outputFilePath << std::endl;
1594 ofs <<
"#include \"" << aliasedFilePath <<
"\"\n";
1604 static const std::streamsize bufferSize = 1025;
1605 bool differs =
false;
1606 std::filesystem::path outputFilePath(
outputFile);
1608 std::string outputDirectory = outputFilePath.parent_path().string();
1609 if (!std::filesystem::exists(outputDirectory))
1610 std::filesystem::create_directories(outputDirectory);
1612 auto expectedSize =
buffer.size();
1615 expectedSize += std::count(
buffer.begin(),
buffer.end(),
'\n');
1618 if (std::filesystem::exists(outputFilePath)
1619 && expectedSize == std::filesystem::file_size(outputFilePath)) {
1620 char rdBuffer[bufferSize];
1621 memset(rdBuffer, 0, bufferSize);
1623 std::ifstream ifs(
outputFile, std::fstream::in);
1624 std::streamsize currentPos = 0;
1626 std::size_t bytesRead = 0;
1628 ifs.read(rdBuffer, bufferSize - 1);
1629 bytesRead = ifs.gcount();
1630 if (
buffer.compare(currentPos, bytesRead, rdBuffer) != 0) {
1634 currentPos += bytesRead;
1635 memset(rdBuffer, 0, bufferSize);
1636 }
while (bytesRead > 0);
1643 scannerDebug() <<
"Update: " <<
outputFile <<
" " << differs << std::endl;
1646 ofs.open(outputFilePath, std::fstream::out | std::ofstream::trunc);
1647 if (!ofs.is_open()) {
1648 std::cerr <<
"Unable to write header content to " << outputFilePath << std::endl;
1670 return scanner.
sync();
const std::string & rhiIncludeDir() const
const std::string & privateIncludeDir() const
const std::string & sourceDir() const
const std::string & binaryDir() const
const std::string & frameworkIncludeDir() const
const std::set< std::string > & generatedHeaders() const
const std::string & moduleName() const
const std::string & versionScriptFile() const
const std::regex & publicNamespaceRegex() const
const std::string & stagingDir() const
const std::string & qpaIncludeDir() const
const std::set< std::string > & knownModules() const
const std::string & includeDir() const
bool isNonQtModule() const
const std::regex & qpaHeadersRegex() const
CommandLineOptions(int argc, char *argv[])
const std::regex & privateHeadersRegex() const
bool printHelpOnly() const
bool warningsAreErrors() const
const std::regex & rhiHeadersRegex() const
const std::set< std::string > & headers() const
iterator find(const T &value)
iterator insert(const T &value)
void clear()
Clears the contents of the string and makes it null.
qsizetype size() const
Returns the number of characters in this string.
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
QString & append(QChar c)
bool generateVersionHeader(const std::string &outputFile)
bool generateLinkerVersionScript()
bool isHeaderQpa(const std::string &headerFileName)
void parseVersionScriptContent(const std::string buffer, ParsingResult &result)
bool updateOrCopy(const std::filesystem::path &src, const std::filesystem::path &dst)
bool checkLineForSymbols(const std::string &line, std::string &symbol)
void updateSymbolDescriptor(const std::string &symbol, const std::string &file, SymbolDescriptor::SourceType type)
bool isHeaderGenerated(const std::string &header)
std::filesystem::path makeHeaderAbsolute(const std::string &filename) const
bool generateMasterHeader()
bool generateAliasedHeaderFileIfTimestampChanged(const std::string &outputFilePath, const std::string &aliasedFilePath, const FileStamp &originalStamp=FileStamp::clock::now())
bool isHeader(const std::filesystem::path &path)
bool generateDeprecatedHeaders()
bool copyGeneratedHeadersToStagingDirectory(const std::string &outputDirectory, bool skipCleanup=false)
bool writeIfDifferent(const std::string &outputFile, const std::string &buffer)
bool isHeaderRhi(const std::string &headerFileName)
bool parseHeader(const std::filesystem::path &headerFile, ParsingResult &result, unsigned int skipChecks)
bool isDocFileHeuristic(const std::string &headerFilePath)
bool isHeaderPCH(const std::string &headerFilename)
bool processHeader(const std::filesystem::path &headerFile)
SyncScanner(CommandLineOptions *commandLineArgs)
bool generateQtCamelCaseFileIfContentChanged(const std::string &outputFilePath, const std::string &aliasedFilePath)
bool generateHeaderCheckExceptions()
void resetCurrentFileInfoData(const std::filesystem::path &headerFile)
bool isHeaderPrivate(const std::string &headerFile)
QSet< QString >::iterator it
QList< QVariant > arguments
std::string asciiToLower(std::string s)
std::filesystem::path normilizedPath(const std::string &path)
utils::DummyOutputStream DummyOutput
void printInternalError()
std::string asciiToUpper(std::string s)
qsizetype erase(QByteArray &ba, const T &t)
DBusConnection const char DBusError * error
static QString outputFile
static QString header(const QString &name)
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
GLboolean GLboolean GLboolean b
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLenum GLsizei length
GLsizei const GLuint * paths
GLsizei const GLchar *const * path
GLenum GLenum GLenum input
static const std::regex GlobalHeaderRegex("^q(.*)global\\.h$")
constexpr std::string_view ErrorMessagePreamble
std::filesystem::file_time_type FileStamp
constexpr int LinkerScriptCommentAlignment
constexpr std::string_view WarningMessagePreamble
bool MasterHeaderIncludeComparator(const std::string &a, const std::string &b)
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)