CopperSpice Experiments – Pt. 5

Sometimes trying to avoid creating a Debian package is more work than actually creating the package.

Before I got deep into coding modifications of Diamond I wanted to get a GUI GDB front end. Yes, I can use Emacs GDB interface and have, but I quasi-suck at the command line. Yes, I would get better over time, but I had used Gede before and it works rather nice. Besides, I’m currently helping on the family farm with crop spraying and getting interrupted every 45 minutes or so to drag the support take out to the sprayer, then drag it back and fill it. Not a situation for deep ponderous thought.

Building Gede

It’s times like these when you make really stupid simple sounding decisions. I didn’t want Qt 5.x development on this machine. Simple solution, on my primary machine create a VM with Ubuntu 20.04 LTS and all updates. This thing could spin itself up while I was out of the office. I just needed to respond to apply updates and a few other things. So far so good.

  1. Installed virtualbox extras so I could use shared directory and have binary cut & paste.
  2. sudo apt install synaptic
  3. sudo apt-get install build-essential libgl1-mesa-dev
  4. sudo apt-get install exuberant-ctags
  5. sudo apt-get install qt5-default qttools5-dev-tools qtbase5-dev
  6. sudo update-alternatives –install /usr/bin/python python /usr/bin/python3 20

That last step is because there is no “default” python set up in Ubuntu. Python3 is installed, but not established as one of the “alternatives” for the python command. You can’t build Gede without python.

Since I copied the directory across from my KDE Neon primary system via the shared drive, I did the following:

make clean
make

Installing Gede

I jumped through the hoops of copying back to shared drive, then thumb drive, and finally from thumb to the CopperSpice development system. Then I found out the creator of the build didn’t have “just a copy step” for the install.

Added a quick hack using Jed editor in a terminal. Copied the elif for “install” to one for “–install” and turned off the build flag.

Yeah, I am “da man!” or so I thought; especially since I hate python.

