Tcl on MacOS

Dear all, Tcl/Tk on MacOS challenges me:

  • an old version of TclTk (8.5) as default on MacOS
  • build applications for the MacOS platform
    • and especially sign a complete app to be recognized on MacOS
  • and maybe also some performance issue.

Let us talk about our experience and best practices.

Manfred


ManfredR - 2025-02-16 18:00:00

Build MacOS-App based on Tcl/Tk-Framework

after long nights of trial and error and with the support from Kevin, Paul and Alexander I can provide a solution for this issue here:

  • Kevin Walzer [L1 ]
  • Paul Obermeier [L2 ]
  • Alexander Schoepe [L3 ]

MacOS

The MacOS, I build this intstruction

uname -a 
    Darwin … 24.3.0 Darwin Kernel Version 24.3.0: 
    Thu Jan 2 20:24:06 PST 2025; 
    root:xnu-11215.81.4~3/RELEASE_ARM64_T8103 
    arm64

Sources:

https://www.tcl-lang.org/software/tcltk/download.html

  • tcl8.6.16-src.tar.gz [L4 ]
  • tk8.6.16-src.tar.gz [L5 ]

Tools:

which gcc
        /usr/bin/gcc
which python3
        /Library/Frameworks/Python.framework/Versions/3.10/bin/python3
which unzip
        /usr/bin/unzip
which install_name_tool
       /usr/bin/install_name_tool
which otool
       /usr/bin/otool

    # unzip tk8.6.16-src.tar.gz
gcc --version
        Apple clang version 16.0.0 (clang-1600.0.26.6)
        Target: arm64-apple-darwin24.3.0
        Thread model: posix
        InstalledDir: /Library/Developer/CommandLineTools/usr/bin

python3 --version
        Python 3.10.5

Prepare to compile a Tcl/Tk-Framework

Sources

extract sources for tcl and tk:

.../myWorkDir
    │
    ├── tcl8616
    │   ├── tcl8.6.16
    │   │   ├── compat
    │   │   ├── doc
    │   │   ├── generic
    │   │   ├── library
    │   │   ├── libtommath
    │   │   ├── macosx
    │   │   ├── pkgs
    │   │   │   ├── itcl4.3.2
    │   │   │   ├── sqlite3.47.2 
    │   │   │   ├── tdbc1.1.10
    │   │   │   └── ...   
    │   │   ├── tests
    │   │   ├── tools
    │   │   ├── unix
    │   │   └── win
    │   │
    │   └── tk8.6.16
    │       ├── bitmaps
    │       ├── compat
    │       ├── doc
    │       ├── generic
    │       ├── library
    │       └── ...
    │
    └── tclbuild.sh

tclbuild.sh

based on a template I found here:

  • stackoverflow.com [L6 ]
  • tclbuild.sh
#!/bin/bash

    #
    # ./tclbuild.sh
    #

macosxminver=11.0
sver=8616
ver=8.6.16
mver=8.6
tclmver=$mver
tkmver=$mver
SRCDIR=$HOME/Development/tcl_lang/tcl${sver}
INSTLOC=$HOME/Development/tcl_lang/install

if [[ $1 != "" ]]; then
  INSTLOC=$1
fi

if [[ -d $INSTLOC ]]; then
  rm -rf $INSTLOC
fi
mkdir $INSTLOC

cd $SRCDIR

test -d build && rm -rf build

cd $SRCDIR
cd tcl${ver}
if [[ $? -eq 0 ]]; then
  f=library/init.tcl
  if [[ ! -f $f-orig ]]; then
    cp -pf $f $f-orig
  fi
  cp -pf $f-orig $f

  make -C macosx \
      PREFIX="" \
      CFLAGS_OPTIMIZE=" -arch x86_64  -arch arm64  -mmacosx-version-min=${macosxminver}" \
      INSTALL_ROOT=$INSTLOC install
      # CFLAGS_OPTIMIZE="-O2 -mmacosx-version-min=${macosxminver}"

  cd $SRCDIR

  chmod u+w $INSTLOC/bin/tclsh${tclmver}
  install_name_tool -change \
      "/Library/Frameworks/Tcl.framework/Versions/${tclmver}/Tcl" \
      @executable_path/../Library/Frameworks/Tcl.framework/Versions/${tclmver}/Tcl \
      $INSTLOC/bin/tclsh${tclmver}
fi

