Installing Harbour, Qt and hbQt on osx

Now that we have a working osx Harbour setup, it is time to add other pieces. My goal is to port a software that runs on Windows under hbQt to Mac (and linux).

So, as the first step, I install the brew package manager, that will handle the setup of external software for me.

To install brew it is necessary to run a script that is downloaded from the web. I report it here but PLEASE do check on its website (http://brew.sh/) it if it is still valid when you will work on it.

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

The script will ask for confirmation and for sudo password.

When ready, we can install Qt5.

brew install qt5
brew linkapps qt5

As for today, it will install Qt 5.6.1. With the second line Qt5 bundled applications are installed in Launchpad.

It’s time to download and compile hbQt, but before I want to update my env setup and I do as usual with a script file set_harbour561.sh:

PATH=/usr/local/opt/qt5/bin/:~/harbour/bin/darwin/clang:$PATH
export QT_PLUGIN_PATH=/usr/local/opt/qt5/plugins/
export HB_WITH_QT=/usr/local/opt/qt5/include/
## rem HB_QT_MAJOR_VER can probably become deprecated in next
## rem Harbour/qtcontribs releases.
export HB_QT_MAJOR_VER=5

and I update the env variables with
. set_harbour561.sh

and then I checkout the sources

cd /harbour
svn checkout http://svn.code.sf.net/p/qtcontribs/code/trunk addons

Before compiling hbQt we need to compile a contrib of Harbour, since it depends on Qt and some hbQt programs depend on it. So compile the contribs:

cd <path>/harbour/contribs
make

Then move to the addons directory:

cd <path>/harbour/addons

We need to modify qtcontribs.hbp, since some Qt modules are not available on osx. So add a # in front of the line of:
hbqtdeclarative.hbp
and
hbqt/qtwebkit/hbqtwebkit.hbp

and finally build hbQt:

hbmk2 -jobs=4 qtcontribs.hbp

We are now ready to start hbide and hbdbu programs but you need to access osx gui to be able to use them. Both work as they should but during my tests I found some discrepancies about fonts, layout and GUI: Qt does its best to adapt one codebase to the GUI peculiarities of each GUI but sometimes the result is not perfect and one form dialog that is perfect on windows can have problems on osx.

Will see in another post.

Installing Harbour on osx

In this blog post I will describe how to install basic Harbour on mac osx. It is a really basic installation but it is also fully functional. In the next blog post I will describe how to install Qt and hbQt to create software that can run on Windows, linux and osx.

From your osx gui, install XCode from the App Store. I then used ssh to connect from another server or you can open a terminal window on osx.

First of all we need to accept the XCode license, and we can do this just by invoking one executable.

sudo git

Follow the on-screen instructions to confirm the license.

As usual, I will run Harbour from a directory and I won’t install it system-wide: with this setup I can have multiple different versions available. I will clone the repository with git in order to receive upstream commits.

git clone https://github.com/harbour/core.git harbour
Cloning into 'harbour'...
remote: Counting objects: 275833, done.
remote: Compressing objects: 100% (76/76), done.
remote: Total 275833 (delta 13), reused 3 (delta 3), pack-reused 275754
Receiving objects: 100% (275833/275833), 116.30 MiB | 297.00 KiB/s, done.
Resolving deltas: 100% (216982/216982), done.
Checking connectivity... done.

cd harbour
make -j 4

On my mac mini end-2015 it took less than 2 minutes to complete. I then run

bin/darwin/clang/hbtest

Harbour Regression Test Suite
Copyright (c) 1999-2016, Viktor Szakats

http://harbour-project.org/

---------------------------------------------------------------------------
      Version: Harbour 3.2.0dev (r1606292102)
     Compiler: LLVM/Clang C 7.3 (clang-703.0.31) (64-bit)
           OS: Darwin 15.5.0 x86_64
   Date, Time: 2016-07-01 17:25:22
Shortcut opt.: On
     Switches:
===========================================================================
R No.  Line            TestCall()                               -> Result                                                                                | Expected
---------------------------------------------------------------------------
!  613 MAIN_HVMA(368)  saArray[ 1000 ]
       Result: "E 2 BASE 1132 Bound error (array access) OS:0 #:0 A:2:A:{.[1].};N:1000 "
     Expected: "E 2 BASE 1132 Bound error (array access) OS:0 #:0 "
!  614 MAIN_HVMA(369)  saArray[ 1000 ] := 1
       Result: "E 2 BASE 1133 Bound error (array assign) OS:0 #:0 A:1:N:1000 "
     Expected: "E 2 BASE 1133 Bound error (array assign) OS:0 #:0 "
!  743 MAIN_HVMA(541)  RTSTR( 00000500000000000000 )
       Result: " 16  500000000000000"
     Expected: " 21       500000000000000"
!  744 MAIN_HVMA(542)  RTSTR( 0500000000000000 )
       Result: " 16  500000000000000"
     Expected: " 17   500000000000000"
! 1088 MAIN_MATH(354)  Str( 1234567890 * 1234567890 )
       Result: " 1524157875019052100"
     Expected: " 1524157875019052000"
! 2894 MAIN_ARRAY(158) ASize( NIL )
       Result: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 A:1:U:NIL "
     Expected: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 "
! 2895 MAIN_ARRAY(159) ASize( {} )
       Result: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 A:1:A:{.[0].} "
     Expected: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 "
! 2896 MAIN_ARRAY(160) ASize( ErrorNew() )
       Result: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 A:1:O:ERROR Object "
     Expected: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 "
! 2897 MAIN_ARRAY(169) ASize( NIL, 0 )
       Result: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 A:2:U:NIL;N:0 "
     Expected: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 "
! 2898 MAIN_ARRAY(170) ASize( NIL, 1 )
       Result: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 A:2:U:NIL;N:1 "
     Expected: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 "
! 2899 MAIN_ARRAY(171) ASize( NIL, -1 )
       Result: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 A:2:U:NIL;N:-1 "
     Expected: "E 1 BASE 2023 Argument error (ASIZE) OS:0 #:0 "
! 2922 MAIN_ARRAY(213) Array( 1, 0, -10 )
       Result: "E 2 BASE 1131 Bound error (array dimension) OS:0 #:0 A:3:N:1;N:0;N:-10 "
     Expected: "E 2 BASE 1131 Bound error (array dimension) OS:0 #:0 "
! 3061 MAIN_ARRAY(396) TAEVSM()
       Result: "N10N 9N 8N 7N 6         5"
     Expected: "N10N 9N 8N 7N 6N 5N 4N 3N 2N 1         0"
! 3062 MAIN_ARRAY(397) TASOSM1()
       Result: "NN 5NN 4         3{ 2, 1, 3 }"
     Expected: "NN 5NN 4NN 3NN 2NN 1NN 0NN 0NN 0NN 0NN 0NN 0NN 0         0{  }"
! 3063 MAIN_ARRAY(398) TASOSM2()
       Result: "NN 5NN 4         3{ 2, 1, 3 }"
     Expected: "NN 5NN 4NN 3NN 2NN 1NN 0NN 0NN 0NN 0NN 0         0{  }"
! 3183 MAIN_MISC(87)   Set( 40  )
       Result: 0
     Expected: NIL
! 3192 MAIN_MISC(97)   Set( 40 , -1 )
       Result: 0
     Expected: NIL
===========================================================================
Test calls passed:       4757 ( 99.64 % )
Test calls failed:         17 ( 0.36 % )
                   ----------
            Total:       4774 ( Time elapsed: 0.02 seconds )

WARNING ! Failures detected

to test that everything is fine. And of course it is. Otherwise it won’t complete Harbour build.

Now to complete the installation, I create a script in the user root directory that sets the enviromental variables to the path where I compiled Harbour.


PATH=$PATH:~/harbour/bin/darwin/clang

And now I can login, set the environment and use harbour as needed:

. set_harbour.sh
harbour -build
Harbour 3.2.0dev (r1606292102)
Copyright (c) 1999-2016, http://harbour-project.org/

Harbour Build Info
---------------------------
Version: Harbour 3.2.0dev (r1606292102)
Compiler: LLVM/Clang C 7.3 (clang-703.0.31) (64-bit)
Platform: Darwin 15.5.0 x86_64
PCode version: 0.3
ChangeLog last entry: 2016-06-29 23:02 UTC+0200 Przemyslaw Czerpak (druzus/at/poczta.onet.pl)
ChangeLog ID: f8ec6e2c46068d3f1759c4d75a8f982fc391fb9e
Built on: Jul  1 2016 17:21:51
Build options: (Clipper 5.3b) (Clipper 5.x undoc)
---------------------------

It’s time to cleanup code…

Now that the programs run as they should…. do they really run as they should?

Well, not fully.

I’ve still some screen functions that have no code and a lot of functions that relate to directories that just trace their use and exit. I still have to write the code to print on PDF….

Since the accounting lady wants to have a demo of the program, it’s time to have a look at the screen related code.

When a popup windows appears on screen, sometimes it is cleared on close, sometimes it is not. Looking at the code I found that the window can “close” in different modes, and which one to use is decided by the current time… It is like if you have one SaveScreen() and 3 possible RestScreen(), closing the window with 3 different visual effects.. no problem here: from these 3 functions I call directly RestScreen()… not visual, not moving, not fancy but it works.

I usually develop in a command line interface environment. I use vim editor… To be able to analyze compile errors I use a command prompt windows that I can scroll up/down. But in this way when the program starts I have to move the scroolbar to make it visible. I need to add a SetMode( 25, 80 ) somewhere.
The first idea is to add this function call to every executable…. there are already several lines that are repeated at the start of each executable… Then I decided that it was not a smart move, to create yet another duplicate code. Since this is a generic, standalone command, I added an INIT PROCEDURE in mylib.prg. INIT PROCEDUREs are run before Harbour VM gives control to the main procedure. You may have several INIT PROCEDUREs but you can’t control the order in which they are called. No problem in this case.

I also moved the check for the ROOTDIR environment variable in this function. Now it displays a message and waits for a keypress, then exits.

Starting the batch file makes the command prompt window flicker, to reduce its dimensions. Selecting a menu item that needs to call another executable makes the windows flicker again, at first it widens when the program exits and the batch file resumes, then it shrinks when the executable starts. I will set to use a 25×80 window in the properties of the batch file, so that when the users double click on the icon, the program starts with the right dimensions.

Doing a tour of the application I got errors for a missing B_DOUBLE_S variable. As you may remember the programmer took a really strange decision: instead of using #define tokens, he created variables with the same name and assigned them a value, the one taken from the include file… So now there are several PUBLIC variables just to handle static values. When I had to decompile some code due to missing sources, that strange decision turned good: instead of literal values I got the variable names… but truncated to 10 chars.

A quick series of grep and I converted the few truncated variable names to their extended counterpart. I can’t say if I spotted all of them… Since I was touching a couple of functions, I decided to do very basic changes, switching from PRIVATE to LOCAL variables.
I don’t think that this program will ever be able to be compiled with -w3 -es2 switches but when it takes so little time…

There is another step I want to make: update part of the main menu system. I can’t update the whole system because it is a very complex one. When executables call each other the old menu is selected, as if you never exited the program. This is done with a sequence of simulated keypresses, like these one:


KEYBOARD Chr( K_ENTER ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP ) + Chr( K_UP )

It’s strange, it works, don’t touch it !!!

What I want to change is what happens next, after the menu function returns the value of the menu option selected by the user. Actually it is something like this:

IF menu > 1 .AND. menu < 8
   ONDO( menu1, NIL, 'M2()', 'M3()', 'M4()', 'M5()', 'M6()', 'M7()' )
ELSEIF menu = 13
   IF File( 'external_module.DBF' )
      ErrorLevel( 1 )
      QUIT
   ENDIF
ELSE
   DO CASE
   CASE menu = 8
      ErrorLevel( 3 )
   CASE menu = 9
      ErrorLevel( 4 )
...
   CASE menu = 17
      ErrorLevel( 17 )
   END CASE
   QUIT
ENDIF

Every time that I need to understand which source file is related to a menu, I need to trace the value of menu variable, check if it is in a range, count the onDo parameters or look at the DO CASE.

So I’d like to streamline this code, removing the IFs, the CASEs and make it self documented. For example, something like this:

ONDO( menu, ;
  myExit(1), ;  // 1 - daily load
 'M2()', ;      // 2 - print totals
 'M3()', ;      // 3 - create new client
 'M4()', 'M5()', 'M6()', 'M7()', ;
  myExit(3), ;  // 8 - Banks - runs exe05
  myExit(4),.., myExit13(), ...,myExit(17))

And of course adding the two functions:

PROCEDURE myExit13()
   IF File( 'external_module.DBF' )
      myExit( 1 )
   ENDIF   
   RETURN

PROCEDURE myExit( nValue )
   ErrorLevel( nValue )
   QUIT
   RETURN  // never executed :-)

Now the various executables run fine, and the screen is always clean as it should be.

In the meantime I exchanged several emails with the accounting lady. She told me which menu options she has never used and will never use again. There are many, and I will save some time. I could have asked this question before…
But unfortunately she also told me that several menu options I disabled are actually used, only for specific cases, but used… so I enabled all of them. One of the features let the user choice a color combination. I had to create a couple of functions but it now works…. just to show that there is something still not good about colors.

I noticed that function CLS(), used to clear the whole screen and paint a background was called several times in the source code. Its color parameter is always a PUBLIC variable, set at startup depending on the color combination chosen by the user. Sometimes the variable was passed as is (a numeric value), sometimes was converted to a color string. It was quite strange. A grep on the decompiled code returned a PROCEDURE CLS with italian variable names: the function has been rewritten keeping the same name!

Looking at the procedure I discovered that for printing on screen it uses the original Funcky PRINT() function and as color parameter passes … the PUBLIC variable! The color parameter of CLS() is not used – converted or not, it is just discarded and the numeric value of the PUBLIC variable is used.

My implementation of Funcky PRINT() is very basic. It lacks color handling and the fifth paramenter, lenght, is wrongly implemented. Infact with this fifth parameter you can pad short strings or trim longer ones. PRINT() also handles printing long strings that spans multiple lines. In CLS() code, there is a single PRINT call that spans 2000 char, the whole 25×80 screen!
Before implementing the fifth parameter, a grep reveals that the function is called with the fifth parameter only in 2 cases: in CLS() function and at program startup, with lenght set to 80 and string to be printed set to one space: used to clear one line.
All the other calls are for cosmetic stuff and without the lenght parameter.
So I decided to change CLS() function to not use PRINT(): I split the 2000 chars string in 80 chars chunks and printed them, one each line.
I kept the “wrong” implementation of the fifth parameter and I used it to replicate() the string to be printed…

There is still to implement colors. I imported NTOCOLOR() function from hbct to convert from color number to color string. Now the screen is properly displayed, with proper colors and proper backgrounds.

I know there is still a lot to be done (printing, directory related functions, error handling) but it is definitively time to show the porting to the accounting lady. The screen doesn’t mess up, the colors and the background are the expected ones, just the fonts can’t be changed… for the moment… ;-)

