From f8b3c9219113ffb0f0579172ac0b555abcd3544b Mon Sep 17 00:00:00 2001 From: klayoutmatthias Date: Sun, 6 Aug 2017 18:18:34 +0200 Subject: [PATCH] Windows and Ruby/Python for deploymenu This commit deals with the deployment issue on Windows where there is no global Ruby/Python installation and the installer needs to package all required files. The solution is to read the Ruby/Python path from a file that is evaluated upon startup. The installer will install these files together with the executable for Windows. This feature is only enabled on Windows. A specific issue occured: since the location of the file needs to be determined, the path of the executable needs to be known. The Ruby initialization requires the path to be set very early, before QCoreApplication is instantiated. But Qt complains in QCoreApplication::applicationDirPath so that this approach cannot be used for this purpose. --- src/pya/pya.cc | 100 +++++++++++++++++++++++++++++++++------- src/rba/rba.cc | 57 +++++++++++++++++++++-- src/tl/tlSystemPaths.cc | 35 +++++++++++++- 3 files changed, 171 insertions(+), 21 deletions(-) diff --git a/src/pya/pya.cc b/src/pya/pya.cc index 111ad9ca0..67cdb2857 100644 --- a/src/pya/pya.cc +++ b/src/pya/pya.cc @@ -38,6 +38,7 @@ #include "tlLog.h" #include "tlStream.h" #include "tlTimer.h" +#include "tlExpression.h" #include #include @@ -47,6 +48,8 @@ #include #include #include +#include +#include namespace pya { @@ -2252,29 +2255,55 @@ PythonInterpreter::PythonInterpreter () const wchar_t *python_path = _wgetenv (L"KLAYOUT_PYTHONPATH"); if (python_path) { + Py_SetPath (python_path); + } else { - // by default take the installation path + "/lib/python" - // and inside this path take "DLLs", "Lib" and "Lib/site-packages". - // This is compatible with the installation. - QDir inst_dir (QCoreApplication::applicationDirPath ()); - inst_dir = inst_dir.filePath (tl::to_qstring ("lib")); - inst_dir = inst_dir.filePath (tl::to_qstring ("python")); + // If present, read the paths from a file in INST_PATH/.python-paths.txt. + // The content of this file is evaluated as an expression and the result + // is placed inside the Python path. + + try { - QDir inst_dir_lib = inst_dir.filePath (tl::to_qstring ("Lib")); + QString path; - QString path = inst_dir.absoluteFilePath (tl::to_qstring ("DLLs")) - + tl::to_qstring (";") - + inst_dir_lib.absolutePath () - + tl::to_qstring (";") - + inst_dir_lib.absoluteFilePath (tl::to_qstring ("site-packages")) - ; + QDir inst_dir (QCoreApplication::applicationDirPath ()); + QFileInfo fi (inst_dir.absoluteFilePath (tl::to_qstring(".python-paths.txt"))); + if (fi.exists ()) { - // note: this is a hack, but linking with toWCharArray fails since wchar_t is treated - // as a built-in type in our build - Py_SetPath ((const wchar_t *) path.utf16 ()); + tl::log << tl::to_string (QObject::tr ("Reading Python path from ")) << tl::to_string (fi.filePath ()); + QFile paths_txt (fi.filePath ()); + paths_txt.open (QIODevice::ReadOnly); + + tl::Eval eval; + eval.set_global_var ("inst_path", tl::Variant (tl::to_string (inst_dir.absolutePath ()))); + tl::Expression ex; + eval.parse (ex, paths_txt.readAll ().constData ()); + tl::Variant v = ex.execute (); + + if (v.is_list ()) { + for (tl::Variant::iterator i = v.begin (); i != v.end (); ++i) { + if (! path.isEmpty ()) { + path += tl::to_qstring (";"); + } + path += tl::to_qstring (i->to_string ()); + } + } + + } + + // note: this is a hack, but linking with toWCharArray fails since wchar_t is treated + // as a built-in type in our build + Py_SetPath ((const wchar_t *) path.utf16 ()); + + } catch (tl::Exception &ex) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")) << ": " << ex.msg (); + } catch (...) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")); + } + } # else @@ -2302,6 +2331,45 @@ PythonInterpreter::PythonInterpreter () Py_InitializeEx (0 /*don't set signals*/); + if (add_path_from_file) { + + // If present, read the paths from a file in INST_PATH/.python-paths.txt. + // The content of this file is evaluated as an expression and the result + // is placed inside the Python path. + + try { + + QDir inst_dir (QCoreApplication::applicationDirPath ()); + QFileInfo fi (inst_dir.absoluteFilePath (tl::to_qstring(".python-paths.txt"))); + if (fi.exists ()) { + + tl::log << tl::to_string (QObject::tr ("Reading Python path from ")) << tl::to_string (fi.filePath ()); + + QFile paths_txt (fi.filePath ()); + paths_txt.open (QIODevice::ReadOnly); + + tl::Eval eval; + eval.set_global_var ("inst_path", tl::Variant (tl::to_string (inst_dir.absolutePath ()))); + tl::Expression ex; + eval.parse (ex, paths_txt.readAll ().constData ()); + tl::Variant v = ex.execute (); + + if (v.is_list ()) { + for (tl::Variant::iterator i = v.begin (); i != v.end (); ++i) { + add_path (i->to_string ()); + } + } + + } + + } catch (tl::Exception &ex) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")) << ": " << ex.msg (); + } catch (...) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Python path expression failed")); + } + + } + // Set dummy argv[] // TODO: more? char *argv[1] = { make_string (app_path) }; diff --git a/src/rba/rba.cc b/src/rba/rba.cc index 123682eeb..4744069f1 100644 --- a/src/rba/rba.cc +++ b/src/rba/rba.cc @@ -32,6 +32,8 @@ #include "tlAssert.h" #include "tlLog.h" #include "tlTimer.h" +#include "tlExpression.h" +#include "tlSystemPaths.h" #include "rba.h" #include "rbaInspector.h" @@ -50,6 +52,8 @@ #include #include #include +#include +#include #if !defined(HAVE_RUBY_VERSION_CODE) # define HAVE_RUBY_VERSION_CODE 10901 @@ -1427,6 +1431,15 @@ struct RubyConstDescriptor extern "C" void ruby_prog_init(); +static void +rba_add_path (const std::string &path) +{ + VALUE pv = rb_gv_get ("$:"); + if (pv != Qnil && TYPE (pv) == T_ARRAY) { + rb_ary_push (pv, rb_str_new (path.c_str (), path.size ())); + } +} + static void rba_init (RubyInterpreterPrivateData *d) { @@ -1778,6 +1791,45 @@ RubyInterpreter::initialize (int main_argc, char **main_argv, int (*main_func) ( ruby_init (); signal (SIGINT, org_sigint); +#if defined(_WIN32) + + // On Windows we derive additional path components from a file called ".ruby-paths.txt" + // inside the installation directory. This way, the installer can copy the deployment-time + // installation easier. + + try { + + QDir inst_dir (tl::to_qstring (tl::get_inst_path ())); + QFileInfo fi (inst_dir.absoluteFilePath (tl::to_qstring(".ruby-paths.txt"))); + if (fi.exists ()) { + + tl::log << tl::to_string (QObject::tr ("Reading Ruby path from ")) << tl::to_string (fi.filePath ()); + + QFile paths_txt (fi.filePath ()); + paths_txt.open (QIODevice::ReadOnly); + + tl::Eval eval; + eval.set_global_var ("inst_path", tl::Variant (tl::to_string (inst_dir.absolutePath ()))); + tl::Expression ex; + eval.parse (ex, paths_txt.readAll ().constData ()); + tl::Variant v = ex.execute (); + + if (v.is_list ()) { + for (tl::Variant::iterator i = v.begin (); i != v.end (); ++i) { + rba_add_path (i->to_string ()); + } + } + + } + + } catch (tl::Exception &ex) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Ruby path expression failed")) << ": " << ex.msg (); + } catch (...) { + tl::error << tl::to_string (QObject::tr ("Evaluation of Ruby path expression failed")); + } + +#endif + // Remove setters for $0 and $PROGRAM_NAME (still both are linked) because // the setter does strange things with the process and the argv, specifically argv[0] above. static VALUE argv0 = Qnil; @@ -1856,10 +1908,7 @@ RubyInterpreter::remove_package_location (const std::string & /*package_path*/) void RubyInterpreter::add_path (const std::string &path) { - VALUE pv = rb_gv_get ("$:"); - if (pv != Qnil && TYPE (pv) == T_ARRAY) { - rb_ary_push (pv, rb_str_new (path.c_str (), path.size ())); - } + rba_add_path (path); } void diff --git a/src/tl/tlSystemPaths.cc b/src/tl/tlSystemPaths.cc index 75343718e..4e3b66097 100644 --- a/src/tl/tlSystemPaths.cc +++ b/src/tl/tlSystemPaths.cc @@ -25,6 +25,7 @@ #include "tlString.h" #include +#include #include #ifdef _WIN32 @@ -66,10 +67,42 @@ get_appdata_path () return tl::to_string (appdata_klayout_path); } +static std::string +get_inst_path_internal () +{ + // Note: the QCoreApplication::applicationDirPath cannot be used before + // a QApplication object is instantiated. Hence this custom implementation. +#ifdef _WIN32 + + wchar_t buffer[MAX_PATH]; + int len; + if ((len = GetModuleFileName(NULL, buffer, MAX_PATH)) > 0) { + QFileInfo fi (QString::fromUtf16 ((const ushort *) buffer, len)); + return tl::to_string (fi.absolutePath ()); + } + +#else + + QFileInfo proc_exe (tl::sprintf ("/proc/%d/exe"), getpid ()); + if (proc_exe.exists () && proc_exe.isSymLink ()) { + return QFileInfo (proc_exe.canonicalFilePath ()).absolutePath (); + } + +#endif + + // As a fallback use QCoreApplication::applicationDirPath, which however it not + // available before QCoreApplication is initialized + return tl::to_string (QCoreApplication::applicationDirPath ()); +} + std::string get_inst_path () { - return tl::to_string (QCoreApplication::applicationDirPath ()); + static std::string s_inst_path; + if (s_inst_path.empty ()) { + s_inst_path = get_inst_path_internal (); + } + return s_inst_path; } #ifdef _WIN32