building python 3.9.1 on jetpack 4.5 and the jetson xavier nx

These instructions will help you build Python 3.9.1 on the Jetson Xavier NX Development Kit running JetPack 4.5. There are two broad stages to building this version. The first is the installation of support developer libraries to allow all Python modules to successfully build, especially _ssl. If _ssl fails to build then pip will not be able to negotiate connectivity with any Python repo, making installation of modules fail. After the installation stage come the build steps.

Install Build Prerequisites

To build all Python modules the following libraries need to be installed. Simply copy-and-paste the following line:

sudo apt install zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev

Build Python

Download the latest version of Python, build, and install:

  1. Download the latest Python, version 3.9.1, from https://www.python.org/downloads/.
  2. Untar the file (we’ll assume it’s downloaded to the default ~/Downloads):
    tar xvf Downloads/Python-3.9.1.tar.xz
  3. Make a build directory at the same level as the untarred source directory. In my case I named the build directory build-python-3.9.1
  4. Change directory into the build directory.
  5. From within the build directory execute the configure script:
    ../Python-3.9.1/configure --enable-optimizations
  6. Run make with all active cores:
    make -j $(nproc)

    On my machine I enabled all six cores of the Jetson Xavier NX. Running with fewer cores will result in a longer build time.

  7. Install into the alternate location for this version of Python:
    sudo -H make altinstall
  8. Check for the alternate location with which python3.9. It should return /usr/local/bin/python3.9.

I created a Python 3.9.1 virtual work environment, something I highly recommend. I do that to keep the stock and newly installed environments distinct from one another. These are the specific steps I used to create that virtual Python environment.

  1. In your home directory create a work folder. On my system I named it ‘vpython’. Change directory (cd) into it.
  2. Then create a Python VE:
    python3.9 -m venv 39
  3. While you’re there you might as well update pip. Always count on the pip bundled with any version to be out of date with the official version. Using my environment as an example:
    $HOME/vpython/39/bin/python3.9 -m pip install --upgrade pip

Now you’re ready to use the latest Python.

Install PyQt5

I use Python library PyQt5. In order to do that you must install Ubuntu’s Qt5 support libraries as well as the Python module.

  1. Install Ubuntu Qt5 support: sudo apt install qt5-default
  2. Enable Python 3.9 virtual environment: source $HOME/vpython/39/bin/activate
  3. Install Python PyQt5: pip install PyQt5

You’re pretty much free to use Python 3.9 with PyQt5 at this point.

Note that these directions also apply to installation on the Jetson Nano on the same Jetpack version.

of pythons and turtles

I was exposed to Turtle Graphics in the dim dark early 1980s via Texas Instruments TI99/4A home computer and their implementation of Logo. Logo itself goes back even further, to the late 1960s (for the history of Logo, see https://el.media.mit.edu/logo-foundation/what_is_logo/history.html ). I thought Logo interesting, but after a time it faded into the background as I moved on to other computers and languages. Fast forward to now, where I came across a problem requiring Turtle graphics be used to draw from one to four copies of simple graphics on a Tcl/Tk canvas. My example draws “trees” using heptagons as the “tree” crowns on both macOS 11 and a CentOS 8.3 VM hosted on my Mac. Through solving the problem I was reintroduced to Turtle graphics and I learned about a number of side issues running Python on both MacOS and CentOS. But first, a screen capture of my application running on macOS.

And now, a screen capture from CentOS.


The big difference between the two screen captures is a lack of anti-aliasing on the CentOS version. Why does the Linux version look so bad compared to the macOS version?

Before I go farther, let me post my source code here.

#!/usr/bin/env python3

import math
import turtle
import tkinter as tk

# RegularPolygon, a regular polygon drawer.
# Three or more equal sides.
#
def RegularPolygon(t, color, x, y, sides, side_length, fill):
    if sides < 3:
        return

    t.penup()
    t.hideturtle()
    t.pencolor(color)
    t.pensize(5)
    #
    # Shift the turtle starting point so that the center
    # of the polygon is actually at x and y.
    #
    inradius = ((side_length/2) / math.tan(math.pi/sides))
    t.setposition(x + side_length/2, y - inradius)
    t.setheading(180)
    t.pensize(5)

    if fill:
        t.fillcolor(color)
        t.begin_fill()

    turn_angle = 360 / sides
    t.pendown()

    for x in range(0, sides):
        t.forward(side_length)
        t.right(turn_angle)

    if fill:
        t.end_fill()

    t.penup()


# Draw an abstract tree.
#
def Tree(t, color, x, y, fill):
    #
    # Draw the trunk.
    # I could, I suppose, write and use a Line function.
    # Too lazy.
    #
    t.penup()
    t.hideturtle()
    t.pencolor('brown')
    t.pensize(5)
    t.setposition(x, y)
    t.pendown()
    t.setheading(90)
    t.forward(50)
    #
    # Draw two branches at 45 degrees from the trunk.
    #
    t.pensize(3)
    t.setposition(x, y+25)
    branch = (25/math.sin(45 * math.pi/180))
    t.setheading(45)
    t.forward(branch)
    t.setposition(x, y+25)
    t.setheading(135)
    t.forward(branch)
    t.setposition(x, y+50)
    t.penup()
    #
    # Draw the tree's crown using a RegularPolygon.
    # Make sure to set the polygon's coordinates so
    # that the base is at the top of the trunk, i.e.
    # where drawing stopped.
    #
    # Too many magic numbers.
    # For example, the number of sides and side length.
    #
    number_of_sides = 7
    side_length = 80
    RegularPolygon(t, color,
        x, (t.ycor() + (side_length/2) / math.tan(math.pi/number_of_sides)),
        number_of_sides, side_length, fill)

# Draw a cross right in the center of the canvas.
# Use this for visual checking of other drawn graphics.
#
def CenterCross(t):
    end_point = 25
    diagonal = (end_point/math.sin(math.radians(45))) * 2
    t.penup()
    t.hideturtle()
    t.pencolor('black')
    t.pensize(3)
    t.setposition(end_point * -1, end_point * -1)
    t.pendown()
    t.setheading(45)
    t.forward(diagonal)
    t.penup()
    t.setposition(end_point * -1, end_point)
    t.pendown()
    t.setheading(-45)
    t.forward(diagonal)
    t.penup()


def DrawTrees(entries):
    tree_colors = []
    for entry in entries:
        tree_color = entry[1].get()
        if len(tree_color) > 0:
            tree_colors.append(tree_color)

    if len(tree_colors) == 0:
        return

    turtle.TurtleScreen._RUNNING = True
    my_turtle = turtle.Turtle()
    my_turtle.speed(0)
    turtle.title('Python Turtle Example')
    #
    # Set up center points for each tree, i.e.
    # where the trunks go.
    #
    startx = (len(tree_colors) - 1) * -100
    object_span = 200
    #
    # Draw all the trees in our selected colors.
    #
    fill = False
    for color in tree_colors:
        try:
            Tree(my_turtle, color, startx, 0, fill)
        except turtle.TurtleGraphicsError:
            print("Bad color " + color);
        startx += object_span
        fill = not fill

    # Visually show where position 0,0
    # is on the canvas.
    #
    CenterCross(my_turtle)
    #
    # Wait for a mouse click anywhere on the
    # window's canvas area, then exit.
    #
    turtle.exitonclick()

def MakeForm(root, fields):
    entries = []
    for field in fields:
        row = tk.Frame(root)
        row.config(background='#d0d0d0')
        lab = tk.Label(row, width=15, text=field,
            anchor='w', background='#d0d0d0')
        ent = tk.Entry(row)
        row.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
        lab.pack(side=tk.LEFT)
        ent.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.X)
        entries.append((field, ent))
    return entries

