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.
This commit is contained in:
klayoutmatthias 2017-08-06 18:18:34 +02:00
parent a8ac6aafe7
commit f8b3c92191
3 changed files with 171 additions and 21 deletions

View File

@ -38,6 +38,7 @@
#include "tlLog.h" #include "tlLog.h"
#include "tlStream.h" #include "tlStream.h"
#include "tlTimer.h" #include "tlTimer.h"
#include "tlExpression.h"
#include <cctype> #include <cctype>
#include <cstdio> #include <cstdio>
@ -47,6 +48,8 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QVector> #include <QVector>
#include <QFile>
#include <QFileInfo>
namespace pya namespace pya
{ {
@ -2252,29 +2255,55 @@ PythonInterpreter::PythonInterpreter ()
const wchar_t *python_path = _wgetenv (L"KLAYOUT_PYTHONPATH"); const wchar_t *python_path = _wgetenv (L"KLAYOUT_PYTHONPATH");
if (python_path) { if (python_path) {
Py_SetPath (python_path); Py_SetPath (python_path);
} else { } else {
// by default take the installation path + "/lib/python" // If present, read the paths from a file in INST_PATH/.python-paths.txt.
// and inside this path take "DLLs", "Lib" and "Lib/site-packages". // The content of this file is evaluated as an expression and the result
// This is compatible with the installation. // is placed inside the Python path.
QDir inst_dir (QCoreApplication::applicationDirPath ());
inst_dir = inst_dir.filePath (tl::to_qstring ("lib")); try {
inst_dir = inst_dir.filePath (tl::to_qstring ("python"));
QDir inst_dir_lib = inst_dir.filePath (tl::to_qstring ("Lib")); QString path;
QString path = inst_dir.absoluteFilePath (tl::to_qstring ("DLLs")) QDir inst_dir (QCoreApplication::applicationDirPath ());
+ tl::to_qstring (";") QFileInfo fi (inst_dir.absoluteFilePath (tl::to_qstring(".python-paths.txt")));
+ inst_dir_lib.absolutePath () if (fi.exists ()) {
+ tl::to_qstring (";")
+ inst_dir_lib.absoluteFilePath (tl::to_qstring ("site-packages"))
;
// note: this is a hack, but linking with toWCharArray fails since wchar_t is treated tl::log << tl::to_string (QObject::tr ("Reading Python path from ")) << tl::to_string (fi.filePath ());
// as a built-in type in our build
Py_SetPath ((const wchar_t *) path.utf16 ());
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 # else
@ -2302,6 +2331,45 @@ PythonInterpreter::PythonInterpreter ()
Py_InitializeEx (0 /*don't set signals*/); 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[] // Set dummy argv[]
// TODO: more? // TODO: more?
char *argv[1] = { make_string (app_path) }; char *argv[1] = { make_string (app_path) };

View File

@ -32,6 +32,8 @@
#include "tlAssert.h" #include "tlAssert.h"
#include "tlLog.h" #include "tlLog.h"
#include "tlTimer.h" #include "tlTimer.h"
#include "tlExpression.h"
#include "tlSystemPaths.h"
#include "rba.h" #include "rba.h"
#include "rbaInspector.h" #include "rbaInspector.h"
@ -50,6 +52,8 @@
#include <QString> #include <QString>
#include <QByteArray> #include <QByteArray>
#include <QDir> #include <QDir>
#include <QFile>
#include <QFileInfo>
#if !defined(HAVE_RUBY_VERSION_CODE) #if !defined(HAVE_RUBY_VERSION_CODE)
# define HAVE_RUBY_VERSION_CODE 10901 # define HAVE_RUBY_VERSION_CODE 10901
@ -1427,6 +1431,15 @@ struct RubyConstDescriptor
extern "C" void ruby_prog_init(); 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 static void
rba_init (RubyInterpreterPrivateData *d) rba_init (RubyInterpreterPrivateData *d)
{ {
@ -1778,6 +1791,45 @@ RubyInterpreter::initialize (int main_argc, char **main_argv, int (*main_func) (
ruby_init (); ruby_init ();
signal (SIGINT, org_sigint); 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 // 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. // the setter does strange things with the process and the argv, specifically argv[0] above.
static VALUE argv0 = Qnil; static VALUE argv0 = Qnil;
@ -1856,10 +1908,7 @@ RubyInterpreter::remove_package_location (const std::string & /*package_path*/)
void void
RubyInterpreter::add_path (const std::string &path) RubyInterpreter::add_path (const std::string &path)
{ {
VALUE pv = rb_gv_get ("$:"); rba_add_path (path);
if (pv != Qnil && TYPE (pv) == T_ARRAY) {
rb_ary_push (pv, rb_str_new (path.c_str (), path.size ()));
}
} }
void void

View File

@ -25,6 +25,7 @@
#include "tlString.h" #include "tlString.h"
#include <QDir> #include <QDir>
#include <QFileInfo>
#include <QCoreApplication> #include <QCoreApplication>
#ifdef _WIN32 #ifdef _WIN32
@ -66,10 +67,42 @@ get_appdata_path ()
return tl::to_string (appdata_klayout_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 std::string
get_inst_path () 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 #ifdef _WIN32