uic: Add option for absolute Python resource imports

Add option that generates an absolute Python import.

import resources.rc_resources

from a path like

../resources/resources.qrc

assuming the project root is .. .

Add an additional option to specify the import paths, from which
the project root can be determined.

Pick-to: 6.5
Task-number: PYSIDE-2191
Change-Id: Ib444eb666217b8c010dba0079b0ffe9ddbaa3414
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io>
This commit is contained in:
Friedemann Kleint 2023-01-13 13:43:44 +01:00
parent d4f72b4de6
commit 814d66d558
4 changed files with 108 additions and 27 deletions

View File

@ -13,11 +13,42 @@
#include <qcoreapplication.h>
#include <qcommandlineoption.h>
#include <qcommandlineparser.h>
#include <qfileinfo.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static const char pythonPathVar[] = "PYTHONPATH";
// From the Python paths, find the component the UI file is under
static QString pythonRoot(const QString &pythonPath, const QString &uiFileIn)
{
#ifdef Q_OS_WIN
static const Qt::CaseSensitivity fsSensitivity = Qt::CaseInsensitive;
#else
static const Qt::CaseSensitivity fsSensitivity = Qt::CaseSensitive;
#endif
if (pythonPath.isEmpty() || uiFileIn.isEmpty())
return {};
const QString uiFile = QFileInfo(uiFileIn).canonicalFilePath();
if (uiFile.isEmpty())
return {};
const auto uiFileSize = uiFile.size();
const auto paths = pythonPath.split(QDir::listSeparator(), Qt::SkipEmptyParts);
for (const auto &path : paths) {
const QString canonicalPath = QFileInfo(path).canonicalFilePath();
const auto canonicalPathSize = canonicalPath.size();
if (uiFileSize > canonicalPathSize
&& uiFile.at(canonicalPathSize) == u'/'
&& uiFile.startsWith(canonicalPath, fsSensitivity)) {
return canonicalPath;
}
}
return {};
}
int runUic(int argc, char *argv[])
{
QHashSeed::setDeterministicGlobalSeed();
@ -90,6 +121,10 @@ int runUic(int argc, char *argv[])
fromImportsOption.setDescription(u"Python: generate imports relative to '.'"_s);
parser.addOption(fromImportsOption);
QCommandLineOption absoluteImportsOption(u"absolute-imports"_s);
absoluteImportsOption.setDescription(u"Python: generate absolute imports"_s);
parser.addOption(absoluteImportsOption);
// FIXME Qt 7: Flip the default?
QCommandLineOption rcPrefixOption(u"rc-prefix"_s);
rcPrefixOption.setDescription(uR"(Python: Generate "rc_file" instead of "file_rc" import)"_s);
@ -100,6 +135,11 @@ int runUic(int argc, char *argv[])
useStarImportsOption.setDescription(u"Python: Use * imports"_s);
parser.addOption(useStarImportsOption);
QCommandLineOption pythonPathOption(u"python-paths"_s);
pythonPathOption.setDescription(u"Python paths for --absolute-imports."_s);
pythonPathOption.setValueName(u"pathlist"_s);
parser.addOption(pythonPathOption);
parser.addPositionalArgument(u"[uifile]"_s, u"Input file (*.ui), otherwise stdin."_s);
parser.process(app);
@ -130,10 +170,19 @@ int runUic(int argc, char *argv[])
}
language::setLanguage(language);
if (language == Language::Python) {
driver.option().fromImports = parser.isSet(fromImportsOption);
if (parser.isSet(fromImportsOption))
driver.option().pythonResourceImport = Option::PythonResourceImport::FromDot;
else if (parser.isSet(absoluteImportsOption))
driver.option().pythonResourceImport = Option::PythonResourceImport::Absolute;
driver.option().useStarImports = parser.isSet(useStarImportsOption);
if (parser.isSet(rcPrefixOption))
driver.option().rcPrefix = 1;
QString pythonPaths;
if (parser.isSet(pythonPathOption))
pythonPaths = parser.value(pythonPathOption);
else if (qEnvironmentVariableIsSet(pythonPathVar))
pythonPaths = QString::fromUtf8(qgetenv(pythonPathVar));
driver.option().pythonRoot = pythonRoot(pythonPaths, inputFile);
}
if (inputFile.isEmpty()) // reading from stdin

View File

