mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8279b22f42 | ||
|
|
83f9d44fc2 | ||
|
|
f0ae40fbfa | ||
|
|
02d51156cd | ||
|
|
3c99576313 | ||
|
|
8837306bb6 | ||
|
|
04c5a77d7e | ||
|
|
85437c8c3d | ||
|
|
15b5b596a0 | ||
|
|
a07180b960 | ||
|
|
815e36e463 | ||
|
|
c49fb3acf3 | ||
|
|
f5232e5cb9 | ||
|
|
5d6ba9d868 | ||
|
|
c6a134adb3 | ||
|
|
56944bc09b | ||
|
|
4ee0562f56 | ||
|
|
394591c805 | ||
|
|
d72ea80f15 | ||
|
|
ee6ff308be | ||
|
|
2f9d74d759 | ||
|
|
07ef48ec06 | ||
|
|
4a33fd5f38 | ||
|
|
8290a19b47 | ||
|
|
653199b8ee | ||
|
|
96c9d9c6af | ||
|
|
8e81ed9c64 |
16
.travis.yml
16
.travis.yml
@@ -1,5 +1,6 @@
|
||||
branches:
|
||||
- master
|
||||
osx_image: xcode7.2b1 # will also get OSX El Capitan
|
||||
language:
|
||||
- objective-c
|
||||
compiler:
|
||||
@@ -7,8 +8,8 @@ compiler:
|
||||
- gcc
|
||||
env:
|
||||
matrix:
|
||||
## For now, but build with QT5
|
||||
## - BRANCH=master QT=qt4 QT_PATH=qt
|
||||
## Test 4.8 or 5.4.2
|
||||
- BRANCH=master QT=qt4 QT_PATH=qt
|
||||
- BRANCH=master QT=qt5 QT_PATH=qt5
|
||||
global:
|
||||
- secure: iqYW7f3//ZkMVzeCEarYn0S0DqKjFU9juBh0KF6WTlUsKX902Jtsk7dFoJlNDYBf63HLgV+wW2Hc6MxI9sGiUkom0gY9/To/aeGIJFGEX2sLm/e0Ok3qN521FA0Q/OiCFsD0RC6J+yrHxzI+rf8Z1rujceUsz2KgsrfAjYYv+BY=
|
||||
@@ -20,7 +21,8 @@ env:
|
||||
before_install:
|
||||
- date
|
||||
- brew update
|
||||
- sh travis/install-qt.sh
|
||||
##- sh travis/install-qt.sh
|
||||
- brew install $QT
|
||||
- brew install libical
|
||||
- brew install libusb libusb-compat
|
||||
- brew install srmio
|
||||
@@ -77,7 +79,6 @@ before_script:
|
||||
- sed -i "" "s|#\(SAMPLERATE_LIBS =\).*|\1 -L/usr/local/lib -lsamplerate|" src/gcconfig.pri
|
||||
- sed -i "" "s|#\(DEFINES += GC_HAVE_LION*\)|\1|" src/gcconfig.pri
|
||||
- sed -i "" "s|#\(HTPATH = ../httpserver.*\)|\1 |" src/gcconfig.pri
|
||||
- sed -i "" "s|#\(DEFINES += GC_DROPBOX*\)|\1 |" src/gcconfig.pri
|
||||
- sed -i "" "s|#\(DEFINES += GC_WANT_ROBOT.*\)|\1 |" src/gcconfig.pri
|
||||
- sed -i "" "s|\(DEFINES += GC_VIDEO_NONE.*\)|#\1 |" src/gcconfig.pri
|
||||
- sed -i "" "s|#\(DEFINES += GC_VIDEO_QUICKTIME.*\)|\1 |" src/gcconfig.pri
|
||||
@@ -95,16 +96,15 @@ script:
|
||||
- CC=clang CXX=clang++ make -j4 sub-src
|
||||
after_success:
|
||||
- cd src
|
||||
#fix for libcocoa.dylib issue with qt5.4.2
|
||||
- mkdir -p GoldenCheetah.app/Contents/MacOS/platforms
|
||||
- cp /usr/local/opt/$QT_PATH/plugins/platforms/libqcocoa.dylib GoldenCheetah.app/Contents/MacOS/platforms/
|
||||
- ls -laR GoldenCheetah.app
|
||||
- sh ../travis/check-gc.sh
|
||||
- /usr/local/opt/$QT_PATH/bin/macdeployqt GoldenCheetah.app -verbose=2 -dmg
|
||||
## fix up the bundle with macdeployqtfix
|
||||
- python ../travis/macdeployqtfix.py GoldenCheetah.app /usr/local/opt/$QT_PATH
|
||||
- brew remove $QT
|
||||
- mv GoldenCheetah.dmg GoldenCheetah_$QT.dmg
|
||||
- hdiutil mount GoldenCheetah_$QT.dmg
|
||||
- cd /Volumes/GoldenCheetah_$QT
|
||||
- cd /Volumes/GoldenCheetah
|
||||
- ls -laR GoldenCheetah.app
|
||||
- GoldenCheetah.app/Contents/MacOS/GoldenCheetah --help
|
||||
|
||||
|
||||
@@ -340,6 +340,8 @@ ANT::setGradient(double gradient)
|
||||
sendMessage(ANTMessage::fecSetTrackResistance(fecChannel, gradient, currentRollingResistance));
|
||||
currentGradient = gradient;
|
||||
// TODO : obtain acknowledge / confirm value using fecRequestCommandStatus
|
||||
// TODO : if trainer does not have simulation capabilities, use power mode & let GC calculate
|
||||
// the desired load based on gradient, wind, rolling resistance...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -586,17 +586,19 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
|
||||
case CHANNEL_TYPE_CADENCE:
|
||||
{
|
||||
float rpm;
|
||||
static float last_measured_rpm;
|
||||
uint16_t time = antMessage.crankMeasurementTime - lastMessage.crankMeasurementTime;
|
||||
uint16_t revs = antMessage.crankRevolutions - lastMessage.crankRevolutions;
|
||||
if (time) {
|
||||
rpm = 1024*60*revs / time;
|
||||
last_measured_rpm = rpm;
|
||||
lastMessageTimestamp = QTime::currentTime();
|
||||
} else {
|
||||
int ms = lastMessageTimestamp.msecsTo(QTime::currentTime());
|
||||
rpm = qMin((float)(1000.0*60.0*1.0) / ms, parent->getCadence());
|
||||
// If we received a message but timestamp remain unchanged then we know that sensor have not detected magnet thus we deduct that rpm cannot be higher than this
|
||||
if (rpm < (float) 4.0)
|
||||
rpm = 0.0; // if rpm is less than 4 then we consider that we are stopped
|
||||
if (rpm < last_measured_rpm / 2.0)
|
||||
rpm = 0.0; // if rpm is less than half previous cadence we consider that we are stopped
|
||||
}
|
||||
parent->setCadence(rpm);
|
||||
value2 = value = rpm;
|
||||
@@ -607,11 +609,13 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
|
||||
case CHANNEL_TYPE_SandC:
|
||||
{
|
||||
float rpm;
|
||||
static float last_measured_rpm;
|
||||
// cadence first...
|
||||
uint16_t time = antMessage.crankMeasurementTime - lastMessage.crankMeasurementTime;
|
||||
uint16_t revs = antMessage.crankRevolutions - lastMessage.crankRevolutions;
|
||||
if (time) {
|
||||
rpm = 1024*60*revs / time;
|
||||
last_measured_rpm = rpm;
|
||||
|
||||
if (is_moxy) /* do nothing for now */ ; //XXX fixme when moxy arrives XXX
|
||||
else parent->setCadence(rpm);
|
||||
@@ -620,8 +624,8 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
|
||||
int ms = lastMessageTimestamp.msecsTo(QTime::currentTime());
|
||||
rpm = qMin((float)(1000.0*60.0*1.0) / ms, parent->getCadence());
|
||||
// If we received a message but timestamp remain unchanged then we know that sensor have not detected magnet thus we deduct that rpm cannot be higher than this
|
||||
if (rpm < (float) 4.0)
|
||||
rpm = 0.0; // if rpm is less than 4 then we consider that we are stopped
|
||||
if (rpm < last_measured_rpm / 2.0)
|
||||
rpm = 0.0; // if rpm is less than half previous cadence we consider that we are stopped
|
||||
parent->setCadence(rpm);
|
||||
}
|
||||
value = rpm;
|
||||
@@ -638,8 +642,8 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
|
||||
int ms = lastMessageTimestamp2.msecsTo(QTime::currentTime());
|
||||
rpm = qMin((float)(1000.0*60.0*1.0) / ms, parent->getWheelRpm());
|
||||
// If we received a message but timestamp remain unchanged then we know that sensor have not detected magnet thus we deduct that rpm cannot be higher than this
|
||||
if (rpm < (float) 4.0)
|
||||
rpm = 0.0; // if rpm is less than 4 then we consider that we are stopped
|
||||
if (rpm < (float) 15.0)
|
||||
rpm = 0.0; // if rpm is less than 15rpm (=4s) then we consider that we are stopped
|
||||
parent->setWheelRpm(rpm);
|
||||
}
|
||||
value2 = rpm;
|
||||
@@ -659,8 +663,8 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
|
||||
int ms = lastMessageTimestamp.msecsTo(QTime::currentTime());
|
||||
rpm = qMin((float)(1000.0*60.0*1.0) / ms, parent->getWheelRpm());
|
||||
// If we received a message but timestamp remain unchanged then we know that sensor have not detected magnet thus we deduct that rpm cannot be higher than this
|
||||
if (rpm < (float) 4.0)
|
||||
rpm = 0.0; // if rpm is less than 4 then we consider that we are stopped
|
||||
if (rpm < (float) 15.0)
|
||||
rpm = 0.0; // if rpm is less than 15 (4s) then we consider that we are stopped
|
||||
}
|
||||
parent->setWheelRpm(rpm);
|
||||
value2=value=rpm;
|
||||
@@ -859,8 +863,10 @@ void ANTChannel::attemptTransition(int message_id)
|
||||
const ant_sensor_type_t *st;
|
||||
int previous_state=state;
|
||||
st=&(parent->ant_sensor_types[channel_type]);
|
||||
device_id=st->device_id;
|
||||
setId();
|
||||
|
||||
//qDebug()<<number<<"type="<<channel_type<<"device type="<<device_id<<"freq="<<st->frequency;
|
||||
qDebug()<<number<<"type="<<channel_type<<"device type="<<device_id<<"freq="<<st->frequency;
|
||||
|
||||
// update state
|
||||
state=message_id;
|
||||
@@ -883,11 +889,9 @@ void ANTChannel::attemptTransition(int message_id)
|
||||
//qDebug()<<number<<"TRANSITION from unassigned";
|
||||
|
||||
qDebug()<<number<<"assign channel type RX";
|
||||
|
||||
// assign and set channel id all in one
|
||||
parent->sendMessage(ANTMessage::assignChannel(number, CHANNEL_TYPE_RX, st->network)); // receive channel on network 1
|
||||
|
||||
device_id=st->device_id;
|
||||
setId();
|
||||
break;
|
||||
|
||||
case ANT_ASSIGN_CHANNEL:
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
#include <QLabel>
|
||||
#include "Context.h"
|
||||
|
||||
// sintegral fork of QwtPlot3D defines IS_NAN as isnan instead of std::isnan
|
||||
// so we add a macro to fix that; this only applies when compiling on QT5.5.1
|
||||
#if QT_VERSION >= 0x050501
|
||||
#define isnan(x) std::isnan(x)
|
||||
#endif
|
||||
|
||||
#include <qwt3d_global.h>
|
||||
|
||||
// qwtplot3d api changes between 0.2.x and 0.3.x
|
||||
|
||||
@@ -1016,9 +1016,13 @@ AboutRiderPage::AboutRiderPage(QWidget *parent, Context *context) : QWidget(pare
|
||||
autoBackupPeriod->setMinimum(0);
|
||||
autoBackupPeriod->setMaximum(9999);
|
||||
autoBackupPeriod->setSingleStep(1);
|
||||
QLabel *autoBackupPeriodLabel = new QLabel(tr("Auto Backup Period"));
|
||||
QLabel *autoBackupPeriodLabel = new QLabel(tr("Auto Backup execution every"));
|
||||
autoBackupPeriod->setValue(appsettings->cvalue(context->athlete->cyclist, GC_AUTOBACKUP_PERIOD, 0).toInt());
|
||||
|
||||
QLabel *autoBackupUnitLabel = new QLabel(tr("times the athlete is closed - 0 means never"));
|
||||
QHBoxLayout *backupInput = new QHBoxLayout();
|
||||
backupInput->addWidget(autoBackupPeriod);
|
||||
//backupInput->addStretch();
|
||||
backupInput->addWidget(autoBackupUnitLabel);
|
||||
|
||||
Qt::Alignment alignment = Qt::AlignLeft|Qt::AlignVCenter;
|
||||
|
||||
@@ -1038,7 +1042,7 @@ AboutRiderPage::AboutRiderPage(QWidget *parent, Context *context) : QWidget(pare
|
||||
grid->addWidget(autoBackupFolder, 7, 1, alignment);
|
||||
grid->addWidget(autoBackupFolderBrowse, 7, 2, alignment);
|
||||
grid->addWidget(autoBackupPeriodLabel, 8, 0,alignment);
|
||||
grid->addWidget(autoBackupPeriod, 8, 1, alignment);
|
||||
grid->addLayout(backupInput, 8, 1, alignment);
|
||||
|
||||
grid->addWidget(avatarButton, 0, 1, 4, 2, Qt::AlignRight|Qt::AlignVCenter);
|
||||
all->addLayout(grid);
|
||||
|
||||
14
src/main.cpp
14
src/main.cpp
@@ -16,23 +16,25 @@
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "Context.h"
|
||||
#include "Athlete.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Settings.h"
|
||||
#include "TrainDB.h"
|
||||
#include "Colors.h"
|
||||
#include "GcUpgrade.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QtGui>
|
||||
#include <QFile>
|
||||
#include <QWebSettings>
|
||||
#include <QMessageBox>
|
||||
#include "ChooseCyclistDialog.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Settings.h"
|
||||
#include "TrainDB.h"
|
||||
#include "Colors.h"
|
||||
#ifdef GC_WANT_HTTP
|
||||
#include "httplistener.h"
|
||||
#include "httprequesthandler.h"
|
||||
#endif
|
||||
|
||||
#include "GcUpgrade.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
// redirect errors to `home'/goldencheetah.log
|
||||
|
||||
@@ -544,6 +544,7 @@ DEFERRES += RouteWindow.h \
|
||||
RouteItem.cpp
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
AboutDialog.cpp \
|
||||
AddDeviceWizard.cpp \
|
||||
AddIntervalDialog.cpp \
|
||||
@@ -780,7 +781,6 @@ SOURCES += \
|
||||
WorkoutWizard.cpp \
|
||||
WPrime.cpp \
|
||||
Zones.cpp \
|
||||
main.cpp \
|
||||
../qtsolutions/json/mvjson.cpp
|
||||
|
||||
RESOURCES = application.qrc \
|
||||
|
||||
332
travis/macdeployqtfix.py
Normal file
332
travis/macdeployqtfix.py
Normal file
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
finish the job started by macdeployqtfix
|
||||
"""
|
||||
from subprocess import Popen, PIPE
|
||||
from string import Template
|
||||
import os, sys
|
||||
import logging
|
||||
import argparse
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
QTLIB_NAME_REGEX = r'^(?:@executable_path)?/.*/(Qt[a-zA-Z]*).framework/(?:Versions/\d/)?\1$'
|
||||
QTPLUGIN_NAME_REGEX = r'^(?:@executable_path)?/.*/[pP]lug[iI]ns/(.*)/(.*).dylib$'
|
||||
QTLIB_NORMALIZED = r'$prefix/Frameworks/$qtlib.framework/Versions/$qtversion/$qtlib'
|
||||
QTPLUGIN_NORMALIZED = r'$prefix/PlugIns/$plugintype/$pluginname.dylib'
|
||||
|
||||
|
||||
class GlobalConfig:
|
||||
|
||||
logger = None
|
||||
qtpath = None
|
||||
exepath = None
|
||||
|
||||
def run_and_get_output(popen_args):
|
||||
"""
|
||||
exec process and get all output
|
||||
"""
|
||||
process_output = namedtuple('ProcessOutput', ['stdout', 'stderr', 'retcode'])
|
||||
try:
|
||||
GlobalConfig.logger.debug('run_and_get_output({0})'.format(repr(popen_args)))
|
||||
|
||||
proc = Popen(popen_args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = proc.communicate(b'')
|
||||
proc_out = process_output(stdout, stderr, proc.returncode)
|
||||
|
||||
GlobalConfig.logger.debug('\tprocess_output: {0}'.format(proc_out))
|
||||
return proc_out
|
||||
except Exception as exc:
|
||||
GlobalConfig.logger.error('\texception: {0}'.format(exc))
|
||||
return process_output('', exc.message, -1)
|
||||
|
||||
def get_dependencies(filename):
|
||||
"""
|
||||
input: filename fullpath
|
||||
should call otool on mac and returns the list of dependencies,
|
||||
unsorted, unmodified, just the raw list
|
||||
so then we could eventually re-use in other more specialized functions
|
||||
"""
|
||||
GlobalConfig.logger.debug('get_dependencies({0})'.format(filename))
|
||||
popen_args = ['otool', '-L', filename]
|
||||
proc_out = run_and_get_output(popen_args)
|
||||
deps = []
|
||||
if proc_out.retcode == 0:
|
||||
# some string splitting
|
||||
deps = [s.strip().split(' ')[0] for s in proc_out.stdout.splitlines()[1:] if s]
|
||||
# prevent infinite recursion when a binary depends on itself (seen with QtWidgets)...
|
||||
deps = [s for s in deps if os.path.basename(filename) not in s]
|
||||
return deps
|
||||
|
||||
def is_qt_plugin(filename):
|
||||
"""
|
||||
check if a given file is a qt plugin
|
||||
accept absolute path as well as path containing @executable_path
|
||||
"""
|
||||
qtlib_name_rgx = re.compile(QTPLUGIN_NAME_REGEX)
|
||||
rgxret = qtlib_name_rgx.match(filename)
|
||||
if rgxret is not None:
|
||||
GlobalConfig.logger.debug('rgxret is not None for {0}: {1}'.format(filename, rgxret.groups()))
|
||||
return rgxret is not None
|
||||
|
||||
def is_qt_lib(filename):
|
||||
"""
|
||||
check if a given file is a qt library
|
||||
accept absolute path as well as path containing @executable_path
|
||||
"""
|
||||
qtlib_name_rgx = re.compile(QTLIB_NAME_REGEX)
|
||||
rgxret = qtlib_name_rgx.match(filename)
|
||||
return rgxret is not None
|
||||
|
||||
def normalize_qtplugin_name(filename):
|
||||
"""
|
||||
input: a path to a qt plugin, as returned by otool, that can have this form :
|
||||
- an absolute path /../plugins/PLUGINTYPE/PLUGINNAME.dylib
|
||||
- @executable_path/../plugins/PLUGINTYPE/PLUGINNAME.dylib
|
||||
output:
|
||||
a tuple (qtlib, abspath, rpath) where:
|
||||
- qtname is the name of the plugin (libqcocoa.dylib, etc.)
|
||||
- abspath is the absolute path of the qt lib inside the app bundle of exepath
|
||||
- relpath is the correct rpath to a qt lib inside the app bundle
|
||||
"""
|
||||
|
||||
GlobalConfig.logger.debug('normalize_plugin_name({0})'.format(filename))
|
||||
|
||||
qtplugin_name_rgx = re.compile(QTPLUGIN_NAME_REGEX)
|
||||
rgxret = qtplugin_name_rgx.match(filename)
|
||||
if not rgxret:
|
||||
msg = 'couldn\'t normalize a non-qt plugin filename: {0}'.format(filename)
|
||||
GlobalConfig.logger.critical(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
# qtplugin normalization settings
|
||||
qtplugintype = rgxret.groups()[0]
|
||||
qtpluginname = rgxret.groups()[1]
|
||||
|
||||
templ = Template(QTPLUGIN_NORMALIZED)
|
||||
# from qtlib, forge 2 path :
|
||||
# - absolute path of qt lib in bundle,
|
||||
abspath = os.path.normpath(templ.safe_substitute(
|
||||
prefix=os.path.dirname(GlobalConfig.exepath) + '/..',
|
||||
plugintype=qtplugintype,
|
||||
pluginname=qtpluginname))
|
||||
# - and rpath containing @executable_path, relative to exepath
|
||||
rpath = templ.safe_substitute(
|
||||
prefix='@executable_path/..',
|
||||
plugintype=qtplugintype,
|
||||
pluginname=qtpluginname)
|
||||
|
||||
GlobalConfig.logger.debug('\treturns({0})'.format((qtpluginname, abspath, rpath)))
|
||||
return qtpluginname, abspath, rpath
|
||||
|
||||
def normalize_qtlib_name(filename):
|
||||
"""
|
||||
input: a path to a qt library, as returned by otool, that can have this form :
|
||||
- an absolute path /lib/xxx/yyy
|
||||
- @executable_path/../Frameworks/QtSerialPort.framework/Versions/5/QtSerialPort
|
||||
output:
|
||||
a tuple (qtlib, abspath, rpath) where:
|
||||
- qtlib is the name of the qtlib (QtCore, QtWidgets, etc.)
|
||||
- abspath is the absolute path of the qt lib inside the app bundle of exepath
|
||||
- relpath is the correct rpath to a qt lib inside the app bundle
|
||||
"""
|
||||
GlobalConfig.logger.debug('normalize_qtlib_name({0})'.format(filename))
|
||||
|
||||
qtlib_name_rgx = re.compile(QTLIB_NAME_REGEX)
|
||||
rgxret = qtlib_name_rgx.match(filename)
|
||||
if not rgxret:
|
||||
msg = 'couldn\'t normalize a non-qt lib filename: {0}'.format(filename)
|
||||
GlobalConfig.logger.critical(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
# qtlib normalization settings
|
||||
qtlib = rgxret.groups()[0]
|
||||
qtversion = 5
|
||||
|
||||
templ = Template(QTLIB_NORMALIZED)
|
||||
# from qtlib, forge 2 path :
|
||||
# - absolute path of qt lib in bundle,
|
||||
abspath = os.path.normpath(templ.safe_substitute(
|
||||
prefix=os.path.dirname(GlobalConfig.exepath) + '/..',
|
||||
qtlib=qtlib,
|
||||
qtversion=qtversion))
|
||||
# - and rpath containing @executable_path, relative to exepath
|
||||
rpath = templ.safe_substitute(
|
||||
prefix='@executable_path/..',
|
||||
qtlib=qtlib,
|
||||
qtversion=qtversion)
|
||||
|
||||
GlobalConfig.logger.debug('\treturns({0})'.format((qtlib, abspath, rpath)))
|
||||
return qtlib, abspath, rpath
|
||||
|
||||
def fix_dependency(binary, dep):
|
||||
"""
|
||||
fix 'dep' dependency of 'binary'. 'dep' is a qt library
|
||||
"""
|
||||
if is_qt_lib(dep):
|
||||
qtname, dep_abspath, dep_rpath = normalize_qtlib_name(dep)
|
||||
elif is_qt_plugin(dep):
|
||||
qtname, dep_abspath, dep_rpath = normalize_qtplugin_name(dep)
|
||||
else:
|
||||
return True
|
||||
|
||||
dep_ok = True
|
||||
# check that rpath of 'dep' inside binary has been correctly set
|
||||
# (ie: relative to exepath using '@executable_path' syntax)
|
||||
if dep != dep_rpath:
|
||||
# dep rpath is not ok
|
||||
GlobalConfig.logger.info('changing rpath \'{0}\' in binary {1}'.format(dep, binary))
|
||||
|
||||
# call install_name_tool -change on binary
|
||||
popen_args = ['install_name_tool', '-change', dep, dep_rpath, binary]
|
||||
proc_out = run_and_get_output(popen_args)
|
||||
if proc_out.retcode != 0:
|
||||
GlobalConfig.logger.error(proc_out.stderr)
|
||||
dep_ok = False
|
||||
else:
|
||||
# call install_name_tool -id on binary
|
||||
popen_args = ['install_name_tool', '-id', dep_rpath, binary]
|
||||
proc_out = run_and_get_output(popen_args)
|
||||
if proc_out.retcode != 0:
|
||||
GlobalConfig.logger.error(proc_out.stderr)
|
||||
dep_ok = False
|
||||
|
||||
# now ensure that 'dep' exists at the specified path, relative to bundle
|
||||
if dep_ok and not os.path.exists(dep_abspath):
|
||||
|
||||
# ensure destination directory exists
|
||||
GlobalConfig.logger.info('ensuring directory \'{0}\' exists: {0}'.
|
||||
format(os.path.dirname(dep_abspath)))
|
||||
popen_args = ['mkdir', '-p', os.path.dirname(dep_abspath)]
|
||||
proc_out = run_and_get_output(popen_args)
|
||||
if proc_out.retcode != 0:
|
||||
GlobalConfig.logger.info(proc_out.stderr)
|
||||
dep_ok = False
|
||||
else:
|
||||
# copy missing dependency into bundle
|
||||
qtnamesrc = os.path.join(GlobalConfig.qtpath, 'lib', '{0}.framework'.
|
||||
format(qtname), qtname)
|
||||
GlobalConfig.logger.info('copying missing dependency in bundle: {0}'.
|
||||
format(qtname))
|
||||
popen_args = ['cp', qtnamesrc, dep_abspath]
|
||||
proc_out = run_and_get_output(popen_args)
|
||||
if proc_out.retcode != 0:
|
||||
GlobalConfig.logger.info(proc_out.stderr)
|
||||
dep_ok = False
|
||||
else:
|
||||
# ensure permissions are correct if we ever have to change its rpath
|
||||
GlobalConfig.logger.info('ensuring 755 perm to {0}'.format(dep_abspath))
|
||||
popen_args = ['chmod', '755', dep_abspath]
|
||||
proc_out = run_and_get_output(popen_args)
|
||||
if proc_out.retcode != 0:
|
||||
GlobalConfig.logger.info(proc_out.stderr)
|
||||
dep_ok = False
|
||||
else:
|
||||
GlobalConfig.logger.debug('{0} is at correct location in bundle'.format(qtname))
|
||||
|
||||
if dep_ok:
|
||||
return fix_binary(dep_abspath)
|
||||
return False
|
||||
|
||||
def fix_binary(binary):
|
||||
"""
|
||||
input:
|
||||
binary: relative or absolute path (no @executable_path syntax)
|
||||
process:
|
||||
- first fix the rpath for the qt libs on which 'binary' depend
|
||||
- copy into the bundle of exepath the eventual libraries that are missing
|
||||
- (create the soft links) needed ?
|
||||
- do the same for all qt dependencies of binary (recursive)
|
||||
"""
|
||||
GlobalConfig.logger.debug('fix_binary({0})'.format(binary))
|
||||
|
||||
# loop on 'binary' dependencies
|
||||
for dep in get_dependencies(binary):
|
||||
if not fix_dependency(binary, dep):
|
||||
GlobalConfig.logger.error('quitting early: couldn\'t fix dependency {0} of {1}'.format(dep, binary))
|
||||
return False
|
||||
return True
|
||||
|
||||
def fix_main_binaries():
|
||||
"""
|
||||
list the main binaries of the app bundle and fix them
|
||||
"""
|
||||
# deduce bundle path
|
||||
bundlepath = os.path.sep.join(GlobalConfig.exepath.split(os.path.sep)[0:-3])
|
||||
|
||||
# fix main binary
|
||||
GlobalConfig.logger.info('fixing executable \'{0}\''.format(GlobalConfig.exepath))
|
||||
if fix_binary(GlobalConfig.exepath):
|
||||
GlobalConfig.logger.info('fixing plugins')
|
||||
for root, dummy, files in os.walk(bundlepath):
|
||||
for name in [f for f in files if os.path.splitext(f)[1] == '.dylib']:
|
||||
GlobalConfig.logger.info('fixing plugin {0}'.format(name))
|
||||
if not fix_binary(os.path.join(root, name)):
|
||||
return False
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""
|
||||
script entry point
|
||||
"""
|
||||
descr = """finish the job started by macdeployqt!
|
||||
- find dependencies/rpathes with otool
|
||||
- copy missed dependencies with cp and mkdir
|
||||
- fix missed rpathes with install_name_tool
|
||||
|
||||
exit codes:
|
||||
- 0 : success
|
||||
- 1 : error
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(description=descr,
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument('exepath', help='path to the binary depending on Qt')
|
||||
parser.add_argument('qtpath', help='path of Qt libraries used to build the Qt application')
|
||||
parser.add_argument('-q', '--quiet', action='store_true', default=False,
|
||||
help='do not create log on standard output')
|
||||
parser.add_argument('-nl', '--no-log-file', action='store_true', default=False,
|
||||
help='do not create log file \'./macdeployqtfix.log\'')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', default=False,
|
||||
help='produce more log messages(debug log)')
|
||||
args = parser.parse_args()
|
||||
|
||||
# globals
|
||||
GlobalConfig.qtpath = os.path.normpath(args.qtpath)
|
||||
GlobalConfig.exepath = args.exepath
|
||||
GlobalConfig.logger = logging.getLogger()
|
||||
|
||||
# configure logging
|
||||
###################
|
||||
|
||||
# create formatter
|
||||
formatter = logging.Formatter('%(levelname)s | %(message)s')
|
||||
# create console GlobalConfig.logger
|
||||
if not args.quiet:
|
||||
chdlr = logging.StreamHandler(sys.stdout)
|
||||
chdlr.setFormatter(formatter)
|
||||
GlobalConfig.logger.addHandler(chdlr)
|
||||
|
||||
# create file GlobalConfig.logger
|
||||
if not args.no_log_file:
|
||||
fhdlr = logging.FileHandler('./macdeployqtfix.log', mode='w')
|
||||
fhdlr.setFormatter(formatter)
|
||||
GlobalConfig.logger.addHandler(fhdlr)
|
||||
|
||||
if args.no_log_file and args.quiet:
|
||||
GlobalConfig.logger.addHandler(logging.NullHandler())
|
||||
else:
|
||||
if args.verbose:
|
||||
GlobalConfig.logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
GlobalConfig.logger.setLevel(logging.INFO)
|
||||
|
||||
if fix_main_binaries():
|
||||
GlobalConfig.logger.info('process terminated with success')
|
||||
sys.exit(0)
|
||||
else:
|
||||
GlobalConfig.logger.error('process terminated with error')
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user