Конец ноября в Питере — крайне мрачное время года, на улице лишь холод, сырость и уныние. Что определенно влияет на психику местных жителей, порождая в головах самые нездоровые желания.

Видимо по этой причине, одним мрачным осенним днем мне захотелось создать графическое приложение на современном C++ под классический Mac из 90х.

В динамике.
В динамике.

История

Честно не знаю что вам сказать, на этот раз у меня нет оправданий или логических объяснений для того что я опять сотворил.

Cпишем на влияние Питера, плохую погоду и рептилоидов с Нибиру:

рассказываю и показываю как создавать приложения на современном C++ для классического Apple Macintosh начала 90х.

Примерно такого:

Macintosh Quadra 700. Компьютер как компьютер.
Macintosh Quadra 700. Компьютер как компьютер.

The Macintosh Quadra 700 is a personal computer designed, manufactured and sold by Apple Computer from October 1991 to March 1993.

Сам я никогда не видел такие машины в живую, в годы когда они выпускались, был школотой и жил в далеких сибирских краях. Хотя даже живи я посреди Нью-Йорка в 90е — врядли бы смог позволить компьютер за почти шесть штук баксов:

The Quadra 700 originally had a list price of US$5,700

Такое и сегодня далеко не для всех.

Однако времена славы Макинтошей давно прошли, ныне все эти некогда «звездные» компьютеры — те что еще остались в рабочем состоянии, являются музейными экспонатами и предметами коллекционирования.

Поэтому, то что опишу ниже — точно не имеет никакого практического применения:

все это лишь эталонная, дистиллированная дичь, без примесей и присадок.

Это на тот случай, если вы вдруг ожидаете от статьи чего-то большего.

Весь сетап целиком.
Весь сетап целиком.

Все началось, когда были выкованы мегакольц.. ээ нет, это из немного другой эпической сказки.

В нашей все было проще — автор самым банальным образом набрел в сети на один интересный репозиторий, обещающий экзотическое путешествие в мир ужаса неведомые дали:

A GCC-based cross-compilation environment for 68K and PowerPC Macs. Why? Because there is no decent C++17 Compiler targeting Apple's System 6. If that's not a sufficient reason for you, I'm sure you will find something more useful elsewhere.

Разве можно было пройти мимо столь вдохновляющего описания?

Я тоже не удержался, убив в итоге несколько лишних дней на оживление и запуск этого чудовища чуда. Но прежде чем пойдем дальше, стоит рассказать читателям немного старой доброй матчасти: 

почему автор так возбудился на какой-то неведомый набор консольных программ без картинок с голыми женщинами, еще и делающий с точки зрения обывателя не пойми что.

Матчасть

Представьте, что на вашем столе лежит вот такой кусок текстолита с железом, для которого надо написать программу:

Хотя именно этот девайс на самом деле весьма мощный.
Хотя именно этот девайс на самом деле весьма мощный.

Да, на фото выше самый настоящий компьютер:

c процессором, памятью и портами ввода-вывода — все как положено.

Но только слишком слабый для развертывания полноценной среды разработки непосредственно на нем.

Поэтому если вы не маньяк-психопат LISP-разработчик — будет откровенно сложно вести какую-либо разработку на такой хне без кросс-компиляции.

Кросс-компиляция это когда на обычном офисном компьютере без отрыва от игор cобирается приложение, предназначенное для запуска на принципиально другом устройстве:

смартфоне, роутере, соковыжималке, встраиваемой платформе умного дома, коптере или еще каком боевом роботе.

Или на другой операционной системе — сборка на линуксе под Windows это тоже вполне себе кросс-компиляция.

Тулчейн для кросс-компиляции обычно устанавливается (и даже собирается) отдельно, часто бывая очень сложным. Еще он содержит компиляторы, линкеры, ассемблеры для «вражеской» архитектуры и многое другое, не менее интересное.

В некоторых случаях тулчейн включает и части целевой системы:

заголовочные файлы, ресурсы и даже готовые библиотеки для линковки.

Без которых создавать что-то сложнее «Hello, world» с помощью кросс-компиляции было бы проблематично.

