diff --git a/.gitignore b/.gitignore index 50dace12..59bac496 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /*.scad *.dmg +*~ *.tar* Makefile objects diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..c499a8d7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: cpp +cache: apt +compiler: gcc + +before_install: + - echo 'yes' | sudo add-apt-repository ppa:chrysn/openscad + - sudo apt-get update -qq + - sudo apt-get install -qq build-essential libqt4-dev libqt4-opengl-dev libxmu-dev cmake bison flex git-core libboost-all-dev libXi-dev libmpfr-dev libboost-dev libglew-dev libeigen2-dev libeigen3-dev libcgal-dev libgmp3-dev libgmp-dev python-paramiko curl imagemagick + - sudo apt-get install -qq libopencsg-dev + +env: OPENSCAD_UPLOAD_TESTS=yes + +branches: + only: + - master + +script: ./scripts/travis-ci.sh diff --git a/README.md b/README.md index 27f12cec..1f7b497c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Travis CI](https://api.travis-ci.org/openscad/openscad.png) + # What is OpenSCAD? [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=openscad&url=http://openscad.org&title=OpenSCAD&language=&tags=github&category=software) @@ -93,6 +95,7 @@ Follow the instructions for the platform you're compiling on below. * [OpenCSG (1.3.2)](http://www.opencsg.org/) * [GLEW (1.5.4 ->)](http://glew.sourceforge.net/) * [Eigen (3.0 - 3.2)](http://eigen.tuxfamily.org/) +* [glib2 (2.2.0)](https://developer.gnome.org/glib/) * [GCC C++ Compiler (4.2 ->)](http://gcc.gnu.org/) * [Bison (2.4)](http://www.gnu.org/software/bison/) * [Flex (2.5.35)](http://flex.sourceforge.net/) diff --git a/common.pri b/common.pri index 7153ded7..696c8b1d 100644 --- a/common.pri +++ b/common.pri @@ -11,3 +11,4 @@ include(opencsg.pri) include(glew.pri) include(eigen.pri) include(boost.pri) +include(glib-2.0.pri) \ No newline at end of file diff --git a/glib-2.0.pri b/glib-2.0.pri new file mode 100644 index 00000000..2f293bf6 --- /dev/null +++ b/glib-2.0.pri @@ -0,0 +1,39 @@ +# Detect glib-2.0, then use this priority list to determine +# which library to use: +# +# Priority +# 1. GLIB2_INCLUDEPATH / GLIB2_LIBPATH (qmake parameter, not checked it given on commandline) +# 2. OPENSCAD_LIBRARIES (environment variable) +# 3. system's standard include paths from pkg-config + +glib-2.0 { +# read environment variables +OPENSCAD_LIBRARIES_DIR = $$(OPENSCAD_LIBRARIES) +GLIB2_DIR = $$(GLIB2DIR) + +!isEmpty(OPENSCAD_LIBRARIES_DIR) { + isEmpty(GLIB2_INCLUDEPATH) { + GLIB2_INCLUDEPATH = $$OPENSCAD_LIBRARIES_DIR/include/glib-2.0 + GLIB2_INCLUDEPATH_2 = $$OPENSCAD_LIBRARIES_DIR/lib/glib-2.0/include + GLIB2_LIBPATH = $$OPENSCAD_LIBRARIES_DIR/lib + } +} + +isEmpty(GLIB2_INCLUDEPATH) { + GLIB2_CFLAGS = $$system("pkg-config --cflags glib-2.0") +} else { + GLIB2_CFLAGS = -I$$GLIB2_INCLUDEPATH + GLIB2_CFLAGS += -I$$GLIB2_INCLUDEPATH_2 +} + +isEmpty(GLIB2_LIBPATH) { + GLIB2_LIBS = $$system("pkg-config --libs glib-2.0") +} else { + GLIB2_LIBS = -L$$GLIB2_LIBPATH -lglib-2.0 +} + +QMAKE_CXXFLAGS += $$GLIB2_CFLAGS +LIBS += $$GLIB2_LIBS + +message("glib: $$GLIB2_CFLAGS") +} diff --git a/icons/openscad.desktop b/icons/openscad.desktop index f0282acd..46150a21 100644 --- a/icons/openscad.desktop +++ b/icons/openscad.desktop @@ -4,4 +4,4 @@ Version=1.0 Name=OpenSCAD Icon=openscad Exec=openscad %f -Categories=Graphics;3DGraphics;Engineering;Programming; +Categories=Graphics;3DGraphics;Engineering;Development; diff --git a/openscad.pro b/openscad.pro index fd9f4947..ec5af20c 100644 --- a/openscad.pro +++ b/openscad.pro @@ -8,7 +8,7 @@ # OPENCSGDIR # OPENSCAD_LIBRARIES # -# Please see the 'Buildling' sections of the OpenSCAD user manual +# Please see the 'Building' sections of the OpenSCAD user manual # for updated tips & workarounds. # # http://en.wikibooks.org/wiki/OpenSCAD_User_Manual @@ -39,6 +39,7 @@ debug: DEFINES += DEBUG TEMPLATE = app INCLUDEPATH += src +DEPENDPATH += src # Handle custom library location. # Used when manually installing 3rd party libraries @@ -155,6 +156,7 @@ CONFIG += cgal CONFIG += opencsg CONFIG += boost CONFIG += eigen +CONFIG += glib-2.0 #Uncomment the following line to enable QCodeEdit #CONFIG += qcodeedit @@ -368,6 +370,7 @@ HEADERS += src/cgal.h \ src/PolySetCGALEvaluator.h \ src/CGALRenderer.h \ src/CGAL_Nef_polyhedron.h \ + src/CGAL_Nef3_workaround.h \ src/cgalworker.h SOURCES += src/cgalutils.cc \ diff --git a/patches/qt4/patch-libtiff.diff b/patches/qt4/patch-libtiff.diff new file mode 100644 index 00000000..5b7f9ec3 --- /dev/null +++ b/patches/qt4/patch-libtiff.diff @@ -0,0 +1,18 @@ +--- src/3rdparty/libtiff/libtiff/tif_config.h ++++ src/3rdparty/libtiff/libtiff/tif_config.h +@@ -317,15 +317,6 @@ + /* Define to empty if `const' does not conform to ANSI C. */ + /* #undef const */ + +-/* Define to `__inline__' or `__inline' if that's what the C compiler +- calls it, or to nothing if 'inline' is not supported under any name. */ +-#ifndef Q_OS_SYMBIAN +-#ifndef __cplusplus +-#undef inline +-#define inline +-#endif +-#endif +- + /* Define to `long int' if does not define. */ + /* #undef off_t */ + diff --git a/patches/qt4/patch-qeventdispatcher.diff b/patches/qt4/patch-qeventdispatcher.diff new file mode 100644 index 00000000..89ed4788 --- /dev/null +++ b/patches/qt4/patch-qeventdispatcher.diff @@ -0,0 +1,86 @@ +--- src/gui/kernel/qeventdispatcher_mac_p.h 2013-06-07 01:16:59.000000000 -0400 ++++ src/gui/kernel/qeventdispatcher_mac_p_new-8184b49c12d887928921ed5b695c8c6f04a07514.h 2013-12-08 14:31:01.000000000 -0500 +@@ -173,6 +173,7 @@ + #ifdef QT_MAC_USE_COCOA + // The following variables help organizing modal sessions: + static QStack cocoaModalSessionStack; ++ static QStack cocoaModalSessionStackPendingEnd; + static bool currentExecIsNSAppRun; + static bool nsAppRunCalledByQt; + static bool cleanupModalSessionsNeeded; +@@ -180,6 +181,7 @@ + static NSModalSession currentModalSession(); + static void updateChildrenWorksWhenModal(); + static void temporarilyStopAllModalSessions(); ++ static void stopAllPendingEndModalSessions(); + static void beginModalSession(QWidget *widget); + static void endModalSession(QWidget *widget); + static void cancelWaitForMoreEvents(); +--- src/gui/kernel/qeventdispatcher_mac.mm 2013-06-07 01:16:59.000000000 -0400 ++++ src/gui/kernel/qeventdispatcher_mac_new-833e02de99494686f8dd7a567f6e19e847508f11.mm 2013-12-08 14:30:59.000000000 -0500 +@@ -603,6 +603,9 @@ + while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt) + qt_mac_waitForMoreModalSessionEvents(); + ++ // stop all pending end modal sessions ++ d->stopAllPendingEndModalSessions(); ++ + if (!d->interrupt && session == d->currentModalSessionCached) { + // Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped +@@ -678,6 +681,9 @@ + if (!d->interrupt) + QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + ++ // stop all pending end modal sessions ++ d->stopAllPendingEndModalSessions(); ++ + // Since the window that holds modality might have changed while processing + // events, we we need to interrupt when we return back the previous process + // event recursion to ensure that we spin the correct modal session. +@@ -781,6 +787,7 @@ + + #ifdef QT_MAC_USE_COCOA + QStack QEventDispatcherMacPrivate::cocoaModalSessionStack; ++QStack QEventDispatcherMacPrivate::cocoaModalSessionStackPendingEnd; + bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false; + bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false; + bool QEventDispatcherMacPrivate::cleanupModalSessionsNeeded = false; +@@ -828,6 +835,20 @@ + currentModalSessionCached = 0; + } + ++void QEventDispatcherMacPrivate::stopAllPendingEndModalSessions() ++{ ++ // stop all modal sessions pending end ++ int stackSize = cocoaModalSessionStackPendingEnd.size(); ++ for (int i=stackSize-1; i>=0; --i) { ++ QCocoaModalSessionInfo &info = cocoaModalSessionStackPendingEnd[i]; ++ cocoaModalSessionStackPendingEnd.remove(i); ++ if (info.session) { ++ [NSApp endModalSession:info.session]; ++ [(NSWindow *)info.nswindow release]; ++ } ++ } ++} ++ + NSModalSession QEventDispatcherMacPrivate::currentModalSession() + { + // If we have one or more modal windows, this function will create +@@ -925,10 +946,12 @@ + } + cocoaModalSessionStack.remove(i); + currentModalSessionCached = 0; +- if (info.session) { +- [NSApp endModalSession:info.session]; +- [(NSWindow *)info.nswindow release]; +- } ++ ++ // Cannot stop the sessions here since we might still be inside a ++ // [NSApp runModalSession:] call. Add the session to the pending end stack and ++ // process the stack after the call to [NSApp runModalSession:] returns. ++ if (info.session) ++ cocoaModalSessionStackPendingEnd.push(info); + } + + updateChildrenWorksWhenModal(); diff --git a/patches/qt4/patch-qfontdatabase.diff b/patches/qt4/patch-qfontdatabase.diff new file mode 100644 index 00000000..c0788906 --- /dev/null +++ b/patches/qt4/patch-qfontdatabase.diff @@ -0,0 +1,29 @@ +--- src/gui/text/qfontdatabase.cpp 2013-06-07 01:16:59.000000000 -0400 ++++ src/gui/text/qfontdatabase_new-bb2beddc3ae55c4676d190d0ac99aa32d322a6a5.cpp 2013-12-08 14:51:10.000000000 -0500 +@@ -441,6 +441,7 @@ + #endif + #if !defined(QWS) && defined(Q_OS_MAC) + bool fixedPitchComputed : 1; ++ QString postscriptName; + #endif + #ifdef Q_WS_X11 + bool symbol_checked : 1; +--- src/gui/text/qfontdatabase_mac.cpp 2013-06-07 01:16:59.000000000 -0400 ++++ src/gui/text/qfontdatabase_mac_new-41f29865db84152efb41c048470f713353a0a84c.cpp 2013-12-08 14:51:05.000000000 -0500 +@@ -147,6 +147,7 @@ + QCFString family_name = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(font, kCTFontFamilyNameAttribute, NULL); + QCFString style_name = (CFStringRef)CTFontDescriptorCopyLocalizedAttribute(font, kCTFontStyleNameAttribute, NULL); + QtFontFamily *family = db->family(family_name, true); ++ family->postscriptName = QCFString((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontNameAttribute)); + + if (QCFType languages = (CFArrayRef) CTFontDescriptorCopyAttribute(font, kCTFontLanguagesAttribute)) { + CFIndex length = CFArrayGetCount(languages); +@@ -327,7 +328,7 @@ + if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) { + QByteArray family_name = db->families[k]->name.toUtf8(); + #if defined(QT_MAC_USE_COCOA) +- QCFType ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL); ++ QCFType ctFont = CTFontCreateWithName(QCFString(db->families[k]->postscriptName), 12, NULL); + if (ctFont) { + fontName = CTFontCopyFullName(ctFont); + goto found; diff --git a/patches/qt4/patch-src_corelib_global_qglobal.h.diff b/patches/qt4/patch-src_corelib_global_qglobal.h.diff new file mode 100644 index 00000000..8c55c5a6 --- /dev/null +++ b/patches/qt4/patch-src_corelib_global_qglobal.h.diff @@ -0,0 +1,14 @@ +--- src/corelib/global/qglobal.h.orig 2013-06-07 07:16:52.000000000 +0200 ++++ src/corelib/global/qglobal.h 2013-10-27 14:05:22.000000000 +0100 +@@ -327,7 +327,10 @@ + # if !defined(MAC_OS_X_VERSION_10_8) + # define MAC_OS_X_VERSION_10_8 MAC_OS_X_VERSION_10_7 + 1 + # endif +-# if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_8) ++# if !defined(MAC_OS_X_VERSION_10_9) ++# define MAC_OS_X_VERSION_10_9 MAC_OS_X_VERSION_10_8 + 1 ++# endif ++# if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_9) + # warning "This version of Mac OS X is unsupported" + # endif + #endif diff --git a/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff b/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff new file mode 100644 index 00000000..61b2eef3 --- /dev/null +++ b/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff @@ -0,0 +1,1382 @@ +--- src/plugins/bearer/corewlan/qcorewlanengine.mm ++++ src/plugins/bearer/corewlan/qcorewlanengine.mm +@@ -52,29 +52,17 @@ + #include + + #include +-#include +-#include +-#include +-#include +-#include +- +-#include +-#include +-#include +-#include +- +-#include ++ ++extern "C" { // Otherwise it won't find CWKeychain* symbols at link time ++#import ++} ++ + #include "private/qcore_mac_p.h" + + #include + #include + +-inline QString qt_NSStringToQString(const NSString *nsstr) +-{ return QCFString::toQString(reinterpret_cast(nsstr)); } +- +-inline NSString *qt_QStringToNSString(const QString &qstr) +-{ return [const_cast(reinterpret_cast(QCFString::toCFStringRef(qstr))) autorelease]; } +- ++#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + + @interface QT_MANGLE_NAMESPACE(QNSListener) : NSObject + { +@@ -86,6 +74,7 @@ inline NSString *qt_QStringToNSString(const QString &qstr) + - (void)notificationHandler;//:(NSNotification *)notification; + - (void)remove; + - (void)setEngine:(QCoreWlanEngine *)coreEngine; ++- (QCoreWlanEngine *)engine; + - (void)dealloc; + + @property (assign) QCoreWlanEngine* engine; +@@ -93,7 +82,6 @@ inline NSString *qt_QStringToNSString(const QString &qstr) + @end + + @implementation QT_MANGLE_NAMESPACE(QNSListener) +-@synthesize engine; + + - (id) init + { +@@ -101,7 +89,7 @@ inline NSString *qt_QStringToNSString(const QString &qstr) + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + notificationCenter = [NSNotificationCenter defaultCenter]; + currentInterface = [CWInterface interfaceWithName:nil]; +- [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:kCWPowerDidChangeNotification object:nil]; ++ [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:CWPowerDidChangeNotification object:nil]; + [locker unlock]; + [autoreleasepool release]; + return self; +@@ -120,6 +108,11 @@ inline NSString *qt_QStringToNSString(const QString &qstr) + [locker unlock]; + } + ++-(QCoreWlanEngine *)engine ++{ ++ return engine; ++} ++ + -(void)remove + { + [locker lock]; +@@ -133,7 +126,7 @@ inline NSString *qt_QStringToNSString(const QString &qstr) + } + @end + +-QT_MANGLE_NAMESPACE(QNSListener) *listener = 0; ++static QT_MANGLE_NAMESPACE(QNSListener) *listener = 0; + + QT_BEGIN_NAMESPACE + +@@ -170,36 +163,28 @@ void QScanThread::run() + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + QStringList found; + mutex.lock(); +- CWInterface *currentInterface = [CWInterface interfaceWithName:qt_QStringToNSString(interfaceName)]; ++ CWInterface *currentInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceName)]; + mutex.unlock(); + +- if([currentInterface power]) { ++ if (currentInterface.powerOn) { + NSError *err = nil; +- NSDictionary *parametersDict = [NSDictionary dictionaryWithObjectsAndKeys: +- [NSNumber numberWithBool:YES], kCWScanKeyMerge, +- [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType, +- [NSNumber numberWithInteger:100], kCWScanKeyRestTime, nil]; + +- NSArray* apArray = [currentInterface scanForNetworksWithParameters:parametersDict error:&err]; +- CWNetwork *apNetwork; ++ NSSet* apSet = [currentInterface scanForNetworksWithName:nil error:&err]; + + if (!err) { +- +- for(uint row=0; row < [apArray count]; row++ ) { +- apNetwork = [apArray objectAtIndex:row]; +- +- const QString networkSsid = qt_NSStringToQString([apNetwork ssid]); ++ for (CWNetwork *apNetwork in apSet) { ++ const QString networkSsid = QCFString::toQString(CFStringRef([apNetwork ssid])); + const QString id = QString::number(qHash(QLatin1String("corewlan:") + networkSsid)); + found.append(id); + + QNetworkConfiguration::StateFlags state = QNetworkConfiguration::Undefined; + bool known = isKnownSsid(networkSsid); +- if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) { +- if( networkSsid == qt_NSStringToQString( [currentInterface ssid])) { ++ if (currentInterface.serviceActive) { ++ if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) { + state = QNetworkConfiguration::Active; + } + } +- if(state == QNetworkConfiguration::Undefined) { ++ if (state == QNetworkConfiguration::Undefined) { + if(known) { + state = QNetworkConfiguration::Discovered; + } else { +@@ -207,7 +192,7 @@ void QScanThread::run() + } + } + QNetworkConfiguration::Purpose purpose = QNetworkConfiguration::UnknownPurpose; +- if([[apNetwork securityMode] intValue] == kCWSecurityModeOpen) { ++ if ([apNetwork supportsSecurity:kCWSecurityNone]) { + purpose = QNetworkConfiguration::PublicPurpose; + } else { + purpose = QNetworkConfiguration::PrivatePurpose; +@@ -237,8 +222,8 @@ void QScanThread::run() + interfaceName = ij.value(); + } + +- if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) { +- if( networkSsid == qt_NSStringToQString([currentInterface ssid])) { ++ if (currentInterface.serviceActive) { ++ if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) { + state = QNetworkConfiguration::Active; + } + } +@@ -300,14 +285,14 @@ void QScanThread::getUserConfigurations() + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + userProfiles.clear(); + +- NSArray *wifiInterfaces = [CWInterface supportedInterfaces]; +- for(uint row=0; row < [wifiInterfaces count]; row++ ) { ++ NSSet *wifiInterfaces = [CWInterface interfaceNames]; ++ for (NSString *ifName in wifiInterfaces) { + +- CWInterface *wifiInterface = [CWInterface interfaceWithName: [wifiInterfaces objectAtIndex:row]]; +- if ( ![wifiInterface power] ) ++ CWInterface *wifiInterface = [CWInterface interfaceWithName: ifName]; ++ if (!wifiInterface.powerOn) + continue; + +- NSString *nsInterfaceName = [wifiInterface name]; ++ NSString *nsInterfaceName = wifiInterface.ssid; + // add user configured system networks + SCDynamicStoreRef dynRef = SCDynamicStoreCreate(kCFAllocatorSystemDefault, (CFStringRef)@"Qt corewlan", nil, nil); + NSDictionary * airportPlist = (NSDictionary *)SCDynamicStoreCopyValue(dynRef, (CFStringRef)[NSString stringWithFormat:@"Setup:/Network/Interface/%@/AirPort", nsInterfaceName]); +@@ -316,11 +301,11 @@ void QScanThread::getUserConfigurations() + NSDictionary *prefNetDict = [airportPlist objectForKey:@"PreferredNetworks"]; + + NSArray *thisSsidarray = [prefNetDict valueForKey:@"SSID_STR"]; +- for(NSString *ssidkey in thisSsidarray) { +- QString thisSsid = qt_NSStringToQString(ssidkey); ++ for (NSString *ssidkey in thisSsidarray) { ++ QString thisSsid = QCFString::toQString(CFStringRef(ssidkey)); + if(!userProfiles.contains(thisSsid)) { + QMap map; +- map.insert(thisSsid, qt_NSStringToQString(nsInterfaceName)); ++ map.insert(thisSsid, QCFString::toQString(CFStringRef(nsInterfaceName))); + userProfiles.insert(thisSsid, map); + } + } +@@ -329,7 +314,7 @@ void QScanThread::getUserConfigurations() + + // 802.1X user profiles + QString userProfilePath = QDir::homePath() + "/Library/Preferences/com.apple.eap.profiles.plist"; +- NSDictionary* eapDict = [[[NSDictionary alloc] initWithContentsOfFile:qt_QStringToNSString(userProfilePath)] autorelease]; ++ NSDictionary* eapDict = [[[NSDictionary alloc] initWithContentsOfFile: (NSString *)QCFString::toCFStringRef(userProfilePath)] autorelease]; + if(eapDict != nil) { + NSString *profileStr= @"Profiles"; + NSString *nameStr = @"UserDefinedName"; +@@ -348,15 +333,15 @@ void QScanThread::getUserConfigurations() + QString ssid; + for(int i = 0; i < dictSize; i++) { + if([nameStr isEqualToString:keys[i]]) { +- networkName = qt_NSStringToQString(objects[i]); ++ networkName = QCFString::toQString(CFStringRef(objects[i])); + } + if([networkSsidStr isEqualToString:keys[i]]) { +- ssid = qt_NSStringToQString(objects[i]); ++ ssid = QCFString::toQString(CFStringRef(objects[i])); + } + if(!userProfiles.contains(networkName) + && !ssid.isEmpty()) { + QMap map; +- map.insert(ssid, qt_NSStringToQString(nsInterfaceName)); ++ map.insert(ssid, QCFString::toQString(CFStringRef(nsInterfaceName))); + userProfiles.insert(networkName, map); + } + } +@@ -444,7 +429,7 @@ void QCoreWlanEngine::initialize() + QMutexLocker locker(&mutex); + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + +- if([[CWInterface supportedInterfaces] count] > 0 && !listener) { ++ if ([[CWInterface interfaceNames] count] > 0 && !listener) { + listener = [[QT_MANGLE_NAMESPACE(QNSListener) alloc] init]; + listener.engine = this; + hasWifi = true; +@@ -479,141 +464,68 @@ void QCoreWlanEngine::connectToId(const QString &id) + QString interfaceString = getInterfaceFromId(id); + + CWInterface *wifiInterface = +- [CWInterface interfaceWithName: qt_QStringToNSString(interfaceString)]; ++ [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)]; + +- if ([wifiInterface power]) { ++ if (wifiInterface.powerOn) { + NSError *err = nil; +- NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; +- + QString wantedSsid; +- + QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id); + + const QString idHash = QString::number(qHash(QLatin1String("corewlan:") + ptr->name)); + const QString idHash2 = QString::number(qHash(QLatin1String("corewlan:") + scanThread->getNetworkNameFromSsid(ptr->name))); + +- bool using8021X = false; +- if (idHash2 != id) { +- NSArray *array = [CW8021XProfile allUser8021XProfiles]; +- +- for (NSUInteger i = 0; i < [array count]; ++i) { +- const QString networkNameHashCheck = QString::number(qHash(QLatin1String("corewlan:") + qt_NSStringToQString([[array objectAtIndex:i] userDefinedName]))); +- +- const QString ssidHash = QString::number(qHash(QLatin1String("corewlan:") + qt_NSStringToQString([[array objectAtIndex:i] ssid]))); +- +- if (id == networkNameHashCheck || id == ssidHash) { +- const QString thisName = scanThread->getSsidFromNetworkName(id); +- if (thisName.isEmpty()) +- wantedSsid = id; +- else +- wantedSsid = thisName; +- +- [params setValue: [array objectAtIndex:i] forKey:kCWAssocKey8021XProfile]; +- using8021X = true; +- break; +- } ++ QString wantedNetwork; ++ QMapIterator > i(scanThread->userProfiles); ++ while (i.hasNext()) { ++ i.next(); ++ wantedNetwork = i.key(); ++ const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") + wantedNetwork)); ++ if (id == networkNameHash) { ++ wantedSsid = scanThread->getSsidFromNetworkName(wantedNetwork); ++ break; + } + } + +- if (!using8021X) { +- QString wantedNetwork; +- QMapIterator > i(scanThread->userProfiles); +- while (i.hasNext()) { +- i.next(); +- wantedNetwork = i.key(); +- const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") + wantedNetwork)); +- if (id == networkNameHash) { +- wantedSsid = scanThread->getSsidFromNetworkName(wantedNetwork); +- break; +- } +- } +- } +- NSDictionary *scanParameters = [NSDictionary dictionaryWithObjectsAndKeys: +- [NSNumber numberWithBool:YES], kCWScanKeyMerge, +- [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType, +- [NSNumber numberWithInteger:100], kCWScanKeyRestTime, +- qt_QStringToNSString(wantedSsid), kCWScanKeySSID, +- nil]; +- +- NSArray *scanArray = [wifiInterface scanForNetworksWithParameters:scanParameters error:&err]; ++ NSSet *scanSet = [wifiInterface scanForNetworksWithName:(NSString *)QCFString::toCFStringRef(wantedSsid) error:&err]; + + if(!err) { +- for(uint row=0; row < [scanArray count]; row++ ) { +- CWNetwork *apNetwork = [scanArray objectAtIndex:row]; +- +- if(wantedSsid == qt_NSStringToQString([apNetwork ssid])) { +- +- if(!using8021X) { +- SecKeychainAttribute attributes[3]; +- +- NSString *account = [apNetwork ssid]; +- NSString *keyKind = @"AirPort network password"; +- NSString *keyName = account; +- +- attributes[0].tag = kSecAccountItemAttr; +- attributes[0].data = (void *)[account UTF8String]; +- attributes[0].length = [account length]; +- +- attributes[1].tag = kSecDescriptionItemAttr; +- attributes[1].data = (void *)[keyKind UTF8String]; +- attributes[1].length = [keyKind length]; +- +- attributes[2].tag = kSecLabelItemAttr; +- attributes[2].data = (void *)[keyName UTF8String]; +- attributes[2].length = [keyName length]; +- +- SecKeychainAttributeList attributeList = {3,attributes}; +- +- SecKeychainSearchRef searchRef; +- SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributeList, &searchRef); +- +- NSString *password = @""; +- SecKeychainItemRef searchItem; +- +- if (SecKeychainSearchCopyNext(searchRef, &searchItem) == noErr) { +- UInt32 realPasswordLength; +- SecKeychainAttribute attributesW[8]; +- attributesW[0].tag = kSecAccountItemAttr; +- SecKeychainAttributeList listW = {1,attributesW}; +- char *realPassword; +- OSStatus status = SecKeychainItemCopyContent(searchItem, NULL, &listW, &realPasswordLength,(void **)&realPassword); +- +- if (status == noErr) { +- if (realPassword != NULL) { +- +- QByteArray pBuf; +- pBuf.resize(realPasswordLength); +- pBuf.prepend(realPassword); +- pBuf.insert(realPasswordLength,'\0'); +- +- password = [NSString stringWithUTF8String:pBuf]; +- } +- SecKeychainItemFreeContent(&listW, realPassword); +- } +- +- CFRelease(searchItem); +- } else { +- qDebug() << "SecKeychainSearchCopyNext error"; +- } +- [params setValue: password forKey: kCWAssocKeyPassphrase]; +- } // end using8021X +- +- +- bool result = [wifiInterface associateToNetwork: apNetwork parameters:[NSDictionary dictionaryWithDictionary:params] error:&err]; ++ for (CWNetwork *apNetwork in scanSet) { ++ CFDataRef ssidData = (CFDataRef)[apNetwork ssidData]; ++ bool result = false; ++ ++ SecIdentityRef identity = 0; ++ // Check first whether we require IEEE 802.1X authentication for the wanted SSID ++ if (CWKeychainCopyEAPIdentity(ssidData, &identity) == errSecSuccess) { ++ CFStringRef username = 0; ++ CFStringRef password = 0; ++ if (CWKeychainCopyEAPUsernameAndPassword(ssidData, &username, &password) == errSecSuccess) { ++ result = [wifiInterface associateToEnterpriseNetwork:apNetwork ++ identity:identity username:(NSString *)username password:(NSString *)password ++ error:&err]; ++ CFRelease(username); ++ CFRelease(password); ++ } ++ CFRelease(identity); ++ } else { ++ CFStringRef password = 0; ++ if (CWKeychainCopyPassword(ssidData, &password) == errSecSuccess) { ++ result = [wifiInterface associateToNetwork:apNetwork password:(NSString *)password error:&err]; ++ CFRelease(password); ++ } ++ } + +- if(!err) { +- if(!result) { +- emit connectionError(id, ConnectError); +- } else { +- return; +- } ++ if (!err) { ++ if (!result) { ++ emit connectionError(id, ConnectError); + } else { +- qDebug() <<"associate ERROR"<< qt_NSStringToQString([err localizedDescription ]); ++ return; + } ++ } else { ++ qDebug() <<"associate ERROR"<< QCFString::toQString(CFStringRef([err localizedDescription ])); + } + } //end scan network + } else { +- qDebug() <<"scan ERROR"<< qt_NSStringToQString([err localizedDescription ]); ++ qDebug() <<"scan ERROR"<< QCFString::toQString(CFStringRef([err localizedDescription ])); + } + emit connectionError(id, InterfaceLookupError); + } +@@ -631,10 +543,10 @@ void QCoreWlanEngine::disconnectFromId(const QString &id) + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + + CWInterface *wifiInterface = +- [CWInterface interfaceWithName: qt_QStringToNSString(interfaceString)]; ++ [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)]; + + [wifiInterface disassociate]; +- if ([[wifiInterface interfaceState]intValue] != kCWInterfaceStateInactive) { ++ if (wifiInterface.serviceActive) { + locker.unlock(); + emit connectionError(id, DisconnectionError); + locker.relock(); +@@ -654,9 +566,9 @@ void QCoreWlanEngine::doRequestUpdate() + + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; + +- NSArray *wifiInterfaces = [CWInterface supportedInterfaces]; +- for (uint row = 0; row < [wifiInterfaces count]; ++row) { +- scanThread->interfaceName = qt_NSStringToQString([wifiInterfaces objectAtIndex:row]); ++ NSSet *wifiInterfaces = [CWInterface interfaceNames]; ++ for (NSString *ifName in wifiInterfaces) { ++ scanThread->interfaceName = QCFString::toQString(CFStringRef(ifName)); + scanThread->start(); + } + locker.unlock(); +@@ -669,8 +581,8 @@ bool QCoreWlanEngine::isWifiReady(const QString &wifiDeviceName) + bool haswifi = false; + if(hasWifi) { + NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; +- CWInterface *defaultInterface = [CWInterface interfaceWithName: qt_QStringToNSString(wifiDeviceName)]; +- if([defaultInterface power]) { ++ CWInterface *defaultInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(wifiDeviceName)]; ++ if (defaultInterface.powerOn) { + haswifi = true; + } + [autoreleasepool release]; +@@ -898,7 +810,7 @@ quint64 QCoreWlanEngine::startTime(const QString &identifier) + bool ok = false; + for(int i = 0; i < dictSize; i++) { + if([ssidStr isEqualToString:keys[i]]) { +- const QString ident = QString::number(qHash(QLatin1String("corewlan:") + qt_NSStringToQString(objects[i]))); ++ const QString ident = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef(objects[i])))); + if(ident == identifier) { + ok = true; + } +@@ -944,3 +856,7 @@ quint64 QCoreWlanEngine::getBytes(const QString &interfaceName, bool b) + } + + QT_END_NAMESPACE ++ ++#else // QT_MAC_PLATFORM_SDK_EQUAL_OR_ABOVE ++#include "qcorewlanengine_10_6.mm" ++#endif +diff --git a/src/plugins/bearer/corewlan/qcorewlanengine_10_6.mm b/src/plugins/bearer/corewlan/qcorewlanengine_10_6.mm +new file mode 100644 +index 0000000..a3bf615 +--- /dev/null ++++ src/plugins/bearer/corewlan/qcorewlanengine_10_6.mm +@@ -0,0 +1,916 @@ ++/**************************************************************************** ++** ++** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ++** Contact: http://www.qt-project.org/legal ++** ++** This file is part of the plugins of the Qt Toolkit. ++** ++** $QT_BEGIN_LICENSE:LGPL$ ++** Commercial License Usage ++** Licensees holding valid commercial Qt licenses may use this file in ++** accordance with the commercial license agreement provided with the ++** Software or, alternatively, in accordance with the terms contained in ++** a written agreement between you and Digia. For licensing terms and ++** conditions see http://qt.digia.com/licensing. For further information ++** use the contact form at http://qt.digia.com/contact-us. ++** ++** GNU Lesser General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU Lesser ++** General Public License version 2.1 as published by the Free Software ++** Foundation and appearing in the file LICENSE.LGPL included in the ++** packaging of this file. Please review the following information to ++** ensure the GNU Lesser General Public License version 2.1 requirements ++** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ++** ++** In addition, as a special exception, Digia gives you certain additional ++** rights. These rights are described in the Digia Qt LGPL Exception ++** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ++** ++** GNU General Public License Usage ++** Alternatively, this file may be used under the terms of the GNU ++** General Public License version 3.0 as published by the Free Software ++** Foundation and appearing in the file LICENSE.GPL included in the ++** packaging of this file. Please review the following information to ++** ensure the GNU General Public License version 3.0 requirements will be ++** met: http://www.gnu.org/copyleft/gpl.html. ++** ++** ++** $QT_END_LICENSE$ ++** ++****************************************************************************/ ++ ++#include ++ ++@interface QT_MANGLE_NAMESPACE(QNSListener) : NSObject ++{ ++ NSNotificationCenter *notificationCenter; ++ CWInterface *currentInterface; ++ QCoreWlanEngine *engine; ++ NSLock *locker; ++} ++- (void)notificationHandler;//:(NSNotification *)notification; ++- (void)remove; ++- (void)setEngine:(QCoreWlanEngine *)coreEngine; ++- (QCoreWlanEngine *)engine; ++- (void)dealloc; ++ ++@property (assign) QCoreWlanEngine* engine; ++ ++@end ++ ++@implementation QT_MANGLE_NAMESPACE(QNSListener) ++ ++- (id) init ++{ ++ [locker lock]; ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ notificationCenter = [NSNotificationCenter defaultCenter]; ++ currentInterface = [CWInterface interfaceWithName:nil]; ++ [notificationCenter addObserver:self selector:@selector(notificationHandler:) name:kCWPowerDidChangeNotification object:nil]; ++ [locker unlock]; ++ [autoreleasepool release]; ++ return self; ++} ++ ++-(void)dealloc ++{ ++ [super dealloc]; ++} ++ ++-(void)setEngine:(QCoreWlanEngine *)coreEngine ++{ ++ [locker lock]; ++ if(!engine) ++ engine = coreEngine; ++ [locker unlock]; ++} ++ ++-(QCoreWlanEngine *)engine ++{ ++ return engine; ++} ++ ++-(void)remove ++{ ++ [locker lock]; ++ [notificationCenter removeObserver:self]; ++ [locker unlock]; ++} ++ ++- (void)notificationHandler//:(NSNotification *)notification ++{ ++ engine->requestUpdate(); ++} ++@end ++ ++static QT_MANGLE_NAMESPACE(QNSListener) *listener = 0; ++ ++QT_BEGIN_NAMESPACE ++ ++void networkChangeCallback(SCDynamicStoreRef/* store*/, CFArrayRef changedKeys, void *info) ++{ ++ for ( long i = 0; i < CFArrayGetCount(changedKeys); i++) { ++ ++ QString changed = QCFString::toQString(CFStringRef((CFStringRef)CFArrayGetValueAtIndex(changedKeys, i))); ++ if( changed.contains("/Network/Global/IPv4")) { ++ QCoreWlanEngine* wlanEngine = static_cast(info); ++ wlanEngine->requestUpdate(); ++ } ++ } ++ return; ++} ++ ++ ++QScanThread::QScanThread(QObject *parent) ++ :QThread(parent) ++{ ++} ++ ++QScanThread::~QScanThread() ++{ ++} ++ ++void QScanThread::quit() ++{ ++ wait(); ++} ++ ++void QScanThread::run() ++{ ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ QStringList found; ++ mutex.lock(); ++ CWInterface *currentInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceName)]; ++ mutex.unlock(); ++ ++ if([currentInterface power]) { ++ NSError *err = nil; ++ NSDictionary *parametersDict = [NSDictionary dictionaryWithObjectsAndKeys: ++ [NSNumber numberWithBool:YES], kCWScanKeyMerge, ++ [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType, ++ [NSNumber numberWithInteger:100], kCWScanKeyRestTime, nil]; ++ ++ NSArray* apArray = [currentInterface scanForNetworksWithParameters:parametersDict error:&err]; ++ CWNetwork *apNetwork; ++ ++ if (!err) { ++ ++ for(uint row=0; row < [apArray count]; row++ ) { ++ apNetwork = [apArray objectAtIndex:row]; ++ ++ const QString networkSsid = QCFString::toQString(CFStringRef([apNetwork ssid])); ++ const QString id = QString::number(qHash(QLatin1String("corewlan:") + networkSsid)); ++ found.append(id); ++ ++ QNetworkConfiguration::StateFlags state = QNetworkConfiguration::Undefined; ++ bool known = isKnownSsid(networkSsid); ++ if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) { ++ if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) { ++ state = QNetworkConfiguration::Active; ++ } ++ } ++ if(state == QNetworkConfiguration::Undefined) { ++ if(known) { ++ state = QNetworkConfiguration::Discovered; ++ } else { ++ state = QNetworkConfiguration::Undefined; ++ } ++ } ++ QNetworkConfiguration::Purpose purpose = QNetworkConfiguration::UnknownPurpose; ++ if([[apNetwork securityMode] intValue] == kCWSecurityModeOpen) { ++ purpose = QNetworkConfiguration::PublicPurpose; ++ } else { ++ purpose = QNetworkConfiguration::PrivatePurpose; ++ } ++ ++ found.append(foundNetwork(id, networkSsid, state, interfaceName, purpose)); ++ ++ } ++ } ++ } ++ // add known configurations that are not around. ++ QMapIterator > i(userProfiles); ++ while (i.hasNext()) { ++ i.next(); ++ ++ QString networkName = i.key(); ++ const QString id = QString::number(qHash(QLatin1String("corewlan:") + networkName)); ++ ++ if(!found.contains(id)) { ++ QString networkSsid = getSsidFromNetworkName(networkName); ++ const QString ssidId = QString::number(qHash(QLatin1String("corewlan:") + networkSsid)); ++ QNetworkConfiguration::StateFlags state = QNetworkConfiguration::Undefined; ++ QString interfaceName; ++ QMapIterator ij(i.value()); ++ while (ij.hasNext()) { ++ ij.next(); ++ interfaceName = ij.value(); ++ } ++ ++ if( [currentInterface.interfaceState intValue] == kCWInterfaceStateRunning) { ++ if( networkSsid == QCFString::toQString(CFStringRef([currentInterface ssid]))) { ++ state = QNetworkConfiguration::Active; ++ } ++ } ++ if(state == QNetworkConfiguration::Undefined) { ++ if( userProfiles.contains(networkName) ++ && found.contains(ssidId)) { ++ state = QNetworkConfiguration::Discovered; ++ } ++ } ++ ++ if(state == QNetworkConfiguration::Undefined) { ++ state = QNetworkConfiguration::Defined; ++ } ++ ++ found.append(foundNetwork(id, networkName, state, interfaceName, QNetworkConfiguration::UnknownPurpose)); ++ } ++ } ++ emit networksChanged(); ++ [autoreleasepool release]; ++} ++ ++QStringList QScanThread::foundNetwork(const QString &id, const QString &name, const QNetworkConfiguration::StateFlags state, const QString &interfaceName, const QNetworkConfiguration::Purpose purpose) ++{ ++ QStringList found; ++ QMutexLocker locker(&mutex); ++ QNetworkConfigurationPrivate *ptr = new QNetworkConfigurationPrivate; ++ ++ ptr->name = name; ++ ptr->isValid = true; ++ ptr->id = id; ++ ptr->state = state; ++ ptr->type = QNetworkConfiguration::InternetAccessPoint; ++ ptr->bearerType = QNetworkConfiguration::BearerWLAN; ++ ptr->purpose = purpose; ++ ++ fetchedConfigurations.append( ptr); ++ configurationInterface.insert(ptr->id, interfaceName); ++ ++ locker.unlock(); ++ locker.relock(); ++ found.append(id); ++ return found; ++} ++ ++QList QScanThread::getConfigurations() ++{ ++ QMutexLocker locker(&mutex); ++ ++ QList foundConfigurations = fetchedConfigurations; ++ fetchedConfigurations.clear(); ++ ++ return foundConfigurations; ++} ++ ++void QScanThread::getUserConfigurations() ++{ ++ QMutexLocker locker(&mutex); ++ ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ userProfiles.clear(); ++ ++ NSArray *wifiInterfaces = [CWInterface supportedInterfaces]; ++ for(uint row=0; row < [wifiInterfaces count]; row++ ) { ++ ++ CWInterface *wifiInterface = [CWInterface interfaceWithName: [wifiInterfaces objectAtIndex:row]]; ++ if ( ![wifiInterface power] ) ++ continue; ++ ++ NSString *nsInterfaceName = [wifiInterface name]; ++// add user configured system networks ++ SCDynamicStoreRef dynRef = SCDynamicStoreCreate(kCFAllocatorSystemDefault, (CFStringRef)@"Qt corewlan", nil, nil); ++ NSDictionary * airportPlist = (NSDictionary *)SCDynamicStoreCopyValue(dynRef, (CFStringRef)[NSString stringWithFormat:@"Setup:/Network/Interface/%@/AirPort", nsInterfaceName]); ++ CFRelease(dynRef); ++ if(airportPlist != nil) { ++ NSDictionary *prefNetDict = [airportPlist objectForKey:@"PreferredNetworks"]; ++ ++ NSArray *thisSsidarray = [prefNetDict valueForKey:@"SSID_STR"]; ++ for(NSString *ssidkey in thisSsidarray) { ++ QString thisSsid = QCFString::toQString(CFStringRef(ssidkey)); ++ if(!userProfiles.contains(thisSsid)) { ++ QMap map; ++ map.insert(thisSsid, QCFString::toQString(CFStringRef(nsInterfaceName))); ++ userProfiles.insert(thisSsid, map); ++ } ++ } ++ CFRelease(airportPlist); ++ } ++ ++ // 802.1X user profiles ++ QString userProfilePath = QDir::homePath() + "/Library/Preferences/com.apple.eap.profiles.plist"; ++ NSDictionary* eapDict = [[[NSDictionary alloc] initWithContentsOfFile: (NSString *)QCFString::toCFStringRef(userProfilePath)] autorelease]; ++ if(eapDict != nil) { ++ NSString *profileStr= @"Profiles"; ++ NSString *nameStr = @"UserDefinedName"; ++ NSString *networkSsidStr = @"Wireless Network"; ++ for (id profileKey in eapDict) { ++ if ([profileStr isEqualToString:profileKey]) { ++ NSDictionary *itemDict = [eapDict objectForKey:profileKey]; ++ for (id itemKey in itemDict) { ++ ++ NSInteger dictSize = [itemKey count]; ++ id objects[dictSize]; ++ id keys[dictSize]; ++ ++ [itemKey getObjects:objects andKeys:keys]; ++ QString networkName; ++ QString ssid; ++ for(int i = 0; i < dictSize; i++) { ++ if([nameStr isEqualToString:keys[i]]) { ++ networkName = QCFString::toQString(CFStringRef(objects[i])); ++ } ++ if([networkSsidStr isEqualToString:keys[i]]) { ++ ssid = QCFString::toQString(CFStringRef(objects[i])); ++ } ++ if(!userProfiles.contains(networkName) ++ && !ssid.isEmpty()) { ++ QMap map; ++ map.insert(ssid, QCFString::toQString(CFStringRef(nsInterfaceName))); ++ userProfiles.insert(networkName, map); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ [autoreleasepool release]; ++} ++ ++QString QScanThread::getSsidFromNetworkName(const QString &name) ++{ ++ QMutexLocker locker(&mutex); ++ ++ QMapIterator > i(userProfiles); ++ while (i.hasNext()) { ++ i.next(); ++ QMap map = i.value(); ++ QMapIterator ij(i.value()); ++ while (ij.hasNext()) { ++ ij.next(); ++ const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") +i.key())); ++ if(name == i.key() || name == networkNameHash) { ++ return ij.key(); ++ } ++ } ++ } ++ return QString(); ++} ++ ++QString QScanThread::getNetworkNameFromSsid(const QString &ssid) ++{ ++ QMutexLocker locker(&mutex); ++ ++ QMapIterator > i(userProfiles); ++ while (i.hasNext()) { ++ i.next(); ++ QMap map = i.value(); ++ QMapIterator ij(i.value()); ++ while (ij.hasNext()) { ++ ij.next(); ++ if(ij.key() == ssid) { ++ return i.key(); ++ } ++ } ++ } ++ return QString(); ++} ++ ++bool QScanThread::isKnownSsid(const QString &ssid) ++{ ++ QMutexLocker locker(&mutex); ++ ++ QMapIterator > i(userProfiles); ++ while (i.hasNext()) { ++ i.next(); ++ QMap map = i.value(); ++ if(map.keys().contains(ssid)) { ++ return true; ++ } ++ } ++ return false; ++} ++ ++ ++QCoreWlanEngine::QCoreWlanEngine(QObject *parent) ++: QBearerEngineImpl(parent), scanThread(0) ++{ ++ scanThread = new QScanThread(this); ++ connect(scanThread, SIGNAL(networksChanged()), ++ this, SLOT(networksChanged())); ++} ++ ++QCoreWlanEngine::~QCoreWlanEngine() ++{ ++ while (!foundConfigurations.isEmpty()) ++ delete foundConfigurations.takeFirst(); ++ [listener remove]; ++ [listener release]; ++} ++ ++void QCoreWlanEngine::initialize() ++{ ++ QMutexLocker locker(&mutex); ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ ++ if([[CWInterface supportedInterfaces] count] > 0 && !listener) { ++ listener = [[QT_MANGLE_NAMESPACE(QNSListener) alloc] init]; ++ listener.engine = this; ++ hasWifi = true; ++ } else { ++ hasWifi = false; ++ } ++ storeSession = NULL; ++ ++ startNetworkChangeLoop(); ++ [autoreleasepool release]; ++} ++ ++ ++QString QCoreWlanEngine::getInterfaceFromId(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ ++ return scanThread->configurationInterface.value(id); ++} ++ ++bool QCoreWlanEngine::hasIdentifier(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ ++ return scanThread->configurationInterface.contains(id); ++} ++ ++void QCoreWlanEngine::connectToId(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ QString interfaceString = getInterfaceFromId(id); ++ ++ CWInterface *wifiInterface = ++ [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)]; ++ ++ if ([wifiInterface power]) { ++ NSError *err = nil; ++ NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; ++ ++ QString wantedSsid; ++ ++ QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id); ++ ++ const QString idHash = QString::number(qHash(QLatin1String("corewlan:") + ptr->name)); ++ const QString idHash2 = QString::number(qHash(QLatin1String("corewlan:") + scanThread->getNetworkNameFromSsid(ptr->name))); ++ ++ bool using8021X = false; ++ if (idHash2 != id) { ++ NSArray *array = [CW8021XProfile allUser8021XProfiles]; ++ ++ for (NSUInteger i = 0; i < [array count]; ++i) { ++ const QString networkNameHashCheck = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef([[array objectAtIndex:i] userDefinedName])))); ++ ++ const QString ssidHash = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef([[array objectAtIndex:i] ssid])))); ++ ++ if (id == networkNameHashCheck || id == ssidHash) { ++ const QString thisName = scanThread->getSsidFromNetworkName(id); ++ if (thisName.isEmpty()) ++ wantedSsid = id; ++ else ++ wantedSsid = thisName; ++ ++ [params setValue: [array objectAtIndex:i] forKey:kCWAssocKey8021XProfile]; ++ using8021X = true; ++ break; ++ } ++ } ++ } ++ ++ if (!using8021X) { ++ QString wantedNetwork; ++ QMapIterator > i(scanThread->userProfiles); ++ while (i.hasNext()) { ++ i.next(); ++ wantedNetwork = i.key(); ++ const QString networkNameHash = QString::number(qHash(QLatin1String("corewlan:") + wantedNetwork)); ++ if (id == networkNameHash) { ++ wantedSsid = scanThread->getSsidFromNetworkName(wantedNetwork); ++ break; ++ } ++ } ++ } ++ NSDictionary *scanParameters = [NSDictionary dictionaryWithObjectsAndKeys: ++ [NSNumber numberWithBool:YES], kCWScanKeyMerge, ++ [NSNumber numberWithInt:kCWScanTypeFast], kCWScanKeyScanType, ++ [NSNumber numberWithInteger:100], kCWScanKeyRestTime, ++ (NSString *)QCFString::toCFStringRef(wantedSsid), kCWScanKeySSID, ++ nil]; ++ ++ NSArray *scanArray = [wifiInterface scanForNetworksWithParameters:scanParameters error:&err]; ++ ++ if(!err) { ++ for(uint row=0; row < [scanArray count]; row++ ) { ++ CWNetwork *apNetwork = [scanArray objectAtIndex:row]; ++ ++ if(wantedSsid == QCFString::toQString(CFStringRef([apNetwork ssid]))) { ++ ++ if(!using8021X) { ++ SecKeychainAttribute attributes[3]; ++ ++ NSString *account = [apNetwork ssid]; ++ NSString *keyKind = @"AirPort network password"; ++ NSString *keyName = account; ++ ++ attributes[0].tag = kSecAccountItemAttr; ++ attributes[0].data = (void *)[account UTF8String]; ++ attributes[0].length = [account length]; ++ ++ attributes[1].tag = kSecDescriptionItemAttr; ++ attributes[1].data = (void *)[keyKind UTF8String]; ++ attributes[1].length = [keyKind length]; ++ ++ attributes[2].tag = kSecLabelItemAttr; ++ attributes[2].data = (void *)[keyName UTF8String]; ++ attributes[2].length = [keyName length]; ++ ++ SecKeychainAttributeList attributeList = {3,attributes}; ++ ++ SecKeychainSearchRef searchRef; ++ SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributeList, &searchRef); ++ ++ NSString *password = @""; ++ SecKeychainItemRef searchItem; ++ ++ if (SecKeychainSearchCopyNext(searchRef, &searchItem) == noErr) { ++ UInt32 realPasswordLength; ++ SecKeychainAttribute attributesW[8]; ++ attributesW[0].tag = kSecAccountItemAttr; ++ SecKeychainAttributeList listW = {1,attributesW}; ++ char *realPassword; ++ OSStatus status = SecKeychainItemCopyContent(searchItem, NULL, &listW, &realPasswordLength,(void **)&realPassword); ++ ++ if (status == noErr) { ++ if (realPassword != NULL) { ++ ++ QByteArray pBuf; ++ pBuf.resize(realPasswordLength); ++ pBuf.prepend(realPassword); ++ pBuf.insert(realPasswordLength,'\0'); ++ ++ password = [NSString stringWithUTF8String:pBuf]; ++ } ++ SecKeychainItemFreeContent(&listW, realPassword); ++ } ++ ++ CFRelease(searchItem); ++ } else { ++ qDebug() << "SecKeychainSearchCopyNext error"; ++ } ++ [params setValue: password forKey: kCWAssocKeyPassphrase]; ++ } // end using8021X ++ ++ ++ bool result = [wifiInterface associateToNetwork: apNetwork parameters:[NSDictionary dictionaryWithDictionary:params] error:&err]; ++ ++ if(!err) { ++ if(!result) { ++ emit connectionError(id, ConnectError); ++ } else { ++ return; ++ } ++ } else { ++ qDebug() <<"associate ERROR"<< QCFString::toQString(CFStringRef([err localizedDescription ])); ++ } ++ } ++ } //end scan network ++ } else { ++ qDebug() <<"scan ERROR"<< QCFString::toQString(CFStringRef([err localizedDescription ])); ++ } ++ emit connectionError(id, InterfaceLookupError); ++ } ++ ++ locker.unlock(); ++ emit connectionError(id, InterfaceLookupError); ++ [autoreleasepool release]; ++} ++ ++void QCoreWlanEngine::disconnectFromId(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ ++ QString interfaceString = getInterfaceFromId(id); ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ ++ CWInterface *wifiInterface = ++ [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(interfaceString)]; ++ ++ [wifiInterface disassociate]; ++ if ([[wifiInterface interfaceState]intValue] != kCWInterfaceStateInactive) { ++ locker.unlock(); ++ emit connectionError(id, DisconnectionError); ++ locker.relock(); ++ } ++ [autoreleasepool release]; ++} ++ ++void QCoreWlanEngine::requestUpdate() ++{ ++ scanThread->getUserConfigurations(); ++ doRequestUpdate(); ++} ++ ++void QCoreWlanEngine::doRequestUpdate() ++{ ++ QMutexLocker locker(&mutex); ++ ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ ++ NSArray *wifiInterfaces = [CWInterface supportedInterfaces]; ++ for (uint row = 0; row < [wifiInterfaces count]; ++row) { ++ scanThread->interfaceName = QCFString::toQString(CFStringRef([wifiInterfaces objectAtIndex:row])); ++ scanThread->start(); ++ } ++ locker.unlock(); ++ [autoreleasepool release]; ++} ++ ++bool QCoreWlanEngine::isWifiReady(const QString &wifiDeviceName) ++{ ++ QMutexLocker locker(&mutex); ++ bool haswifi = false; ++ if(hasWifi) { ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ CWInterface *defaultInterface = [CWInterface interfaceWithName: (NSString *)QCFString::toCFStringRef(wifiDeviceName)]; ++ if([defaultInterface power]) { ++ haswifi = true; ++ } ++ [autoreleasepool release]; ++ } ++ return haswifi; ++} ++ ++ ++QNetworkSession::State QCoreWlanEngine::sessionStateForId(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id); ++ ++ if (!ptr) ++ return QNetworkSession::Invalid; ++ ++ if (!ptr->isValid) { ++ return QNetworkSession::Invalid; ++ } else if ((ptr->state & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) { ++ return QNetworkSession::Connected; ++ } else if ((ptr->state & QNetworkConfiguration::Discovered) == ++ QNetworkConfiguration::Discovered) { ++ return QNetworkSession::Disconnected; ++ } else if ((ptr->state & QNetworkConfiguration::Defined) == QNetworkConfiguration::Defined) { ++ return QNetworkSession::NotAvailable; ++ } else if ((ptr->state & QNetworkConfiguration::Undefined) == ++ QNetworkConfiguration::Undefined) { ++ return QNetworkSession::NotAvailable; ++ } ++ ++ return QNetworkSession::Invalid; ++} ++ ++QNetworkConfigurationManager::Capabilities QCoreWlanEngine::capabilities() const ++{ ++ return QNetworkConfigurationManager::ForcedRoaming; ++} ++ ++void QCoreWlanEngine::startNetworkChangeLoop() ++{ ++ ++ SCDynamicStoreContext dynStoreContext = { 0, this/*(void *)storeSession*/, NULL, NULL, NULL }; ++ storeSession = SCDynamicStoreCreate(NULL, ++ CFSTR("networkChangeCallback"), ++ networkChangeCallback, ++ &dynStoreContext); ++ if (!storeSession ) { ++ qWarning() << "could not open dynamic store: error:" << SCErrorString(SCError()); ++ return; ++ } ++ ++ CFMutableArrayRef notificationKeys; ++ notificationKeys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); ++ CFMutableArrayRef patternsArray; ++ patternsArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); ++ ++ CFStringRef storeKey; ++ storeKey = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, ++ kSCDynamicStoreDomainState, ++ kSCEntNetIPv4); ++ CFArrayAppendValue(notificationKeys, storeKey); ++ CFRelease(storeKey); ++ ++ storeKey = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, ++ kSCDynamicStoreDomainState, ++ kSCCompAnyRegex, ++ kSCEntNetIPv4); ++ CFArrayAppendValue(patternsArray, storeKey); ++ CFRelease(storeKey); ++ ++ if (!SCDynamicStoreSetNotificationKeys(storeSession , notificationKeys, patternsArray)) { ++ qWarning() << "register notification error:"<< SCErrorString(SCError()); ++ CFRelease(storeSession ); ++ CFRelease(notificationKeys); ++ CFRelease(patternsArray); ++ return; ++ } ++ CFRelease(notificationKeys); ++ CFRelease(patternsArray); ++ ++ runloopSource = SCDynamicStoreCreateRunLoopSource(NULL, storeSession , 0); ++ if (!runloopSource) { ++ qWarning() << "runloop source error:"<< SCErrorString(SCError()); ++ CFRelease(storeSession ); ++ return; ++ } ++ ++ CFRunLoopAddSource(CFRunLoopGetCurrent(), runloopSource, kCFRunLoopDefaultMode); ++ return; ++} ++ ++QNetworkSessionPrivate *QCoreWlanEngine::createSessionBackend() ++{ ++ return new QNetworkSessionPrivateImpl; ++} ++ ++QNetworkConfigurationPrivatePointer QCoreWlanEngine::defaultConfiguration() ++{ ++ return QNetworkConfigurationPrivatePointer(); ++} ++ ++bool QCoreWlanEngine::requiresPolling() const ++{ ++ return true; ++} ++ ++void QCoreWlanEngine::networksChanged() ++{ ++ QMutexLocker locker(&mutex); ++ ++ QStringList previous = accessPointConfigurations.keys(); ++ ++ QList foundConfigurations = scanThread->getConfigurations(); ++ while (!foundConfigurations.isEmpty()) { ++ QNetworkConfigurationPrivate *cpPriv = foundConfigurations.takeFirst(); ++ ++ previous.removeAll(cpPriv->id); ++ ++ if (accessPointConfigurations.contains(cpPriv->id)) { ++ QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(cpPriv->id); ++ ++ bool changed = false; ++ ++ ptr->mutex.lock(); ++ ++ if (ptr->isValid != cpPriv->isValid) { ++ ptr->isValid = cpPriv->isValid; ++ changed = true; ++ } ++ ++ if (ptr->name != cpPriv->name) { ++ ptr->name = cpPriv->name; ++ changed = true; ++ } ++ ++ if (ptr->bearerType != cpPriv->bearerType) { ++ ptr->bearerType = cpPriv->bearerType; ++ changed = true; ++ } ++ ++ if (ptr->state != cpPriv->state) { ++ ptr->state = cpPriv->state; ++ changed = true; ++ } ++ ++ ptr->mutex.unlock(); ++ ++ if (changed) { ++ locker.unlock(); ++ emit configurationChanged(ptr); ++ locker.relock(); ++ } ++ ++ delete cpPriv; ++ } else { ++ QNetworkConfigurationPrivatePointer ptr(cpPriv); ++ ++ accessPointConfigurations.insert(ptr->id, ptr); ++ ++ locker.unlock(); ++ emit configurationAdded(ptr); ++ locker.relock(); ++ } ++ } ++ ++ while (!previous.isEmpty()) { ++ QNetworkConfigurationPrivatePointer ptr = ++ accessPointConfigurations.take(previous.takeFirst()); ++ ++ locker.unlock(); ++ emit configurationRemoved(ptr); ++ locker.relock(); ++ } ++ ++ locker.unlock(); ++ emit updateCompleted(); ++ ++} ++ ++quint64 QCoreWlanEngine::bytesWritten(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ const QString interfaceStr = getInterfaceFromId(id); ++ return getBytes(interfaceStr,false); ++} ++ ++quint64 QCoreWlanEngine::bytesReceived(const QString &id) ++{ ++ QMutexLocker locker(&mutex); ++ const QString interfaceStr = getInterfaceFromId(id); ++ return getBytes(interfaceStr,true); ++} ++ ++quint64 QCoreWlanEngine::startTime(const QString &identifier) ++{ ++ QMutexLocker locker(&mutex); ++ NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init]; ++ quint64 timestamp = 0; ++ ++ NSString *filePath = @"/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"; ++ NSDictionary* plistDict = [[[NSDictionary alloc] initWithContentsOfFile:filePath] autorelease]; ++ if(plistDict == nil) ++ return timestamp; ++ NSString *input = @"KnownNetworks"; ++ NSString *timeStampStr = @"_timeStamp"; ++ ++ NSString *ssidStr = @"SSID_STR"; ++ ++ for (id key in plistDict) { ++ if ([input isEqualToString:key]) { ++ ++ NSDictionary *knownNetworksDict = [plistDict objectForKey:key]; ++ if(knownNetworksDict == nil) ++ return timestamp; ++ for (id networkKey in knownNetworksDict) { ++ bool isFound = false; ++ NSDictionary *itemDict = [knownNetworksDict objectForKey:networkKey]; ++ if(itemDict == nil) ++ return timestamp; ++ NSInteger dictSize = [itemDict count]; ++ id objects[dictSize]; ++ id keys[dictSize]; ++ ++ [itemDict getObjects:objects andKeys:keys]; ++ bool ok = false; ++ for(int i = 0; i < dictSize; i++) { ++ if([ssidStr isEqualToString:keys[i]]) { ++ const QString ident = QString::number(qHash(QLatin1String("corewlan:") + QCFString::toQString(CFStringRef(objects[i])))); ++ if(ident == identifier) { ++ ok = true; ++ } ++ } ++ if(ok && [timeStampStr isEqualToString:keys[i]]) { ++ timestamp = (quint64)[objects[i] timeIntervalSince1970]; ++ isFound = true; ++ break; ++ } ++ } ++ if(isFound) ++ break; ++ } ++ } ++ } ++ [autoreleasepool release]; ++ return timestamp; ++} ++ ++quint64 QCoreWlanEngine::getBytes(const QString &interfaceName, bool b) ++{ ++ struct ifaddrs *ifAddressList, *ifAddress; ++ struct if_data *if_data; ++ ++ quint64 bytes = 0; ++ ifAddressList = nil; ++ if(getifaddrs(&ifAddressList) == 0) { ++ for(ifAddress = ifAddressList; ifAddress; ifAddress = ifAddress->ifa_next) { ++ if(interfaceName == ifAddress->ifa_name) { ++ if_data = (struct if_data*)ifAddress->ifa_data; ++ if(b) { ++ bytes = if_data->ifi_ibytes; ++ break; ++ } else { ++ bytes = if_data->ifi_obytes; ++ break; ++ } ++ } ++ } ++ freeifaddrs(ifAddressList); ++ } ++ return bytes; ++} ++ ++QT_END_NAMESPACE diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh index b63c6770..72c4c749 100755 --- a/scripts/check-dependencies.sh +++ b/scripts/check-dependencies.sh @@ -66,6 +66,31 @@ cgal_sysver() cgal_sysver_result=`grep "define *CGAL_VERSION *[0-9.]*" $cgalpath | awk '{print $3}'` } +glib2_sysver() +{ + #Get architecture triplet - e.g. x86_64-linux-gnu + glib2archtriplet=`gcc -dumpmachine 2>/dev/null` + if [ -z "$VAR" ]; then + glib2archtriplet=`dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null` + fi + glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h + if [ ! -e $glib2path ]; then + #No glib found + #glib can be installed in /usr/lib/i386-linux-gnu/glib-2.0/ on arch i686-linux-gnu (sometimes?) + if [ $glib2archtriplet = "i686-linux-gnu" ]; then + glib2archtriplet=i386-linux-gnu + glib2path=$1/lib/$glib2archtriplet/glib-2.0/include/glibconfig.h + if [ ! -e $glib2path ]; then return; fi + else + return; + fi + fi + glib2major=`grep "define *GLIB_MAJOR_VERSION *[0-9.]*" $glib2path | awk '{print $3}'` + glib2minor=`grep "define *GLIB_MINOR_VERSION *[0-9.]*" $glib2path | awk '{print $3}'` + glib2micro=`grep "define *GLIB_MICRO_VERSION *[0-9.]*" $glib2path | awk '{print $3}'` + glib2_sysver_result="${glib2major}.${glib2minor}.${glib2micro}" +} + boost_sysver() { boostpath=$1/include/boost/version.hpp @@ -530,7 +555,7 @@ checkargs() main() { - deps="qt4 cgal gmp mpfr boost opencsg glew eigen gcc bison flex make" + deps="qt4 cgal gmp mpfr boost opencsg glew eigen glib2 gcc bison flex make" #deps="$deps curl git" # not technically necessary for build #deps="$deps python cmake imagemagick" # only needed for tests #deps="cgal" diff --git a/scripts/macosx-build-dependencies.sh b/scripts/macosx-build-dependencies.sh index 19c97097..45f68188 100755 --- a/scripts/macosx-build-dependencies.sh +++ b/scripts/macosx-build-dependencies.sh @@ -24,7 +24,7 @@ BASEDIR=$PWD/../libraries OPENSCADDIR=$PWD SRCDIR=$BASEDIR/src DEPLOYDIR=$BASEDIR/install -MAC_OSX_VERSION_MIN=10.6 +MAC_OSX_VERSION_MIN=10.7 OPTION_32BIT=false OPTION_LLVM=false OPTION_CLANG=false @@ -54,6 +54,9 @@ build_qt() fi tar xzf qt-everywhere-opensource-src-$version.tar.gz cd qt-everywhere-opensource-src-$version + patch -p0 < $OPENSCADDIR/patches/qt4/patch-src_corelib_global_qglobal.h.diff + patch -p0 < $OPENSCADDIR/patches/qt4/patch-libtiff.diff + patch -p0 < $OPENSCADDIR/patches/qt4/patch-src_plugins_bearer_corewlan_qcorewlanengine.mm.diff if $USING_CLANG; then # FIX for clang sed -i "" -e "s/::TabletProximityRec/TabletProximityRec/g" src/gui/kernel/qt_cocoa_helpers_mac_p.h @@ -80,6 +83,51 @@ build_gmp() fi tar xjf gmp-$version.tar.bz2 cd gmp-$version + patch -p0 gmp-h.in << EOF +--- gmp-5.1.3/gmp-h.in.old 2013-12-02 20:16:26.000000000 -0800 ++++ gmp-5.1.3/gmp-h.in 2013-12-02 20:21:22.000000000 -0800 +@@ -27,13 +27,38 @@ + #endif + + +-/* Instantiated by configure. */ + #if ! defined (__GMP_WITHIN_CONFIGURE) ++/* For benefit of fat builds on MacOSX, generate a .h file that can ++ * be used with a universal fat library ++ */ ++#if defined(__x86_64__) ++#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 ++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0 ++#define GMP_LIMB_BITS 64 ++#define GMP_NAIL_BITS 0 ++#elif defined(__i386__) ++#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 ++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0 ++#define GMP_LIMB_BITS 32 ++#define GMP_NAIL_BITS 0 ++#elif defined(__powerpc64__) ++#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 ++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1 ++#define GMP_LIMB_BITS 64 ++#define GMP_NAIL_BITS 0 ++#elif defined(__ppc__) ++#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 ++#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1 ++#define GMP_LIMB_BITS 32 ++#define GMP_NAIL_BITS 0 ++#else ++/* For other architectures, fall back on values computed by configure */ + #define __GMP_HAVE_HOST_CPU_FAMILY_power @HAVE_HOST_CPU_FAMILY_power@ + #define __GMP_HAVE_HOST_CPU_FAMILY_powerpc @HAVE_HOST_CPU_FAMILY_powerpc@ + #define GMP_LIMB_BITS @GMP_LIMB_BITS@ + #define GMP_NAIL_BITS @GMP_NAIL_BITS@ + #endif ++#endif + #define GMP_NUMB_BITS (GMP_LIMB_BITS - GMP_NAIL_BITS) + #define GMP_NUMB_MASK ((~ __GMP_CAST (mp_limb_t, 0)) >> GMP_NAIL_BITS) + #define GMP_NUMB_MAX GMP_NUMB_MASK +EOF + if $OPTION_32BIT; then mkdir build-i386 cd build-i386 @@ -116,42 +164,6 @@ build_gmp() mkdir -p include cp x86_64/include/gmp.h include/ cp x86_64/include/gmpxx.h include/ - - patch -p0 include/gmp.h << EOF ---- gmp.h.orig 2011-11-08 01:03:41.000000000 +0100 -+++ gmp.h 2011-11-08 01:06:21.000000000 +0100 -@@ -26,12 +26,28 @@ - #endif - - --/* Instantiated by configure. */ --#if ! defined (__GMP_WITHIN_CONFIGURE) -+#if defined(__i386__) -+#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 -+#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0 -+#define GMP_LIMB_BITS 32 -+#define GMP_NAIL_BITS 0 -+#elif defined(__x86_64__) - #define __GMP_HAVE_HOST_CPU_FAMILY_power 0 - #define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 0 - #define GMP_LIMB_BITS 64 - #define GMP_NAIL_BITS 0 -+#elif defined(__ppc__) -+#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 -+#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1 -+#define GMP_LIMB_BITS 32 -+#define GMP_NAIL_BITS 0 -+#elif defined(__powerpc64__) -+#define __GMP_HAVE_HOST_CPU_FAMILY_power 0 -+#define __GMP_HAVE_HOST_CPU_FAMILY_powerpc 1 -+#define GMP_LIMB_BITS 64 -+#define GMP_NAIL_BITS 0 -+#else -+#error Unsupported architecture - #endif - #define GMP_NUMB_BITS (GMP_LIMB_BITS - GMP_NAIL_BITS) - #define GMP_NUMB_MASK ((~ __GMP_CAST (mp_limb_t, 0)) >> GMP_NAIL_BITS) -EOF } # As with gmplib, mpfr is built separately in 32-bit and 64-bit mode and then merged @@ -220,7 +232,7 @@ build_boost() BOOST_TOOLSET="toolset=clang" echo "using clang ;" >> tools/build/v2/user-config.jam fi - ./b2 -d+2 $BOOST_TOOLSET cflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS" linkflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS -headerpad_max_install_names" install + ./b2 -j6 -d+2 $BOOST_TOOLSET cflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS" linkflags="-mmacosx-version-min=$MAC_OSX_VERSION_MIN -arch x86_64 $BOOST_EXTRA_FLAGS -headerpad_max_install_names" install install_name_tool -id $DEPLOYDIR/lib/libboost_thread.dylib $DEPLOYDIR/lib/libboost_thread.dylib install_name_tool -change libboost_system.dylib $DEPLOYDIR/lib/libboost_system.dylib $DEPLOYDIR/lib/libboost_thread.dylib install_name_tool -change libboost_chrono.dylib $DEPLOYDIR/lib/libboost_chrono.dylib $DEPLOYDIR/lib/libboost_thread.dylib @@ -282,6 +294,43 @@ build_glew() make GLEW_DEST=$DEPLOYDIR CC=$CC CFLAGS.EXTRA="-no-cpp-precomp -dynamic -fno-common -mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" LDFLAGS.EXTRA="-mmacosx-version-min=$MAC_OSX_VERSION_MIN $GLEW_EXTRA_FLAGS -arch x86_64" STRIP= install } +build_gettext() +{ + version=$1 + echo "Building gettext $version..." + + cd "$BASEDIR"/src + rm -rf "gettext-$version" + if [ ! -f "glib-$version.tar.xz" ]; then + curl --insecure -LO "http://ftpmirror.gnu.org/gettext/gettext-$version.tar.gz" + fi + tar xzf "gettext-$version.tar.gz" + cd "gettext-$version" + + ./configure --prefix="$DEPLOYDIR" + make -j4 + make install +} + +build_glib2() +{ + version=$1 + echo "Building glib2 $version..." + + cd "$BASEDIR"/src + rm -rf "glib-$version" + maj_min_version="${version%.*}" #Drop micro + if [ ! -f "glib-$version.tar.xz" ]; then + curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz" + fi + tar xJf "glib-$version.tar.xz" + cd "glib-$version" + + ./configure --disable-gtk-doc --disable-man --prefix="$DEPLOYDIR" CFLAGS="-I$DEPLOYDIR/include" LDFLAGS="-L$DEPLOYDIR/lib" + make -j4 + make install +} + build_opencsg() { version=$1 @@ -446,6 +495,12 @@ build_boost 1.54.0 # NB! For CGAL, also update the actual download URL in the function build_cgal 4.3 build_glew 1.10.0 +<<<<<<< HEAD +build_gettext 0.18.3.1 +build_glib2 2.38.2 +======= +build_glib2 2.38.1 +>>>>>>> d7d5bea7363703c76b9787598304bfc838e893ee build_opencsg 1.3.2 if $OPTION_DEPLOY; then # build_sparkle andymatuschak 0ed83cf9f2eeb425d4fdd141c01a29d843970c20 diff --git a/scripts/travis-ci.sh b/scripts/travis-ci.sh new file mode 100755 index 00000000..9f44b0c9 --- /dev/null +++ b/scripts/travis-ci.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +cd tests +cmake . +if [[ $? != 0 ]]; then + echo "Error configuring test suite" + exit 1 +fi +make -j2 +if [[ $? != 0 ]]; then + echo "Error building test suite" + exit 1 +fi +ctest -j8 +if [[ $? != 0 ]]; then + echo "Test failure" + exit 1 +fi diff --git a/scripts/uni-build-dependencies.sh b/scripts/uni-build-dependencies.sh index e652c473..5b9e1290 100755 --- a/scripts/uni-build-dependencies.sh +++ b/scripts/uni-build-dependencies.sh @@ -409,6 +409,48 @@ build_glew() GLEW_DEST=$DEPLOYDIR $MAKER install } +build_gettext() +{ + version=$1 + echo "Building gettext $version..." + + cd "$BASEDIR"/src + rm -rf "gettext-$version" + if [ ! -f "glib-$version.tar.xz" ]; then + curl --insecure -LO "http://ftpmirror.gnu.org/gettext/gettext-$version.tar.gz" + fi + tar xzf "gettext-$version.tar.gz" + cd "gettext-$version" + + ./configure --prefix="$DEPLOYDIR" + make -j4 + make install +} + +build_glib2() +{ + version="$1" + maj_min_version="${version%.*}" #Drop micro + + if [ -e $DEPLOYDIR/lib/glib-2.0 ]; then +echo "glib2 already installed. not building" + return +fi + +echo "Building glib2 $version..." + cd "$BASEDIR"/src + rm -rf "glib-$version" + if [ ! -f "glib-$version.tar.xz" ]; then +curl --insecure -LO "http://ftp.gnome.org/pub/gnome/sources/glib/$maj_min_version/glib-$version.tar.xz" + fi +tar xJf "glib-$version.tar.xz" + cd "glib-$version" + + ./configure --disable-gtk-doc --disable-man --prefix="$DEPLOYDIR" CFLAGS="-I$DEPLOYDIR/include" LDFLAGS="-L$DEPLOYDIR/lib" + make -j$NUMCPU + make install +} + build_opencsg() { if [ -e $DEPLOYDIR/lib/libopencsg.so ]; then @@ -603,5 +645,7 @@ build_boost 1.53.0 build_cgal 4.0.2 build_glew 1.9.0 build_opencsg 1.3.2 +build_gettext 0.18.3.1 +build_glib2 2.38.2 echo "OpenSCAD dependencies built and installed to " $BASEDIR diff --git a/scripts/uni-get-dependencies.sh b/scripts/uni-get-dependencies.sh index a0306ef8..d2408c00 100755 --- a/scripts/uni-get-dependencies.sh +++ b/scripts/uni-get-dependencies.sh @@ -8,7 +8,7 @@ get_fedora_deps() { sudo yum install qt-devel bison flex eigen3-devel python-paramiko \ boost-devel mpfr-devel gmp-devel glew-devel CGAL-devel gcc gcc-c++ pkgconfig \ - opencsg-devel git libXmu-devel curl imagemagick ImageMagick make \ + opencsg-devel git libXmu-devel curl imagemagick ImageMagick glib2-devel make \ xorg-x11-server-Xvfb } @@ -21,7 +21,7 @@ get_altlinux_deps() { for i in boost-devel boost-filesystem-devel gcc4.5 gcc4.5-c++ boost-program_options-devel \ boost-thread-devel boost-system-devel boost-regex-devel eigen3 libmpfr libgmp libgmp_cxx-devel qt4-devel libcgal-devel git-core \ - libglew-devel flex bison curl imagemagick; do sudo apt-get install $i; done + libglew-devel flex bison curl imagemagick glib2-devel; do sudo apt-get install $i; done } get_freebsd_deps() @@ -29,20 +29,21 @@ get_freebsd_deps() pkg_add -r bison boost-libs cmake git bash eigen3 flex gmake gmp mpfr \ xorg libGLU libXmu libXi xorg-vfbserver glew \ qt4-corelib qt4-gui qt4-moc qt4-opengl qt4-qmake qt4-rcc qt4-uic \ - opencsg cgal curl imagemagick + opencsg cgal curl imagemagick glib2-devel } get_netbsd_deps() { sudo pkgin install bison boost cmake git bash eigen flex gmake gmp mpfr \ qt4 glew cgal opencsg modular-xorg python27 py27-paramiko curl \ - imagemagick ImageMagick + imagemagick ImageMagick glib2-devel } get_opensuse_deps() { sudo zypper install libeigen3-devel mpfr-devel gmp-devel boost-devel \ - libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl + libqt4-devel glew-devel cmake git bison flex cgal-devel opencsg-devel curl \ + glib2-devel } get_mageia_deps() @@ -50,7 +51,7 @@ get_mageia_deps() sudo urpmi ctags sudo urpmi task-c-devel task-c++-devel libqt4-devel libgmp-devel \ libmpfr-devel libboost-devel eigen3-devel libglew-devel bison flex \ - cmake imagemagick python curl git x11-server-xvfb + cmake imagemagick glib2-devel python curl git x11-server-xvfb } get_debian_deps() @@ -59,7 +60,7 @@ get_debian_deps() libxmu-dev cmake bison flex git-core libboost-all-dev \ libXi-dev libmpfr-dev libboost-dev libglew-dev \ libeigen3-dev libcgal-dev libopencsg-dev libgmp3-dev libgmp-dev \ - python-paramiko curl imagemagick; do + python-paramiko curl imagemagick libglib2.0-dev; do sudo apt-get -y install $pkg; done } diff --git a/src/AboutDialog.html b/src/AboutDialog.html index 99e7c3b2..65a54d71 100644 --- a/src/AboutDialog.html +++ b/src/AboutDialog.html @@ -64,6 +64,7 @@ Please visit this link for a copy of the license: C++, GCC, clang
  • python
  • Nullsoft installer +
  • GLib

    diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 3a05b2b5..20c5d5ec 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -159,9 +159,21 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) PRINT("Hull() currently requires a valid 2-manifold. Please modify your design. See http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/STL_Import_and_Export"); } else { - chN.p3->convert_to_Polyhedron(P); - std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), - boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); + bool err = false; + std::string errmsg(""); + try { + err = nefworkaround::convert_to_Polyhedron( *(chN.p3), P ); + //chN.p3->convert_to_Polyhedron(P); + } catch (const CGAL::Failure_exception &e) { + err = true; + errmsg = std::string(e.what()); + } + if (err) { + PRINTB("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed. %s", errmsg); + } else { + std::transform(P.vertices_begin(), P.vertices_end(), std::back_inserter(points3d), + boost::bind(static_cast(&CGAL_Polyhedron::Vertex::point), _1)); + } } } chnode->progress_report(); diff --git a/src/CGAL_Nef3_workaround.h b/src/CGAL_Nef3_workaround.h new file mode 100644 index 00000000..c2482ac4 --- /dev/null +++ b/src/CGAL_Nef3_workaround.h @@ -0,0 +1,352 @@ +// Copyright (c) 1997-2002,2005 Max-Planck-Institute Saarbruecken (Germany). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// You can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation, +// either version 3 of the License, or (at your option) any later version. +// +// Licensees holding a valid commercial license may use this file in +// accordance with the commercial license agreement provided with the software. +// +// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// $URL: svn+ssh://scm.gforge.inria.fr/svn/cgal/branches/releases/CGAL-4.0-branch/Nef_3/include/CGAL/Nef_helper_3.h $ +// $Id: Nef_helper_3.h 67117 2012-01-13 18:14:48Z lrineau $ +// +// +// Author(s) : Michael Seel +// Miguel Granados +// Susan Hert +// Lutz Kettner +// Ralf Osbild +// Peter Hachenberger + +/* + modified by don bright for OpenSCAD, 2013. + +This works around issue #410, where CGAL's Nef_Polyhedron3.convert_to_Polyhedron +throws an uncatchable exception, due to an CGAL_Assertion being thrown in +Polyhedron Incremental Builder's destructor while a Triangulation exception +is still active, resulting in program termination (crashing). + +The purpose here is not to improve/change the way CGAL's Nef code works, +but instead to tweak it slightly to prevent OpenSCAD from crashing. The +behavior of the code should otherwise be exactly the same as CGAL's standard +code. + +This file was created by copying three sections +from CGAL's Nef_polyhedron3.h that were protected/private: + + Triangulation_handler2 + Build_Polyhedron + convert_to_polyhedron + +Very small code changes have been made. First, there are many template +type specifiers added to enable the movement of the code to the outside +of the Nef Polyhedron class. Second, there is a try{}catch(...){} block +added in the Builder around the Triangulation code. Third, there is an error +variable added for non-Exception communication with the caller. + +Eventually, if CGAL itself is updated and the update is widely +distributed, this file may become obsolete and can be deleted from OpenSCAD + +*/ + + +#ifndef _CGAL_NEF3_WORKAROUND_H +#define _CGAL_NEF3_WORKAROUND_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "printutils.h" + +namespace nefworkaround { + +template +class Triangulation_handler2 { + + typedef typename CGAL::Triangulation_vertex_base_2 Vb; + typedef typename CGAL::Constrained_triangulation_face_base_2 Fb; + typedef typename CGAL::Triangulation_data_structure_2 TDS; + typedef typename CGAL::Constrained_triangulation_2 CT; + + typedef typename CT::Face_handle Face_handle; + typedef typename CT::Vertex_handle CTVertex_handle; + typedef typename CT::Finite_faces_iterator Finite_face_iterator; + typedef typename CT::Edge Edge; + CT ct; + CGAL::Unique_hash_map visited; + CGAL::Unique_hash_map ctv2v; + Finite_face_iterator fi; + typename Nef::Plane_3 supporting_plane; + + public: + Triangulation_handler2(typename Nef::Halffacet_const_handle f) : + visited(false), supporting_plane(f->plane()) { + + typename Nef::Halffacet_cycle_const_iterator fci; + for(fci=f->facet_cycles_begin(); fci!=f->facet_cycles_end(); ++fci) { + if(fci.is_shalfedge()) { + typename Nef::SHalfedge_around_facet_const_circulator sfc(fci), send(sfc); + CGAL_For_all(sfc,send) { + CGAL_NEF_TRACEN(" insert point" << sfc->source()->source()->point()); + CTVertex_handle ctv = ct.insert(sfc->source()->source()->point()); + ctv2v[ctv] = sfc->source()->source(); + } + } + } + + for(fci=f->facet_cycles_begin(); fci!=f->facet_cycles_end(); ++fci) { + if(fci.is_shalfedge()) { + typename Nef::SHalfedge_around_facet_const_circulator sfc(fci), send(sfc); + CGAL_For_all(sfc,send) { + CGAL_NEF_TRACEN(" insert constraint" << sfc->source()->source()->point() + << "->" << sfc->source()->twin()->source()->point()); + ct.insert_constraint(sfc->source()->source()->point(), + sfc->source()->twin()->source()->point()); + } + } + } + CGAL_assertion(ct.is_valid()); + + CGAL_NEF_TRACEN("number of finite triangles " << ct.number_of_faces()); + + typename CT::Face_handle infinite = ct.infinite_face(); + typename CT::Vertex_handle ctv = infinite->vertex(1); + if(ct.is_infinite(ctv)) ctv = infinite->vertex(2); + CGAL_assertion(!ct.is_infinite(ctv)); + + typename CT::Face_handle opposite; + typename CT::Face_circulator vc(ctv,infinite); + do { opposite = vc++; + } while(!ct.is_constrained(typename CT::Edge(vc,vc->index(opposite)))); + typename CT::Face_handle first = vc; + + CGAL_assertion(!ct.is_infinite(first)); + traverse_triangulation(first, first->index(opposite)); + + fi = ct.finite_faces_begin(); + } + + void traverse_triangulation(Face_handle f, int parent) { + visited[f] = true; + if(!ct.is_constrained(Edge(f,ct.cw(parent))) && !visited[f->neighbor(ct.cw(parent))]) { + Face_handle child(f->neighbor(ct.cw(parent))); + traverse_triangulation(child, child->index(f)); + } + if(!ct.is_constrained(Edge(f,ct.ccw(parent))) && !visited[f->neighbor(ct.ccw(parent))]) { + Face_handle child(f->neighbor(ct.ccw(parent))); + traverse_triangulation(child, child->index(f)); + } + } + + template + bool get_next_triangle(Triangle_3& tr) { + while(fi != ct.finite_faces_end() && visited[fi] == false) ++fi; + if(fi == ct.finite_faces_end()) return false; + tr = Triangle_3(fi->vertex(0)->point(), fi->vertex(1)->point(), fi->vertex(2)->point()); + ++fi; + return true; + } + + bool same_orientation(typename Nef::Plane_3 p1) const { + if(p1.a() != 0) + return CGAL::sign(p1.a()) == CGAL::sign(supporting_plane.a()); + if(p1.b() != 0) + return CGAL::sign(p1.b()) == CGAL::sign(supporting_plane.b()); + return CGAL::sign(p1.c()) == CGAL::sign(supporting_plane.c()); + } + + template + void handle_triangles(PIB& pib, Index& VI) { + while(fi != ct.finite_faces_end() && visited[fi] == false) ++fi; + while(fi != ct.finite_faces_end()) { + typename Nef::Plane_3 plane(fi->vertex(0)->point(), + fi->vertex(1)->point(), + fi->vertex(2)->point()); + pib.begin_facet(); + if(same_orientation(plane)) { + pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(0)]]); + pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(1)]]); + pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(2)]]); + } else { + pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(0)]]); + pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(2)]]); + pib.add_vertex_to_facet(VI[ctv2v[fi->vertex(1)]]); + } + pib.end_facet(); + do { + ++fi; + } while(fi != ct.finite_faces_end() && visited[fi] == false); + } + } +}; + + + + + + + + +template +class Build_polyhedron : public CGAL::Modifier_base { +public: + bool error; // added for OpenSCAD + class Visitor { + typedef typename CGAL::Projection_traits_xy_3 XY; + typedef typename CGAL::Projection_traits_yz_3 YZ; + typedef typename CGAL::Projection_traits_xz_3 XZ; + + const CGAL::Object_index& VI; + CGAL::Polyhedron_incremental_builder_3& B; + const typename Nef::SNC_const_decorator& D; + + public: + bool error;//added for OpenSCAD + Visitor(CGAL::Polyhedron_incremental_builder_3& BB, + const typename Nef::SNC_const_decorator& sd, + CGAL::Object_index& vi) : VI(vi), B(BB), D(sd), error(false) {} + + void visit(typename Nef::Halffacet_const_handle opposite_facet) { + + CGAL_NEF_TRACEN("Build_polyhedron: visit facet " << opposite_facet->plane()); + + CGAL_assertion(Nef::Infi_box::is_standard(opposite_facet->plane())); + + typename Nef::SHalfedge_const_handle se; + typename Nef::Halffacet_cycle_const_iterator fc; + + typename Nef::Halffacet_const_handle f = opposite_facet->twin(); + + typename Nef::SHalfedge_around_facet_const_circulator + sfc1(f->facet_cycles_begin()), sfc2(sfc1); + + if(++f->facet_cycles_begin() != f->facet_cycles_end() || + ++(++(++sfc1)) != sfc2) { + typename Nef::Vector_3 orth = f->plane().orthogonal_vector(); + int c = CGAL::abs(orth[0]) > CGAL::abs(orth[1]) ? 0 : 1; + c = CGAL::abs(orth[2]) > CGAL::abs(orth[c]) ? 2 : c; + + try{ // added for OpenSCAD + if(c == 0) { + Triangulation_handler2 th(f); + th.handle_triangles(B, VI); + } else if(c == 1) { + Triangulation_handler2 th(f); + th.handle_triangles(B, VI); + } else if(c == 2) { + Triangulation_handler2 th(f); + th.handle_triangles(B, VI); + } else + CGAL_error_msg( "wrong value"); + } catch(...) { // added for OpenSCAD + PRINT("ERROR: CGAL NefPolyhedron Triangulation failed"); // added for OpenSCAD + this->error=true; //added for OpenSCAD + } // added for OpenSCAD + } else { + + B.begin_facet(); + fc = f->facet_cycles_begin(); + se = typename Nef::SHalfedge_const_handle(fc); + CGAL_assertion(se!=0); + typename Nef::SHalfedge_around_facet_const_circulator hc_start(se); + typename Nef::SHalfedge_around_facet_const_circulator hc_end(hc_start); + CGAL_For_all(hc_start,hc_end) { + CGAL_NEF_TRACEN(" add vertex " << hc_start->source()->center_vertex()->point()); + B.add_vertex_to_facet(VI[hc_start->source()->center_vertex()]); + } + B.end_facet(); + } + } + + void visit(typename Nef::SFace_const_handle) {} + void visit(typename Nef::Halfedge_const_handle) {} + void visit(typename Nef::Vertex_const_handle) {} + void visit(typename Nef::SHalfedge_const_handle) {} + void visit(typename Nef::SHalfloop_const_handle) {} + }; + + public: + + const typename Nef::SNC_const_decorator& scd; + CGAL::Object_index VI; + + Build_polyhedron(const typename Nef::SNC_const_decorator& s) : error(false), + scd(s), VI(s.vertices_begin(),s.vertices_end(),'V') {} + + void operator()( HDS& hds) { + CGAL::Polyhedron_incremental_builder_3 B(hds, true); + + int skip_volumes; + if(Nef::Infi_box::extended_kernel()) { + B.begin_surface(scd.number_of_vertices()-8, + scd.number_of_facets()-6, + scd.number_of_edges()-12); + skip_volumes = 2; + } + else { + B.begin_surface(scd.number_of_vertices(), + 2*scd.number_of_vertices()-4, + 3*scd.number_of_vertices()-6); + skip_volumes = 1; + } + + int vertex_index = 0; + typename Nef::Vertex_const_iterator v; + CGAL_forall_vertices(v,scd) { + if(Nef::Infi_box::is_standard(v->point())) { + VI[v]=vertex_index++; + B.add_vertex(v->point()); + } + } + + Visitor V(B,scd,VI); + typename Nef::Volume_const_handle c; + CGAL_forall_volumes(c,scd) + if(skip_volumes-- <= 0) { + scd.visit_shell_objects(typename Nef:: SFace_const_handle(c->shells_begin()),V); + } + B.end_surface(); + this->error=B.error()||V.error; // added for OpenSCAD + if (B.error()) B.rollback(); // added for OpenSCAD + } + +}; + +template +bool convert_to_Polyhedron( const CGAL::Nef_polyhedron_3 &N, CGAL::Polyhedron_3 &P ) +{ + // several lines here added for OpenSCAD + typedef typename CGAL::Nef_polyhedron_3 Nef3; + typedef typename CGAL::Polyhedron_3 Polyhedron; + typedef typename Polyhedron::HalfedgeDS HalfedgeDS; + CGAL_precondition(N.is_simple()); + P.clear(); + Build_polyhedron bp(N); + P.delegate(bp); + return bp.error; +} + + + + + +} //namespace nefworkaround + + + + +#endif + diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index 8b54eba9..ea9accdc 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -96,16 +96,23 @@ PolySet *CGAL_Nef_polyhedron::convertToPolyset() } else if (this->dim == 3) { CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + ps = new PolySet(); + bool err = true; + std::string errmsg(""); + CGAL_Polyhedron P; try { - ps = new PolySet(); - CGAL_Polyhedron P; - this->p3->convert_to_Polyhedron(P); - bool err = createPolySetFromPolyhedron(P, *ps); - if (err) delete ps; + err = nefworkaround::convert_to_Polyhedron( *(this->p3), P ); + //this->p3->convert_to_Polyhedron(P); } - catch (const CGAL::Precondition_exception &e) { - delete ps; - PRINTB("CGAL error in CGAL_Nef_polyhedron::convertToPolyset(): %s", e.what()); + catch (const CGAL::Failure_exception &e) { + err = true; + errmsg = std::string(e.what()); + } + if (!err) err = createPolySetFromPolyhedron(P, *ps); + if (err) { + PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed."); + if (errmsg!="") PRINTB("ERROR: %s",errmsg); + delete ps; ps = NULL; } CGAL::set_error_behaviour(old_behaviour); } diff --git a/src/CocoaUtils.mm b/src/CocoaUtils.mm index 92640fdd..9856b3d7 100644 --- a/src/CocoaUtils.mm +++ b/src/CocoaUtils.mm @@ -8,7 +8,7 @@ void CocoaUtils::endApplication() object:nil]; } -void CocoaUtils::nslog(const std::string &str, void *userdata) +void CocoaUtils::nslog(const std::string &str, void * /* userdata */) { - NSLog([NSString stringWithUTF8String: str.c_str()]); + NSLog(@"%s", str.c_str()); } diff --git a/src/PlatformUtils.cc b/src/PlatformUtils.cc index b02b822e..8b39f6df 100644 --- a/src/PlatformUtils.cc +++ b/src/PlatformUtils.cc @@ -1,6 +1,8 @@ #include "PlatformUtils.h" #include "boosty.h" +#include + bool PlatformUtils::createLibraryPath() { std::string path = PlatformUtils::libraryPath(); @@ -114,6 +116,7 @@ std::string PlatformUtils::info() << "\nOpenCSG version: " << OPENCSG_VERSION_STRING << "\nQt version: " << qtVersion << "\nMingW build: " << mingwstatus + << "\nGLib version: " << GLIB_MAJOR_VERSION << "." << GLIB_MINOR_VERSION << "." << GLIB_MICRO_VERSION << "\nOPENSCADPATH: " << getenv("OPENSCADPATH") << "\n" ; return s.str(); diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index bc9206f3..a2d896d8 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -455,12 +455,21 @@ PolySet *PolySetCGALEvaluator::rotateDxfData(const RotateExtrudeNode &node, DxfD for (size_t i = 0; i < dxf.paths.size(); i++) { + double min_x = 0; double max_x = 0; for (size_t j = 0; j < dxf.paths[i].indices.size(); j++) { - max_x = fmax(max_x, dxf.points[dxf.paths[i].indices[j]][0]); + double point_x = dxf.points[dxf.paths[i].indices[j]][0]; + min_x = fmin(min_x, point_x); + max_x = fmax(max_x, point_x); + + if ((max_x - min_x) > max_x && (max_x - min_x) > fabs(min_x)) { + PRINTB("ERROR: all points for rotate_extrude() must have the same X coordinate sign (range is %.2f -> %.2f)", min_x % max_x); + delete ps; + return NULL; + } } - int fragments = get_fragments_from_r(max_x, node.fn, node.fs, node.fa); + int fragments = get_fragments_from_r(max_x-min_x, node.fn, node.fs, node.fa); double ***points; points = new double**[fragments]; diff --git a/src/cgal.h b/src/cgal.h index efc53d39..69c8c270 100644 --- a/src/cgal.h +++ b/src/cgal.h @@ -27,6 +27,7 @@ using boost::uintmax_t; #include #include #include +#include #include #include #include diff --git a/src/export.cc b/src/export.cc index ec6e576a..cef323e8 100644 --- a/src/export.cc +++ b/src/export.cc @@ -42,7 +42,12 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output) CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { CGAL_Polyhedron P; - root_N->p3->convert_to_Polyhedron(P); + //root_N->p3->convert_to_Polyhedron(P); + bool err = nefworkaround::convert_to_Polyhedron( *(root_N->p3), P ); + if (err) { + PRINT("ERROR: CGAL NefPolyhedron->Polyhedron conversion failed"); + return; + } typedef CGAL_Polyhedron::Vertex Vertex; typedef CGAL_Polyhedron::Vertex_const_iterator VCI; @@ -114,6 +119,9 @@ void export_stl(CGAL_Nef_polyhedron *root_N, std::ostream &output) catch (const CGAL::Assertion_exception &e) { PRINTB("CGAL error in CGAL_Nef_polyhedron3::convert_to_Polyhedron(): %s", e.what()); } + catch (...) { + PRINT("CGAL unknown error in CGAL_Nef_polyhedron3::convert_to_Polyhedron()"); + } CGAL::set_error_behaviour(old_behaviour); } diff --git a/src/func.cc b/src/func.cc index 865a2b42..4587f725 100644 --- a/src/func.cc +++ b/src/func.cc @@ -45,6 +45,8 @@ #include #include +/*Unicode support for string lengths and array accesses*/ +#include #ifdef __WIN32__ #include @@ -306,7 +308,11 @@ Value builtin_length(const Context *, const EvalContext *evalctx) { if (evalctx->numArgs() == 1) { if (evalctx->getArgValue(0).type() == Value::VECTOR) return Value(int(evalctx->getArgValue(0).toVector().size())); - if (evalctx->getArgValue(0).type() == Value::STRING) return Value(int(evalctx->getArgValue(0).toString().size())); + if (evalctx->getArgValue(0).type() == Value::STRING) { + //Unicode glyph count for the length -- rather than the string (num. of bytes) length. + std::string text = evalctx->getArgValue(0).toString(); + return Value(int( g_utf8_strlen( text.c_str(), text.size() ) )); + } } return Value(); } @@ -380,10 +386,17 @@ Value builtin_lookup(const Context *, const EvalContext *evalctx) num_returns_per_match : int; index_col_num : int; + The search string and searched strings can be unicode strings. Examples: Index values return as list: search("a","abcdabcd"); - - returns [0,4] + - returns [0] + search("Π›","Π›"); //A unicode string + - returns [0] + search("πŸ‚‘aΠ›","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a",0); + - returns [[1,3,5,7],[0,4,8],[2,6]] + search("a","abcdabcd",0); //Search up to all matches + - returns [[0,4]] search("a","abcdabcd",1); - returns [0] search("e","abcdabcd",1); @@ -433,16 +446,25 @@ Value builtin_search(const Context *, const EvalContext *evalctx) } } else if (findThis.type() == Value::STRING) { unsigned int searchTableSize; - if (searchTable.type() == Value::STRING) searchTableSize = searchTable.toString().size(); - else searchTableSize = searchTable.toVector().size(); - for (size_t i = 0; i < findThis.toString().size(); i++) { + //Unicode glyph count for the length + unsigned int findThisSize = g_utf8_strlen( findThis.toString().c_str(), findThis.toString().size() ); + if (searchTable.type() == Value::STRING) { + searchTableSize = g_utf8_strlen( searchTable.toString().c_str(), searchTable.toString().size() ); + } else { + searchTableSize = searchTable.toVector().size(); + } + for (size_t i = 0; i < findThisSize; i++) { unsigned int matchCount = 0; Value::VectorType resultvec; for (size_t j = 0; j < searchTableSize; j++) { - if ((searchTable.type() == Value::VECTOR && - findThis.toString()[i] == searchTable.toVector()[j].toVector()[index_col_num].toString()[0]) || - (searchTable.type() == Value::STRING && - findThis.toString()[i] == searchTable.toString()[j])) { + gchar* ptr_ft = g_utf8_offset_to_pointer(findThis.toString().c_str(), i); + gchar* ptr_st = NULL; + if(searchTable.type() == Value::VECTOR) { + ptr_st = g_utf8_offset_to_pointer(searchTable.toVector()[j].toVector()[index_col_num].toString().c_str(), 0); + } else if(searchTable.type() == Value::STRING){ + ptr_st = g_utf8_offset_to_pointer(searchTable.toString().c_str(), j); + } + if( (ptr_ft) && (ptr_st) && (g_utf8_get_char(ptr_ft) == g_utf8_get_char(ptr_st)) ) { Value resultValue((double(j))); matchCount++; if (num_returns_per_match == 1) { @@ -454,7 +476,14 @@ Value builtin_search(const Context *, const EvalContext *evalctx) if (num_returns_per_match > 1 && matchCount >= num_returns_per_match) break; } } - if (matchCount == 0) PRINTB(" WARNING: search term not found: \"%s\"", findThis.toString()[i]); + if (matchCount == 0) { + gchar* ptr_ft = g_utf8_offset_to_pointer(findThis.toString().c_str(), i); + gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into + if(ptr_ft) { + g_utf8_strncpy( utf8_of_cp, ptr_ft, 1 ); + } + PRINTB(" WARNING: search term not found: \"%s\"", utf8_of_cp ); + } if (num_returns_per_match == 0 || num_returns_per_match > 1) { returnvec.push_back(Value(resultvec)); } diff --git a/src/modcontext.cc b/src/modcontext.cc index 5b480097..7941cf5c 100644 --- a/src/modcontext.cc +++ b/src/modcontext.cc @@ -162,7 +162,7 @@ void ModuleContext::dump(const AbstractModule *mod, const ModuleInstantiation *i #endif FileContext::FileContext(const class FileModule &module, const Context *parent) - : usedlibs(module.usedlibs), ModuleContext(parent) + : ModuleContext(parent), usedlibs(module.usedlibs) { if (!module.modulePath().empty()) this->document_path = module.modulePath(); } diff --git a/src/openscad.cc b/src/openscad.cc index ece68189..ab842357 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -474,6 +474,13 @@ bool QtUseGUI() int gui(vector &inputFiles, const fs::path &original_path, int argc, char ** argv) { +#ifdef Q_OS_MACX + if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) { + // fix Mac OS X 10.9 (mavericks) font issue + // https://bugreports.qt-project.org/browse/QTBUG-32789 + QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); + } +#endif QApplication app(argc, argv, true); //useGUI); #ifdef Q_WS_MAC app.installEventFilter(new EventFilter(&app)); diff --git a/src/stl-utils.cc b/src/stl-utils.cc index 790fd179..027339cc 100644 --- a/src/stl-utils.cc +++ b/src/stl-utils.cc @@ -1,4 +1,4 @@ -#if defined(__APPLE__) && defined(__GNUC__) +#if defined(__APPLE__) && defined(__GNUC__) && !defined(__clang__) #include diff --git a/src/value.cc b/src/value.cc index 5afb650c..c8a88c69 100644 --- a/src/value.cc +++ b/src/value.cc @@ -36,6 +36,8 @@ #include #include "boost-utils.h" #include "boosty.h" +/*Unicode support for string lengths and array accesses*/ +#include std::ostream &operator<<(std::ostream &stream, const Filename &filename) { @@ -579,14 +581,28 @@ Value Value::operator-() const } */ +/* + * bracket operation [] detecting multi-byte unicode. + * If the string is multi-byte unicode then the index will offset to the character (2 or 4 byte) and not to the byte. + * A 'normal' string with byte chars are a subset of unicode and still work. + */ class bracket_visitor : public boost::static_visitor { public: Value operator()(const std::string &str, const double &idx) const { int i = int(idx); Value v; + //Check that the index is positive and less than the size in bytes if ((i >= 0) && (i < (int)str.size())) { - v = Value(str[int(idx)]); + //Ensure character (not byte) index is inside the character/glyph array + if( (unsigned) i < g_utf8_strlen( str.c_str(), str.size() ) ) { + gchar utf8_of_cp[6] = ""; //A buffer for a single unicode character to be copied into + gchar* ptr = g_utf8_offset_to_pointer(str.c_str(), i); + if(ptr) { + g_utf8_strncpy(utf8_of_cp, ptr, 1); + } + v = std::string(utf8_of_cp); + } // std::cout << "bracket_visitor: " << v << "\n"; } return v; diff --git a/src/version_check.h b/src/version_check.h index fbea077f..2688f2a5 100644 --- a/src/version_check.h +++ b/src/version_check.h @@ -113,7 +113,7 @@ a time, to avoid confusion. + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) #if GCC_VERSION == 40802 -#error OpenSCAD isn't compatible with gcc 4.8.2. Please try a different version +#warning "gcc 4.8.2 contains a bug causing a crash in CGAL." #endif #endif // OPENSCAD_SKIP_VERSION_CHECK diff --git a/testdata/scad/features/polyhedron-tests.scad b/testdata/scad/features/polyhedron-tests.scad index 690d9627..1f11d7ef 100644 --- a/testdata/scad/features/polyhedron-tests.scad +++ b/testdata/scad/features/polyhedron-tests.scad @@ -11,10 +11,35 @@ module polyhedrons() { translate([4,0,0]) polyhedron(points = [[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]], triangles = [[0,2,4],[0,5,2],[0,4,3],[0,3,5],[1,4,2],[1,2,5],[1,3,4], [1,5,3]]); + +// Containing concave polygons +translate([6,0,0]) +polyhedron(points=[ + [-0.8,-0.8,-0.8], + [0,0,-0.8], + [0.8,-0.8,-0.8], + [0.8,0.8,-0.8], + [-0.8,0.8,-0.8], + [-0.8,-0.8,0.8], + [0,0,0.8], + [0.8,-0.8,0.8], + [0.8,0.8,0.8], + [-0.8,0.8,0.8], + ], + triangles=[ + [0,1,2,3,4], + [5,6,1,0], + [6,7,2,1], + [7,8,3,2], + [8,9,4,3], + [9,5,0,4], + [9,8,7,6,5], + ], convexity=2); } polyhedrons(); translate([0,2,0]) difference() { polyhedrons(); - translate([2,0,2]) cube([8,3,3], center=true); + translate([3,0,2]) cube([8,3,3], center=true); } + diff --git a/testdata/scad/features/rotate_extrude-tests.scad b/testdata/scad/features/rotate_extrude-tests.scad index 010b7d22..ec8d1cce 100644 --- a/testdata/scad/features/rotate_extrude-tests.scad +++ b/testdata/scad/features/rotate_extrude-tests.scad @@ -32,3 +32,6 @@ translate([50,50,0]) { // Minimal $fn translate([0,-60,0]) rotate_extrude($fn=1) translate([20,0,0]) circle(r=10,$fn=1); +// Object in negative X +translate([0,60,0]) rotate_extrude() translate([-20,0]) square(10); + diff --git a/testdata/scad/misc/search-tests-unicode.scad b/testdata/scad/misc/search-tests-unicode.scad new file mode 100644 index 00000000..d863eff9 --- /dev/null +++ b/testdata/scad/misc/search-tests-unicode.scad @@ -0,0 +1,116 @@ +//Test search with unicode strings + +//Helper function that pretty prints our search test +//Expected result is checked against execution of a search() invocation and OK/FAIL is indicated +module test_search_and_echo( exp_res, search_to_find, search_to_search, search_up_to_num_matches = undef) +{ + if(undef != search_up_to_num_matches) + { + assign( test_res = search(search_to_find, search_to_search, search_up_to_num_matches) ) + echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ", ", search_up_to_num_matches, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL" )); + } + else + { + assign( test_res = search(search_to_find, search_to_search) ) + echo(str("Expect ", exp_res, " for search(", search_to_find, ", ", search_to_search, ")=", test_res, ". ", (exp_res == test_res)?"OK":"FAIL" )); + } +} + + +//"Normal" text for comparison +echo ("----- Lookup of 1 byte into 1 byte"); +//Hits - up_to_count 1 +test_search_and_echo( [0], "a","aaaa" ); +test_search_and_echo( [0], "a","aaaa",1 ); +test_search_and_echo( [0,0], "aa","aaaa" ); +test_search_and_echo( [0,0], "aa","aaaa",1 ); + + +//Hits - up to count 1+ (incl 0 == all) +test_search_and_echo( [[0,1,2,3]] , "a","aaaa",0 ); +test_search_and_echo( [[0,1]], "a","aaaa",2 ); +test_search_and_echo( [[0,1,2]], "a","aaaa",3 ); +test_search_and_echo( [[0,1,2,3]] , "a","aaaa",4 ); +test_search_and_echo( [[0,1,2,3],[0,1,2,3]] , "aa","aaaa",0 ); +//Misses +test_search_and_echo( [], "b","aaaa" ); +test_search_and_echo( [], "b","aaaa",1 ); +test_search_and_echo( [[]], "b","aaaa",0 ); +test_search_and_echo( [[]], "b","aaaa",2 ); + +test_search_and_echo( [], "bb","aaaa" ); +test_search_and_echo( [], "bb","aaaa",1 ); +test_search_and_echo( [[],[]], "bb","aaaa",0 ); +test_search_and_echo( [[],[]], "bb","aaaa",2 ); +//Miss - empties +test_search_and_echo( [], "","aaaa" ); +test_search_and_echo( [], "","" ); +test_search_and_echo( [], "a","" ); + + +//Unicode tests +echo ("----- Lookup of multi-byte into 1 byte"); +test_search_and_echo( [], "Π›","aaaa" ); +test_search_and_echo( [], "πŸ‚‘","aaaa" ); +test_search_and_echo( [[]], "Π›","aaaa",0 ); +test_search_and_echo( [[]], "πŸ‚‘","aaaa",0 ); + +test_search_and_echo( [], "Π›Π›","aaaa" ); +test_search_and_echo( [], "πŸ‚‘πŸ‚‘","aaaa" ); +test_search_and_echo( [[],[]], "Π›Π›","aaaa",0 ); +test_search_and_echo( [[],[]], "πŸ‚‘πŸ‚‘","aaaa",0 ); + +echo ("----- Lookup of 1-byte into multi-byte"); +test_search_and_echo( [] , "a","Π›Π›Π›Π›" ); +test_search_and_echo( [] , "a","πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘" ); +test_search_and_echo( [] , "a","Π›Π›Π›Π›",1 ); + +test_search_and_echo( [[]] , "a","πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘",0 ); +test_search_and_echo( [[]] , "a","πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘",2 ); + +echo ("----- Lookup of 1-byte into mixed multi-byte"); +test_search_and_echo( [0], "a","aΠ›aΠ›aΠ›aΠ›a" ); +test_search_and_echo( [0], "a","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a" ); +test_search_and_echo( [0], "a","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a" ); + +test_search_and_echo( [[0,2,4,6,8]], "a","aΠ›aΠ›aΠ›aΠ›a",0 ); +test_search_and_echo( [[0,2,4,6,8]], "a","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a", 0 ); +test_search_and_echo( [[0,4,8]] , "a","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a", 0 ); + +echo ("----- Lookup of 2-byte into 2-byte"); +test_search_and_echo( [0] , "Π›","Π›Π›Π›Π›" ); +test_search_and_echo( [[0,1,2,3]] , "Π›","Π›Π›Π›Π›",0 ); + +echo ("----- Lookup of 2-byte into 4-byte"); +test_search_and_echo( [] , "Π›","πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘" ); + +echo ("----- Lookup of 4-byte into 4-byte"); +test_search_and_echo( [0] , "πŸ‚‘","πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘" ); +test_search_and_echo( [[0,1,2,3]], "πŸ‚‘","πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘",0 ); + +echo ("----- Lookup of 4-byte into 2-byte"); +test_search_and_echo( [] , "πŸ‚‘","Π›Π›Π›Π›" ); + +echo ("----- Lookup of 2-byte into mixed multi-byte"); +test_search_and_echo( [1] , "Π›","aΠ›aΠ›aΠ›aΠ›a",1 ); +test_search_and_echo( [] , "Π›","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a", 1 ); +test_search_and_echo( [2] , "Π›","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a", 1 ); + +test_search_and_echo( [[1,3,5,7]] , "Π›","aΠ›aΠ›aΠ›aΠ›a",0 ); +test_search_and_echo( [[]] , "Π›","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a", 0 ); +test_search_and_echo( [[2,6]] , "Π›","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a", 0 ); + +echo ("----- Lookup of 4-byte into mixed multi-byte"); +test_search_and_echo( [] , "πŸ‚‘","aΠ›aΠ›aΠ›aΠ›a",1 ); +test_search_and_echo( [1] , "πŸ‚‘","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a", 1 ); + +test_search_and_echo( [[]] , "πŸ‚‘","aΠ›aΠ›aΠ›aΠ›a",0 ); +test_search_and_echo( [[1,3,5,7]] , "πŸ‚‘","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a", 0 ); +test_search_and_echo( [[1,3,5,7]] , "πŸ‚‘","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a", 0 ); + +echo ("----- Lookup of mixed multi-byte into mixed multi-byte"); +test_search_and_echo( [[0,2,4,6,8],[1,3,5,7],[]], "aΠ›πŸ‚‘","aΠ›aΠ›aΠ›aΠ›a",0 ); +test_search_and_echo( [[0,2,4,6,8],[],[1,3,5,7]], "aΠ›πŸ‚‘","aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a", 0 ); +test_search_and_echo( [[0,4,8],[2,6],[1,3,5,7]] , "aΠ›πŸ‚‘","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a", 0 ); +test_search_and_echo( [[1,3,5,7],[0,4,8],[2,6]] , "πŸ‚‘aΠ›","aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a", 0 ); + diff --git a/testdata/scad/misc/string-unicode.scad b/testdata/scad/misc/string-unicode.scad new file mode 100644 index 00000000..1386d63d --- /dev/null +++ b/testdata/scad/misc/string-unicode.scad @@ -0,0 +1,44 @@ +//Test length reporting +text_1bytes_len = "1234"; +text_2bytes_len = "Π›Π›Π›Π›"; +text_4bytes_len = "πŸ‚‘πŸ‚±πŸƒπŸƒ‘"; + +echo( "text_1bytes_len = ", text_1bytes_len, " len = ", len(text_1bytes_len) ); +echo( "text_2bytes_len = ", text_2bytes_len, " len = ", len(text_2bytes_len) ); +echo( "text_4bytes_len = ", text_4bytes_len, " len = ", len(text_4bytes_len) ); + +//Test how well arrays of unicode string are accessed. + +texts_array = [ +"DEADBEEF", +"Π›Π΅Π½ΠΈΠ²Ρ‹ΠΉ Ρ€Ρ‹ΠΆΠΈΠΉ ΠΊΠΎΡ‚", +"ΩƒΨ³ΩˆΩ„ Ψ§Ω„Ψ²Ω†Ψ¬Ψ¨ΩŠΩ„ Ψ§Ω„Ω‚Ψ·", +"ζ‡Άζƒ°ηš„ε§œθ²“", +"Àâü Γ„Γ–Γœ ß", +"πŸ˜πŸ˜‚πŸ˜ƒπŸ˜„πŸ˜…πŸ˜†πŸ˜‡πŸ˜ˆπŸ˜‰πŸ˜ŠπŸ˜‹πŸ˜ŒπŸ˜πŸ˜ŽπŸ˜πŸ˜", +"β β ‚β ƒβ „β …β †β ‡β ˆβ ‰β Šβ ‹β Œβ β Žβ ", +"πŸ‚‘πŸ‚±πŸƒπŸƒ‘", +]; + +text_2bytes = "Π›Π΅Π½ΠΈΠ²Ρ‹ΠΉ Ρ€Ρ‹ΠΆΠΈΠΉ ΠΊΠΎΡ‚"; +text_4bytes = "πŸ‚‘πŸ‚±πŸƒπŸƒ‘"; + + +//Test all the normal accesses +for (text_array_idx = [0:(len(texts_array)-1)]) +{ + echo( "[", text_array_idx, "] = ", texts_array[text_array_idx], " of len=", len(texts_array[text_array_idx]), ":" ); + for (text_idx = [0:(len(texts_array[text_array_idx])-1)]) + { + echo( " [", text_idx, ,"]=", texts_array[text_array_idx][text_idx] ); + } +} + +//Test one past the last element of (x-byte unicode). This will be one past the length but inside the char length of the string +echo( "Past end of unicode only 2-byte ", text_2bytes[len(text_2bytes)] ); +echo( "Past end of unicode only 4-byte ", text_4bytes[len(text_4bytes)] ); + +//Test past the last element of (x-byte unicode). Outside both lengths. +echo( "Past end of both 2-byte ", text_2bytes[ len(text_2bytes) * 2 ] ); +echo( "Past end of both 4-byte ", text_4bytes[ len(text_4bytes) * 4 ] ); + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7db0874f..20d4dc45 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,6 @@ # instructions - see ../doc/testing.txt -# set(DEBUG_OSCD 1) # print debug info during cmake +#set(DEBUG_OSCD 1) # print debug info during cmake cmake_minimum_required(VERSION 2.8) if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" VERSION_GREATER 2.8.3) @@ -15,8 +15,15 @@ include(CMakeParseArguments.cmake) # Detect Lion and force gcc IF (APPLE) EXECUTE_PROCESS(COMMAND sw_vers -productVersion OUTPUT_VARIABLE MACOSX_VERSION) - IF (NOT ${MACOSX_VERSION} VERSION_LESS "10.8.0") - message("Detected Mountain Lion (10.8) or later") + IF (NOT ${MACOSX_VERSION} VERSION_LESS "10.9.0") + message("Detected Maverick (10.9) or later") + set(CMAKE_C_COMPILER "clang") + set(CMAKE_CXX_COMPILER "clang++") + # Somehow, since we build dependencies for 10.7, we need to also build executables + # for 10.7. This used to not be necessary, but since 10.9 it apparently is.. + SET(CMAKE_OSX_DEPLOYMENT_TARGET 10.7 CACHE STRING "Deployment target") + ELSEIF (NOT ${MACOSX_VERSION} VERSION_LESS "10.8.0") + message("Detected Mountain Lion (10.8)") set(CMAKE_C_COMPILER "clang") set(CMAKE_CXX_COMPILER "clang++") ELSEIF (NOT ${MACOSX_VERSION} VERSION_LESS "10.7.0") @@ -359,10 +366,10 @@ if (NOT $ENV{CGALDIR} STREQUAL "") elseif (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "") if (EXISTS "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL") set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}/lib/CGAL") - set(CMAKE_MODULE_PATH "${CGAL_DIR}") + set(CMAKE_MODULE_PATH "${CGAL_DIR}" ${CMAKE_MODULE_PATH}) elseif (EXISTS "$ENV{OPENSCAD_LIBRARIES}/include/CGAL") set(CGAL_DIR "$ENV{OPENSCAD_LIBRARIES}") - set(CMAKE_MODULE_PATH "${CGAL_DIR}") + set(CMAKE_MODULE_PATH "${CGAL_DIR}" ${CMAKE_MODULE_PATH}) endif() endif() message(STATUS "CGAL_DIR: " ${CGAL_DIR}) @@ -376,12 +383,40 @@ if("${CGAL_MAJOR_VERSION}.${CGAL_MINOR_VERSION}" VERSION_LESS 3.6) message(FATAL_ERROR "CGAL >= 3.6 required") endif() inclusion(CGAL_DIR CGAL_INCLUDE_DIRS) +#Remove bad BOOST libraries from CGAL 3rd party dependencies when they don't exist (such as on 64-bit Ubuntu 13.10). +#Libs of concern are /usr/lib/libboost_thread.so;/usr/lib/libboost_system.so; +#Confirmed bug in CGAL @ https://bugs.launchpad.net/ubuntu/+source/cgal/+bug/1242111 +string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_system.so" FIND_POSITION ) +if(NOT "-1" STREQUAL ${FIND_POSITION} ) + if(NOT EXISTS "/usr/lib/libboost_system.so") + MESSAGE( STATUS "CGAL_3RD_PARTY_LIBRARIES:Removing non-existent /usr/lib/libboost_system.so" ) + string(REPLACE "/usr/lib/libboost_system.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES}) + endif() +endif() +string(FIND "${CGAL_3RD_PARTY_LIBRARIES}" "/usr/lib/libboost_thread.so" FIND_POSITION ) +if(NOT "-1" STREQUAL ${FIND_POSITION} ) + if(NOT EXISTS "/usr/lib/libboost_thread.so") + MESSAGE( STATUS "CGAL_3RD_PARTY_LIBRARIES:Removing non-existent /usr/lib/libboost_thread.so" ) + string(REPLACE "/usr/lib/libboost_thread.so" "" CGAL_3RD_PARTY_LIBRARIES ${CGAL_3RD_PARTY_LIBRARIES}) + endif() +endif() if(${CMAKE_CXX_COMPILER} MATCHES ".*clang.*" AND NOT ${CGAL_CXX_FLAGS_INIT} STREQUAL "" ) string(REPLACE "-frounding-math" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT}) string(REPLACE "--param=ssp-buffer-size=4" "" CGAL_CXX_FLAGS_INIT ${CGAL_CXX_FLAGS_INIT}) endif() +if (NOT $ENV{OPENSCAD_LIBRARIES} STREQUAL "") + # Force pkg-config to look _only_ in the local library folder + # in case OPENSCAD_LIBRARIES is set. + set(ENV{PKG_CONFIG_PATH} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig") + set(ENV{PKG_CONFIG_LIBDIR} "$ENV{OPENSCAD_LIBRARIES}/lib/pkgconfig") +endif() + +find_package(GLIB2 2.2.0 REQUIRED) +add_definitions(${GLIB2_DEFINITIONS}) +inclusion(GLIB2_DIR GLIB2_INCLUDE_DIRS) + # Imagemagick if (SKIP_IMAGEMAGICK) @@ -553,8 +588,8 @@ set(OFFSCREEN_SOURCES ../src/OpenCSGRenderer.cc) add_library(tests-core STATIC ${CORE_SOURCES}) -target_link_libraries(tests-core ${OPENGL_LIBRARIES}) -set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${Boost_LIBRARIES}) +target_link_libraries(tests-core ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ) +set(TESTS-CORE-LIBRARIES ${OPENGL_LIBRARIES} ${GLIB2_LIBRARIES} ${Boost_LIBRARIES} ) add_library(tests-common STATIC ${COMMON_SOURCES}) target_link_libraries(tests-common tests-core) @@ -574,7 +609,7 @@ set(TESTS-NOCGAL-LIBRARIES ${TESTS-CORE-LIBRARIES}) # modulecachetest # add_executable(modulecachetest modulecachetest.cc) -target_link_libraries(modulecachetest tests-nocgal ${TESTS-NOCGAL-LIBRARIES} ${Boost_LIBRARIES}) +target_link_libraries(modulecachetest tests-nocgal ${TESTS-NOCGAL-LIBRARIES}) # # csgtexttest @@ -594,7 +629,7 @@ target_link_libraries(cgalcachetest tests-cgal ${TESTS-CGAL-LIBRARIES} ${GLEW_LI # add_executable(openscad_nogui ../src/openscad.cc) set_target_properties(openscad_nogui PROPERTIES COMPILE_FLAGS "-fno-strict-aliasing -DEIGEN_DONT_ALIGN -DENABLE_CGAL -DENABLE_OPENCSG ${CGAL_CXX_FLAGS_INIT}") -target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${GLEW_LIBRARY} ${Boost_LIBRARIES} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} ) +target_link_libraries(openscad_nogui tests-offscreen tests-cgal tests-nocgal ${TESTS-CORE-LIBRARIES} ${TESTS-CGAL-LIBRARIES} ${GLEW_LIBRARY} ${OPENCSG_LIBRARY} ${COCOA_LIBRARY} ) # # GUI binary tests @@ -774,8 +809,10 @@ list(APPEND ECHO_FILES ${FUNCTION_FILES} ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/dim-all.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-test.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-indexing.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/string-unicode.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/vector-values.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/search-tests.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/search-tests-unicode.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/recursion-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/value-reassignment-tests2.scad @@ -795,13 +832,15 @@ list(APPEND DUMPTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/escape-test ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allexpressions.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allfunctions.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/allmodules.scad - ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/nonplanar_polyhedron.scad) + ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/nonplanar_polyhedron.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/stl-cgal-convert_to_Polyhedron-crash.scad) list(APPEND CGALPNGTEST_FILES ${FEATURES_FILES} ${SCAD_DXF_FILES} ${EXAMPLE_FILES}) list(APPEND CGALPNGTEST_FILES ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/include-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/use-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/transform-nan-inf-tests.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/nonplanar_polyhedron.scad + ${CMAKE_SOURCE_DIR}/../testdata/scad/bugs/stl-cgal-convert_to_Polyhedron-crash.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles-test.scad ${CMAKE_SOURCE_DIR}/../testdata/scad/misc/localfiles_dir/localfiles-compatibility-test.scad) @@ -824,6 +863,10 @@ disable_tests(openscad-csgpng_child-background) disable_tests(opencsgtest_example006 cgalpngtest_example006) disable_tests(openscad-csgpng_example006 openscad-cgalpng_example006) +# NefPolyhedron->Polyhedron conversion failures. No images for OpenCSG/Thrown +disable_tests(opencsgtest_stl-cgal-convert_to_Polyhedron-crash) +disable_tests(throwntogethertest_stl-cgal-convert_to_Polyhedron-crash) + # These tests only makes sense in OpenCSG mode disable_tests(cgalpngtest_child-background cgalpngtest_highlight-and-background-modifier @@ -893,6 +936,14 @@ string(REPLACE __header__ "Generated by cmake from ${CMAKE_CURRENT_SOURCE_DIR}/C string(REPLACE __cmake_system_name__ ${CMAKE_SYSTEM_NAME} TMP ${TMP}) string(REPLACE __openscad_binpath__ ${OPENSCAD_BINPATH} TMP ${TMP}) +set(OPENSCAD_UPLOAD_TESTS $ENV{OPENSCAD_UPLOAD_TESTS}) +if (OPENSCAD_UPLOAD_TESTS) + set(UPLOADARG "--upload") +endif() +if (UPLOADARG) + string(REPLACE __openscad_upload_tests__ ${UPLOADARG} TMP ${TMP}) +endif() + if (MINGW_CROSS_ENV_DIR) string(REPLACE __wine__ wine TMP ${TMP}) else() diff --git a/tests/CTestCustom.template b/tests/CTestCustom.template index 3f82d734..a01f2b52 100644 --- a/tests/CTestCustom.template +++ b/tests/CTestCustom.template @@ -63,7 +63,12 @@ endif() message("running '__openscad_binpath__ --info' to generate sysinfo.txt") execute_process(COMMAND __wine__ __openscad_binpath__ --info OUTPUT_FILE sysinfo.txt) -set(CTEST_CUSTOM_POST_TEST ${CTEST_CUSTOM_POST_TEST} "__cmake_current_binary_dir__/test_pretty_print") + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}" VERSION_LESS 2.8.12) + set(CTEST_CUSTOM_POST_TEST ${CTEST_CUSTOM_POST_TEST} "__cmake_current_binary_dir__/test_pretty_print") +else() + set(CTEST_CUSTOM_POST_TEST ${CTEST_CUSTOM_POST_TEST} "__python__ __cmake_current_source_dir__/test_pretty_print.py --builddir=__cmake_current_binary_dir__ __openscad_upload_tests__") +endif() if ( ${debug_openscad_template} ) foreach(post_test ${CTEST_CUSTOM_POST_TEST} ) diff --git a/tests/FindGLIB2.cmake b/tests/FindGLIB2.cmake new file mode 100644 index 00000000..9164c391 --- /dev/null +++ b/tests/FindGLIB2.cmake @@ -0,0 +1,28 @@ +find_package(PkgConfig REQUIRED) + +pkg_search_module(GLIB2 REQUIRED glib-2.0) +#message("GLIB2_LIBRARIES ${GLIB2_LIBRARIES}") +message("GLIB2_LIBRARY_DIRS ${GLIB2_LIBRARY_DIRS}") +#message("GLIB2_LDFLAGS ${GLIB2_LDFLAGS}") +#message("GLIB2_LDFLAGS_OTHER ${GLIB2_LDFLAGS_OTHER}") +message("GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS}") +#message("GLIB2_CFLAGS ${GLIB2_CFLAGS}") +#message("GLIB2_CFLAGS_OTHER ${GLIB2_CFLAGS_OTHER}") +message("GLIB2_LIBDIR ${GLIB2_LIBDIR}") + +set(GLIB2_DEFINITIONS ${GLIB2_CFLAGS_OTHER}) +#message("GLIB2_DEFINITIONS ${GLIB2_DEFINITIONS}") + +set(GLIB2_LIBRARY_NAMES ${GLIB2_LIBRARIES}) +set(GLIB2_LIBRARIES "") +foreach(GLIB2_LIB ${GLIB2_LIBRARY_NAMES}) +# message("lib: ${GLIB2_LIB}") + set(TMP TMP-NOTFOUND) + find_library(TMP NAMES ${GLIB2_LIB} + PATHS ${GLIB2_LIBRARY_DIRS} + PATHS ${GLIB2_LIBDIR} + NO_DEFAULT_PATH) +# message("TMP: ${TMP}") + list(APPEND GLIB2_LIBRARIES "${TMP}") +endforeach() +message("GLIB2_LIBRARIES: ${GLIB2_LIBRARIES}") diff --git a/tests/regression/cgalpngtest/polyhedron-tests-expected.png b/tests/regression/cgalpngtest/polyhedron-tests-expected.png index c80990fa..322160db 100644 Binary files a/tests/regression/cgalpngtest/polyhedron-tests-expected.png and b/tests/regression/cgalpngtest/polyhedron-tests-expected.png differ diff --git a/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png b/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png index 1488c85e..d198344f 100644 Binary files a/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png and b/tests/regression/cgalpngtest/rotate_extrude-tests-expected.png differ diff --git a/tests/regression/cgalpngtest/stl-cgal-convert_to_Polyhedron-crash-expected.png b/tests/regression/cgalpngtest/stl-cgal-convert_to_Polyhedron-crash-expected.png new file mode 100644 index 00000000..318cbaab Binary files /dev/null and b/tests/regression/cgalpngtest/stl-cgal-convert_to_Polyhedron-crash-expected.png differ diff --git a/tests/regression/dumptest/polyhedron-tests-expected.csg b/tests/regression/dumptest/polyhedron-tests-expected.csg index 1bb36d9b..dabdf943 100644 --- a/tests/regression/dumptest/polyhedron-tests-expected.csg +++ b/tests/regression/dumptest/polyhedron-tests-expected.csg @@ -7,6 +7,9 @@ group() { multmatrix([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { polyhedron(points = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]], faces = [[0, 2, 4], [0, 5, 2], [0, 4, 3], [0, 3, 5], [1, 4, 2], [1, 2, 5], [1, 3, 4], [1, 5, 3]], convexity = 1); } + multmatrix([[1, 0, 0, 6], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + polyhedron(points = [[-0.8, -0.8, -0.8], [0, 0, -0.8], [0.8, -0.8, -0.8], [0.8, 0.8, -0.8], [-0.8, 0.8, -0.8], [-0.8, -0.8, 0.8], [0, 0, 0.8], [0.8, -0.8, 0.8], [0.8, 0.8, 0.8], [-0.8, 0.8, 0.8]], triangles = [[0, 1, 2, 3, 4], [5, 6, 1, 0], [6, 7, 2, 1], [7, 8, 3, 2], [8, 9, 4, 3], [9, 5, 0, 4], [9, 8, 7, 6, 5]], convexity = 2); + } } multmatrix([[1, 0, 0, 0], [0, 1, 0, 2], [0, 0, 1, 0], [0, 0, 0, 1]]) { difference() { @@ -18,8 +21,11 @@ group() { multmatrix([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { polyhedron(points = [[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]], faces = [[0, 2, 4], [0, 5, 2], [0, 4, 3], [0, 3, 5], [1, 4, 2], [1, 2, 5], [1, 3, 4], [1, 5, 3]], convexity = 1); } + multmatrix([[1, 0, 0, 6], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + polyhedron(points = [[-0.8, -0.8, -0.8], [0, 0, -0.8], [0.8, -0.8, -0.8], [0.8, 0.8, -0.8], [-0.8, 0.8, -0.8], [-0.8, -0.8, 0.8], [0, 0, 0.8], [0.8, -0.8, 0.8], [0.8, 0.8, 0.8], [-0.8, 0.8, 0.8]], triangles = [[0, 1, 2, 3, 4], [5, 6, 1, 0], [6, 7, 2, 1], [7, 8, 3, 2], [8, 9, 4, 3], [9, 5, 0, 4], [9, 8, 7, 6, 5]], convexity = 2); + } } - multmatrix([[1, 0, 0, 2], [0, 1, 0, 0], [0, 0, 1, 2], [0, 0, 0, 1]]) { + multmatrix([[1, 0, 0, 3], [0, 1, 0, 0], [0, 0, 1, 2], [0, 0, 0, 1]]) { cube(size = [8, 3, 3], center = true); } } diff --git a/tests/regression/dumptest/rotate_extrude-tests-expected.csg b/tests/regression/dumptest/rotate_extrude-tests-expected.csg index a86dd8f0..d010bfd6 100644 --- a/tests/regression/dumptest/rotate_extrude-tests-expected.csg +++ b/tests/regression/dumptest/rotate_extrude-tests-expected.csg @@ -50,4 +50,11 @@ group() { } } } + multmatrix([[1, 0, 0, 0], [0, 1, 0, 60], [0, 0, 1, 0], [0, 0, 0, 1]]) { + rotate_extrude(convexity = 1, $fn = 0, $fa = 12, $fs = 2) { + multmatrix([[1, 0, 0, -20], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [10, 10], center = false); + } + } + } } diff --git a/tests/regression/dumptest/stl-cgal-convert_to_Polyhedron-crash-expected.csg b/tests/regression/dumptest/stl-cgal-convert_to_Polyhedron-crash-expected.csg new file mode 100644 index 00000000..acad52fd --- /dev/null +++ b/tests/regression/dumptest/stl-cgal-convert_to_Polyhedron-crash-expected.csg @@ -0,0 +1,5 @@ +group() { + render(convexity = 1) { + import(file = "stl-cgal-convert_to_Polyhedron-crash.stl", layer = "", origin = [0, 0], scale = 1, convexity = 1, $fn = 0, $fa = 12, $fs = 2); + } +} diff --git a/tests/regression/echotest/search-tests-unicode-expected.echo b/tests/regression/echotest/search-tests-unicode-expected.echo new file mode 100644 index 00000000..801bc8c8 --- /dev/null +++ b/tests/regression/echotest/search-tests-unicode-expected.echo @@ -0,0 +1,109 @@ +ECHO: "----- Lookup of 1 byte into 1 byte" +ECHO: "Expect [0] for search(a, aaaa)=[0]. OK" +ECHO: "Expect [0] for search(a, aaaa, 1)=[0]. OK" +ECHO: "Expect [0, 0] for search(aa, aaaa)=[0, 0]. OK" +ECHO: "Expect [0, 0] for search(aa, aaaa, 1)=[0, 0]. OK" +ECHO: "Expect [[0, 1, 2, 3]] for search(a, aaaa, 0)=[[0, 1, 2, 3]]. OK" +ECHO: "Expect [[0, 1]] for search(a, aaaa, 2)=[[0, 1]]. OK" +ECHO: "Expect [[0, 1, 2]] for search(a, aaaa, 3)=[[0, 1, 2]]. OK" +ECHO: "Expect [[0, 1, 2, 3]] for search(a, aaaa, 4)=[[0, 1, 2, 3]]. OK" +ECHO: "Expect [[0, 1, 2, 3], [0, 1, 2, 3]] for search(aa, aaaa, 0)=[[0, 1, 2, 3], [0, 1, 2, 3]]. OK" + WARNING: search term not found: "b" +ECHO: "Expect [] for search(b, aaaa)=[]. OK" + WARNING: search term not found: "b" +ECHO: "Expect [] for search(b, aaaa, 1)=[]. OK" + WARNING: search term not found: "b" +ECHO: "Expect [[]] for search(b, aaaa, 0)=[[]]. OK" + WARNING: search term not found: "b" +ECHO: "Expect [[]] for search(b, aaaa, 2)=[[]]. OK" + WARNING: search term not found: "b" + WARNING: search term not found: "b" +ECHO: "Expect [] for search(bb, aaaa)=[]. OK" + WARNING: search term not found: "b" + WARNING: search term not found: "b" +ECHO: "Expect [] for search(bb, aaaa, 1)=[]. OK" + WARNING: search term not found: "b" + WARNING: search term not found: "b" +ECHO: "Expect [[], []] for search(bb, aaaa, 0)=[[], []]. OK" + WARNING: search term not found: "b" + WARNING: search term not found: "b" +ECHO: "Expect [[], []] for search(bb, aaaa, 2)=[[], []]. OK" +ECHO: "Expect [] for search(, aaaa)=[]. OK" +ECHO: "Expect [] for search(, )=[]. OK" + WARNING: search term not found: "a" +ECHO: "Expect [] for search(a, )=[]. OK" +ECHO: "----- Lookup of multi-byte into 1 byte" + WARNING: search term not found: "Π›" +ECHO: "Expect [] for search(Π›, aaaa)=[]. OK" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [] for search(πŸ‚‘, aaaa)=[]. OK" + WARNING: search term not found: "Π›" +ECHO: "Expect [[]] for search(Π›, aaaa, 0)=[[]]. OK" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [[]] for search(πŸ‚‘, aaaa, 0)=[[]]. OK" + WARNING: search term not found: "Π›" + WARNING: search term not found: "Π›" +ECHO: "Expect [] for search(Π›Π›, aaaa)=[]. OK" + WARNING: search term not found: "πŸ‚‘" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [] for search(πŸ‚‘πŸ‚‘, aaaa)=[]. OK" + WARNING: search term not found: "Π›" + WARNING: search term not found: "Π›" +ECHO: "Expect [[], []] for search(Π›Π›, aaaa, 0)=[[], []]. OK" + WARNING: search term not found: "πŸ‚‘" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [[], []] for search(πŸ‚‘πŸ‚‘, aaaa, 0)=[[], []]. OK" +ECHO: "----- Lookup of 1-byte into multi-byte" + WARNING: search term not found: "a" +ECHO: "Expect [] for search(a, Π›Π›Π›Π›)=[]. OK" + WARNING: search term not found: "a" +ECHO: "Expect [] for search(a, πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘)=[]. OK" + WARNING: search term not found: "a" +ECHO: "Expect [] for search(a, Π›Π›Π›Π›, 1)=[]. OK" + WARNING: search term not found: "a" +ECHO: "Expect [[]] for search(a, πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘, 0)=[[]]. OK" + WARNING: search term not found: "a" +ECHO: "Expect [[]] for search(a, πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘, 2)=[[]]. OK" +ECHO: "----- Lookup of 1-byte into mixed multi-byte" +ECHO: "Expect [0] for search(a, aΠ›aΠ›aΠ›aΠ›a)=[0]. OK" +ECHO: "Expect [0] for search(a, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a)=[0]. OK" +ECHO: "Expect [0] for search(a, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a)=[0]. OK" +ECHO: "Expect [[0, 2, 4, 6, 8]] for search(a, aΠ›aΠ›aΠ›aΠ›a, 0)=[[0, 2, 4, 6, 8]]. OK" +ECHO: "Expect [[0, 2, 4, 6, 8]] for search(a, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a, 0)=[[0, 2, 4, 6, 8]]. OK" +ECHO: "Expect [[0, 4, 8]] for search(a, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a, 0)=[[0, 4, 8]]. OK" +ECHO: "----- Lookup of 2-byte into 2-byte" +ECHO: "Expect [0] for search(Π›, Π›Π›Π›Π›)=[0]. OK" +ECHO: "Expect [[0, 1, 2, 3]] for search(Π›, Π›Π›Π›Π›, 0)=[[0, 1, 2, 3]]. OK" +ECHO: "----- Lookup of 2-byte into 4-byte" + WARNING: search term not found: "Π›" +ECHO: "Expect [] for search(Π›, πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘)=[]. OK" +ECHO: "----- Lookup of 4-byte into 4-byte" +ECHO: "Expect [0] for search(πŸ‚‘, πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘)=[0]. OK" +ECHO: "Expect [[0, 1, 2, 3]] for search(πŸ‚‘, πŸ‚‘πŸ‚‘πŸ‚‘πŸ‚‘, 0)=[[0, 1, 2, 3]]. OK" +ECHO: "----- Lookup of 4-byte into 2-byte" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [] for search(πŸ‚‘, Π›Π›Π›Π›)=[]. OK" +ECHO: "----- Lookup of 2-byte into mixed multi-byte" +ECHO: "Expect [1] for search(Π›, aΠ›aΠ›aΠ›aΠ›a, 1)=[1]. OK" + WARNING: search term not found: "Π›" +ECHO: "Expect [] for search(Π›, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a, 1)=[]. OK" +ECHO: "Expect [2] for search(Π›, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a, 1)=[2]. OK" +ECHO: "Expect [[1, 3, 5, 7]] for search(Π›, aΠ›aΠ›aΠ›aΠ›a, 0)=[[1, 3, 5, 7]]. OK" + WARNING: search term not found: "Π›" +ECHO: "Expect [[]] for search(Π›, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a, 0)=[[]]. OK" +ECHO: "Expect [[2, 6]] for search(Π›, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a, 0)=[[2, 6]]. OK" +ECHO: "----- Lookup of 4-byte into mixed multi-byte" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [] for search(πŸ‚‘, aΠ›aΠ›aΠ›aΠ›a, 1)=[]. OK" +ECHO: "Expect [1] for search(πŸ‚‘, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a, 1)=[1]. OK" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [[]] for search(πŸ‚‘, aΠ›aΠ›aΠ›aΠ›a, 0)=[[]]. OK" +ECHO: "Expect [[1, 3, 5, 7]] for search(πŸ‚‘, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a, 0)=[[1, 3, 5, 7]]. OK" +ECHO: "Expect [[1, 3, 5, 7]] for search(πŸ‚‘, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a, 0)=[[1, 3, 5, 7]]. OK" +ECHO: "----- Lookup of mixed multi-byte into mixed multi-byte" + WARNING: search term not found: "πŸ‚‘" +ECHO: "Expect [[0, 2, 4, 6, 8], [1, 3, 5, 7], []] for search(aΠ›πŸ‚‘, aΠ›aΠ›aΠ›aΠ›a, 0)=[[0, 2, 4, 6, 8], [1, 3, 5, 7], []]. OK" + WARNING: search term not found: "Π›" +ECHO: "Expect [[0, 2, 4, 6, 8], [], [1, 3, 5, 7]] for search(aΠ›πŸ‚‘, aπŸ‚‘aπŸ‚‘aπŸ‚‘aπŸ‚‘a, 0)=[[0, 2, 4, 6, 8], [], [1, 3, 5, 7]]. OK" +ECHO: "Expect [[0, 4, 8], [2, 6], [1, 3, 5, 7]] for search(aΠ›πŸ‚‘, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a, 0)=[[0, 4, 8], [2, 6], [1, 3, 5, 7]]. OK" +ECHO: "Expect [[1, 3, 5, 7], [0, 4, 8], [2, 6]] for search(πŸ‚‘aΠ›, aπŸ‚‘Π›πŸ‚‘aπŸ‚‘Π›πŸ‚‘a, 0)=[[1, 3, 5, 7], [0, 4, 8], [2, 6]]. OK" diff --git a/tests/regression/echotest/string-unicode-expected.echo b/tests/regression/echotest/string-unicode-expected.echo new file mode 100644 index 00000000..a1cd3bec --- /dev/null +++ b/tests/regression/echotest/string-unicode-expected.echo @@ -0,0 +1,107 @@ +ECHO: "text_1bytes_len = ", "1234", " len = ", 4 +ECHO: "text_2bytes_len = ", "Π›Π›Π›Π›", " len = ", 4 +ECHO: "text_4bytes_len = ", "πŸ‚‘πŸ‚±πŸƒπŸƒ‘", " len = ", 4 +ECHO: "[", 0, "] = ", "DEADBEEF", " of len=", 8, ":" +ECHO: " [", 0, "]=", "D" +ECHO: " [", 1, "]=", "E" +ECHO: " [", 2, "]=", "A" +ECHO: " [", 3, "]=", "D" +ECHO: " [", 4, "]=", "B" +ECHO: " [", 5, "]=", "E" +ECHO: " [", 6, "]=", "E" +ECHO: " [", 7, "]=", "F" +ECHO: "[", 1, "] = ", "Π›Π΅Π½ΠΈΠ²Ρ‹ΠΉ Ρ€Ρ‹ΠΆΠΈΠΉ ΠΊΠΎΡ‚", " of len=", 17, ":" +ECHO: " [", 0, "]=", "Π›" +ECHO: " [", 1, "]=", "Π΅" +ECHO: " [", 2, "]=", "Π½" +ECHO: " [", 3, "]=", "ΠΈ" +ECHO: " [", 4, "]=", "Π²" +ECHO: " [", 5, "]=", "Ρ‹" +ECHO: " [", 6, "]=", "ΠΉ" +ECHO: " [", 7, "]=", " " +ECHO: " [", 8, "]=", "Ρ€" +ECHO: " [", 9, "]=", "Ρ‹" +ECHO: " [", 10, "]=", "ΠΆ" +ECHO: " [", 11, "]=", "ΠΈ" +ECHO: " [", 12, "]=", "ΠΉ" +ECHO: " [", 13, "]=", " " +ECHO: " [", 14, "]=", "ΠΊ" +ECHO: " [", 15, "]=", "ΠΎ" +ECHO: " [", 16, "]=", "Ρ‚" +ECHO: "[", 2, "] = ", "ΩƒΨ³ΩˆΩ„ Ψ§Ω„Ψ²Ω†Ψ¬Ψ¨ΩŠΩ„ Ψ§Ω„Ω‚Ψ·", " of len=", 18, ":" +ECHO: " [", 0, "]=", "Ωƒ" +ECHO: " [", 1, "]=", "Ψ³" +ECHO: " [", 2, "]=", "و" +ECHO: " [", 3, "]=", "Ω„" +ECHO: " [", 4, "]=", " " +ECHO: " [", 5, "]=", "Ψ§" +ECHO: " [", 6, "]=", "Ω„" +ECHO: " [", 7, "]=", "Ψ²" +ECHO: " [", 8, "]=", "Ω†" +ECHO: " [", 9, "]=", "Ψ¬" +ECHO: " [", 10, "]=", "Ψ¨" +ECHO: " [", 11, "]=", "ي" +ECHO: " [", 12, "]=", "Ω„" +ECHO: " [", 13, "]=", " " +ECHO: " [", 14, "]=", "Ψ§" +ECHO: " [", 15, "]=", "Ω„" +ECHO: " [", 16, "]=", "Ω‚" +ECHO: " [", 17, "]=", "Ψ·" +ECHO: "[", 3, "] = ", "ζ‡Άζƒ°ηš„ε§œθ²“", " of len=", 5, ":" +ECHO: " [", 0, "]=", "ζ‡Ά" +ECHO: " [", 1, "]=", "ζƒ°" +ECHO: " [", 2, "]=", "ηš„" +ECHO: " [", 3, "]=", "姜" +ECHO: " [", 4, "]=", "貓" +ECHO: "[", 4, "] = ", "Àâü Γ„Γ–Γœ ß", " of len=", 9, ":" +ECHO: " [", 0, "]=", "Γ€" +ECHO: " [", 1, "]=", "ΓΆ" +ECHO: " [", 2, "]=", "ΓΌ" +ECHO: " [", 3, "]=", " " +ECHO: " [", 4, "]=", "Γ„" +ECHO: " [", 5, "]=", "Γ–" +ECHO: " [", 6, "]=", "Ü" +ECHO: " [", 7, "]=", " " +ECHO: " [", 8, "]=", "ß" +ECHO: "[", 5, "] = ", "πŸ˜πŸ˜‚πŸ˜ƒπŸ˜„πŸ˜…πŸ˜†πŸ˜‡πŸ˜ˆπŸ˜‰πŸ˜ŠπŸ˜‹πŸ˜ŒπŸ˜πŸ˜ŽπŸ˜πŸ˜", " of len=", 16, ":" +ECHO: " [", 0, "]=", "😁" +ECHO: " [", 1, "]=", "πŸ˜‚" +ECHO: " [", 2, "]=", "πŸ˜ƒ" +ECHO: " [", 3, "]=", "πŸ˜„" +ECHO: " [", 4, "]=", "πŸ˜…" +ECHO: " [", 5, "]=", "πŸ˜†" +ECHO: " [", 6, "]=", "πŸ˜‡" +ECHO: " [", 7, "]=", "😈" +ECHO: " [", 8, "]=", "πŸ˜‰" +ECHO: " [", 9, "]=", "😊" +ECHO: " [", 10, "]=", "πŸ˜‹" +ECHO: " [", 11, "]=", "😌" +ECHO: " [", 12, "]=", "😍" +ECHO: " [", 13, "]=", "😎" +ECHO: " [", 14, "]=", "😏" +ECHO: " [", 15, "]=", "😐" +ECHO: "[", 6, "] = ", "β β ‚β ƒβ „β …β †β ‡β ˆβ ‰β Šβ ‹β Œβ β Žβ ", " of len=", 15, ":" +ECHO: " [", 0, "]=", "⠁" +ECHO: " [", 1, "]=", "β ‚" +ECHO: " [", 2, "]=", "β ƒ" +ECHO: " [", 3, "]=", "β „" +ECHO: " [", 4, "]=", "β …" +ECHO: " [", 5, "]=", "β †" +ECHO: " [", 6, "]=", "β ‡" +ECHO: " [", 7, "]=", "⠈" +ECHO: " [", 8, "]=", "β ‰" +ECHO: " [", 9, "]=", "⠊" +ECHO: " [", 10, "]=", "β ‹" +ECHO: " [", 11, "]=", "⠌" +ECHO: " [", 12, "]=", "⠍" +ECHO: " [", 13, "]=", "⠎" +ECHO: " [", 14, "]=", "⠏" +ECHO: "[", 7, "] = ", "πŸ‚‘πŸ‚±πŸƒπŸƒ‘", " of len=", 4, ":" +ECHO: " [", 0, "]=", "πŸ‚‘" +ECHO: " [", 1, "]=", "πŸ‚±" +ECHO: " [", 2, "]=", "πŸƒ" +ECHO: " [", 3, "]=", "πŸƒ‘" +ECHO: "Past end of unicode only 2-byte ", undef +ECHO: "Past end of unicode only 4-byte ", undef +ECHO: "Past end of both 2-byte ", undef +ECHO: "Past end of both 4-byte ", undef diff --git a/tests/regression/opencsgtest/polyhedron-tests-expected.png b/tests/regression/opencsgtest/polyhedron-tests-expected.png index 0740f1ae..f6665cfa 100644 Binary files a/tests/regression/opencsgtest/polyhedron-tests-expected.png and b/tests/regression/opencsgtest/polyhedron-tests-expected.png differ diff --git a/tests/regression/opencsgtest/rotate_extrude-tests-expected.png b/tests/regression/opencsgtest/rotate_extrude-tests-expected.png index 861f6ab4..0be247ee 100644 Binary files a/tests/regression/opencsgtest/rotate_extrude-tests-expected.png and b/tests/regression/opencsgtest/rotate_extrude-tests-expected.png differ diff --git a/tests/regression/throwntogethertest/polyhedron-tests-expected.png b/tests/regression/throwntogethertest/polyhedron-tests-expected.png index 95ffeaa5..c293d88c 100644 Binary files a/tests/regression/throwntogethertest/polyhedron-tests-expected.png and b/tests/regression/throwntogethertest/polyhedron-tests-expected.png differ diff --git a/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png b/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png index 8956be23..1da48739 100644 Binary files a/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png and b/tests/regression/throwntogethertest/rotate_extrude-tests-expected.png differ diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index a31b1a86..4c3993cc 100755 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -25,544 +25,447 @@ # todo # -# 1. Note: Wiki code is deprecated. All future development should move to -# create html output (or even xml output). Wiki design was based on the -# wrong assumption of easily accessible public wiki servers with -# auto-upload scripts available. wiki code should be removed and code -# simplified. -# -# to still use wiki, use args '--wiki' and/or '--wiki-upload' -# -# 2. why is hash differing +# 1. why is hash differing -import string,sys,re,os,hashlib,subprocess,textwrap,time,platform +import string +import sys +import re +import os +import hashlib +import subprocess +import time +import platform +try: + from urllib.request import urlopen + from urllib.parse import urlencode +except: + from urllib2 import urlopen + from urllib import urlencode def tryread(filename): - data = None - try: - f = open(filename,'rb') - data = f.read() - f.close() - except Exception, e: - print 'couldn\'t open ',filename - print type(e), e - return data + data = None + try: + f = open(filename,'rb') + data = f.read() + f.close() + except Exception as e: + print 'couldn\'t open ',filename + print type(e), e + return data -def trysave(filename,data): - dir = os.path.dirname(filename) - try: - if not os.path.isdir(dir): - if not dir == '': - debug( 'creating' + dir) - os.mkdir(dir) - f=open(filename,'wb') - f.write(data) - f.close() - except Exception, e: - print 'problem writing to',filename - print type(e), e - return None - return True +def trysave(filename, data): + dir = os.path.dirname(filename) + try: + if not os.path.isdir(dir): + if not dir == '': + debug( 'creating' + dir) + os.mkdir(dir) + f=open(filename,'wb') + f.write(data) + f.close() + except Exception as e: + print 'problem writing to',filename + print type(e), e + return None + return True -def ezsearch(pattern,str): - x = re.search(pattern,str,re.DOTALL|re.MULTILINE) - if x and len(x.groups())>0: return x.group(1).strip() - return '' - +def ezsearch(pattern, str): + x = re.search(pattern,str,re.DOTALL|re.MULTILINE) + if x and len(x.groups())>0: return x.group(1).strip() + return '' + def read_gitinfo(): - # won't work if run from outside of branch. - try: - data = subprocess.Popen(['git','remote','-v'],stdout=subprocess.PIPE).stdout.read() - origin = ezsearch('^origin *?(.*?)\(fetch.*?$',data) - upstream = ezsearch('^upstream *?(.*?)\(fetch.*?$',data) - data = subprocess.Popen(['git','branch'],stdout=subprocess.PIPE).stdout.read() - branch = ezsearch('^\*(.*?)$',data) - out = 'Git branch: ' + branch + ' from origin ' + origin + '\n' - out += 'Git upstream: ' + upstream + '\n' - except: - out = 'Problem running git' - return out + # won't work if run from outside of branch. + try: + data = subprocess.Popen(['git', 'remote', '-v'], stdout=subprocess.PIPE).stdout.read() + origin = ezsearch('^origin *?(.*?)\(fetch.*?$', data) + upstream = ezsearch('^upstream *?(.*?)\(fetch.*?$', data) + data = subprocess.Popen(['git', 'branch'], stdout=subprocess.PIPE).stdout.read() + branch = ezsearch('^\*(.*?)$', data) + out = 'Git branch: ' + branch + ' from origin ' + origin + '\n' + out += 'Git upstream: ' + upstream + '\n' + except: + out = 'Problem running git' + return out def read_sysinfo(filename): - data = tryread(filename) - if not data: - sinfo = platform.sys.platform - sinfo += '\nsystem cannot create offscreen GL framebuffer object' - sinfo += '\nsystem cannot create GL based images' - sysid = platform.sys.platform+'_no_GL_renderer' - return sinfo, sysid + data = tryread(filename) + if not data: + sinfo = platform.sys.platform + sinfo += '\nsystem cannot create offscreen GL framebuffer object' + sinfo += '\nsystem cannot create GL based images' + sysid = platform.sys.platform+'_no_GL_renderer' + return sinfo, sysid - machine = ezsearch('Machine:(.*?)\n',data) - machine = machine.replace(' ','-').replace('/','-') + machine = ezsearch('Machine:(.*?)\n',data) + machine = machine.replace(' ','-').replace('/','-') - osinfo = ezsearch('OS info:(.*?)\n',data) - osplain = osinfo.split(' ')[0].strip().replace('/','-') - if 'windows' in osinfo.lower(): osplain = 'win' + osinfo = ezsearch('OS info:(.*?)\n',data) + osplain = osinfo.split(' ')[0].strip().replace('/','-') + if 'windows' in osinfo.lower(): + osplain = 'win' - renderer = ezsearch('GL Renderer:(.*?)\n',data) - tmp = renderer.split(' ') - tmp = string.join(tmp[0:3],'-') - tmp = tmp.split('/')[0] - renderer = tmp + renderer = ezsearch('GL Renderer:(.*?)\n',data) + tmp = renderer.split(' ') + tmp = string.join(tmp[0:3],'-') + tmp = tmp.split('/')[0] + renderer = tmp - data += read_gitinfo() + data += read_gitinfo() - data += 'Image comparison: ImageMagick' + data += 'Image comparison: ImageMagick' - data = data.strip() + data = data.strip() - # create 4 letter hash and stick on end of sysid - nondate_data = re.sub("\n.*?ompile date.*?\n","\n",data).strip() - hexhash = hashlib.md5() - hexhash.update(nondate_data) - hexhash = hexhash.hexdigest()[-4:].upper() - hash = '' - for c in hexhash: hash += chr(ord(c)+97-48) + # create 4 letter hash and stick on end of sysid + nondate_data = re.sub("\n.*?ompile date.*?\n", "\n", data).strip() + hexhash = hashlib.md5(nondate_data).hexdigest()[-4:].upper() + hash_ = ''.join(chr(ord(c) + 97 - 48) for c in hexhash) - sysid = osplain + '_' + machine + '_' + renderer + '_' + hash - sysid = sysid.replace('(','_').replace(')','_') - sysid = sysid.lower() + sysid = '_'.join([osplain, machine, renderer, hash_]) + sysid = sysid.replace('(', '_').replace(')', '_') + sysid = sysid.lower() - return data, sysid + return data, sysid class Test: - def __init__(self,fullname,time,passed,output,type,actualfile,expectedfile,scadfile,log): - self.fullname,self.time,self.passed,self.output = \ - fullname, time, passed, output - self.type, self.actualfile, self.expectedfile, self.scadfile = \ - type, actualfile, expectedfile, scadfile - self.fulltestlog = log - - def __str__(self): - x = 'fullname: ' + self.fullname - x+= '\nactualfile: ' + self.actualfile - x+= '\nexpectedfile: ' + self.expectedfile - x+= '\ntesttime: ' + self.time - x+= '\ntesttype: ' + self.type - x+= '\npassed: ' + str(self.passed) - x+= '\nscadfile: ' + self.scadfile - x+= '\noutput bytes: ' + str(len(self.output)) - x+= '\ntestlog bytes: ' + str(len(self.fulltestlog)) - x+= '\n' - return x + def __init__(self, fullname, subpr, passed, output, type, actualfile, + expectedfile, scadfile, log): + self.fullname, self.time = fullname, time + self.passed, self.output = passed, output + self.type, self.actualfile = type, actualfile + self.expectedfile, self.scadfile = expectedfile, scadfile + self.fulltestlog = log + + def __str__(self): + x = 'fullname: ' + self.fullname + x+= '\nactualfile: ' + self.actualfile + x+= '\nexpectedfile: ' + self.expectedfile + x+= '\ntesttime: ' + self.time + x+= '\ntesttype: ' + self.type + x+= '\npassed: ' + str(self.passed) + x+= '\nscadfile: ' + self.scadfile + x+= '\noutput bytes: ' + str(len(self.output)) + x+= '\ntestlog bytes: ' + str(len(self.fulltestlog)) + x+= '\n' + return x def parsetest(teststring): - patterns = ["Test:(.*?)\n", # fullname - "Test time =(.*?) sec\n", - "Test time.*?Test (Passed)", # pass/fail - "Output:(.*?)", - 'Command:.*?-s" "(.*?)"', # type - "^ actual .*?:(.*?)\n", - "^ expected .*?:(.*?)\n", - 'Command:.*?(testdata.*?)"' # scadfile - ] - hits = map( lambda pattern: ezsearch(pattern,teststring), patterns ) - test = Test(hits[0],hits[1],hits[2]=='Passed',hits[3],hits[4],hits[5],hits[6],hits[7],teststring) - if len(test.actualfile) > 0: test.actualfile_data = tryread(test.actualfile) - if len(test.expectedfile) > 0: test.expectedfile_data = tryread(test.expectedfile) - return test + patterns = ["Test:(.*?)\n", # fullname + "Test time =(.*?) sec\n", + "Test time.*?Test (Passed)", # pass/fail + "Output:(.*?)", + 'Command:.*?-s" "(.*?)"', # type + "^ actual .*?:(.*?)\n", + "^ expected .*?:(.*?)\n", + 'Command:.*?(testdata.*?)"' # scadfile + ] + hits = map( lambda pattern: ezsearch(pattern, teststring), patterns) + test = Test(hits[0], hits[1], hits[2]=='Passed', hits[3], hits[4], hits[5], + hits[6], hits[7], teststring) + if len(test.actualfile) > 0: + test.actualfile_data = tryread(test.actualfile) + if len(test.expectedfile) > 0: + test.expectedfile_data = tryread(test.expectedfile) + return test def parselog(data): - startdate = ezsearch('Start testing: (.*?)\n',data) - enddate = ezsearch('End testing: (.*?)\n',data) - pattern = '([0-9]*/[0-9]* Testing:.*?time elapsed.*?\n)' - test_chunks = re.findall(pattern,data,re.S) - tests = map( parsetest, test_chunks ) - tests = sorted(tests, key = lambda t:t.passed) - return startdate, tests, enddate + startdate = ezsearch('Start testing: (.*?)\n', data) + enddate = ezsearch('End testing: (.*?)\n', data) + pattern = '([0-9]*/[0-9]* Testing:.*?time elapsed.*?\n)' + test_chunks = re.findall(pattern,data, re.S) + tests = map( parsetest, test_chunks ) + tests = sorted(tests, key = lambda t: t.passed) + return startdate, tests, enddate def load_makefiles(builddir): - filelist = [] - for root, dirs, files in os.walk(builddir): - for fname in files: filelist += [ os.path.join(root, fname) ] - files = filter(lambda x: 'build.make' in os.path.basename(x), filelist) - files += filter(lambda x: 'flags.make' in os.path.basename(x), filelist) - files = filter(lambda x: 'esting' not in x and 'emporary' not in x, files) - result = {} - for fname in files: - result[fname.replace(builddir,'')] = open(fname,'rb').read() - return result - -def wikify_filename(fname, wiki_rootpath, sysid): - wikifname = fname.replace('/','_').replace('\\','_').strip('.') - return wiki_rootpath + '_' + sysid + '_' + wikifname - -def towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles): - - wiki_template = """ -

    [[WIKI_ROOTPATH]] test run report

    - -'''Sysid''': SYSID - -'''Result summary''': NUMPASSED / NUMTESTS tests passed ( PERCENTPASSED % )
    - -'''System info''': -
    -SYSINFO
    -
    - -start time: STARTDATE
    -end time : ENDDATE
    - -'''Image tests''' - - -{| border=1 cellspacing=0 cellpadding=1 -|- -| colspan=2 | FTESTNAME -|- -| Expected image || Actual image -|- -| [[File:EXPECTEDFILE|250px]] || ACTUALFILE_WIKI -|} - -
    -TESTLOG
    -
    + filelist = [] + for root, dirs, files in os.walk(builddir): + for fname in files: filelist += [ os.path.join(root, fname) ] + files = [file for file in filelist if 'build.make' in os.path.basename(file) + or 'flags.make' in os.path.basename(file)] + files = [file for file in files if 'esting' not in file and 'emporary' not in file] + result = {} + for fname in files: + result[fname.replace(builddir, '')] = tryread(fname) + return result +def png_encode64(fname, width=250, data=None): + # en.wikipedia.org/wiki/Data_URI_scheme + data = data or tryread(fname) or '' + data_uri = data.encode('base64').replace('\n', '') + tag = '''''' + if data == '': + alt = 'alt="error: no image generated" ' + else: + alt = 'alt="openscad_test_image" ' + tag %= (data_uri, width, alt) + return tag -
    - - -'''Text tests''' - - -{|border=1 cellspacing=0 cellpadding=1 -|- -| FTESTNAME -|} - -
    -TESTLOG
    -
    - - -
    - -'''build.make and flags.make''' - -*[[MAKEFILE_NAME]] - -""" - txtpages = {} - imgs = {} - passed_tests = filter(lambda x: x.passed, tests) - failed_tests = filter(lambda x: not x.passed, tests) - - tests_to_report = failed_tests - if include_passed: tests_to_report = tests - - try: percent = str(int(100.0*len(passed_tests) / len(tests))) - except ZeroDivisionError: percent = 'n/a' - s = wiki_template - repeat1 = ezsearch('(.*?)',s) - repeat2 = ezsearch('(.*?)',s) - repeat3 = ezsearch('(.*?)',s) - dic = { 'STARTDATE': startdate, 'ENDDATE': enddate, 'WIKI_ROOTPATH': wiki_rootpath, - 'SYSINFO': sysinfo, 'SYSID':sysid, - 'NUMTESTS':len(tests), 'NUMPASSED':len(passed_tests), 'PERCENTPASSED':percent } - for key in dic.keys(): - s = s.replace(key,str(dic[key])) - - for t in tests_to_report: - if t.type in ('txt', 'ast', 'csg', 'term', 'echo'): - newchunk = re.sub('FTESTNAME',t.fullname,repeat2) - newchunk = newchunk.replace('TESTLOG',t.fulltestlog) - s = s.replace(repeat2, newchunk+repeat2) - elif t.type=='png': - tmp = t.actualfile.replace(builddir,'') - wikiname_a = wikify_filename(tmp,wiki_rootpath,sysid) - tmp = t.expectedfile.replace(os.path.dirname(builddir),'') - wikiname_e = wikify_filename(tmp,wiki_rootpath,sysid) - if hasattr(t, 'expectedfile_data'): - imgs[wikiname_e] = t.expectedfile_data - if t.actualfile: - actualfile_wiki = '[[File:'+wikiname_a+'|250px]]' - if hasattr(t, 'actualfile_data'): - imgs[wikiname_a] = t.actualfile_data - else: - actualfile_wiki = 'No image generated.' - newchunk = re.sub('FTESTNAME',t.fullname,repeat1) - newchunk = newchunk.replace('ACTUALFILE_WIKI',actualfile_wiki) - newchunk = newchunk.replace('EXPECTEDFILE',wikiname_e) - newchunk = newchunk.replace('TESTLOG',t.fulltestlog) - s = s.replace(repeat1, newchunk+repeat1) - else: - raise Exception("Unknown test type %r"%t.type) - - makefiles_wikinames = {} - for mf in sorted(makefiles.keys()): - tmp = mf.replace('CMakeFiles','').replace('.dir','') - wikiname = wikify_filename(tmp,wiki_rootpath,sysid) - newchunk = re.sub('MAKEFILE_NAME',wikiname,repeat3) - s = s.replace(repeat3, newchunk+repeat3) - makefiles_wikinames[mf] = wikiname - - s = s.replace(repeat1,'') - s = s.replace(repeat2,'') - s = s.replace(repeat3,'') - s = re.sub('\n','',s) - s = re.sub('','',s) - - mainpage_wikiname = wiki_rootpath + '_' + sysid + '_test_report' - txtpages[ mainpage_wikiname ] = s - for mf in sorted(makefiles.keys()): - txtpages[ makefiles_wikinames[ mf ] ] = '\n*Subreport from [['+mainpage_wikiname+']]\n\n\n
    \n'+makefiles[mf]+'\n
    ' - - return imgs, txtpages - -def png_encode64( fname, width=250 ): - # en.wikipedia.org/wiki/Data_URI_scheme - try: - f = open( fname, "rb" ) - data = f.read() - except: - data = '' - data_uri = data.encode("base64").replace("\n","") - tag = '' - tail = '' - - passed_tests = filter(lambda x: x.passed, tests) - failed_tests = filter(lambda x: not x.passed, tests) - try: percent = str(int(100.0*len(passed_tests) / len(tests))) - except ZeroDivisionError: percent = 'n/a' - - tests_to_report = failed_tests - if include_passed: tests_to_report = tests - - s='' - - s+= '\n

    ' - s+= '\nSystem info\n' - s+= '\n

    ' - s+= '

    '+sysinfo+'
    \n' - - s+= '\n
    '
    -	s+= '\nSTARTDATE: '+ startdate
    -	s+= '\nENDDATE: '+ enddate
    -	s+= '\nWIKI_ROOTPATH: '+ wiki_rootpath
    -	s+= '\nSYSID: '+sysid
    -	s+= '\nNUMTESTS: '+str(len(tests))
    -	s+= '\nNUMPASSED: '+str(len(passed_tests))
    -	s+= '\nPERCENTPASSED: '+ percent
    -	s+= '\n
    ' - - if not include_passed: - s+= '

    Failed tests:

    \n' - - if len(tests_to_report)==0: - s+= '

    none

    ' - - for t in tests_to_report: - if t.type in ('txt', 'ast', 'csg', 'term', 'echo'): - s+='\n
    '+t.fullname+'
    \n' - s+='

    '+t.fulltestlog+'
    \n\n' - elif t.type=='png': - tmp = t.actualfile.replace(builddir,'') - wikiname_a = wikify_filename(tmp,wiki_rootpath,sysid) - tmp = t.expectedfile.replace(os.path.dirname(builddir),'') - wikiname_e = wikify_filename(tmp,wiki_rootpath,sysid) - # imgtag_e = ' - # imatag_a = ' - imgtag_e = png_encode64( t.expectedfile, 250 ) - imgtag_a = png_encode64( t.actualfile, 250 ) - s+='' - s+='\n' - s+='\n ' - s+='\n
    '+t.fullname - s+='\n
    ExpectedActual' - s+='\n
    ' + imgtag_e + '' + imgtag_a + '
    ' - s+='\n
    '
    -			s+=t.fulltestlog
    -			s+='\n
    ' - else: - raise Exception("Unknown test type %r"%t.type) - - s+='\n\n

    \n\n' - - s+= '

    CMake .build files

    \n' - s+= '\n
    '
    -	makefiles_wikinames = {}
    -	for mf in sorted(makefiles.keys()):
    -		mfname = mf.strip().lstrip(os.path.sep)
    -		text = open(os.path.join(builddir,mfname)).read()
    -		s+= '

    '+mfname+'

    '
    -		s+= text
    -		tmp = mf.replace('CMakeFiles','').replace('.dir','')
    -		wikiname = wikify_filename(tmp,wiki_rootpath,sysid)
    -		# s += '\n'+wikiname+'
    ' - s+= '\n
    ' - s+='\n' - - return head + s + tail - -def wiki_login(wikiurl,api_php_path,botname,botpass): - site = mwclient.Site(wikiurl,api_php_path) - site.login(botname,botpass) - return site - -def wiki_upload(wikiurl,api_php_path,botname,botpass,filedata,wikipgname): - counter = 0 - done = False - descrip = 'test' - time.sleep(1) - while not done: - try: - print 'login',botname,'to',wikiurl - site = wiki_login(wikiurl,api_php_path,botname,botpass) - print 'uploading...', - if wikipgname.endswith('png'): - site.upload(filedata,wikipgname,descrip,ignore=True) - else: - page = site.Pages[wikipgname] - text = page.edit() - page.save(filedata) - done = True - print 'transfer ok' - except Exception, e: - print 'Error:', type(e),e - counter += 1 - if counter>maxretry: - print 'giving up. please try a different wiki site' - done = True - else: - print 'wiki',wikiurl,'down. retrying in 15 seconds' - time.sleep(15) - -def upload(wikiurl,api_php_path='/',wiki_rootpath='test', sysid='null', botname='cakebaby',botpass='anniew',wikidir='.',dryrun=True): - wetrun = not dryrun - if dryrun: print 'dry run' - try: - global mwclient - import mwclient - except: - print 'please download mwclient 0.6.5 and unpack here:', os.getcwd() - sys.exit() - - if wetrun: site = wiki_login(wikiurl,api_php_path,botname,botpass) - - wikifiles = os.listdir(wikidir) - testreport_page = filter( lambda x: 'test_report' in x, wikifiles ) - if (len(testreport_page)>1): - print 'multiple test reports found, please clean dir',wikidir - sys.exit() - - rootpage = testreport_page[0] - print 'add',rootpage,' to main report page ',wiki_rootpath - if wetrun: - page = site.Pages[wiki_rootpath] - text = page.edit() - if not '[['+rootpage+']]' in text: - page.save(text +'\n*[['+rootpage+']]\n') - - wikifiles = os.listdir(wikidir) - wikifiles = filter(lambda x: not x.endswith('html'), wikifiles) - - print 'upload wiki pages:' - for wikiname in wikifiles: - filename = os.path.join(wikidir,wikiname) - filedata = tryread(filename) - print 'upload',len(filedata),'bytes from',wikiname - if wetrun and len(filedata)>0: - wiki_upload(wikiurl,api_php_path,botname,botpass,filedata,wikiname) - if len(filedata)==0: - print 'cancelling empty upload' def findlogfile(builddir): - logpath = os.path.join(builddir,'Testing','Temporary') - logfilename = os.path.join(logpath,'LastTest.log.tmp') - if not os.path.isfile(logfilename): - logfilename = os.path.join(logpath,'LastTest.log') - if not os.path.isfile(logfilename): - print 'cant find and/or open logfile',logfilename - sys.exit() - return logpath, logfilename + logpath = os.path.join(builddir, 'Testing', 'Temporary') + logfilename = os.path.join(logpath, 'LastTest.log.tmp') + if not os.path.isfile(logfilename): + logfilename = os.path.join(logpath, 'LastTest.log') + if not os.path.isfile(logfilename): + print 'can\'t find and/or open logfile', logfilename + sys.exit() + return logfilename + +# --- Templating --- + +class Templates(object): + html_template = ''' + Test run for {sysid} + {style} + + +

    {project_name} test run report

    +

    + Sysid: {sysid} +

    +

    + Result summary: {numpassed} / {numtests} tests passed ({percent}%) +

    + +

    System info

    +
    {sysinfo}
    + +

    start time: {startdate}

    +

    end time: {enddate}

    + +

    Image tests

    + {image_tests} + +

    Text tests

    + {text_tests} + +

    build.make and flags.make

    + {makefiles} + ''' + + style = ''' + ''' + + image_template = ''' + + + + + +
    {test_name}
    Expected image Actual image
    {expected} {actual}
    + +
    +    {test_log}
    +    
    + ''' + + text_template = ''' + {test_name} + +
    +    {test_log}
    +    
    + ''' + + makefile_template = ''' +

    {name}

    +
    +        {text}
    +    
    + ''' + + def __init__(self, **defaults): + self.filled = {} + self.defaults = defaults + + def fill(self, template, *args, **kwargs): + kwds = self.defaults.copy() + kwds.update(kwargs) + return getattr(self, template).format(*args, **kwds) + + def add(self, template, var, *args, **kwargs): + self.filled[var] = self.filled.get(var, '') + self.fill(template, *args, **kwargs) + return self.filled[var] + + def get(self, var): + return self.filled.get(var, '') + + +def to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles): + passed_tests = [test for test in tests if test.passed] + failed_tests = [test for test in tests if not test.passed] + + report_tests = failed_tests + if include_passed: + report_tests = tests + + percent = '%.0f' % (100.0 * len(passed_tests) / len(tests)) if tests else 'n/a' + + templates = Templates() + for test in report_tests: + if test.type in ('txt', 'ast', 'csg', 'term', 'echo'): + templates.add('text_template', 'text_tests', + test_name=test.fullname, + test_log=test.fulltestlog) + elif test.type == 'png': + actual_img = png_encode64(test.actualfile, + data=vars(test).get('actualfile_data')) + expected_img = png_encode64(test.expectedfile, + data=vars(test).get('expectedfile_data')) + templates.add('image_template', 'image_tests', + test_name=test.fullname, + test_log=test.fulltestlog, + actual=actual_img, + expected=expected_img) + else: + raise TypeError('Unknown test type %r' % test.type) + + for mf in sorted(makefiles.keys()): + mfname = mf.strip().lstrip(os.path.sep) + text = open(os.path.join(builddir, mfname)).read() + templates.add('makefile_template', 'makefiles', name=mfname, text=text) + + text_tests = templates.get('text_tests') + image_tests = templates.get('image_tests') + makefiles_str = templates.get('makefiles') + + return templates.fill('html_template', style=Templates.style, + sysid=sysid, sysinfo=sysinfo, + startdate=startdate, enddate=enddate, + project_name=project_name, + numtests=len(tests), + numpassed=len(passed_tests), + percent=percent, image_tests=image_tests, + text_tests=text_tests, makefiles=makefiles_str) + +# --- End Templating --- + +# --- Web Upload --- + +def postify(data): + return urlencode(data).encode() + +def create_page(): + data = { + 'action': 'create', + 'type': 'html' + } + try: + response = urlopen('http://www.dinkypage.com', data=postify(data)) + except: + return None + return response.geturl() + +def upload_html(page_url, title, html): + data = { + 'mode': 'editor', + 'title': title, + 'html': html, + 'ajax': '1' + } + try: + response = urlopen(page_url, data=postify(data)) + except: + return False + return 'success' in response.read().decode() + +# --- End Web Upload --- def debug(x): - if debug_test_pp: print 'test_pretty_print: '+x + if debug_test_pp: + print 'test_pretty_print: ' + x -debug_test_pp=False -builddir=os.getcwd() +debug_test_pp = False +include_passed = False +builddir = os.getcwd() def main(): - #wikisite = 'cakebaby.referata.com' - #wiki_api_path = '' - global wikisite, wiki_api_path, wiki_rootpath, builddir, debug_test_pp - global maxretry, dry, include_passed + global builddir, debug_test_pp + global maxretry, dry, include_passed + project_name = 'OpenSCAD' + + if bool(os.getenv("TEST_GENERATE")): + sys.exit(0) + + # --- Command Line Parsing --- + + if '--debug' in ' '.join(sys.argv): + debug_test_pp = True + maxretry = 10 - wikisite = 'cakebaby.wikia.com' - wiki_api_path = '/' - wiki_rootpath = 'OpenSCAD' - if '--debug' in string.join(sys.argv): debug_test_pp=True - maxretry = 10 + if '--include-passed' in sys.argv: + include_passed = True - if bool(os.getenv("TEST_GENERATE")): sys.exit(0) + dry = False + debug('running test_pretty_print') + if '--dryrun' in sys.argv: + dry = True - include_passed = False - if '--include-passed' in sys.argv: include_passed = True + suffix = ezsearch('--suffix=(.*?) ', ' '.join(sys.argv) + ' ') + builddir = ezsearch('--builddir=(.*?) ', ' '.join(sys.argv) + ' ') + if not builddir: + builddir = os.getcwd() + debug('build dir set to ' + builddir) - dry = False - debug( 'running test_pretty_print' ) - if '--dryrun' in sys.argv: dry=True - suffix = ezsearch('--suffix=(.*?) ',string.join(sys.argv)+' ') - builddir = ezsearch('--builddir=(.*?) ',string.join(sys.argv)+' ') - if builddir=='': builddir=os.getcwd() - debug( 'build dir set to ' + builddir ) + upload = False + if '--upload' in sys.argv: + upload = True + debug('will upload test report') - sysinfo, sysid = read_sysinfo(os.path.join(builddir,'sysinfo.txt')) - makefiles = load_makefiles(builddir) - logpath, logfilename = findlogfile(builddir) - testlog = tryread(logfilename) - startdate, tests, enddate = parselog(testlog) - if debug_test_pp: - print 'found sysinfo.txt,', - print 'found', len(makefiles),'makefiles,', - print 'found', len(tests),'test results' + # Workaround for old cmake's not being able to pass parameters + # to CTEST_CUSTOM_POST_TEST + if bool(os.getenv("OPENSCAD_UPLOAD_TESTS")): + upload = True + + # --- End Command Line Parsing --- - imgs, txtpages = towiki(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles) + sysinfo, sysid = read_sysinfo(os.path.join(builddir, 'sysinfo.txt')) + makefiles = load_makefiles(builddir) + logfilename = findlogfile(builddir) + testlog = tryread(logfilename) + startdate, tests, enddate = parselog(testlog) + if debug_test_pp: + print 'found sysinfo.txt,', + print 'found', len(makefiles),'makefiles,', + print 'found', len(tests),'test results' - wikidir = os.path.join(logpath,sysid+'_report') - debug( 'erasing files in ' + wikidir ) - try: map(lambda x:os.remove(os.path.join(wikidir,x)), os.listdir(wikidir)) - except: pass - debug( 'output dir:\n' + wikidir.replace(os.getcwd(),'') ) - debug( 'writing ' + str(len(imgs)) + ' images' ) - debug( 'writing ' + str(len(txtpages)-1) + ' text pages' ) - debug( 'writing index.html ' ) - if '--wiki' in string.join(sys.argv): - print "wiki output is deprecated" - for pgname in sorted(imgs): trysave( os.path.join(wikidir,pgname), imgs[pgname]) - for pgname in sorted(txtpages): trysave( os.path.join(wikidir,pgname), txtpages[pgname]) - htmldata = tohtml(wiki_rootpath, startdate, tests, enddate, sysinfo, sysid, makefiles) - html_basename = sysid+'_report.html' - html_filename = os.path.join(builddir,'Testing','Temporary',html_basename) - debug('saving ' +html_filename + ' ' + str(len(htmldata)) + ' bytes') - trysave( html_filename, htmldata ) - print "report saved:", html_filename.replace(os.getcwd()+os.path.sep,'') + html = to_html(project_name, startdate, tests, enddate, sysinfo, sysid, makefiles) + html_basename = sysid + '_report.html' + html_filename = os.path.join(builddir, 'Testing', 'Temporary', html_basename) + debug('saving ' + html_filename + ' ' + str(len(html)) + ' bytes') + trysave(html_filename, html) + print "report saved:\n", html_filename.replace(os.getcwd()+os.path.sep,'') - if '--wiki-upload' in sys.argv: - print "wiki upload is deprecated." - upload(wikisite,wiki_api_path,wiki_rootpath,sysid,'openscadbot', - 'tobdacsnepo',wikidir,dryrun=dry) - print 'upload attempt complete' + if upload: + page_url = create_page() + if upload_html(page_url, title='OpenSCAD test results', html=html): + share_url = page_url.partition('?')[0] + print 'html report uploaded at', share_url + else: + print 'could not upload html report' - debug( 'test_pretty_print complete' ) + debug('test_pretty_print complete') if __name__=='__main__': - main() + main()