cd $SRCDIR
cd tk${ver}
if [[ $? -eq 0 ]]; then
  make -C macosx \
      PREFIX="" \
      CFLAGS_OPTIMIZE=" -arch x86_64  -arch arm64  -mmacosx-version-min=${macosxminver}" \
      INSTALL_ROOT=$INSTLOC install
      # CFLAGS_OPTIMIZE="-O2 -mmacosx-version-min=${macosxminver}"
  cd $SRCDIR

  chmod u+w $INSTLOC/Library/Frameworks/Tk.framework/Versions/${tkmver}/Resources/Wish.app/Contents/MacOS/Wish
  install_name_tool -change \
      "/Library/Frameworks/Tk.framework/Versions/${tkmver}/Tk" \
      @executable_path/../../../../Tk \
      $INSTLOC/Library/Frameworks/Tk.framework/Versions/${tkmver}/Resources/Wish.app/Contents/MacOS/Wish
  install_name_tool -change \
      "/Library/Frameworks/Tcl.framework/Versions/${tclmver}/Tcl" \
      @executable_path/../../../../../../../Tcl.framework/Versions/${tclmver}/Tcl \
      $INSTLOC/Library/Frameworks/Tk.framework/Versions/${tkmver}/Resources/Wish.app/Contents/MacOS/Wish
fi

cd $SRCDIR
find $INSTLOC -type f -print0 | xargs -0 chmod u+w
exit 0

compile Tcl/Tk-Framework

let the compiler do its job now …

cd .../myWorkDir/

%./tclbuild.sh


 >> ...
 >> ...
 >> 
 >> scanning section Tk Commands, version 8.6.16
 >> shuffling ttk_widget.n to front of processing queue
 >> shuffling options.n to front of processing queue
 >> .....................................
 >> menubar: make-manpage-section: discarding menubar
 >> ......
 >> pack-old: make-manpage-section: discarding pack-old
 >> ............................................
 >> scanning section Tk C API, version 8.6.16
 >> .......................................................................................
 >> Assembling index
 >> Rescanning 173 pages to build cross links and write out
 >> ......................................................................................................................
 >> Done

Result of compilation:

.../myWorkDir
    │
    ├── tcl8616
    │   ├── build
    │   ├── tcl8.6.16
    │   └── tk8.6.16
    │
    └── install
        │
        ├── Applications
        │   └── Utilities
        │       ├── Wish\ Shell.app  --->  Wish.app
        │       └── Wish.app  --->  .//../..//Library/Frameworks/Tk.framework/Resources/Wish.app
        ├── Library
        │   ├── Frameworks
        │   │   ├── Tcl.framework
        │   │   │   ├── Headers  --->  Versions/Current/Headers
        │   │   │   ├── PrivateHeaders  --->  Versions/Current/PrivateHeaders
        │   │   │   ├── Resources  --->  Versions/Current/Resources
        │   │   │   └── Versions
        │   │   │       ├── 8.6
        │   │   │       │   ├── Headers
        │   │   │       │   ├── PrivateHeaders
        │   │   │       │   ├── Resources
        │   │   │       │   │   ├── Documentation
        │   │   │       │   │   │   └── Reference
        │   │   │       │   │   │       └── Tcl
        │   │   │       │   │   │           └── ...
        │   │   │       │   │   ├── Scripts
        │   │   │       │   │   │   ├── encoding
        │   │   │       │   │   │   ├── http1.0
        │   │   │       │   │   │   └── opt0.4
        │   │   │       │   │   └── tcl8
        │   │   │       │   │       ├── 8.4
        │   │   │       │   │       ├── 8.5
        │   │   │       │   │       └── 8.6
        │   │   │       │   └── pkgconfig
        │   │   │       └── Current  --->  8.6
        │   │   └── Tk.framework
        │   │       ├── Headers  --->  Versions/Current/Headers
        │   │       ├── PrivateHeaders  --->  Versions/Current/PrivateHeaders
        │   │       ├── Resources  --->  Versions/Current/Resources
        │   │       └── Versions
        │   │           ├── 8.6
        │   │           │   ├── Headers
        │   │           │   │   └── X11
        │   │           │   ├── PrivateHeaders
        │   │           │   ├── Resources
        │   │           │   │   ├── Documentation
        │   │           │   │   │   └── Reference
        │   │           │   │   │       └── Tk
        │   │           │   │   │           └── ... 
        │   │           │   │   ├── Scripts
        │   │           │   │   │   └── ...
        │   │           │   │   ├── Wish Shell.app  --->  Wish.app
        │   │           │   │   └── Wish.app
        │   │           │   │       └── Contents
        │   │           │   │           ├── MacOS
        │   │           │   │           └── Resources
        │   │           │   └── pkgconfig
        │   │           └── Current  --->  8.6
        │   └── Tcl
        │       ├── itcl4.3.2
        │       ├── sqlite3.47.2
        │       ├── tcl8
        │       ├── tdbc1.1.10
        │       ├── tdbcmysql1.1.10
        │       ├── tdbcodbc1.1.10
        │       ├── tdbcpostgres1.1.10
        │       └── thread2.8.11
        ├── bin
        ├── include
        └── man
            ├── man3
            └── mann