*^)(*&)(*& should have took the time to make a .deb package on the other system. Both of these are systems are running Ubuntu 20.04 LTS though. I quick run through applying updates that had no impact what-so-ever. Adding a big red herring to the issue is that 5.12 is what is in the repos. There is no 5.14 in the repos. Adding insult to injury libQt5Core.so.5 is exactly where it says it can’t find it. I should have included a few more lines of the error because later in the ldd listing it actually found the library and there was a good (non-zero) hex number behind it.

One by one I start installing Qt stuff from the above list. I skipped the build-essentials line (because it is already here) but one by one I added the packages. Exact same error message every time. I found a pair of .sh files in the tree after some searching that weren’t flagged as executable. Once I looked at them I realized they were another Red Herring because they were only to run tests.

This should have worked.

Back to the other machine I go. I create a minimal install of Ubuntu 20.04 LTS with all updates applied in a VM. I then clone the VM just in case I trash the test bed. After quite a bit of trail and error (I’m not great with Python and generally don’t like script languages) I modified build.py to be as follows:

#!/usr/bin/env python
#
# Written by Johan Henriksson. Copyright (C) 2014-2017.
#
import sys
import os
import subprocess
import tempfile
import shutil
import platform

FORCE_QT4=1
FORCE_QT5=2
AUTODETECT=3

g_dest_path = "/usr/local"
g_verbose = False
g_exeName = "gede"
g_qtVersionToUse = AUTODETECT
g_qmakeQt4 = ""
g_qmakeQt5 = ""
g_debVersion = "1.0"
g_baseDir = os.getcwd()

print("g_baseDir: %s" % (g_baseDir))
        
# Run the make command
def run_make(a_list):
    if g_verbose:
        errcode = subprocess.call(['make'] + a_list)
    else:
        p = subprocess.Popen(['make'] + a_list, stdout=subprocess.PIPE)
        out, err = p.communicate()
        errcode = p.returncode
        if err:
            print(err)
    return errcode

# Remove a file
def removeFile(filename):
    try:
        os.remove(filename)
    except OSError:
        pass

# Do a cleanup
def doClean():
    for p in ["./src",
            "./tests/tagtest",
            "./tests/highlightertest",
            "./tests/ini"
            ]:
        print("Cleaning up in %s" % (p))
        oldP = os.getcwd()
        os.chdir(p)
        if os.path.exists("Makefile"):
            if run_make(["clean"]):
                exit(1)
        else:
            os.system("rm -f *.o")
        removeFile(g_exeName)
        removeFile("Makefile")
        removeFile(".qmake.stash")
        os.chdir(oldP)

    
# Show usage
def dump_usage():
    print("./build.py [OPTIONS]... COMMAND")
    print("where COMMAND is one of:")
    print("      install         Installs the program")
    print("      install-nomake  Installs the program")
    print("      clean           Cleans the source directory")
    print("      deb             Build and create .deb")
    print("where OPTIONS are:")
    print("      --prefix=DESTDIR  The path to install to (default is %s)." % (g_dest_path))
    print("      --verbose         Verbose output.")
    print("      --use-qt4         Use qt4")
    print("      --use-qt5         Use qt5")
    print("      --version=n.x.s   Assign version to .deb")
    print("")
    return 1


def exeExist(name):
    pathEnv = os.environ["PATH"]
    for path in pathEnv.split(":"):
        if os.path.isfile(path + "/" + name):
            return path
    return ""

def printRed(textString):
    """ Print in red text """
    CSI="\x1B["
    print(CSI+"1;31;40m" + textString + CSI + "0m")
        
def ensureExist(name):
    """ Checks if an executable exist in the PATH. """
    sys.stdout.write("Checking for " + name + "... "),
    foundPath = exeExist(name)
    if foundPath:
        print(" found in " + foundPath)
    else:
        printRed(" not found!!")

def detectQt():
    """ @brief Detects the Qt version installed in the system.
        @return The name of the qmake executable. 
    """
    global g_qmakeQt4
    global g_qmakeQt5
    sys.stdout.write("Detecting Qt version... "),
    qtVerList = []
    if exeExist("qmake-qt4"):
        qtVerList += ["Qt4"]
        g_qmakeQt4 = "qmake-qt4";
    if exeExist("qmake-qt5"):
        qtVerList += ["Qt5"]
        g_qmakeQt5 = "qmake-qt5";
    if exeExist("qmake"):
        qtVerList += ["Qt?"]
        if not g_qmakeQt5:
            g_qmakeQt5 = "qmake";
        else:
            g_qmakeQt4 = "qmake";
    sys.stdout.write(", ".join(qtVerList) + "\n")
    if (not g_qmakeQt4) and (not g_qmakeQt5):
        print("No Qt found");

    # Which version to use?
    qmakeName = "qmake"
    if g_qtVersionToUse == FORCE_QT4 and g_qmakeQt4:
        qmakeName = g_qmakeQt4;
    elif g_qmakeQt5:
        qmakeName = g_qmakeQt5;
    elif g_qmakeQt4:
        qmakeName = g_qmakeQt4;
    print("Using '" + qmakeName + "'")

    return qmakeName;

def is_os_64bit():
    return platform.machine().endswith('64')

def dependencies_installed():
    if (shutil.which("fakeroot") != "None" and shutil.which("gzip") != "None"
        and shutil.which("md5sum") != None and shutil.which("dpkg-deb") != "None"):
        return True
    else:
        print("Requires: fakeroot  gzip  md5sum  dpkg-deb be installed")
        return False
       

# Main entry
if __name__ == "__main__":
    try:
        do_clean = False
        do_install = False
        do_build = True
        do_buildAll = False
        do_debian = False
        
        for arg in sys.argv[1:]:
            if arg == "clean":
                do_build = False
                do_clean = True
                do_debian = False
            elif arg == "install":
                do_install = True
                do_build = True
                do_debian = False
            elif arg == "install-nomake":
                do_install = True
                do_build = False
                do_debian = False
            elif arg == "deb":
                do_clean = True
                do_install = False
                do_build = True
                do_debian = True
            elif arg == "--help" or arg == "help":
                exit( dump_usage())
            elif arg == "--verbose":
                g_verbose = True
            elif arg == "--buildall":
                do_buildAll = True
            elif arg.find("--prefix=") == 0:
                g_dest_path = arg[9:]
            elif arg == "--use-qt4":
                g_qtVersionToUse = FORCE_QT4
            elif arg == "--use-qt5":
                g_qtVersionToUse = FORCE_QT5
            elif arg.find("--version=") == 0:
                g_debVersion = arg[10:]
            else:
                exit(dump_usage())

        if do_clean:
            print("Cleaning")
            doClean();
        if do_build:
            print("building")
            ensureExist("make")
            ensureExist("gcc")
            ensureExist("ctags")
            sys.stdout.flush()
            
            olddir = os.getcwd()
            if do_buildAll:
                srcDirList = ["src", "tests/tagtest", "tests/highlightertest"]
            else:
                srcDirList = ["src"]
            for srcdir in srcDirList:
                os.chdir(srcdir)
                if not os.path.exists("Makefile"):
                    qmakeName = detectQt();
                    print("Generating makefile")
                    if subprocess.call([qmakeName]):
                        exit(1)
                print("Compiling in " + srcdir + " (please wait)")
                if run_make(["-j4"]):
                    exit(1)
                os.chdir(olddir)
                
        if do_install:
            print("installing")
            os.chdir("src")
            print("Installing to '%s'" % (g_dest_path) )
            try:
                os.makedirs(g_dest_path + "/bin")
            except:
                pass
            if not os.path.isdir(g_dest_path + "/bin"):
                print("Failed to create dir")
                exit(1)
            try:
                shutil.copyfile("gede", g_dest_path + "/bin/gede")
                os.chmod(g_dest_path + "/bin/gede", 0o775);
            except:
                print("Failed to install files to " + g_dest_path)
                raise

            print("")
            print("Gede has been installed to " + g_dest_path + "/bin")
            print("Start it by running gede")

        # This will probably fail if run on RPM based distro
        # Build will have changed directory so need to get
        # back to base dir first.
        #
        if do_debian:
            g_tmpDir = tempfile.mkdtemp()
            print("g_tmpDir: %s"  % (g_tmpDir))
            os.chdir(g_tmpDir)
            try:
                os.makedirs("debian")
                arr = os.listdir()
                print(arr)
            except:
                pass
            if not os.path.isdir(g_tmpDir + "/debian"):
                print("Failed to create debian dir")
                exit(1)
            # if I can create the "root" I can create subdirs
            #
            try:
                os.chdir("debian")
                os.makedirs("DEBIAN")
                os.makedirs("usr/share/doc/gede")
                os.makedirs("usr/share/applications")
                os.makedirs("usr/local/bin")
            except:
                print("Failed to create debian directories")
                raise
            #
            #  desktop file
            #
            try:
                os.chdir("usr/share/applications")
                desktop_file = open("gede.desktop", "w")
                desktop_file.write("[Desktop Entry]\n")
                desktop_file.write("Name=Gede\n")
                desktop_file.write("GenericName=Gede GDB frontend\n")
                desktop_file.write("Comment=GUI GDB interface\n")
                desktop_file.write("Path=/usr/local/bin\n")
                desktop_file.write("Exec=gede\n")
                desktop_file.write("Termanal=false\n")
                desktop_file.write("Type=Application\n")
                desktop_file.write("StartupNotify=false\n")
                desktop_file.write("MimeType=text/plain;\n")
                desktop_file.write("NoDisplay=false\n")
                desktop_file.close()
            except:
                print("Failed to create desktop file")
                raise

            #
            #  Required DEBIAN package files
            #
            try:
                os.chdir(g_tmpDir)
                os.chdir("debian/DEBIAN")
                control_file = open("control", "w")
                control_file.write("Package: gede\n")
                control_file.write("Priority: optional\n")
                control_file.write("Section: Development\n")
                control_file.write("Maintainer: Johan H. johan@acidron.com\n")
                control_file.write("Installed-Size: 1500\n")
                if is_os_64bit():
                    control_file.write("Architecture: amd64\n")
                else:
                    control_file.write("Architecture: i386\n")

                control_file.write("Version: %s\n" % (g_debVersion))
                qtVersion = detectQt()
                control_file.write("Depends: exuberant-ctags, gdb, ")
                if qtVersion == g_qmakeQt5:
                    control_file.write("qtbase5-dev\n")
                else:
                    control_file.write("libqt4-dev\n")

                control_file.write("""Description: Gede is a graphical frontend (GUI) to GDB written in C++ and using the Qt5 or Qt4 toolkit.
 Gede can be compiled on (all?) Linux distributions and on FreeBSD.
 Gede supports debugging programs written in Ada, FreeBasic, C++, C, Rust, Fortran and Go.
 The Gede sourcecode is licensed under the terms of the BSD license. The icons used are from the NetBeans project and licensed under the terms of the NetBeans License Agreement \n """)
                control_file.close()
                
            except:
                print("Failed to create required DEBIAN package files")
                raise

            try:
                os.chdir(g_baseDir)
                os.chdir("src")

                shutil.copyfile("gede", g_tmpDir + "/debian/usr/local/bin/gede")
                os.chmod(g_tmpDir + "/debian/usr/local/bin/gede", 0o775);
                shutil.copyfile("changelog", g_tmpDir + "/debian/usr/share/doc/gede/changelog.Debian")
                shutil.copyfile("../LICENSE", g_tmpDir + "/debian/usr/share/doc/gede/LICENSE")
                #
                # make the sacred zip
                #
                os.chdir(g_tmpDir)
                os.system("gzip --best debian/usr/share/doc/gede/changelog*")
                os.system("md5sum debian/usr/share/doc/gede/* 2>/dev/null >debian/DEBIAN/md5sums")
                #
                # may need to chmod go-w on everything now
                #
                os.system("fakeroot dpkg-deb -Zgzip --build debian")
                shutil.copyfile("debian.deb", g_baseDir + "/gede_" + g_debVersion + ".deb")
                os.chdir(g_baseDir)
                shutil.rmtree(g_tmpDir)
            except:
                print("Failed to create debian")
                raise
            

    except IOError as e:
        print("I/O error({0}): {1}".format(e.errno, e.strerror))
    except SystemExit as e:
        pass
        raise e
    except:
        print("Error occured")
        raise



The Package

This requires a changelog file be placed in the src directory. I put one line in mine: Empty changelog file. Yes, I sent this stuff to the maintainer.

which version logic doesn’t work

The existing logic to determine which version of Qt doesn’t work in a qtchooser world. I didn’t bother fixing it, just made sure it defaulted to Qt5. In a good and just world I would have written something to parse the output of ldd and built my dependency list from that.

ldd output

In a just and perfect world I wouldn’t have used the -dev files for Qt4 and Qt5. In that world I would have only installed the packages containing the needed libraries. Yes, I took the short route. If you are going to use this to debug GnuCOBOL (which translates to C before compiling) you only need enough Qt stuff to run this.

After install on minimal machine

After you sudo dpkg -i gede_whatever.deb you need to sudo apt install -f to install the dependencies. As you can see I was able to launch the debugger front end immediately after the installation on the minimal system.

# Show usage
def dump_usage():
    print("./build.py [OPTIONS]... COMMAND")
    print("where COMMAND is one of:")
    print("      install         Installs the program")
    print("      install-nomake  Installs the program")
    print("      clean           Cleans the source directory")
    print("      deb             Build and create .deb")
    print("where OPTIONS are:")
    print("      --prefix=DESTDIR  The path to install to (default is %s)." % (g_dest_path))
    print("      --verbose         Verbose output.")
    print("      --use-qt4         Use qt4")
    print("      --use-qt5         Use qt5")
    print("      --version=n.x.s   Assign version to .deb")
    print("")
    return 1

When you want to generate a .deb you assign a version to it with the –version=something option. Please make certain this is text without any spaces or characters that are not allowed in a file name.

I’m not good with cmake either, but as part of my experiments with CopperSpice and modifying of Diamond editor to have some features I want, I plan on making the build process allow for a Debian build rather than just an install build.

<Previous-part Next-part>