Так выглядит тот самый "Hello,world", собранный через кросс-компиляцию и запущенный в эмуляторе с MacOS 7.
Так выглядит тот самый "Hello,world", собранный через кросс-компиляцию и запущенный в эмуляторе с MacOS 7.

Retro68

Теперь, после раскрытия матчасти можно переходить к этому замечательному проекту, чтобы оценить по достоинству масштаб проделанной работы и ее важность:

a gcc-based cross-compiler for classic 68K and PPC Macintoshes

Во-первых «gcc-based», что означает определенную совместимость с нормальными и современными компиляторами, а также отсутствие экзотических проблем, характерных для редких и/или устаревших компиляторов, вроде bcc.

Во-вторых тут две разных целевых архитектуры: 68k и PowerPC.

А значит можно собирать софт для всей линейки Маков с начала 90х и до начала 2000х — до MacOS X Tiger, последней поддерживающей архитектуру PowerPC.

Но пожалуй самое важное это поддержка трех разных UI-фреймворков на целевых системах:

Что означает возможность создавать приложения с графическим интерфейсом для всего зоопарка винтажных маков.

Типа такого:

Так выглядит демо с диалогом,  созданное при помощи кросс-компиляции.
Так выглядит демо с диалогом, созданное при помощи кросс-компиляции.

И все это без доступа к самому физическому устройству и пыток разработкой непосредственно в эмуляторе.

Кстати процесс переноса собранного приложения (в примерах) в целевую систему также максимально упрощен:

сразу собирается готовый образ диска, который затем можно записать на носитель, либо подсунуть эмулятору.

Терминал

Когда только начинал собирать материал для этой статьи, узнал что в первых MacOS (внезапно) не было консоли и абсолютно все приложения были только и исключительно графическими:

Консоль появилась только в MacOS X, в 2001м году.
Консоль появилась только в MacOS X, в 2001м году.

Поэтому тот факт, что классическое консольное приложение на С:

#include <stdio.h>

int main(int argc, char** argv)
{
    printf("Hello, world.\n");
    printf("\n(Press Return)\n");
    getchar();
    return 0;
}

можно собрать в графическое, с вот таким псевдо-терминалом в комплекте:

Тоже заслуга тулчейна Retro68 и интересной линковки при сборке:

LDFLAGS=-lRetroConsole

Сборка на FreeBSD

Тулчейн официально поддерживает несколько популярных операционных систем:

  • Linux, Mac OS X or Windows (via Cygwin)

И действительно совершенно спокойно, без каких-либо проблем и нюансов собирается на линуксе.

Что мне показалось слишком скучным и унылым, поэтому ради гусарской забавы портировал этот проект на FreeBSD.

Всегда так делаю.

Если вам такие забавы не близки, сообщаю что для линукса достаточно по шагам выполнить инструкцию в README.

И все сразу будет хорошо, только не у вас.

Ну а мы как обычно пойдем путем хардкора — соберем весь тулчейн на FreeeBSD с нуля.

Кстати форк Retro68 с поддержкой сборки на FreeBSD выложен в отдельном репозитории.

Первым делом надо установить ряд инструментов для разработки и системных библиотек:

pkg install cmake gmp mpfr mpc boost-all bison flex texinfo ruby

Забираем исходники и поскольку репозиторий большой — выгружаем без истории коммитов (ключ depth):

git clone --depth 1 https://github.com/autc04/Retro68.git

Выкачиваем зависимые репозитории:

git submodule update --init
git pull
git submodule update

Теперь надо немного изменить скрипт сборки, для учета особенностей FreeBSD.

Первым делом заменяем вызовы make на gmake, поскольку FreeBSD использует свою версию утилиты make, несовместимую с GNU-версией.

Которая конечно также присутствует в пакетах, но называется gmake.

Дальше необходимо переделать вызовы configure для вложенных проектов из скрипта сборки, суть правок: 

пробросить указание на использование библиотек из каталога /usr/local, куда устанавливаются все внешние с точки зрения системы библиотеки в FreeBSD.

Например для строки:

$SRC/binutils/configure --target=m68k-apple-macos --prefix=$PREFIX --disable-doc 

необходимо дописать в конец:

CFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib 