Test the Result

We are looking for a Wish.app

cd .../myWorkDir/

open ./install/Applications/Utilities/Wish.app
        # is a link to: ./../../Library/Frameworks/Tk.framework/Resources/Wish.app
        # ... failed ???

open ./install/Library/Frameworks/Tk.framework/Resources/Wish.app
        # is a link to: Versions/Current/Resources
        # ... OK

open ./install/Library/Frameworks/Tk.framework/Versions/Current/Resources/Wish.app
        # ... OK
        #     ... this is the template we will use in the next step
        #

Extract a standalone Wish.app

What is needed

… a standalone Wish.app with a given structure. We name it myApp.app.

.../myWorkDir 
    │
    └── myApp.app
        └── Contents
            ├── Info.plist
            ├── Frameworks/
            │   ├── Tcl.framework/
            │   ├── Tk.framework/
            ├── Tcl
            │   └── ...
            ├── MacOS/
            │   └── myApp
            └─── Resources/
                └── myApp.icns

Create myApp.app

We use the compiled result as a base.

cd .../myWorkDir

cp -Rf  ./install/Library/Frameworks/Tk.framework/Resources/Wish.app ./myApp.app

mkdir -p ./myApp.app/Contents/Frameworks

cp -Rf  ./install/Library/Frameworks/Tk.framework    ./myApp.app/Contents/Frameworks
cp -Rf  ./install/Library/Frameworks/Tcl.framework   ./myApp.app/Contents/Frameworks

mkdir -p ./myApp.app/Contents/Library

cp -Rf  ./install/Library/Tcl                        ./myApp.app/Contents/Library/Tcl

Now we have to test our app

open ./myApp.app

        #
        # -> failed
        #

Debug our App

… but don’t panic

    #
    # -------------------------------------
    # Translated Report (Full Report Below)
    # -------------------------------------
    # 
    # Process:               Wish [5329]
    # Path:                  .../*/Wish.app/Contents/MacOS/Wish
    # Identifier:            com.tcltk.wish
    # Version:               8.6.16 (8.6.16)
    # Code Type:             ARM-64 (Native)
    # 
    # Crashed Thread:        0
    # 
    # Exception Type:        EXC_CRASH (SIGABRT)
    # Exception Codes:       0x0000000000000000, 0x0000000000000000
    # 
    # Termination Reason:    Namespace DYLD, Code 1 Library missing
    # Library not loaded: @executable_path/../../../../Tk
    # Referenced from: <E91A1C5F-BF22-3AB4-87AA-F312F03B9FDE> .../*/myApp.app/Contents/MacOS/Wish
    # Reason: tried: '.../Tk' (no such file)
    # (terminated at launch; ignore backtrace)
    # 

The reason for this, are the libraries searched for in our executable in our app.

otool -L ./myApp.app/Contents/MacOS/Wish
            
    #
    # /Users/manfred/Development/tcl_lang/Wish.app/Contents/MacOS/Wish (architecture x86_64):
    #     @executable_path/../../../../Tk (compatibility version 8.6.0, current version 8.6.16)
    #     @executable_path/../../../../../../../Tcl.framework/Versions/8.6/Tcl (...)
    #     /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)
    #     ...
    #

We see that the executable is unable to find Tk and Tcl. We have to change these references:

install_name_tool -change \
            @executable_path/../../../../Tk \
            @executable_path/../Frameworks/Tk.framework/Versions/8.6/Tk \
    ./myApp.app/Contents/MacOS/Wish

install_name_tool -change \
            @executable_path/../../../../../../../Tcl.framework/Versions/8.6/Tcl \
            @executable_path/../Frameworks/Tcl.framework/Versions/8.6/Tcl \
    ./myApp.app/Contents/MacOS/Wish

We test again

open ./myApp.app

        #
        # -> OK
        #

https://i.imgur.com/Glw8gZL.png|png|jpg

Make our app running Tcl-Code

... myApp.app should not only work as an interpreter. It shall run our own Tcl/Tk-Application. We must extend our app with some tcl-Scripts:

  • AppMain.tcl (mandatory) and
  • myApp.tcl
.../myWorkDir 
    │
    └── myApp.app
        └── Contents
            ├── Info.plist
            ├── Frameworks
            │   ├── Tcl.framework
            │   └── Tk.framework
            ├── Library
            │   └── Tcl
            │       └── ...
            ├── MacOS
            │   └── myApp
            └── Resources
                ├── myApp.icns
                └── Resources
                    │
                    └── Scripts
                        └── AppMain.tcl