@ -11,6 +11,12 @@ QT_BEGIN_NAMESPACE
struct Option
{
enum class PythonResourceImport {
Default, // "import rc_file"
FromDot, // "from . import rc_file"
Absolute // "import path.rc_file"
};
unsigned int headerProtection : 1;
unsigned int copyrightHeader : 1;
unsigned int generateImplemetation : 1;
@ -20,7 +26,6 @@ struct Option
unsigned int limitXPM_LineLength : 1;
unsigned int implicitIncludes: 1;
unsigned int idBased: 1;
unsigned int fromImports: 1;
unsigned int forceMemberFnPtrConnectionSyntax: 1;
unsigned int forceStringConnectionSyntax: 1;
unsigned int useStarImports: 1;
@ -34,6 +39,9 @@ struct Option
QString postfix;
QString translateFunction;
QString includeFile;
QString pythonRoot;
PythonResourceImport pythonResourceImport = PythonResourceImport::Default;
Option()
: headerProtection(1),
@ -45,7 +53,6 @@ struct Option
limitXPM_LineLength(0),
implicitIncludes(1),
idBased(0),
fromImports(0),
forceMemberFnPtrConnectionSyntax(0),
forceStringConnectionSyntax(0),
useStarImports(0),

View File

@ -10,6 +10,8 @@
#include <ui4.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qtextstream.h>
#include <algorithm>
@ -54,23 +56,6 @@ static WriteImports::ClassesPerModule defaultClasses()
};
}
// Change the name of a qrc file "dir/foo.qrc" file to the Python
// module name "foo_rc" according to project conventions.
static QString pythonResource(QString resource, bool prefix)
{
const qsizetype lastSlash = resource.lastIndexOf(u'/');
if (lastSlash != -1)
resource.remove(0, lastSlash + 1);
if (resource.endsWith(".qrc"_L1)) {
resource.chop(4);
if (prefix)
resource.prepend("rc_"_L1);
else
resource.append("_rc"_L1);
}
return resource;
}
// Helpers for WriteImports::ClassesPerModule maps
static void insertClass(const QString &module, const QString &className,
WriteImports::ClassesPerModule *c)
@ -143,18 +128,57 @@ void WriteImports::acceptUI(DomUI *node)
const auto includes = resources->elementInclude();
for (auto include : includes) {
if (include->hasAttributeLocation())
writeImport(pythonResource(include->attributeLocation(),
uic()->option().rcPrefix));
writeResourceImport(include->attributeLocation());
}
output << '\n';
}
}
void WriteImports::writeImport(const QString &module)
QString WriteImports::resourceAbsolutePath(QString resource) const
{
if (uic()->option().fromImports)
uic()->output() << "from . ";
uic()->output() << "import " << module << '\n';
// If we know the project root, generate an absolute Python import
// to the resource. options. pythonRoot is the Python path component
// under which the UI file is.
const auto &options = uic()->option();
if (!options.inputFile.isEmpty() && !options.pythonRoot.isEmpty()) {
resource = QDir::cleanPath(QFileInfo(options.inputFile).canonicalPath() + u'/' + resource);
if (resource.size() > options.pythonRoot.size())
resource.remove(0, options.pythonRoot.size() + 1);
}
// If nothing is known, we assume the directory pointed by "../" is the root
while (resource.startsWith(u"../"))
resource.remove(0, 3);
resource.replace(u'/', u'.');
return resource;
}
void WriteImports::writeResourceImport(const QString &module)
{
const auto &options = uic()->option();
auto &str = uic()->output();
QString resource = QDir::cleanPath(module);
if (resource.endsWith(u".qrc"))
resource.chop(4);
const qsizetype basePos = resource.lastIndexOf(u'/') + 1;
// Change the name of a qrc file "dir/foo.qrc" file to the Python
// module name "foo_rc" according to project conventions.
if (options.rcPrefix)
resource.insert(basePos, u"rc_");
else
resource.append(u"_rc");
switch (options.pythonResourceImport) {
case Option::PythonResourceImport::Default:
str << "import " << QStringView{resource}.sliced(basePos) << '\n';
break;
case Option::PythonResourceImport::FromDot:
str << "from . import " << QStringView{resource}.sliced(basePos) << '\n';
break;
case Option::PythonResourceImport::Absolute:
str << "import " << resourceAbsolutePath(resource) << '\n';
break;
}
}
void WriteImports::doAdd(const QString &className, const DomCustomWidget *dcw)

View File

@ -31,7 +31,8 @@ private:
void addPythonCustomWidget(const QString &className, const DomCustomWidget *dcw);
bool addQtClass(const QString &className);
void addEnumBaseClass(const QString &v);
void writeImport(const QString &module);
void writeResourceImport(const QString &module);
QString resourceAbsolutePath(QString resource) const;
QHash<QString, QString> m_classToModule;
// Module->class (modules sorted)