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 "tlStream.h"
#include "tlTimer.h"
#include "tlExpression.h"
#include <cctype>
#include <cstdio>
@ -47,6 +48,8 @@
#include <QCoreApplication>
#include <QDir>
#include <QVector>
#include <QFile>
#include <QFileInfo>
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) };

View File

@ -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 <QString>
#include <QByteArray>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#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

View File

@ -25,6 +25,7 @@
#include "tlString.h"
#include <QDir>
#include <QFileInfo>
#include <QCoreApplication>
#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