… but we'll do a little more

.../myWorkDir 
    │
    └── myApp.app
        └── Contents
            ├── Info.plist
            ├── Frameworks/
            │   ├── Tcl.framework/
            │   └── Tk.framework/
            ├── MacOS/
            │   └── myApp
            ├── Library
            │   └── Tcl
            │       ├── itcl4.3.2
            │       ├── sqlite3.47.2
            │       ├── tcl8
            │       └── ...
            └── Resources/
                ├── myApp.icns
                └── Resources
                    │
                    └── Scripts
                        ├── AppMain.tcl
                        ├── bin
                        │   └── myApp.tcl                  
                        └── lib
                            └── ...                                    

so we have to prepare our app and add some files

mkdir -p ./myApp.app/Contents/Resources/Scripts/bin
mkdir -p ./myApp.app/Contents/Resources/Scripts/lib

touch ./myApp.app/Contents/Resources/Scripts/AppMain.tcl
touch ./myApp.app/Contents/Resources/Scripts/bin/myApp.tcl

# vi  ./myApp.app/Contents/Resources/Scripts/AppMain.tcl
# vi  ./myApp.app/Contents/Resources/Scripts/bin/myApp.tcl

…/Scripts/AppMain.tcl

    #
    # …/Scripts/AppMain.tcl
    #
    
    #
set __script_Dir___ [file dirname [info script]]
set __script__Tcl__ [file normalize [file join $__script_Dir___ .. .. Library Tcl]]
set __script__Lib__ [file normalize [file join $__script_Dir___ lib]]
    #
lappend auto_path $__script__Tcl__
lappend auto_path $__script__Lib__
    #
puts "  -- \$auto_path -- "
foreach dir $auto_path {
    puts "    $dir"
}
    #
    # ... check availability of itcl
    #
if [file exists [file join $__script__Tcl__ itcl4.3.2]] {
    set env(ITCL_LIBRARY) [file join $__script__Tcl__ itcl4.3.2]
} else {
    puts "      package: itcl "
    puts "            ... \$env(ITCL_LIBRARY)  ... not available"
}
    #
    # ... check availability of Tk
    #
    # if [file exists [file join $__script__Lib__ itk4.2.5]] {
    #     set env(ITK_LIBRARY)  [file join $__script__Lib__ itk4.2.5]
    # } else {
    #     puts "      package: itk "
    #     puts "            ... \$env(ITK_LIBRARY)   ... not available"
    # }
    #
puts "  -- \$__script_Dir___ --"
puts "    $__script_Dir___ \n"
puts ""
    #
source [file join $__script_Dir___  bin myApp.tcl]
    #
# package require starkit
# starkit::startup
# source [file join $__script_Dir___  __myApp.kit]
    #

…/Scripts/bin/myApp.tcl

    #
    # …/Scripts/bin/myApp.tcl
    #
    #
package require Tk

    # Create the main window
wm title . "myApp @ Tcl/Tk - Framework-Wish.app"
wm geometry . 600x300+100+100

    # Procedure to report Environment to the Text widget
proc reportEnv {} {
    .textWidget delete 1.0 end
    .textWidget insert end "\n"
    .textWidget insert end "--- auto_path ---\n"
    .textWidget insert end "\n"
    foreach dir $::auto_path {
        .textWidget insert end "    ... $dir\n"
    }
    
    .textWidget insert end "\n"
    .textWidget insert end "--- env ---\n"
    .textWidget insert end "\n"
    .textWidget insert end [format {    ... %-15s  -> %s} ITCL_LIBRARY $::env(ITCL_LIBRARY)]\n
    
    .textWidget insert end "\n"
    .textWidget insert end "--- env ---\n"
    .textWidget insert end "\n"
    foreach name [array names ::env] {
        .textWidget insert end [format {    ... %-15s  -> %s} $name $::env($name)]\n
    }
    .textWidget insert end "\n\n                              ... done!\n"
}   
    # Procedure to load Packages
proc requirePackages {} {
    puts "\n"
    puts "    ... load packages"
    puts ""
    puts "        ... load Itcl"
    package require Itcl
    puts ""
    puts "             ... done"

}   

    # Create a button to report Environment
button .buttonEnv -text " report Environment " -command {reportEnv}

    # Create a button to require Packages
button .buttonPkg -text "  require Packages  " -command {requirePackages}

    # Create a frame to hold the Text widget and scrollbars
    # frame .textFrame

    # Create a Text widget with scrollbars
text .textWidget -width 120 -height 25 -yscrollcommand {.yScroll set} -xscrollcommand {.xScroll set} 
scrollbar .yScroll -orient vertical   -command {.textWidget yview}
scrollbar .xScroll -orient horizontal -command {.textWidget xview}

