06 Jul 2026

TMSA Installation and Build Troubleshooting Report

1. Executive Summary

On a Xubuntu 26.04 LTS system, the Time Matters / TMSA application was installed from source at:

/home/christian/Homemade/tmsa

The application was successfully launched from source using a project-local Python virtual environment:

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
python -X faulthandler -m src.tmsa

The GUI opened successfully under X11 and exited cleanly with code 0.

The Linux installer was then built successfully after two targeted fixes to setup.py. The final build produced both a Makeself installer and a distribution zip:

build/exe.linux-x86_64-3.10/TimeMatters-0.7.1-linux-x86_64.sh
dist/TimeMatters-0.7.1-linux-x86_64.zip

The generated installer was tested in a clean disposable directory:

/home/christian/Homemade/tmsa-installer-test

The installer extracted successfully, created the run-tmsa -> ./tmsa symlink, and the packaged application launched successfully from:

/home/christian/Homemade/tmsa-installer-test/exe.linux-x86_64-3.10

The packaged application exited cleanly with code 0.

The main technical issue was not a missing dependency or broken system Python. The primary blocker was a Linux build-script ordering problem in setup.py: the script attempted to locate the cx_Freeze build output directory before cx_Freeze had created it. A second related failure occurred because another Linux packaging block still referenced a global build_dir value that remained None after the first fix. Both were resolved by resolving the Linux build directory lazily after build_exe completed.

A separate early environment issue occurred when an install command was run under sh=/=dash, where source is not available. That caused the first dependency installation attempt to target the pyenv base Python instead of the project virtual environment. The project .venv was recreated, activated correctly, and dependencies were installed cleanly.

2. Environment and Affected Components

Item Value
Operating system Ubuntu/Xubuntu 26.04 LTS, codename resolute
Kernel 7.0.0-27-generic
Shell /bin/bash
System Python Python 3.14.4
System pip pip 25.1.1 from /usr/lib/python3/dist-packages/pip
Git 2.53.0
pyenv 2.6.8
pyenv root /home/christian/.pyenv
Selected project Python Python 3.10.18
Project venv /home/christian/Homemade/tmsa/.venv
Project path /home/christian/Homemade/tmsa
Repository commit 0cf3637
TMSA version built 0.7.1
GUI toolkit Tkinter / Tk 8.6
Installer tool Makeself 2.5.0
Build tool cx_Freeze 7.0.0
Build output directory build/exe.linux-x86_64-3.10
Runtime data directory ~/.tmsa

3. Symptoms

3.1. Initial environment risk

The system default Python was too new for the TMSA Linux build path:

/usr/bin/python3
Python 3.14.4
/usr/bin/pip3
pip 25.1.1 from /usr/lib/python3/dist-packages/pip (python 3.14)

The project README's Linux workflow expects Python 3.10 through pyenv, so using the system Python would have been risky and was avoided.

3.2. Virtual environment activation failure

During the first dependency installation attempt, activation failed:

install-requirements.sh: 17: source: not found

The subsequent which python and which pip output showed that the shell was using the pyenv base Python, not the project virtual environment:

/home/christian/.pyenv/shims/python
Python 3.10.18
/home/christian/.pyenv/shims/pip
pip 26.1.2 from /home/christian/.pyenv/versions/3.10.18/lib/python3.10/site-packages/pip (python 3.10)

3.3. First installer build failure

A clean python setup.py build_installer failed immediately:

Traceback (most recent call last):
  File "/home/christian/Homemade/tmsa/setup.py", line 27, in <module>
    raise RuntimeError(
RuntimeError: Could not find the cx_Freeze build output directory

No installer could be built from a clean tree with the original script.

3.4. Second installer build failure

After the first build-directory fix, the Makeself installer was generated, but the script still exited with an error:

Traceback (most recent call last):
  File "/home/christian/Homemade/tmsa/setup.py", line 307, in <module>
    if os.path.exists(os.path.join(build_dir, installer_name)):
  File "/home/christian/.pyenv/versions/3.10.18/lib/python3.10/posixpath.py", line 76, in join
    a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not NoneType

This showed that another Linux packaging block still expected the global build_dir variable to be populated.

3.5. Installer launch path confusion

The generated Makeself installer extracted successfully, but the first launch attempt failed:

./installer-test.sh: line 29: ./run-tmsa: No such file or directory

The installer had created a subdirectory named exe.linux-x86_64-3.10, so run-tmsa existed inside that directory rather than in the caller's current directory.

3.6. Non-blocking runtime warnings and UI observation

The packaged app emitted a Fontconfig warning on launch:

Fontconfig warning: "/usr/share/fontconfig/conf.avail/05-reset-dirs-sample.conf", line 6: unknown element "reset-dirs"

The application still launched and exited with code 0, so this was treated as non-blocking.

A screenshot of the source-run GUI showed that button text was slightly cramped. This was captured as a non-blocking UI rendering/layout note rather than an installation blocker.

4. Root Cause Analysis

4.1. Technical root cause: build directory resolved too early

The original setup.py attempted to locate the Linux cx_Freeze output directory at import time:

case 'linux':
    base = None
    import glob

    build_dirs = glob.glob('build/exe.linux-*')
    build_dir = build_dirs[0] if build_dirs else None
    if build_dir is None:
        raise RuntimeError(
            'Could not find the cx_Freeze build output directory'
        )

This is too early for a clean build. At the moment setup.py is imported, build/exe.linux-* does not yet exist. The directory is created later by:

self.run_command('build_exe')

Because the script required the directory before creating it, the clean build failed before build_exe could run.

4.2. Secondary technical root cause: stale global builddir assumption

After moving build-directory detection out of the import-time Linux block, the later post-setup Linux packaging code still assumed that a global build_dir variable was available:

if os.path.exists(os.path.join(build_dir, installer_name)):

Because build_dir remained None at that point, os.path.join() raised a TypeError. The script had to resolve the build directory again inside the post-setup Linux packaging block.

4.3. Contributing factor: shell mismatch during dependency installation

The first dependency installation attempt was run in a way that used sh=/=dash rather than bash. Ubuntu's dash shell does not support source, so virtual environment activation failed.

The portable POSIX-compatible activation form is:

. .venv/bin/activate

Using that form ensured dependencies were installed into:

/home/christian/Homemade/tmsa/.venv

rather than into the pyenv base environment.

4.4. Contributing factor: misleading postinstall message context

The installer's postinstall.sh says:

Unpacked successfully. Use ./run-tmsa to launch.

However, Makeself extracted into:

exe.linux-x86_64-3.10

Therefore, the correct launch sequence from the parent test directory was:

cd exe.linux-x86_64-3.10
./run-tmsa

This is a usability note, not a runtime failure. The default extraction directory was intentionally left unchanged.

4.5. 5 Whys

  1. Why did python setup.py build_installer fail on a clean tree?

    Because setup.py raised RuntimeError: Could not find the cx_Freeze build output directory before the build began.

  2. Why was the build output directory missing?

    Because build/exe.linux-* is created by the cx_Freeze build_exe command, and build_exe had not run yet.

  3. Why did the script look for the directory before build_exe ran?

    Because Linux build_dir detection was performed at module import time in the top-level platform switch.

  4. Why did the script still fail after the first patch?

    Because a later Linux packaging block still referenced the global build_dir variable, which was now intentionally left unset until after build_exe.

  5. Why was this not caught by dependency checks?

    Because the dependencies were valid. pip check reported no broken requirements. The failure was build-script control flow, not an absent Python package or missing system library.

5. Resolution and Recovery

5.1. Step 1: Use pyenv Python 3.10.18 locally

The project was pinned to the already-installed pyenv Python 3.10.18:

cd /home/christian/Homemade/tmsa
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"
eval "$(pyenv init -)"
pyenv local 3.10.18

Validation:

/home/christian/.pyenv/shims/python
Python 3.10.18
/home/christian/.pyenv/shims/pip
pip 26.1.2 from /home/christian/.pyenv/versions/3.10.18/lib/python3.10/site-packages/pip (python 3.10)

Tkinter support was confirmed:

Python: 3.10.18 (main, Jul  6 2026, 08:46:01) [GCC 15.2.0]
tkinter OK: 8.6

5.2. Step 2: Install Linux build dependencies

The following packages were confirmed installed, with cmake installed during the session:

makeself
tcl-dev
tk-dev
build-essential
python3-dev
python3-pip
cmake

5.3. Step 3: Recreate and activate the project virtual environment

The bad initial environment state was corrected by recreating the project virtual environment and activating it with POSIX-compatible syntax:

cd /home/christian/Homemade/tmsa
rm -rf .venv
python -m venv .venv
. .venv/bin/activate

Validation:

/home/christian/Homemade/tmsa/.venv/bin/python
Python 3.10.18
/home/christian/Homemade/tmsa/.venv/bin/pip
pip 23.0.1 from /home/christian/Homemade/tmsa/.venv/lib/python3.10/site-packages/pip (python 3.10)
sys.executable: /home/christian/Homemade/tmsa/.venv/bin/python
sys.prefix: /home/christian/Homemade/tmsa/.venv
sys.base_prefix: /home/christian/.pyenv/versions/3.10.18
venv active: True

5.4. Step 4: Install pinned Python requirements into .venv

Because cx_Freeze 7.0.0 requires older packaging tools, compatible versions were installed:

python -m pip install --upgrade pip
python -m pip install "setuptools>=62.6,<70" "wheel>=0.42.0,<=0.43.0"
python -m pip install -r etc/requirements-linux.txt
python -m pip check

Validation:

Successfully installed ... cx_Freeze-7.0.0 ... tkextrafont-0.6.1 ... timezonefinder-6.5.0 ...
No broken requirements found.

5.5. Step 5: Confirm runtime assets

The required runtime asset directories were present:

src/dll
src/ephe

Important files included:

src/dll/libswe.so
src/dll/swephexp.h
src/dll/sweodef.h
src/ephe/sefstars.txt
src/ephe/seasnam.txt
src/ephe/seorbel.txt

5.6. Step 6: Validate source run

The application was launched from source:

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
python -X faulthandler -m src.tmsa

Validation:

DISPLAY=:0.0
WAYLAND_DISPLAY=
/home/christian/Homemade/tmsa/.venv/bin/python
Python 3.10.18
exit code: 0

The run created or used runtime configuration under ~/.tmsa:

/home/christian/.tmsa/logs/error.txt
/home/christian/.tmsa/options/colors.json
/home/christian/.tmsa/options/Cosmobiology.opt
/home/christian/.tmsa/options/data_entry.json
/home/christian/.tmsa/options/Ingress_Default.opt
/home/christian/.tmsa/options/locations.json
/home/christian/.tmsa/options/Natal_Default.opt
/home/christian/.tmsa/options/Progressed_Default.opt
/home/christian/.tmsa/options/recent.json
/home/christian/.tmsa/options/Return_Default.opt
/home/christian/.tmsa/options/Student_Natal.opt
/home/christian/.tmsa/program_options.opt

The error log showed no recent error content.

5.7. Step 7: Patch setup.py for lazy Linux build-directory resolution

A helper was added to resolve the newest Linux cx_Freeze build directory after build_exe runs:

def resolve_linux_build_dir():
    import glob

    build_dirs = sorted(
        glob.glob(os.path.join('build', 'exe.linux-*')),
        key=os.path.getmtime,
        reverse=True,
    )
    if not build_dirs:
        raise RuntimeError('Could not find the cx_Freeze build output directory')
    return build_dirs[0]

The import-time Linux block was simplified so it no longer failed before build_exe:

case 'linux':
    base = None

The BuildInstaller.run() method was updated to resolve build_dir after build_exe:

self.run_command('build_exe')
build_dir = resolve_linux_build_dir()

The BuildAppImage.run() method was similarly updated. AppImage output was not tested in this session.

The post-setup Linux packaging block was also updated to resolve build_dir locally before using it:

elif sys.platform == 'linux':
    os.makedirs(out_dir, exist_ok=True)
    build_dir = resolve_linux_build_dir()
    installer_name = f'TimeMatters-{VERSION}-linux-x86_64.sh'

The installer move was made safe for reruns:

if os.path.exists(installer_name):
    shutil.move(installer_name, build_dir)

5.8. Step 8: Build the Linux installer

The installer build was rerun:

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
rm -f TimeMatters-0.7.1-linux-x86_64.sh
rm -f dist/TimeMatters-0.7.1-linux-x86_64.zip
python setup.py build_installer

Validation:

=== build result ===
exit code: 0
build log: /tmp/tmsa-build-installer-after-direct-patch-20260706-224830.log

Generated files:

build/exe.linux-x86_64-3.10/TimeMatters-0.7.1-linux-x86_64.sh
dist/TimeMatters-0.7.1-linux-x86_64.zip

The installer was identified as:

POSIX shell script executable (binary data), self-executable archive, Makeself 2.5.0

The installer and zip were both approximately 187M.

5.9. Step 9: Validate the generated installer

A clean installer test directory was created:

TEST_DIR="/home/christian/Homemade/tmsa-installer-test"
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"
cd "$TEST_DIR"
/home/christian/Homemade/tmsa/build/exe.linux-x86_64-3.10/TimeMatters-0.7.1-linux-x86_64.sh

Installer output:

Creating directory exe.linux-x86_64-3.10
Verifying archive integrity...  100%   MD5 checksums are OK. All good.
Uncompressing Time Matters 0.7.1 Installer  100%  
Unpacked successfully. Use ./run-tmsa to launch.

The extracted directory contained:

exe.linux-x86_64-3.10/tmsa
exe.linux-x86_64-3.10/run-tmsa
exe.linux-x86_64-3.10/postinstall.sh
exe.linux-x86_64-3.10/dll/libswe.so

The launch symlink was correct:

run-tmsa -> ./tmsa

5.10. Step 10: Validate the packaged application

The packaged app was launched from the extracted directory:

cd /home/christian/Homemade/tmsa-installer-test/exe.linux-x86_64-3.10
./run-tmsa

Validation:

PWD=/home/christian/Homemade/tmsa-installer-test/exe.linux-x86_64-3.10
DISPLAY=:0.0
Fontconfig warning: "/usr/share/fontconfig/conf.avail/05-reset-dirs-sample.conf", line 6: unknown element "reset-dirs"
exit code: 0

The warning did not prevent the app from running.

6. Current Known-Good Configuration

6.1. Source checkout

/home/christian/Homemade/tmsa

6.2. Repository state

Repository commit: 0cf3637
Untracked local files: .python-version, .venv/
Modified file: setup.py
Backup created: setup.py.before-build-dir-fix

6.3. Python environment

pyenv Python: 3.10.18
Project venv: /home/christian/Homemade/tmsa/.venv
Active Python: /home/christian/Homemade/tmsa/.venv/bin/python
Tk version: 8.6

6.4. Dependency sanity

python -m pip check
No broken requirements found.

6.5. Source launch command

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
python -X faulthandler -m src.tmsa

6.6. Build command

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
python setup.py build_installer

6.7. Installer artifacts

build/exe.linux-x86_64-3.10/TimeMatters-0.7.1-linux-x86_64.sh
dist/TimeMatters-0.7.1-linux-x86_64.zip

6.8. Packaged app launch command

cd /home/christian/Homemade/tmsa-installer-test/exe.linux-x86_64-3.10
./run-tmsa

7. Action Items

7.1. Completed

  • [X] Confirmed system OS, kernel, shell, Python, pip, git, and pyenv.
  • [X] Cloned TMSA into /home/christian/Homemade/tmsa.
  • [X] Recorded repository commit 0cf3637.
  • [X] Confirmed pyenv Python 3.10.18 exists.
  • [X] Set repo-local .python-version to 3.10.18.
  • [X] Installed or confirmed Linux build dependencies: makeself, tcl-dev, tk-dev, build-essential, python3-dev, python3-pip, and cmake.
  • [X] Confirmed Tkinter works with pyenv Python 3.10.18.
  • [X] Recreated project .venv after initial shell activation issue.
  • [X] Installed all pinned Linux Python requirements into .venv.
  • [X] Confirmed python -m pip check reports no broken requirements.
  • [X] Confirmed src/dll and src/ephe assets exist.
  • [X] Launched TMSA successfully from source.
  • [X] Patched setup.py to resolve Linux build_dir after build_exe.
  • [X] Patched post-setup Linux packaging block to resolve build_dir locally.
  • [X] Built the Makeself installer successfully.
  • [X] Built dist/TimeMatters-0.7.1-linux-x86_64.zip successfully.
  • [X] Ran the generated installer in a clean test directory.
  • [X] Launched the packaged app successfully via ./run-tmsa from the extracted directory.

7.2. Recommended follow-up

  • [ ] Keep setup.py.before-build-dir-fix until the local patch is no longer needed.
  • [ ] Consider committing the local setup.py patch to a branch if this installation will be rebuilt later.
  • [ ] Consider opening an upstream issue or pull request for the Linux build-directory resolution fix.
  • [ ] Add .venv/ to local/global ignore rules if needed; do not commit the virtual environment.
  • [ ] Decide whether to keep the disposable installer test directory or replace it with a permanent packaged-app directory.
  • [ ] Re-check the non-blocking cramped button text on different DPI/font settings before changing application layout code.
  • [ ] Monitor whether the Fontconfig warning appears elsewhere; treat it as low priority unless it correlates with font rendering problems.
  • [ ] If a desktop launcher is desired later, create one that points to the chosen packaged run-tmsa path or to the source-run command.

8. Quick Reference

8.1. Activate the project environment

cd /home/christian/Homemade/tmsa
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"
eval "$(pyenv init -)"
pyenv local 3.10.18
. .venv/bin/activate

8.2. Verify the environment

which python
python --version
python -m pip check
python - <<'PY'
import tkinter
import tkextrafont
import cx_Freeze
import numpy
import timezonefinder
print("tkinter OK:", tkinter.TkVersion)
print("tkextrafont OK")
print("cx_Freeze OK:", cx_Freeze.__version__)
print("numpy OK:", numpy.__version__)
print("timezonefinder OK")
PY

8.3. Run from source

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
python -X faulthandler -m src.tmsa

8.4. Build installer

cd /home/christian/Homemade/tmsa
. .venv/bin/activate
python setup.py build_installer

8.5. Inspect generated artifacts

ls -lh build/exe.linux-x86_64-3.10/TimeMatters-0.7.1-linux-x86_64.sh
ls -lh dist/TimeMatters-0.7.1-linux-x86_64.zip
unzip -l dist/TimeMatters-0.7.1-linux-x86_64.zip

8.6. Test installer in a disposable directory

TEST_DIR="/home/christian/Homemade/tmsa-installer-test"
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"
cd "$TEST_DIR"
/home/christian/Homemade/tmsa/build/exe.linux-x86_64-3.10/TimeMatters-0.7.1-linux-x86_64.sh
cd exe.linux-x86_64-3.10
./run-tmsa

8.7. Good state

Source app launches and exits with code 0.
Installer builds and exits with code 0.
Installer extracts successfully.
Packaged app launches from exe.linux-x86_64-3.10 and exits with code 0.

8.8. Bad states observed

source: not found
RuntimeError: Could not find the cx_Freeze build output directory
TypeError: expected str, bytes or os.PathLike object, not NoneType
./run-tmsa: No such file or directory from parent extraction directory

9. Notes and Caveats

  • The source checkout remains at /home/christian/Homemade/tmsa.
  • The successful packaged-app test used the sibling disposable directory /home/christian/Homemade/tmsa-installer-test.
  • The default Makeself extraction directory exe.linux-x86_64-3.10 was left unchanged by preference.
  • The installer's postinstall.sh creates run-tmsa -> ./tmsa inside the extracted directory.
  • The postinstall message is technically correct only after changing into the extraction directory.
  • The Fontconfig warning observed during packaged launch did not prevent a successful launch or clean exit.
  • Button text appeared slightly cramped in the GUI screenshot. This was not treated as an install failure.
  • The display hardware is a Panasonic Toughbook CF-31-5; the cramped button text may be related to limited screen real estate, display resolution, DPI scaling, or Tk font/layout assumptions rather than a packaging fault.
  • The BuildAppImage path was patched in the same style as BuildInstaller but was not tested in this session.
  • The local setup.py patch is not present in upstream commit 0cf3637 unless later committed or submitted upstream.
Tags: xubuntu linux python pyenv tkinter tmsa cx-freeze makeself rca toughbook