def ShowForm():
    root = tk.Tk()
    root['background'] = '#d0d0d0'
    ents = MakeForm(root, ['Tree 1','Tree 2','Tree 3','Tree 4'])
    root.bind('<Return>', (lambda event, e=ents: DrawTrees(e)))   
    b1 = tk.Button(root, text='Show',
        command=(lambda e=ents: DrawTrees(e)))
    b1.pack(side=tk.LEFT, padx=5, pady=5)
    b2 = tk.Button(root, text='Quit', command=root.quit)
    b2.pack(side=tk.LEFT, padx=5, pady=5)
    root.mainloop()

if __name__ == "__main__":
    ShowForm()

To drive the Turtle graphics, I used Tcl/Tk to create a very simple GUI. Not much there, four fields to fill in a color, a Show button to show the Turtle graphics window, and a Quit button.
And once again the version under Linux.

Unlike Turtle graphics rendering, the Linux version of the Tk form looks a lot better than the macOS version. By the way, for those who want to give this a spin, you can enter from one to four colors, and you will draw as many trees as the number of colors you enter. Clear all the fields and nothing happens.

Working with Python on macOS isn’t smooth sailing. While working with Python3 (3.8.6) I came across two messages sent to the console every time I ran the application:

DEPRECATION WARNING: The system version of Tk is deprecated and may be removed in a future release. Please don't rely on it. Set TK_SILENCE_DEPRECATION=1 to suppress this warning.
2020-12-05 15:35:16.336 Python[67143:3480650] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/d9/n152_yx519121c0nlmlds71c0000gn/T/org.python.python.savedState

The first line is easy enough to eliminate by following the advice of the first warning by adding export TK_SILENCE_DEPRECATION=1 to your .zshrc file. The second warning is a bit more obscure, but I found a solution in a StackOverflow forum posting. At the command line execute defaults write org.python.python ApplePersistenceIgnoreState NO in a terminal, and that should take care of that. For more details see https://stackoverflow.com/questions/18733965/annoying-message-when-opening-windows-from-python-on-os-x-10-8 . Needless to say I never saw either of those messages until I stepped up to macOS 11.0.1. Just lovely.

In the end I got tired of having that message pop out of the version of Tcl/Tk that ships with macOS. I downloaded the latest version from ActiveState and installed it, then downloaded the latest version of Python from Python.org and installed it on top of that. I tried the same thing through Brew, but the Brew version of Python wouldn’t bind with the Brew version of Tcl/Tk, only the system version. Out of annoyance I force removed both Brew’s Tcl/Tk and Python, then installed ActiveState’s Tcl/Tk and Python.org’s Python 3.9.1. No more deprecation messages, and the Tcl/Tk widgets look a little better on my Mac, but not by much.

Working with Python on CentOS 8.3 (and RHEL 8.3 for that matter) required building a more up-to-date version of Python on those platforms. I tried stepping up to Python 3.9.1, but building that version of Python on those platforms is something of a hot mess, especially when trying to build the SSL modules required by pip to install third-party Python modules. Those SSL modules failed to build, and without them, pip won’t download anything. This build issue is also found with Ubuntu 20.04 and 20.10. Even though Python 3.9.1 is officially released, for all practical purposes it’s still bleeding edge enough and should be left alone unless you can get it pre-built, such as for macOS. Stick with Python 3.8.x as the latest to build and use.