Таким образом переменные CFLAGS и LDFLAGS будут переданы скрипту configure в качестве аргумента при вызове.

Чуть сложнее со сборкой GCC (да, мы тут собираем компилятор из исходников), строка:

$SRC/gcc/configure --target=m68k-apple-macos --prefix=$PREFIX \
				--enable-languages=c,c++ --with-arch=m68k --with-cpu=m68000 \
				--disable-libssp MAKEINFO=missing 

Тут уже нельзя использовать переменные окружения CFLAGS и LDFLAGS из-за особенностей сборки GCC и необходимо передавать пути для ключевых библиотек специальными аргументами:

--with-mpc=/usr/local --with-mpfr=/usr/local --with-gmp=/usr/local --with-isl=/usr/local --with-libiconv-prefix=/usr/local	

Дописав их в конец вызова configure.

Таким же образом необходимо поправить еще два места в скрипте сборки: раз и два.

После чего можно запускать сборку.

Запускается она из отдельного каталога:

mkdir ../Retro68-build
cd ../Retro68-build
../Retro68/build-toolchain.bash

После весьма продолжительной (даже на мощном компьютере) и жутко выглядящей сборки, по накалу страстей на экране напоминающей знаменитый make buildworld из FreeBSD:

..все упадет на вот такой «страшной» ошибке:

Для исправления ситуации, надо всего лишь изменить один import в файле portable_endian.h , в этом месте:

#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)

#	include <sys/endian.h>

Суть проблемы в том что расположение заголовка endian.h поменялось и теперь его необходимо включать без указания каталога sys:

#	include <endian.h>

Исправляем импорт и перезапускаем сборку.

Через какое-то время напряженной работы, в очередной раз напугав обывателя простыней текста, сборка.. опять упадет.

Таков путь, что поделать.

На этот раз падение произойдет на стадии сборки из cmake:

Несмотря на весь внешний ужас, исправляется эта ошибка очень просто (если знать где копать разумеется):

удалением импорта этого заголовка из файла PEFTools/MakePEF.cc

Поскольку его содержимое в последних версиях FreeBSD добавили в stdlib.

Так что смело удаляем импорт и перезапускаем сборку.

Следующее падение сборки и следующая проблема заключается в пересечении заголовков — системного, из самой FreeBSD и локального — из библиотеки libelf:

Тут к сожалению я не нашел изящного решения, достойного быть упомянутым в летописях, поэтому сделал «колхоз»:

скопировал файл gelf.h из libelf/include в каталог Elf2Mac, и поменял ссылки на этот заголовок из глобального на локальный.

Было:

#include <gelf.h>

Стало:

#include "gelf.h"

Поменять ссылки необходимо во всех файлах проекта Elf2Mac где встречается данный импорт.

Наконец последняя ошибка сборки, связанная с особенностями FreeBSD:

Связана эта ошибка с тем, что часть необходимых структур для работы с сетью на FreeBSD вынесена в отдельный заголовок.

Для исправления достаточно добавить:

#include <netinet/in.h>

в файл LaunchAPPL/Client/TCP.cc

Так выглядит успешное завершение сборкиRetro68:

Готовый к использованию тулчейн будет находиться в каталоге toolchain, который стоит добавить в переменную окружения PATH.

Теперь переходим к эмулятору, поскольку живого Mac тех лет, на котором возможно запустить результат сборки у меня нет.

Эмулятор

Вообще эмуляторов винтажных Mac довольно много, поскольку маководы это сектанты очень увлеченные люди и многие из них используют и устаревшие компьютеры Apple и устаревший софт до сих пор, всячески отвергая прогресс и здравый смысл.

Такова сила бренда, созданного Стивом Джобсом.

Я буду использовать один из самых популярных и известных эмуляторов классических Macintosh:

Basilisk II is an Open Source 68k Macintosh emulator.

Разработан он был еще в начале 2000х, немецким инженером Christian Bauer, ныне активная разработка уже не ведется.

Поэтому я использовал более современный форк проекта, где решены вопросы совместимости с современным же окружением и обновлены используемые библиотеки.

И внезапно добавлен JIT (Just-In-Time Compiler).

Еще форкнутая версия немного стабильнее, с более адекватным поведением перехвата курсора.

