-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open and load --code file and --py-args arguments in C++ #60134
base: master
Are you sure you want to change the base?
Conversation
@btzy when we introduced @btzy If you need arguments valid only in the context of the |
No strong opinion. What would be the reason to not make it also available for other python code than |
@domi4484 I misunderstood the help string of |
I'm not sure how to trigger the automatic Windows build that other PRs have; can someone help me get the GitHub Action to run? |
Separately, I made #60183 to fix the grammar error in the help, which was what originally misled me to think that |
ef2a7f6
to
20e2318
Compare
🪟 Windows buildsDownload Windows builds of this PR for testing. 🪟 Windows Qt6 buildsDownload Windows Qt6 builds of this PR for testing. |
64c5dda
to
1f44ac4
Compare
I've tested that the Windows build works:
|
@m-kuhn Can I get a review for this? |
The QGIS project highly values your contribution and would love to see this work merged! Unfortunately this PR has not had any activity in the last 14 days and is being automatically marked as "stale". If you think this pull request should be merged, please check
|
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) | ||
{ | ||
ret = "Cannot open file"; | ||
goto error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid goto
if ( !argsobj ) | ||
{ | ||
ret = "Error occurred in PyList_New"; | ||
goto error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
ret = QString( "SetArgvTraceback" ) + getTraceback(); | ||
else | ||
ret = "Error occurred in PyImport_ImportModule"; | ||
goto error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you like me to rewrite this code? We need to execute the cleanup code at the end whether or not this function succeeds.
The most C++-ic way to do this is to write an RAII wrapper class around PyGILState_Ensure/PyGILState_Release and PyObject/Py_XDECREF. Or for a more ad-hoc mechanism, I might have used the equivalent of std::experimental::scope_exit
] (both Folly and Abseil have this in some form, but QGIS uses neither of them). I would have written code in one of the above two ways if this was for my own project, but I do not see existing code in QGIS that leverages either of them.
If the above solutions are unacceptable, we would have to either invert the guard clauses into nested if-statements, which is generally considered to be bad practice. We are thus left with goto statements, which seem to be an idiom for writing cleanup code in C.
What would be most appropriate for the QGIS codebase? Writing my own scope guard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the slow feedback, time is very limited recently!
I realize that we still do have some goto:
in this file, so you are building on precendence in which case I do think this is acceptable.
What I did in the past was introducing a custom deleter for std::unique_ptr
QGIS/src/core/qgssqliteutils.h
Line 67 in 41d5fc2
class CORE_EXPORT sqlite3_statement_unique_ptr : public std::unique_ptr< sqlite3_stmt, QgsSqlite3StatementFinalizer> |
QGIS/src/core/qgssqliteutils.cpp
Lines 37 to 40 in 41d5fc2
void QgsSqlite3StatementFinalizer::operator()( sqlite3_stmt *statement ) const | |
{ | |
sqlite3_finalize( statement ); | |
} |
Something along these lines would be great to have (something similar to https://pythonextensionpatterns.readthedocs.io/en/latest/cpp_and_cpython.html ) but is of bigger scope.
So, this is good to merge, but I would love to hear your thoughts about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read through section 20.1 of your linked webpage.
What they call DecRefDtor
is exactly what I mean when I said "an RAII wrapper class around ... PyObject/Py_XDECREF". I would perhaps just change the implicit conversion to PyObject*
to operator*
and operator->
like how std::unique_ptr
does it, because having the implicit conversion makes it too easy to perform that conversion by accident (and also, once we start using DecRefDtor
everywhere, we'd start to see plain PyObject*
as something to be discouraged, and so we shouldn't make it easy to convert to it).
Their BorrowedRef
and NewRef
are pointless and dangerous though - they're dangerous because you'd be able to cast a BorrowedRef
into a DecRefDtor
(which people will be tempted to do, to avoid writing one version of a function for BorrowedRef
and another version for NewRef
) and end up with slicing, and they're pointless because BorrowedRef
's functionality can be written as a copy constructor for DecRefDtor
, and NewRef
's functionality is already in DecRefDtor
(it already has a constructor taking a PyObject*
). Another way to see it is that you might have multiple of these DecRefDtor
instances referencing the same PyObject, but since they're all equivalent, there's no reason at all to encode in the type system that one was constructed by a borrow while the other was constructed fresh.
I think using std::unique_ptr
with a custom deleter is nice; I did not think of that, and I think it would be quite appropriate for wrapping a PyObject (unless we want shared ownership - but we don't need that here, and it's quite rare to actually need shared ownership in properly written C++ code). I would write it as an alias, like using UniquePyObject = std::unique_ptr<PyObject, decltype([](PyObject* obj) { Py_XDECREF(obj); })>;
, instead of using inheritance, because there's no need to inherit. Whether we would want just a UniquePyObject
alias or a full-fledged DecRefDtor
(i.e. that is copyable, and hence behaves more like a shared_ptr) is probably a matter of preference - UniquePyObject
is simpler to write and reason about, but a full-fledged DecRefDtor
abstracts the underlying Python API more fully without any extra overhead (as the Python API already has built-int ref-counting which we can't opt out of).
IMO your use of public inheritance in the definition of sqlite3_statement_unique_ptr
is a code smell - you probably don't need or want the user of this class to twiddle with the std::unique_ptr
inside it directly, and so I would go with composition instead (and add a get()
member function that returns a plain sqlite3_stmt*
). Inheritance may be justified to save characters if sqlite3_stmt
had many member functions that you would like users to call directly, but it seems to be an opaque struct in a C library, so this isn't the case.
I would be happy to write a version of DecRefDtor
if it's something you want for QGIS, but it's a separate change and so should not be combined into this PR.
Thanks, using sys.argv does look like the correct approach! |
The QGIS project highly values your contribution and would love to see this work merged! Unfortunately this PR has not had any activity in the last 14 days and is being automatically marked as "stale". If you think this pull request should be merged, please check
|
This PR is ready for review. |
The QGIS project highly values your contribution and would love to see this work merged! Unfortunately this PR has not had any activity in the last 14 days and is being automatically marked as "stale". If you think this pull request should be merged, please check
|
Ready for review |
Description
I took a stab at #60120 to see if we can design a better alternative to #60121.
Instead of doing string substitution into Python code, we use the proper Python C API functions to assign to
sys.argv
and execute a Python script from a file. This avoids the issues that arise due to improper escaping of strings (e.g.'
and\
). Unix file names in particular may contain any non-null character (including non-printable characters), and trying to escape such characters manually would otherwise be difficult to get right. String substitution is also possibly vulnerable to code injection attacks, as it is possible to carefully craft a string containing code that will be executed.There was a hack that converts Windows-style directory separators to Unix-style directory separators. This is no longer necessary, and therefore has been removed.
Fixes #60120.