summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTopi Reinio <topi.reinio@qt.io>2021-10-02 00:01:58 +0200
committerTopi Reinio <topi.reinio@qt.io>2021-10-08 10:35:03 +0200
commit6ee3cad531e782cf82777a4eca2f93f0469b2791 (patch)
tree81594a836466d3d41fe7cb067c2bc518c30e94ed
parent3cee558b50ef87d08f34dc9d8ef0a1eab1a8fb2a (diff)
qdoc: Teach Config::getCanonicalPathList() to read include paths
Include paths must be canonicalized for them to be correct. Previously, the ClangCodeParser did this step, but it was done in relation to the current working directory (cwd), and at that point cwd is the directory of the main (top-level) qdocconf file - if includepaths variable was used in another included config file and contained relative paths, this produced incorrect results. As Config already knowns how to canonicalize paths, teach it to consider the common prefixes associated with include paths. As config variables store their location, we can produce absolute paths reliably in all situations. Document the includepaths and the associated moduleheader variables. Fixes: QTBUG-97034 Pick-to: 6.2 Change-Id: Iecf68e3cb09ced732f7a05270441da046e8df8d8 Reviewed-by: Paul Wicking <paul.wicking@qt.io>
-rw-r--r--src/qdoc/clangcodeparser.cpp36
-rw-r--r--src/qdoc/config.cpp77
-rw-r--r--src/qdoc/config.h9
-rw-r--r--src/qdoc/doc/qdoc-manual-qdocconf.qdoc70
-rw-r--r--src/qdoc/generator.cpp2
-rw-r--r--src/qdoc/location.h2
-rw-r--r--tests/auto/qdoc/config/testdata/configs/includepaths.qdocconf2
-rw-r--r--tests/auto/qdoc/config/testdata/includepaths/include/framework/ignore.h1
-rw-r--r--tests/auto/qdoc/config/testdata/includepaths/include/more/ignore.h1
-rw-r--r--tests/auto/qdoc/config/testdata/includepaths/include/purpose.h1
-rw-r--r--tests/auto/qdoc/config/testdata/includepaths/include/system/ignore.h1
-rw-r--r--tests/auto/qdoc/config/testdata/includepaths/includepaths.qdocconf16
-rw-r--r--tests/auto/qdoc/config/tst_config.cpp23
13 files changed, 197 insertions, 44 deletions
diff --git a/src/qdoc/clangcodeparser.cpp b/src/qdoc/clangcodeparser.cpp
index b8feca108..d61b24270 100644
--- a/src/qdoc/clangcodeparser.cpp
+++ b/src/qdoc/clangcodeparser.cpp
@@ -1096,38 +1096,18 @@ void ClangCodeParser::initializeParser()
{
Config &config = Config::instance();
m_version = config.getString(CONFIG_VERSION);
- auto args = config.getStringList(CONFIG_INCLUDEPATHS);
- QSet<QString> seen;
- m_includePaths.clear();
-
+ auto args = config.getCanonicalPathList(CONFIG_INCLUDEPATHS,
+ Config::IncludePaths);
#ifdef Q_OS_MACOS
args.append(Utilities::getInternalIncludePaths(QStringLiteral("clang++")));
#endif
-
- // Remove empty paths and duplicates and add -I and canonicalize if necessary
- for (const auto &p : args) {
- QByteArray option;
- QString rawpath;
- if (p.startsWith(QLatin1String("-I")) || p.startsWith(QLatin1String("-F"))) {
- rawpath = p.mid(2).trimmed();
- option = p.left(2).toUtf8();
- } else if (p.startsWith(QLatin1String("-isystem"))) {
- rawpath = p.mid(8).trimmed();
- option = "-isystem";
- } else {
- rawpath = p;
- option = "-I";
- }
- if (rawpath.isEmpty() || seen.contains(rawpath))
- continue;
- seen.insert(rawpath);
- QByteArray path(rawpath.toUtf8());
- QFileInfo fi(QDir::current(), rawpath);
- if (fi.exists())
- path = fi.canonicalFilePath().toUtf8();
- path.prepend(option);
- m_includePaths.append(path);
+ m_includePaths.clear();
+ for (const auto &path : qAsConst(args)) {
+ if (!path.isEmpty())
+ m_includePaths.append(path.toUtf8());
}
+ m_includePaths.erase(std::unique(m_includePaths.begin(), m_includePaths.end()),
+ m_includePaths.end());
CppCodeParser::initializeParser();
m_pchFileDir.reset(nullptr);
m_allHeaders.clear();
diff --git a/src/qdoc/config.cpp b/src/qdoc/config.cpp
index c2beda80d..41dc9b2c6 100644
--- a/src/qdoc/config.cpp
+++ b/src/qdoc/config.cpp
@@ -235,10 +235,29 @@ QMap<QString, QStringList> Config::m_includeFilesMap;
\brief The Config class contains the configuration variables
for controlling how qdoc produces documentation.
- Its load() function, reads, parses, and processes a qdocconf file.
+ Its load() function reads, parses, and processes a qdocconf file.
*/
/*!
+ \enum Config::PathFlags
+
+ Flags used for retrieving canonicalized paths from Config.
+
+ \value Validate
+ Issue a warning for paths that do not exist and
+ remove them from the returned list.
+
+ \value IncludePaths
+ Assume the variable contains include paths with
+ prefixes such as \c{-I} that are to be removed
+ before canonicalizing and then re-inserted.
+
+ \omitvalue None
+
+ \sa getCanonicalPathList()
+*/
+
+/*!
Initializes the Config with \a programName and sets all
internal state variables to either default values or to ones
defined in command line arguments \a args.
@@ -573,12 +592,16 @@ QStringList Config::getStringList(const QString &var) const
}
/*!
- Returns the a path list where all paths from the config variable \a var
- are canonicalized. If \a validate is true, outputs a warning for invalid
- paths. If \a var is defined, updates the internal location to the
- location of \a var for the purposes of error reporting.
+ Returns a path list where all paths from the config variable \a var
+ are canonicalized. If \a flags contains \c Validate, outputs a warning
+ for invalid paths. The \c IncludePaths flag is used as a hint to strip
+ away potential prefixes found in include paths before attempting to
+ canonicalize.
+
+ \note The internal location is updated to the location of \a var for
+ the purposes of error reporting.
*/
-QStringList Config::getCanonicalPathList(const QString &var, bool validate) const
+QStringList Config::getCanonicalPathList(const QString &var, PathFlags flags) const
{
QStringList result;
const auto &configVar = m_configVars.value(var);
@@ -586,19 +609,47 @@ QStringList Config::getCanonicalPathList(const QString &var, bool validate) cons
for (const auto &value : configVar.m_values) {
const QString &currentPath = value.m_path;
- QDir dir(value.m_value.simplified());
+ QString rawValue = value.m_value.simplified();
+ QString prefix;
+
+ if (flags & IncludePaths) {
+ const QStringList prefixes = QStringList()
+ << QLatin1String("-I")
+ << QLatin1String("-F")
+ << QLatin1String("-isystem");
+ const auto end = std::end(prefixes);
+ const auto it =
+ std::find_if(std::begin(prefixes), end,
+ [&rawValue](const QString &p) {
+ return rawValue.startsWith(p);
+ });
+ if (it != end) {
+ prefix = *it;
+ rawValue.remove(0, it->size());
+ if (rawValue.isEmpty())
+ continue;
+ } else {
+ prefix = prefixes[0]; // -I as default
+ }
+ }
+
+ QDir dir(rawValue.trimmed());
const QString path = dir.path();
if (dir.isRelative())
dir.setPath(currentPath + QLatin1Char('/') + path);
- if (validate && !QFileInfo::exists(dir.path()))
+ if ((flags & Validate) && !QFileInfo::exists(dir.path()))
m_lastLocation.warning(QStringLiteral("Cannot find file or directory: %1").arg(path));
else {
const QString canonicalPath = dir.canonicalPath();
if (!canonicalPath.isEmpty())
- result.append(canonicalPath);
+ result.append(prefix + canonicalPath);
else if (path.contains(QLatin1Char('*')) || path.contains(QLatin1Char('?')))
result.append(path);
+ else
+ qCDebug(lcQdoc) <<
+ qUtf8Printable(QStringLiteral("%1: Ignored nonexistent path \'%2\'")
+ .arg(m_lastLocation.toString()).arg(rawValue));
}
}
return result;
@@ -607,8 +658,8 @@ QStringList Config::getCanonicalPathList(const QString &var, bool validate) cons
/*!
Calls getRegExpList() with the control variable \a var and
iterates through the resulting list of regular expressions,
- concatening them with some extras characters to form a single
- QRegularExpression, which is returned/
+ concatenating them with extra characters to form a single
+ QRegularExpression, which is then returned.
\sa getRegExpList()
*/
@@ -712,8 +763,8 @@ QStringList Config::getAllFiles(const QString &filesVar, const QString &dirsVar,
const QSet<QString> &excludedDirs,
const QSet<QString> &excludedFiles)
{
- QStringList result = getCanonicalPathList(filesVar, true);
- const QStringList dirs = getCanonicalPathList(dirsVar, true);
+ QStringList result = getCanonicalPathList(filesVar, Validate);
+ const QStringList dirs = getCanonicalPathList(dirsVar, Validate);
const QString nameFilter = getString(filesVar + dot + CONFIG_FILEEXTENSIONS);
diff --git a/src/qdoc/config.h b/src/qdoc/config.h
index 00c741d9e..e3b8bf9d8 100644
--- a/src/qdoc/config.h
+++ b/src/qdoc/config.h
@@ -118,6 +118,12 @@ public:
enum QDocPass { Neither, Prepare, Generate };
+ enum PathFlags : unsigned char {
+ None = 0x0,
+ Validate = 0x1,
+ IncludePaths = 0x2
+ };
+
void init(const QString &programName, const QStringList &args);
[[nodiscard]] bool getDebug() const { return m_debug; }
[[nodiscard]] bool showInternal() const { return m_showInternal; }
@@ -142,7 +148,8 @@ public:
const QString &defaultString = QString()) const;
[[nodiscard]] QSet<QString> getStringSet(const QString &var) const;
[[nodiscard]] QStringList getStringList(const QString &var) const;
- [[nodiscard]] QStringList getCanonicalPathList(const QString &var, bool validate = false) const;
+ [[nodiscard]] QStringList getCanonicalPathList(const QString &var,
+ PathFlags flags = None) const;
[[nodiscard]] QRegularExpression getRegExp(const QString &var) const;
[[nodiscard]] QList<QRegularExpression> getRegExpList(const QString &var) const;
[[nodiscard]] QSet<QString> subVars(const QString &var) const;
diff --git a/src/qdoc/doc/qdoc-manual-qdocconf.qdoc b/src/qdoc/doc/qdoc-manual-qdocconf.qdoc
index b1bda2955..c47d49784 100644
--- a/src/qdoc/doc/qdoc-manual-qdocconf.qdoc
+++ b/src/qdoc/doc/qdoc-manual-qdocconf.qdoc
@@ -182,6 +182,7 @@
\li \l {HTML.footer-variable} {HTML.footer}
\li \l {HTML.postheader-variable} {HTML.postheader}
\li \l {HTML.style-variable} {HTML.style}
+ \li \l {includepaths-variable} {includepaths}
\li \l {ignorewords-variable} {ignorewords}
\li \l {ignoresince-variable} {ignoresince}
\li \l {imagedirs-variable} {imagedirs}
@@ -192,6 +193,7 @@
\li \l {locationinfo-variable} {locationinfo}
\li \l {macro-variable} {macro}
\li \l {manifestmeta-variable} {manifestmeta}
+ \li \l {moduleheader-variable} {moduleheader}
\li \l {navigation-variable} {navigation}
\li \l {outputdir-variable} {outputdir}
\li \l {outputformats-variable} {outputformats}
@@ -697,6 +699,28 @@
See also \l headerdirs.
+ \target includepaths-variable
+ \section1 includepaths
+
+ The \c includepaths variable is used for passing additional
+ include paths to the Clang parser that QDoc uses for parsing C++
+ code for documentation comments.
+
+ The variable accepts a list of paths, prefixed with \c{-I} (include
+ path), \c {-F} (\macos framework include path), or \c{-isystem}
+ (system include path). If a prefix is omitted, \c{-I} is used by
+ default.
+
+ Paths relative to the current .qdocconf file are resolved into
+ absolute paths. Paths that do not exist in the file system are
+ ignored.
+
+ \note For Qt documentation projects, the build system typically
+ provides the required include paths as command line
+ arguments when invoking QDoc.
+
+ See also \l moduleheader.
+
\target ignorewords-variable
\section1 ignorewords
@@ -982,6 +1006,52 @@
See the \l{Manifest Meta Content} section for more information.
+ \target moduleheader-variable
+ \section1 moduleheader
+
+ The \c moduleheader variable defines the name of the module
+ header of a documented C++ module.
+
+ Projects that document C++ APIs require a module-level header
+ that includes all public classes, namespaces and header files
+ for the module. The Clang parser in QDoc uses this file to
+ build a pre-compiled header (PCH) for the module to increase
+ the speed of parsing source files.
+
+ By default, the \l{project-variable}{project} name is used
+ also as the module header name.
+
+ \badcode
+ project = QtCore
+ \endcode
+
+ With the above project name, QDoc searches a module header
+ \e QtCore in all known include paths; first using the paths
+ passed as command line arguments, then the paths listed in
+ the \l includepaths variable.
+
+ QDoc will issue a warning if the module header is not found.
+ It will then attempt to build an artificial module header
+ based on the headers listed in the \l {headerdirs-variable}
+ {headerdirs} variable.
+
+ For Qt documentation projects, the build system typically
+ provides QDoc with correct include paths to locate the
+ module header, provided that the \c project variable is set
+ correctly. The \c moduleheader variable provides an
+ alternative file name for QDoc to search for.
+
+ If the project contains no C++ documentation, QDoc should be
+ instructed to skip generating a PCH by setting \c moduleheader
+ to an empty string:
+
+ \badcode
+ # No C++ code to document in this project
+ moduleheader =
+ \endcode
+
+ See also \l includepaths and \l project.
+
\target naturallanguage-variable
\section1 naturallanguage
diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp
index 50904cb49..5f65236f2 100644
--- a/src/qdoc/generator.cpp
+++ b/src/qdoc/generator.cpp
@@ -1715,7 +1715,7 @@ void Generator::initialize()
void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir)
{
Config &config = Config::instance();
- QStringList files = config.getCanonicalPathList(configVar, true);
+ QStringList files = config.getCanonicalPathList(configVar, Config::Validate);
if (!files.isEmpty()) {
QDir dirInfo;
QString templateDir = s_outDir + QLatin1Char('/') + subDir;
diff --git a/src/qdoc/location.h b/src/qdoc/location.h
index dbf603394..bb1b6f01f 100644
--- a/src/qdoc/location.h
+++ b/src/qdoc/location.h
@@ -68,6 +68,7 @@ public:
[[nodiscard]] int lineNo() const { return m_stkTop->m_lineNo; }
[[nodiscard]] int columnNo() const { return m_stkTop->m_columnNo; }
[[nodiscard]] bool etc() const { return m_etc; }
+ [[nodiscard]] QString toString() const;
void warning(const QString &message, const QString &details = QString()) const;
void error(const QString &message, const QString &details = QString()) const;
void fatal(const QString &message, const QString &details = QString()) const;
@@ -92,7 +93,6 @@ private:
friend class QTypeInfo<StackEntry>;
void emitMessage(MessageType type, const QString &message, const QString &details) const;
- [[nodiscard]] QString toString() const;
[[nodiscard]] QString top() const;
private:
diff --git a/tests/auto/qdoc/config/testdata/configs/includepaths.qdocconf b/tests/auto/qdoc/config/testdata/configs/includepaths.qdocconf
new file mode 100644
index 000000000..2d6ff22af
--- /dev/null
+++ b/tests/auto/qdoc/config/testdata/configs/includepaths.qdocconf
@@ -0,0 +1,2 @@
+project = IncludePaths
+include(../includepaths/includepaths.qdocconf)
diff --git a/tests/auto/qdoc/config/testdata/includepaths/include/framework/ignore.h b/tests/auto/qdoc/config/testdata/includepaths/include/framework/ignore.h
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/tests/auto/qdoc/config/testdata/includepaths/include/framework/ignore.h
@@ -0,0 +1 @@
+# nothing here
diff --git a/tests/auto/qdoc/config/testdata/includepaths/include/more/ignore.h b/tests/auto/qdoc/config/testdata/includepaths/include/more/ignore.h
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/tests/auto/qdoc/config/testdata/includepaths/include/more/ignore.h
@@ -0,0 +1 @@
+# nothing here
diff --git a/tests/auto/qdoc/config/testdata/includepaths/include/purpose.h b/tests/auto/qdoc/config/testdata/includepaths/include/purpose.h
new file mode 100644
index 000000000..0f7af352b
--- /dev/null
+++ b/tests/auto/qdoc/config/testdata/includepaths/include/purpose.h
@@ -0,0 +1 @@
+#define PURPOSE "Pass butter"
diff --git a/tests/auto/qdoc/config/testdata/includepaths/include/system/ignore.h b/tests/auto/qdoc/config/testdata/includepaths/include/system/ignore.h
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/tests/auto/qdoc/config/testdata/includepaths/include/system/ignore.h
@@ -0,0 +1 @@
+# nothing here
diff --git a/tests/auto/qdoc/config/testdata/includepaths/includepaths.qdocconf b/tests/auto/qdoc/config/testdata/includepaths/includepaths.qdocconf
new file mode 100644
index 000000000..6288c4258
--- /dev/null
+++ b/tests/auto/qdoc/config/testdata/includepaths/includepaths.qdocconf
@@ -0,0 +1,16 @@
+includepaths = -I./include
+
+# without prefix but same path, should be identical
+# (Config should not remove duplicates)
+includepaths += include
+
+# space between prefix and path - incorrect but we allow it
+includepaths += -I include/more
+
+# system paths and framework paths
+includepaths += \
+ -F./include/framework \
+ -isysteminclude/system
+
+# nonexistent paths are to be ignored
+includepaths += invalid
diff --git a/tests/auto/qdoc/config/tst_config.cpp b/tests/auto/qdoc/config/tst_config.cpp
index 58d8eae15..dddfd0878 100644
--- a/tests/auto/qdoc/config/tst_config.cpp
+++ b/tests/auto/qdoc/config/tst_config.cpp
@@ -43,6 +43,7 @@ private slots:
void includePathsFromCommandLine();
void variables();
void paths();
+ void includepaths();
void getExampleProjectFile();
void expandVars();
@@ -142,6 +143,28 @@ void tst_Config::paths()
QCOMPARE(paths[2], rootDir.absoluteFilePath("configs/includes"));
}
+// Tests whether includepaths are resolved correctly
+void tst_Config::includepaths()
+{
+ auto &config = initConfig();
+ const auto docConfig = QFINDTESTDATA("/testdata/configs/includepaths.qdocconf");
+ if (!docConfig.isEmpty())
+ config.load(docConfig);
+
+ auto rootDir = QFileInfo(docConfig).dir();
+ QVERIFY(rootDir.cdUp());
+
+ const auto paths = config.getCanonicalPathList("includepaths",
+ Config::IncludePaths);
+ QVERIFY(paths.size() == 5);
+
+ QCOMPARE(paths[0], "-I" + rootDir.absoluteFilePath("includepaths/include"));
+ QCOMPARE(paths[0], paths[1]);
+ QCOMPARE(paths[2], "-I" + rootDir.absoluteFilePath("includepaths/include/more"));
+ QCOMPARE(paths[3], "-F" + rootDir.absoluteFilePath("includepaths/include/framework"));
+ QCOMPARE(paths[4], "-isystem" + rootDir.absoluteFilePath("includepaths/include/system"));
+}
+
void::tst_Config::getExampleProjectFile()
{
auto &config = initConfig();