grid .textWidget .yScroll   -sticky news
grid .xScroll               -sticky news
    
    # Configure grid weights
grid rowconfigure    . 0 -weight 1
grid columnconfigure . 0 -weight 1

    # Grid the button in the main window
grid .buttonEnv -row 2 -column 0 -columnspan 2  -pady 2  -ipadx 10
grid .buttonPkg -row 3 -column 0 -columnspan 2  -pady 2  -ipadx 10

…/Scripts/lib ... reserved to place additional packages required by your application and are not provided by this TclTk-Framework

    tcllib, 
    tdom, 
    ... 

Test your application

open ./myApp.app

        #
        # -> OK
        #

https://i.imgur.com/3Jrf5Md.png|png|jpg

Codesigning and notarization

Before start codesigning you have to unlock your signing-keychain

security unlock-keychain login.keychain  

        #
        # -> requires a Developer-ID and a password
        #

Lets do the codesigning not on the original myApp but do it in a copy. The following manual is inspired from a proposal of Kevin Walzer [L7 ]

Requirements

Apple Developer ID:

security find-identity -p codesigning

        Policy: Code Signing
          Matching identities
          1) …
          2) …
          3) ABC12345A1B23456789012345678901234567890 "Developer ID Application: Small Feather (1AB2CD3EF5)"
             3 identities found
        
          Valid identities only
          1) ABC12345A1B23456789012345678901234567890 "Developer ID Application: Small Feather (1AB2CD3EF5)"
             1 valid identities found

Notarization Profile

xcrun notarytool store-credentials <profile-name> \
        --apple-id "<your-apple-id>" \
        --team-id "<your-team-id>" \
        --password "<your-app-specific-password>"

xcrun notarytool store-credentials "myNotarizationProfile" \
    --apple-id    "[email protected]" \
    --team-id     "1AB2CD3EF5" \
    --password    "abcd-efgh-ijkl-mnop"

    #
    # in this manual: <profile-name> myNotarizationProfile
    #

entitlements.plist

... please care about unix linefeeds !?!? ... do not ask!!

.../myWorkDir 
    │
    ├── tcl8616
    ├── install
    ├── myApp.app
    │   └── ...
    ├── build
    │   └── myApp.app
    ├── tclbuild.sh
    │
    └── entitlements.plist

./entitlements.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
    <dict>
        <key>com.apple.security.app-sandbox</key>                           <false/>
        <key>com.apple.security.automation.apple-events</key>               <true/>
        <key>com.apple.security.cs.allow-jit</key>                          <true/>
        <key>com.apple.security.cs.disable-library-validation</key>         <true/>
        <key>com.apple.security.get-task-allow</key>                        <true/>
    </dict>
</plist>

... now step by step

cd .../myWorkDir

mkdir build

cp -Rf myApp.app ./build/myApp.app


    #
    # ... cleanup ./build/myApp.app
    #

xattr -cr ./build/myApp.app

    # ... if neccessary
    #         python3 -mmacholib standalone ./build/myApp.app

rm -rf ./build/myApp.app/Contents/Frameworks/Tcl.framework/Versions/8.6/Resources/Documentation
rm -rf ./build/myApp.app/Contents/Frameworks/Tk.framework/Versions/8.6/Resources/Documentation

rm -rf ./build/myApp.app/Contents/Frameworks/Tk.framework/Versions/8.6/Resources/Wish.app
rm -rf ./build/myApp.app/Contents/Frameworks/Tk.framework/Versions/8.6/Resources/"Wish Shell.app"
rm -rf ./build/myApp.app/Contents/MacOS/"Wish Shell"

find ./build/myApp.app  -name "*.a"     -exec rm -rf {} \;
find ./build/myApp.app  -name "*debug"  -exec rm -rf {} \;
find ./build/myApp.app  -name "*.sh"    -exec rm -rf {} \;

chmod -R a+rw ./build/myApp.app


    #
    # ... codesign ./build/myApp.app
    #

find ./build/myApp.app  -type f -name "*.bundle"  -exec \
        codesign  --verbose  -f \
            --signature-size 9400 \
            --sign "Developer ID Application" {} \;

find ./build/myApp.app -type f -name "*.dylib"  -exec \
        codesign  --verbose  -f \
            --signature-size 9400 \
            --sign "Developer ID Application" {} \; 

codesign  --verbose  -f \
            --signature-size 9400 \
            --sign "Developer ID Application" \
        ./build/myApp.app/Contents/Frameworks/Tk.framework/Versions/8.6

codesign  --verbose  -f  \
            --signature-size 9400 \
            --sign "Developer ID Application" \
        ./build/myApp.app/Contents/Frameworks/Tcl.framework/Versions/8.6

