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.

the death of centos as we know it

If you haven’t read the news already, then here it is: RedHat has declared that CentOS 8 will officially cease at the end of 2021, which is the end of next year. It doesn’t effect me all that much, but I’ve worked at places and on projects that used CentOS as a binary stand-in for official RedHat because they were already pretty Linux savvy and didn’t want to pay a yearly fee for support. This allowed them to deploy as many instances as needed for development and operations, and not give a wit about if they had enough “entitlements” or not to do so. Developing and working on CentOS also gave those down-stream customers who depended on this work and who ran on official RedHat Enterprise Linux the comfort in knowing that binary compatibility between CentOS and RHEL meant no issues when deploying software developed on CentOS onto their RHEL systems.

But no more after the end of 2021.

RedHat decreed this week that regular CentOS would be replaced by CentOS Stream, a rolling distribution of RHEL. That has lit a fire under a whole lot of folk who were depending on CentOS 8 to have a 10-year run, up to the end of 2029, instead of the end of 2021. So anybody who built or is building an important part of their business on top of CentOS has been blown out of the water.

So why did this happen? Here are my personal theories based on the current circumstances. Remember that RedHat is owned by IBM. IBM closed that deal back in July 2019 and paid a princely $34 Billion dollars. Even for IBM that’s a lot of cash, and IBM is going to want a decent ROI, with ‘decent’ defined by IBM. IBM is not going to look kindly on a distribution supported essentially by RedHat, based on RHEL, that nobody pays for. Remember that before IBM acquired RedHat, RedHat started “sponsoring” CentOS and obtained the CentOS trademarks. RedHat essentially purchased CentOS, naming it the CentOS Project. This added to Fedora Linux, RHEL’s upstream development distro. When IBM came along and made its acquisition it gained control of all three.

So what does all this mean?

  • IBM spent $34 Billion to acquire RedHat, which includes RHEL, CentOS, and Fedora Linux. The majority of the money will come from RHEL, meaning paying customers. IBM’s attitude is if you want the stability that RHEL provides, then you need to pay for it. A lot of money has gone into making it stable, even more in marketing it and convincing major Linux users to use RHEL and to pay for RedHat support and services. IBM does not look kindly on those who get a free pass with CentOS.
  • IBM’s attitude is if you want to not pay directly for RHEL, then you can help by being a beta tester. Make no mistake, CentOS Stream is upstream and beta to RHEL. You get to try out all the new and exciting features and report any problems. Sigh me up!
  • Finally, you now have an opportunity to have your GitHub pull request more ‘seriously’ considered, which means you can volunteer to fix issues and be a part of the Open Source Software unpaid legions. Sign me up!

You can talk about RHEL all you want, more out of inertia than any other reason, but at the end of the day it’s all IBM and will remain all IBM until IBM either completely subsumes RedHat et. el., or it sells it on again after stripping out all the good bits.

As for a replacement to CentOS, you might want to look at Oracle Linux. Oracle sees this change to CentOS as a golden opportunity and has thus opened up its distribution. I personally was able to download Oracle Linux 8.3 and spin up a VM using Parallels on my MBP. It was very easy to install and get running, more so than CentOS. And that was no typo. Oracle Linux 8.3 is tracking RHEL, which is also 8.3, a lot faster than CentOS. CentOS didn’t step up to 8.3 until sometime this week (I know this because I ran dnf to update, and pulled down nearly 600 packages. When dnf was finished my CentOS VM was at 8.3.2011).

There is one very good reason to use Oracle Linux, and that’s the version of the kernel it’s using. Right now heavy Linux kernel development is with the 5.x version kernels. Everything earlier than that, such as the 4.x version kernels, are essentially in sustainment. RedHat, and thus CentOS, are using a 4.18 kernel. If you query Oracle Linux, this is what it shows:

[oracle8@oracle8 ~]$ uname -sa
Linux oracle8 5.4.17-2036.100.6.1.el8uek.x86_64 #2 SMP Thu Oct 29 17:06:00 PDT 2020 x86_64 x86_64 x86_64 GNU/Linux
[oracle8@oracle8 ~]$

It’s certainly not the latest 5.x kernel, but it’s both current and stable. The other tools aren’t up to date (for example, Python is version 3.6.8), but I can work around those issues far more easily than swapping out an older kernel for a more up-to-date kernel.

My only qualm with Oracle is that it’s Oracle I’m dealing with, and with great suspicion. I suppose I shouldn’t look a gift horse in the mouth, but I don’t trust Oracle all that much these days because of the way Java has been blown up by them.

There will more than likely be other CentOS replacement distributions if the rage on the various fora are to be believed, and I’m sure there’ll be a number of noble efforts. Unfortunately the Linux road is littered with the rotting carcasses of distributions that were all hailed as Great Ideas, but nothing came of them after a time because of the inertia over keeping with the established players. My advice is if you’re unhappy with CentOS then consider either Ubuntu or Suse. For me the choice is between Ubuntu and Oracle Linux.

Link: https://arstechnica.com/gadgets/2020/12/centos-shifts-from-red-hat-unbranded-to-red-hat-beta/