Now tt’s time to book some time and show the progress to the accounting lady.

A persistent CHDIR()

I now have all the executables compiling and running. The main problem I face now is to set the working directory when calling the programs via a batch file. I think you know the problem: the application uses chdir() to set a subdirectory as the working workarea and moving from one executable to another this setting is kept. But not with Harbour equivalent function.
With my original idea of one executable I solved the problem, since once set, you should not care passing the info to other executables. If you remember I created a patch to not quit the program after selecting a workarea but to return to the menu. This is no more valid since I will not have one executable.
The old solution was also incomplete: the user can select a combination of colors the program uses for the screen display, so that the user can give each client/year a different and easily recognizable style. Each program loads the colors using RESTORE FROM command, that follows the SET PATH rules. In my case, the main program was always started from the root data directory and after the choice of the workarea the colors were not loaded again. Just a couple lines of code to add, but since we are going to have multiple executables anyway I instead prefer to revert the patch and set the errorlevel and quit – as the original code did.

I still need to pass the working dir setting between executables. I tried a couple of ways and I settled using an external file. My solution is now:

FUNCTION loadWorkarea()
   LOCAL wDir
   LOCAL cFileName := getE( "ROOTDIR" ) + "workarea.txt"
   IF ! file( cFileName )
      RETURN .F.
   ENDIF
   wDir := MemoRead( cFileName )
   IF ! empty( wDir )
// TODO: need to check if files exist in that directory
      CHDIR( wDir )
      RETURN .T.
   ENDIF
   RETURN .F.

   RETURN MemoRead( cFileName )

FUNCTION saveWorkarea( cText )
   LOCAL cFileName := getE( "ROOTDIR" ) + "workarea.txt"
   RETURN MemoWrit( cFileName, cText )

To make it work I added loadWorkarea() as the first line of the executables… it works! I will add a deleteWorkarea to be called when exiting the main menu or from the error handler so that the next time the program is run there is no selection active.