codesign  --verbose=2  -f  --deep  --timestamp \
            --signature-size 9400 \
            --sign "Developer ID Application" \
            --options runtime \
            --entitlements ./entitlements.plist \
        ./build/myApp.app



    #
    # ... check entitlements
    #

codesign -d --entitlements - ./build/myApp.app

    #
    # ... create a zip for notarization
    #
cd ./build

ditto -c -k --sequesterRsrc --keepParent  \
        myApp.app \
        myApp_Setup.zip

    #
    # ... notarize your app
    #

xcrun notarytool submit myApp_Setup.zip \
            --keychain-profile myNotarizationProfile \
            --wait

    #
    # ... in notarization was successful, staple your app
    #

xcrun stapler staple myApp.app
                
    #
    # … build a .dmg-container to deliver your app
    #
    
hdiutil create -srcfolder myApp.app -volname myApp_Demo myApp_Demo_$$.dmg

cd ../

    #
    # ... Done.
    #

some additional commands

plutil myApp.app/Contents/Info.plist
lldb myApp.app
...

Discussions

TR: my experience:

  • do not use the built-in version but install your own recent Tcl 8.6 (I just compile from the sources myself)
  • build applications using the bundle facility of macOS, here is a recipe -> https://www.codebykevin.com/tutorial.html
  • code-signing a macOS app can be done with a self-signed signature using the codesign utility (codesign --force --deep -s - $exeFile) but this will still trigger a warning as the certificate is not a paid one (you only get full peace of mind when you pay Apple $ 100 per year)
  • What particular performance issues do you have?

Otherwise, make sure to check the New Tcl/TkAqua FAQ (and yes, it needs an update!)


ManfredR - 2025-01-10 21:55:20

I tried to codesign "vanillawish","tclkit" and a single "wish" and "tclsh". my observation:

  • I could codesign "wish" and "tclsh"
  • but could not codesign "vanillawish" and "tclkit".

I fear this observation is based on, that codesign does not sign binaries with a payload. If this is the case "vanillawish" and "tclkit" are no oportunity to provide a tcl-runtime with batteries included for MacOS apps.

  • the --deep option should no longer be used

ALX I assume that all files embedded into the VFS must be code-signed first. Afterwards, the executable needs to be signed.


ManfredR - 2025-01-29 15:00:00

I found an approach used to build tkChat for MacOS:

  • TclApps Library Source Code [L8 ]

and a manual

  • Building Stand-Alone Tcl/Tk Applications under Mac OS X [L9 ]

This approach requires wish.app as template

 wish.app/
    Contents/
        Info.plist
        Frameworks/
            Tcl.framework/
            Tk.framework/
        MacOS/
            wish
        Resources/
            wish.icns

to use this template with the name myApp all whish should be renamed to e.g. myApp

 myApp.app/
    Contents/
        Info.plist
        Frameworks/
            Tcl.framework/
            Tk.framework/
        MacOS/
            myApp
        Resources/
            myApp.icns

... and if I got it right:

 ./myApp.app/Contents/MacOS/myApp

is a nearly native

wish

looking for a tcl-script

 ./myApp.app/Resources/Scripts/AppMain.tcl (or main.tcl???)

my Question:

... is there anybody out there, providing a wish.app as a template (for tcl 8.6)?


ABU 16-02-2025

Instead of building a tcltk interpreter for MacOS (it's always a long and complex task), I just downloaded a "single-file-snapshot' from github. This is a fully working 'wish' 9.0.2

    https://github.com/tcltk/tk/actions/runs/13280568013

Then I can start to create macOS App, just by packing this executable and my specific scripts (Tcl plus some binary libraries).

Well , this is just the start of the story .. the problem is that I have no intention to pay a Developer License to Apple just to deliver some open-source apps. Here is a full dmg you can download, install and run ... but there're many manual steps required you should do to pass all the security checks.

    https://sourceforge.net/projects/irrational-numbers/files/Caligraft/Caligraft2025.dmg/download

I will be grateful to anyone who tries to test this app. (Note: it's for Intel-Mac ; I don't know how it works on new Apple processors).

TR - 2025-02-16 I have tried to open the app on macOS 15.2 (Sequoia; M2 processor). I was able to get it running, finally. First, I got the message that "Apple could not check whether the app is free from malicious software". I could circumvent this with the usual methods (context menu). Also, calling the wrapper under "Contents > MacOS" directly from the command line did not help. I then needed to go into the system settings, and under privacy I needed to explicitly allow the app. After this, it worked.

reply to TR - 2025-02-16 by ManfredR for MacOS Sequioa

  • its confusing, but make it run on your "Developer Mac" does not mean that the same app will also run on another Mac
  • you have to codesign and notarize your app, to prevent the Systems & Security settings

ABU Great ! Could you explain what you did into system settings ? I've heard something like "xattr -d com.apple.quarantine ..all files.." , isn't it enough ? Just last question (some beta users told me it didn't work ...) : First, you run the 'main app' (it's just a simple Tk panel with some images), but then, were you able to launch the real demos by double-clicking over one of the images ?

TR - I used the GUI app and navigated to "Privacy $ Security" (it might be called slightly different - I am using a German macOS), then I scrolled Doen to the bottom of that page. There, macOS lists the Caligraft app (since I had tried to open it, otherwise it would not pop up there) and gives me the opportunity to to click on a button to allow running this app anyway). The method using xattr is nice but nothing you can tell your normal users ... I was not able to run the real demos. Double-clicking results in another instance if Calicraft launching (so it seems judging be the icon that comes up) which then died after less than a second. I see no other window. However, when I start it from the command line, I get this:

