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.
- Installed virtualbox extras so I could use shared directory and have binary cut & paste.
- sudo apt install synaptic
- sudo apt-get install build-essential libgl1-mesa-dev
- sudo apt-get install exuberant-ctags
- sudo apt-get install qt5-default qttools5-dev-tools qtbase5-dev
- 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.
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.
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 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.