Эмулятор использует следующие библиотеки, которые необходимо установить до попыток сборки:

pkg install sdl2 autoconf automake mpfr gmp

Теперь забираем исходники:

git clone https://github.com/kanjitalk755/macemu.git

Собираем:

cd macemu/BasiliskII/src/Unix
$ ./autogen.sh
$ gmake

В результате в текущем каталоге появится бинарник BasiliskII, которым и запускается эмулятор:

Для того чтобы запускать MacOS7 в эмуляторе, нужен загружаемый образ диска с установленной системой (или ее инсталлятор) и ROM-файл.

Образ диска можно взять например тут или тут, ROM-файл например отсюда или отсюда. Еще пара вариантов.

Еще вот тут находится крайне интересная табличка, с описанием практически каждого доступного ROM, его особенностей и работоспособности.

Напомню, что для этой статьи я эмулировал модель Quadra 600:

Для эмулятора использовался вот такой ROM-файл:

Указав в разделе «Volumes» путь к образу диска с MacOS7, затем в разделе «Memory/Misc» путь к ROM-файлу, можно наконец запускать эмулятор:

Теперь переходим к самому веселому — к разработке.

Разработка с помощью Retro68

Собственно после сборки Retro68, у вас будет все что нужно для разработки конечного софта, поскольку в каталоге build-target/Samples будут собранные примеры приложений:

Эти примеры собираются автоматически, во время сборки самого тулчейна Retro68, в качестве дополнительных тестов работоспособности.

Для сборки во всех случаях используется cmake, исходники находятся в каталоге Retro68/Samples.

Чтобы собрать проект с HelloWorld отдельно, выполняем:

сd /opt/src/Retro68/Samples/HelloWorld
mkdir build
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=/opt/src/Retro68-build/toolchain/m68k-apple-macos/cmake/retro68.toolchain.cmake
gmake

Результат сборки:

Каждый пример при сборке генерирует образ диска, который можно сразу подключить к эмулятору:

Достаточно указать путь к dsk-файлу, который находится в каталоге build и запустить эмулятор:

На рабочем столе появится иконка с названием, совпадающим с названием примера:

После двойного клика по этой иконке, раскроется окно с содержимым этого виртуального диска в виде единственного запускаемого файла:

Двойной клик по файлу HelloWorld запустит наше приложение.

Примеры приложений

Как уже упоминал, в составе Retro68 есть довольно много демонстрационных приложений.

Каждый пример раскрывает аспект разработки и создан так, чтобы его можно было использовать в качестве основы для собственных проектов.

Автор тулчейна постарался, поэтому все примеры логичны, просты и понятны.

Raytracer

Retro68/Samples/Raytracer

Трассировщик лучей, реализующий генерацию псевдотрехмерной картинки в реальном времени. Собственно процесс отрисовки в этом приложении как раз показан на заглавной картинке к статье.

В проекте на самом деле присутствуют сразу две реализации, одна на С, другая на C++, видимо для сравнения во время работы.

Так выглядит финальный результат рендеринга:

Проект ценен в первую очередь как демонстрация расчетной логики и операций с числами с плавающей точкой.

Dialog

Retro68/Samples/Dialog

Демонстрация работы с модальными диалогами — как задавать размеры и расположение элементов, как создавать формы ввода, как получать вводимые данные и так далее.

В работе:

WDEF

Retro68/Samples/WDEF

Демонстрация работы с многооконными интерфейсами (MDI), создание окна с кастомным оформлением и работа с ресурсами окон.

Так это выглядит в действии:

Кратко по остальным примерам, реализующим какую-то специфическую логику.

MPWTool

Retro68/Samples/MPWTool