Error in startup script: dlopen(/Applications/Caligraft2025.app/Contents/Caligraft2025/lib/blend2d-1.4/darwin-x64/tkb2d90.dylib, 0x0006): tried: '/Applications/Caligraft2025.app/Contents/Caligraft2025/lib/blend2d-1.4/darwin-x64/tkb2d90.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e' or 'arm64')), '/System/Volumes/Preboot/Cryptexes/OS/Applications/Caligraft2025.app/Contents/Caligraft2025/lib/blend2d-1.4/darwin-x64/tkb2d90.dylib' (no such file), '/Applications/Caligraft2025.app/Contents/Caligraft2025/lib/blend2d-1.4/darwin-x64/tkb2d90.dylib' (mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64e' or 'arm64'))
    while executing
"load [BL::_findDLL $dir "tkBlend2d"] T2d"
    (lambda term " dir  {
        source [file join $dir t2d.tcl]
        load [BL::_findDLL..." line 3)
    invoked from within
"apply { dir  {
        source [file join $dir t2d.tcl]
        load [BL::_findDLL $dir "tkBlend2d"] T2d
        package provide tkBlend2d 1.4
}} /Applications/Caligraft202..."
    ("package ifneeded tkBlend2d 1.4" script)
    invoked from within
"package require -exact tkBlend2d $ver"
    (lambda term " dir  {
        set ver 1.4
        package require -exact tkBlend2d $ver
..." line 3)
    invoked from within
"apply { dir  {
        set ver 1.4
        package require -exact tkBlend2d $ver
        package provide Blend2d $ver
}} /Applications/Caligraft2025.app/Contents/Caligraft..."
    ("package ifneeded Blend2d 1.4" script)
    invoked from within
"package require Blend2d 1.4"
    (file "/Applications/Caligraft2025.app/Contents/Caligraft2025/lalana-v1.tcl" line 45)
invalid command name "::tkerror"
    while executing
"::tkerror $err"

So, as I have an arm machine, I need the library compiled for arm, not for x86. That is because the app launches as an arm app (you have provided a fat binary with both architectures so the native one will take preceedence. But then of course, all libraries also need the same architecture, which the hay not.


ManfredR - 2025-02-19

Is it an approach to have every binary compiled for intel as long as not all tcl-packages (libraries) are also available for the arm architecture although your mac is a newer one?

MacOS handles opening your intel based app via Rosetta mode ...

I am looking for fat binaries (arm & intel) of:

  • tcltls (8.6 & 9.0 ... to connect to web-services and
  • vqtcl (8.6 & 9.0 ... to open starkit from Wish.app

ManfredR - 2025-02-18

% pwd
/Volumes/Caligraft Installer

1) Info.plist

% cat Caligraft2025.app/Contents/Info.plist      
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleName</key>
    <string>xCaligraft</string>
  <key>CFBundleExecutable</key>
    <string>wrapper</string>
  <key>CFBundleIdentifier</key>
    <string>abu-irrational.github.com.Caligraft</string>
  <key>CFBundleVersion</key>
    <string>1.2.3</string>

  <key>CFBundleIconFile</key>
         <string>Caligraft2025.icns</string>
</dict>
</plist>

2) Caligraft2025.app/Contents/MacOS/wrapper ... is a shell script

#!/bin/bash

DIR="$(dirname "$0")"

$DIR/wish902 $DIR/../Caligraft2025/Caligraft.tcl

3) Caligraft2025.app

% lldb Caligraft2025.app
(lldb) target create "Caligraft2025.app"
error: '/Volumes/Caligraft Installer/Caligraft2025.app/Contents/MacOS/wrapper' doesn't contain any 'host' platform architectures: arm64, armv7, armv7f, armv7k, armv7s, armv7m, armv7em, armv6m, armv6, armv5, armv4, arm, thumbv7, thumbv7k, thumbv7s, thumbv7f, thumbv7m, thumbv7em, thumbv6m, thumbv6, thumbv5, thumbv4t, thumb, x86_64, x86_64, arm64, arm64e, arm64, arm64e
(lldb) 

my conclusion:

  • since Macos Sequoia MacOS expects CFBundleExecutable: .../MacOS/wrapper as a binary and not as a shell-script (see: lldb Caligraft2025.app)
  • a script can not provide required platform architectures
  • MacOS Sequioa also does not accept a starpack as CFBundleExecutable, raises an error when codesigning
  • therfore this approach using Wish.app with its Tcl & Tk-Framework structure
  • e.g. have a look on the TkChat.app TkChat.app

The proposed structure for Caligraft could look like this. I havent done it yet, but follow the same steps as in this manual with Tcl9

.../myWorkDir 
    │
    └── Caligraft.app
        └── Contents
            ├── Info.plist
            ├── Frameworks/
            │   ├── Tcl.framework/
            │   └── Tk.framework/
            ├── MacOS/
            │   └── Caligraft  (... just rename the file located here and update the reference in Info.plist) 
            ├── Library
            │   └── Tcl
            │       ├── itcl4.3.2
            │       ├── sqlite3.47.2
            │       ├── tcl8
            │       └── ...
            └── Resources/
                ├── Caligraft .icns
                └── Resources
                    │
                    └── Scripts
                        ├── AppMain.tcl  (... update this script to point to ./bin/Caligraft.kit)
                        ├── bin
                        │   └── Caligraft.kit
                        └── lib
                            └── ...  

... maybe you want to send me a personal message ManfredR


ManfredR - 2025-02-17

if an app is not codesigned and notarized by apple, MacOS sees your app as a threat. in Systems Privacy .. you can define an exception to run your app.

the second point is if you make your app run on your Mac does not mean that the same app runs on an other Mac.

... but works if it is signed and notarized only

ABU in reply to ManfredR

I know and agree on the code-signed Apps. This is a question to solve, because I don't want to pay Apple for delivering free and open-source code, so, my advice was simply to find a way to deliver a beta-version to people with a low technical/hacking profile.

Second point: what do you mean "-- if you make your app run on your Mac does not mean that the same app runs on an other Mac" ? I delivered an App with no external dependencies; A full 'wish' interpreter, with all the required libraries (tcl and dylib) included. I built this App on my Intel-Mac, and then, as TR pointed out above, it works even on M2-Mac. Well .. not on every Mac, and this is hard to discover, since I don't have another Mac; I'm just trying to collect response from beta-users.

All these troubles because *today* MacOS is still equipped with TclTk 8.5 !! There's no official/supported TclTk App for MacOS, and we cannot ask users to build Tcl and Tk themself.

ManfredR in reply to ABU

"if you make your app run on your Mac does not mean that the same app runs on an other Mac"

Its the restrictive gatekeeper of MacOS Sequioa. For Test-Purpose I borrowed a MacOS from macincloud .

It's just as confusing for me. I can't do the final user test for my app on my own Mac

Tcl 8.5 on Mac: Homebrew brings Tcl 9.0 to MacOS allready


ALX - 2025-02-17

What ManfredR has compiled here is truly a valuable contribution – a big kudos for that. The core issue is that macOS classifies programs that are not officially signed as potentially unsafe or even as malware. When I compile a program on my Mac, it is usually locally signed, so my system accepts the corresponding signature as trustworthy. However, when the same program is run on another Mac, this trust-building signature is missing, and the tightened security mechanisms kick in – often in the form of warning messages or even blocking of execution. These security precautions have been continuously tightened over the years to prevent the spread of malware. A comparable approach is now also found in Windows, where unsigned or not trustworthily certified programs are also considered potentially dangerous.



NR - 2025-02-18 09:16:03

ABU, you can try this (not tested) : https://signpath.org/

ABU This is a great idea, except that they require a complete CI Workflow to be implemented. And to do that, this would cost me much, much more than paying $100 for an Apple certificate.


ManfredR 2025-02-19 commenting ABU 16-02-2025

>>> Instead of building a tcltk interpreter for MacOS (it's always a long and complex task), I just downloaded a "single-file-snapshot' from github. This is a fully working 'wish' 9.0.2

    https://github.com/tcltk/tk/actions/runs/13280568013

is there also a link to download a snapshot?

ABU Yes, but you need to be logged-in to download the snapshots.

ManfredR Thanks. Did anyone try to build a MacOs-App based on this Wish?