Реализует расширение для MPW (Macintosh Programmer's Workshop), точнее для MPWShell, с обработкой запуска оттуда и корректного возвращения в консоль.

SharedLibrary

Retro68/Samples/SharedLibrary

Реализует разделяемую библиотеку и приложение, его использующее в рантайме.

SystemExtension

Retro68/Samples/SystemExtension

Пример простейшего расширения для самой операционной системы MacOS 7, в данном случае вся видимая логика заключается в показе новой иконки при запуске ОС.

Launcher

Retro68/Samples/Launcher

Реализует запуск сторонних приложений через перетаскивание (Drag&Drop).

"Masters of Hardcore" - вбейте в поиск если не жалко уши и соседей.
"Masters of Hardcore" - вбейте в поиск если не жалко уши и соседей.

Настоящий хардкор

Вы же не думали, что я успокоюсь на банальной сборке тулчейна с эмулятором под FreeBSD и запуске пары тестов?

Праздник должен продолжаться:

Clapkit (CLassic APplication KIT) is a framework for developing applications for Classic Mac OS (System 7.x - Mac OS 9.x), basically a C++/object-oriented wrapper for Macintosh Toolbox functions.

Да, глаза вас не подводят, это самый настоящий современный графический фреймворк для компьютеров начала 90х.

Так это выглядит:

Картина неизвестного питерского художника "Мак под маком".
Картина неизвестного питерского художника "Мак под маком".

Так выглядит код:

#include <ckApp.h>

int main() {

	CKApp* app = CKNew CKApp();
	
	app->CKNewMsgBoxNote("Hello world!", nullptr, "OK", 
	                            nullptr, [app](int button) {
		app->CKQuit();
	});
	// Run loop: without this, your app will quit as soon as it launches.
	while (!app->CKLoop(5));
	delete app;
	return 0;
}

Так выглядит мое тестовое приложение на современном C++, собранное с помощью cmake на FreeBSD и запущенное на MacOS 7 из 90х:

Поддержки юникода тогда еще не было, как и локализации на русский, поэтому "великий и могучий" есть лишь в комментариях.
Поддержки юникода тогда еще не было, как и локализации на русский, поэтому "великий и могучий" есть лишь в комментариях.

Ну что дорогие читатели, ударим шизой по осенней хандре?

MPW

Конечно ударим, но прежде стоит рассказать про нормальную разработку для продукции Apple, как это происходило у нормальных людей.

Разумеется на маках всегда была собственная среда разработки:

Macintosh Programmer's Workshop (MPW) is a software development environment for the Classic Mac OS operating system, written by Apple Computer.

Ее вполне можно использовать до сих пор, благо образов дисков в сети хватает.

Именно так я и советую поступать (т.е. использовать MPW) в случае любого более-менее серьезного проекта.

Где и когда закончатся возможности кросс-компиляции и вы опустите руки — предсказать не берусь.

Как-то так MPW выглядит в эмуляторе:

Тут находится отличная статья по созданию «Hello, world» на MPW в эмуляторе, которой вполне хватит для начала приключений.

Но в этот раз нам нужен не MPW, а только его часть — системные заголовки, которые мы будем использовать для злодейства сборки.

В Retro68, в каталоге InterfacesAndLibraries находится файл Readme.md, содержимое которого явно намекает на дальнейшие действия:

Find a copy of Apple's Universal Interfaces 3.x (preferrably 3.4) and put them here.

Вообще «Find a copy» это такой толерантный и социально приемлемый синоним старого доброго «скачать варез», прямо из 90х.

Потому как купить официально старое ПО не представляется возможным, если подобное вдруг придет в голову. Даже если компания-разработчик существует (в случае Apple), старые версии своих продуктов она так просто не продаст.

Потому что есть поддержка, гарантии и защита прав потребителей.

И репутация компании.

Поэтому сообщаю по секрету, что «найти» эти самые универсальные интерфейсы можно например тут:

wget https://henlin.net/images/Interfaces\&Libraries.zip
unzip "Interfaces&Libraries.zip" -d /opt/src/Retro68/InterfacesAndLibraries

Ссылка кстати взята из другой интересной статьи, в которой автор реализует слой рендера для фреймворка Nuklear, о котором я уже внезапно писал.

Так это демо выглядит в работе, порт эмулятора на Javascript внезапно оказался встроен в статью:

JS-версия загружается довольно долго, браузер может подвисать.
JS-версия загружается довольно долго, браузер может подвисать.

Хотя этот проект успешно собрался из исходников, при попытке запуска в эмуляторе программа на базе Nuklear вылетала.

Разгадка оказалась в необходимости использовать другой эмулятор и собственно полностью другую эмулируемую систему.

Так что расскажу про связку из Nuklear, QuickDraw и Retro68 в другой раз, чтобы не раздувать эту статью до совсем уж космических размеров.

А пока для затравки, показываю эту связку в действии:

Не пугайтесь размеров элементов интерфейса в эмуляторе - игрался с зумом, проиграл.
Не пугайтесь размеров элементов интерфейса в эмуляторе - игрался с зумом, проиграл.

Но вернемся к фреймворку ClapKit, благо история с ним еще не закончена.

После распаковки заголовков из MPW, необходимо перезапустить сборку самого тулчейна Retro68:

cd Retro68-build
../Retro68/build-toolchain.bash

И наконец можно приступать к половым утехам с самим ClapKit.

Забираем исходники:

git clone https://github.com/macinlink/clapkit.git

Сборка построена на cmake, но перед тем как собирать Clapkit, необходимо указать путь к Retro68 в скрипте сборки:

# Ensure Retro68 toolchain is set BEFORE defining project
set(CMAKE_TOOLCHAIN_FILE "/opt/src/Retro68-build/toolchain/m68k-apple-macos/cmake/retro68.toolchain.cmake")

и чуть ниже:

# Define Retro68 path properly
set(RETRO68_PATH /opt/src/Retro68-build/toolchain/m68k-apple-macos)

К сожалению в проекте есть небольшой баг с пропущенными заголовками, из-за которых он не собирается.

Для исправления достаточно добавить в include/ckTypes.h два импорта:

#include <Quickdraw.h>
#include <Events.h>

В репозитории проекта находится готовый Showcase с демонстрацией возможностей фреймворка, на базе которого я и делал свое тестовое приложение (см. ниже).

Так выглядит сам Showcase в работе:

Напоминаю, что все это создано с помощью кросс-компиляции и затем запущено в целевой MacOS 7.
Напоминаю, что все это создано с помощью кросс-компиляции и затем запущено в целевой MacOS 7.

Выглядит заметно веселее, чем примеры из самого Retro68, согласитесь.

Тестовый образец

Я не хотел сразу пускаться во все тяжкие и портировать Doom/Quake/Cyberpunk 2077 под винтажные Маки из 90х — надо же оставить место воображению, поэтому мой тестовый образец для этой статьи это в первую очередь пример автономного, отделяемого приложения с минимальной логикой.

Которое собирается без привязки к иерархии каталогов Clapkit или Retro68.

Проект выложен в отдельном репозитории на Github.

Так мое приложение выглядит в работе на MacOS 7:

Все что оно делает:

отображает меню «Yo» в Finder, с единственным пунктом «Run me», по клику на который вызывается модальный диалог с двумя кнопками. По нажатию на «Quit» произойдет завершение работы программы.

Фигня-фигней, но только разработано и собрано оно полностью и целиком вне инфраструктуры MacOS и без средств разработки от Apple, даже без техники Apple.

На современном С++, под FreeBSD темной питерской ночью.

Так выглядит основной код на C++, файл main.cpp:

#include "main.h"
#include <ckMenu.h>

CKTestAlexs* app;

int main() {
    // класс переименован ради тестов
	app = CKNew CKTestAlexs();
    // false - не использовать стандартные пункты меню
	CKMenuBar* menuBar = CKNew CKMenuBar(false);
	// добавляем свой пункт меню в Finder
	CKMenu* menuTests = CKNew CKMenu("Yo");
	menuBar->AddMenu(menuTests);
    // затем элемент в выпадающем списке
    CKMenuItem* item = CKNew CKMenuItem("Run me", 'R', [&item](CKEvent e) {
            // по клику произойдет отображение стандартного 
            // модального диалога
			app->CKNewMsgBoxNote("Welcome to old school!", 
			    "Hello world", "Mkay", "Quit", [](int button) {
            // если нажали Quit - завершить работу приложения.    
            if (button == 0) {
				app->CKQuit();
			 }
		   });
		});
	// связывание элемента с пунктом меню	
    menuTests->AddItem(item);
    // связывание меню с приложением
    app->CKSetMenu(menuBar);
    // бесконечный цикл ожидания для отрисовки, 
    // без которого приложение сразу завершится
	while (!app->CKLoop(5));
	delete app;
	return 0;
}

Класс описан в заголовке main.h, декларации методов я убрал за ненадобностью:

#include <ckApp.h>

class CKTestAlexs : public CKApp {	
};

Так выглядит скрипт сборки для cmake, как можно заметить тут происходит автоматическое скачивание родительского фреймворка Clapkit и наложение патча с пропущенными импортами (описаны выше):

cmake_minimum_required(VERSION 3.16)

# Ensure Retro68 toolchain is set BEFORE defining project
set(RETRO68_PATH "/opt/src/Retro68-build/toolchain/m68k-apple-macos")
set(CMAKE_TOOLCHAIN_FILE "${RETRO68_PATH}/cmake/retro68.toolchain.cmake")

project(clapkit VERSION 1.0)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# Include Retro68 headers
include_directories(${RETRO68_PATH}/include)

# Set default build type to Release if not specified
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (Debug or Release)" FORCE)
endif()

set(CLAPKIT_LOCAL_PATH "${CMAKE_SOURCE_DIR}/../../")
option(USE_LOCAL_CLAPKIT "Use local checkout of clapkit instead of fetching from Git" ON)

if(USE_LOCAL_CLAPKIT AND EXISTS "${CLAPKIT_LOCAL_PATH}/CMakeLists.txt")
    message(STATUS "Using local clapkit at ${CLAPKIT_LOCAL_PATH}")
    add_subdirectory(${CLAPKIT_LOCAL_PATH} clapkit)
    set(USE_LOCAL_CLAPKIT ON)
else()

	set(cktypes_patch git apply ${CMAKE_SOURCE_DIR}/patches/cktypes.patch)

    message(STATUS "Fetching clapkit from GitHub...")
    include(FetchContent)
    FetchContent_Declare(
        clapkit
        GIT_REPOSITORY https://github.com/macinlink/clapkit.git
        GIT_TAG main
        PATCH_COMMAND ${cktypes_patch}
        UPDATE_DISCONNECTED 1
    )
    FetchContent_MakeAvailable(clapkit)
    set(USE_LOCAL_CLAPKIT OFF)
endif()

# Add application
add_application(CKTestAlexs
    TYPE "APPL"
    CREATOR "CKTS"
    main.cpp
    cktest.r
)

# Debug vs Release settings
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "Configuring Debug Build")
    target_compile_definitions(CKTestAlexs PRIVATE kCKAPPDEBUG=1)
    target_compile_definitions(clapkit PRIVATE kCKAPPDEBUG=1)

    set_target_properties(CKTestAlexs PROPERTIES 
        COMPILE_FLAGS "-O0 -Wall -g -fdata-sections -ffunction-sections"
        LINK_FLAGS "-Wl,--mac-single"
    )

elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
    message(STATUS "Configuring Release Build")
    target_compile_definitions(CKTestAlexs PRIVATE NDEBUG)  # Disable debugging in Release
    target_compile_definitions(clapkit PRIVATE NDEBUG)

    set_target_properties(CKTestAlexs PROPERTIES 
        COMPILE_FLAGS "-s -O1 -Wall -fmerge-all-constants -fmerge-constants -fstrict-aliasing -fomit-frame-pointer -fdata-sections -ffunction-sections -finline-functions"
        LINK_FLAGS "-Wl,--mac-single -Wl,--mac-strip-macsbug"
    )
endif()

# Link clapkit
target_link_libraries(CKTestAlexs PRIVATE clapkit)

Поскольку пути к Retro68 задаются внутри скрипта сборки, а фреймворк Clapkit автоматически скачивается из Github, сама сборка максимально упрощена:

mkdir build
cd build
cmake ..
gmake

При успешной сборке будет создан образ диска с приложением внутри, который можно легко подцепить в эмулятор:

Продолжение банкета

Ниже несколько интересных (но все еще социально одобряемых) идей, на тему что еще можно сделать с эмулятором винтажного Macintosh.

Для начала, можно довести накал программерского угара до предела — интегрировав Retro68 с полноценным IDE и отладчиком:

Developing vintage 68K Macintosh apps with CodeLite IDE, Retro68 and pce-macplus emulator

С автодополнением из среды разработки:

С запуском собранного приложения в эмуляторе сразу из среды разработки:

И даже с удаленной отладкой через эмуляцию последовательного порта:

Есть даже отдельное видео с демонстрацией работы:

Следующая замечательная идея в моем списке это очевидная попытка использовать Rust для кросс-компиляции под старые Mac:

Building a Classic Mac OS App in Rust

Нет, автор не шутит, он действительно это сделал:

Through some luck and a little persistence I have actually managed to get Rust code running on classic Mac OS (I’ve tried Mac OS 7.5 and 8.1)

Репозиторий находится тут, проект достаточно свежий (2023 год) и вполне рабочий.

Но предупреждаю сразу, что от исходного кода может случиться сердечный приступ, если любите классический C — на это лучше не смотреть.

Так оно выглядит в работе:

Кстати приложение сетевое, так что на Rust реализована не только работа с интерфейсом, но и с TCP/IP и даже HTTP протоколом.

Для операционной системы из 1994 года.

В догонку

Напоследок и буквально «одной строкой» еще несколько интересных проектов, так или иначе использующих Retro68, которые я тоже когда‑нибудь обязательно разберу.

Как только в сутках появится 25й час.

sdl2macos9

https://github.com/laulandn/sdl2macos9

Порт знаменитой библиотеки SDL для работы на классических Mac:

Despite the name, for MacOS 7/8/9 m68k and ppc, and AmigaOS 3 m68k

peppc

https://github.com/Wack0/peppc

Форк тулчейна Retro68 для сборки приложений под.. Windows NT, причем для архитектуры PowerPC (!):

GCC 9 (Retro68) fork - compiler targeting PowerPC Windows NT (with PASM.EXE assembler and VC4.x linker)

csend

https://github.com/matthewdeaves/csend

Автор в 2025м году пилит P2P-мессенджер для работы как на обычном Linux/BSD так и на классических Mac:

CSend is a cross-platform peer-to-peer chat application written in C, supporting both modern POSIX systems and Classic Macintosh (System 7.x). It demonstrates network programming across different eras of computing, from modern multi-threaded applications to single-threaded GUI applications.

Видео с демонстрацией работы:

P.S.

За время написания этой статьи погода успела радикально поменяться и сегодня уже идет красивый зимний снежок, создавая новогоднее настроение.

Снято сегодня, на Выборгской набережной.
Снято сегодня, на Выборгской набережной.

Более вольный оригинал статьи как обычно в нашем блоге, форк Retro68 с поддержкой сборки на FreeBSD выложен в отдельном репозитории на Github и пока вы читаете эти строки — идет переписка с автором о включении этой логики в основную ветку.

Комментарии (3)


  1. adrozhzhov
    24.11.2025 16:05

    В линейке было несколько процессоров, с очень удобными машинными кодами.

    На Базе 68040 делалась нортелевская Option 11e.

    На которой был vxworks, с кросплатформенной компиляцией под него с Санов на спарках (на алголо-60-подобном языке, компиляторы для которого делали в Новосибирске). Давно это было...


    1. alex0x08 Автор
      24.11.2025 16:05

      Зато я видел бекстейдж этого фильма!


  1. Jijiki
    24.11.2025 16:05

    можно добавить нескучное сжатие из будущего jpeg-xl, или завелосипедить на С ТГА -труколор сжатие чередующихся длин серий ) ух, первый просто фантастика, второй без магии на выходе, но если велосипедить, то мозг поломает нормально ), что интересно на С в строках второй алгоритм наитривиальнейший - просто можно недели ловить эти пиксели )

    зная что поиск пикселей не поможет от примера со строками )

    Скрытый текст
        uint8_t* pString="sssssssssssssssaaaaaaaaaaaaaaaaaafffffffffffffffffffffaaaaaa";
        size_t len=strlen(pString);
        int count=1;
        for(int i=0;i<len;i+=1){
            if( (pString[i]==pString[i+1]) && i+1<=len  ){
                count++;
                // i+=count;
            }
            else {
                printf("%d%c",count,pString[i]);
                // i+=count;
                count=1;
            }
        }