it-history
October 12, 2023

GNUstep: разрывая все шаблоны

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

Внимание! Начинается форматирование мозга!
Саундтрек к посту

О чем речь

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

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

Даже краткая аннотация из википедии легко подрывает пердаки неподготовленным разработчикам:

GNUstepсвободная реализация Cocoa (ранее OpenStep) — объектно-ориентированного API (Objective-C) для объектно-ориентированных операционных систем.

Objective-C (основной язык разработки под MacOS и iOS)и свободная реализация Cocoa (самый главный UI/UX фреймворк Apple)— расскажите об этом типичному разработчику под Mac, потратившему несколько тысяч долларов на обязательную покупку железа Apple и платную подписку на XCode. Увидите как у человека начинается нервный тик.

Еще можно троллинга ради сдать тестовое задание для MacOS Developer:

#import <AppKit/AppKit.h>
int main(int argc, const char *argv[])
{
  return NSApplicationMain (argc, argv);
}

Код выше ничем не отличается от стандартного main() на маковском Objective-C. Вот только никакого отношения к Apple и MacOS он не имеет.

И даже если копнуть чуть поглубже:

@implementation CalcBrain: NSObject
-(id) init
{
  [super init];
  result = 0;
  enteredNumber = 0;
  operation = none;
  fractionalDigits = 0;
  decimalSeparator = NO;
  editing = YES;
  face = nil;
  return self;
}

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

Вот так выглядит showcase — тестовое приложение с демонстрацией функционала:

Вся королевская рать

История

Разумеется история появления такого чуда не менее эпичная:

Проект был начат Паулем Кунцем (Paul Kunz) с командой из Стенфордского Центра линейного ускорителя (Stanford Linear Accelerator Center) которым был нужен порт HippoDraw из NeXTSTEP на другую платформу. Вместо того, чтобы переписывать программу с нуля, используя ее архитектуру, разработчики решили переписать слой NeXTSTEP, от которого зависело приложение. Это была первая версия libobjcX.

Вот так выглядит хардкор в разработке: взять и переписать к х#ям существенный кусок операционной системы ради портирования одного приложения.

Диды могли, молодежь так больше не умеет

Справедливости ради в те времена графические библиотеки были сильно проще, а само действо происходило в закрытом НИИ на государственные гранты, что сильно отличается от современных реалий и сверхперегретого рынка ПО.

Разрыв номер раз

Итак, на дворе далекий 1994й год. Времена модемов, досок BBS, ФИДО и первых персональных компьютеров.

А кое-где и кое у кого уже был самый мать его настоящий Graphical Interface Builder:

Представляете это в 1994м году?

Вот снимок с еще ЭЛТ монитора тех лет:

Это не шутка и не прикол, самый настоящий конструктор интерфейсов был реализован еще в 1988 году.

Вот такое:

Gorm (Graphical Object Relationship Modeller) is a graphical user interface builder application. It is part of the developer tools of GNUstep. Gorm is the equivalent of Interface Builder that was originally found on NeXTSTEP, then OPENSTEP, and finally on Mac OS X. It supports the old .nib files as well as its own .gorm file format.

Было создано и работало задолго до рождения большинства читающих эту статью.

Вот вам еще скриншот с местной «косынкой»:

NeXTSTEP 0.8 запущенный в эмуляторе.

А теперь внимание на даты:

A preview release of NeXTSTEP (version 0.8) was shown with the launch of the NeXT Computer on October 12, 1988

Мне тогда было 5 лет а Советский Союз был крепок. Для сравнения, вот так примерно в те времена выглядел весь софт в СССР а затем в РФ:

Да, это FoxPro.

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

Еще один замечательный пример:

Видите открытый документ, с разными шрифтами и разметкой? А теперь смотрим на его далекого предка из 1988 года:

.wn это формат WriteNow:

WriteNow is a word processor application for the original Apple Macintosh and later computers in the NeXT product line. The application is one of two word processors that were first developed with the goal that they be available at the time of the Mac product launch in 1984, and was the primary word processor for computers manufactured by NeXT.[2

Эм вообщем 1984 и 1988 — те давние года. И вот такая красота:

Вот так выглядела сама рабочая станция:

Разрыв шаблона номер два

Компания NeXT давно закрыта, никакие ее продукты уже давно не продаются, даже само API OpenStep — заброшено, а «свободная реализация» в виде GNUstep на сегодняшний день выглядит как оживший труп из далекого прошлого.

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

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

Windows Bridge for iOS (codenamed "Islandwood") is an open-source middleware toolkit that allows iOS apps developed in Objective-C to be ported to Windows 10 by using Visual Studio 2015 to convert the Xcode project into a Visual Studio project.[7][9][10] An early build of Windows Bridge for iOS was released as open-source software under the MIT License on August 6, 2015, while the Android version was in closed beta.[7]
This "WinObjC" project is open source on GitHub. It contains code from various existing implementations of Cocoa Touch like Cocotron and GNUstep as well as Microsoft's own code that implements iOS frameworks using UWP methods. It uses a version of the LLVM clang compiler.[11]

Причем проект очень даже живой. Но выглядит это как-то как п#здец так:

Нет у Microsoft вкуса, увы.

Немного про сборку

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

Я использовал Mageia Linux 9, но описанное будет актуально и для других дистрибьютивов и ОС.

Поскольку проект GNUstep находится в полузаброшенном состоянии, лучше не пытаться ставить его из пакетов а собирать самостоятельно.

tools-make

Начнем не поверите со своей системы сборки:

The makefile package is a simple, powerful and extensible way to write
makefiles for a GNUstep-based project. It allows the user to write a
project without having to deal with the complex issues associated with
configuration, building, installation, and packaging. It also allows
the user to easily create cross-compiled binaries.

Да, я тоже офигел был удивлен.

Забираем это чудо:

git clone https://github.com/gnustep/tools-make.git

Собираем:

cd tools-make 
./configure --prefix=/opt/gnustep
make
make install

Тут надо сразу пояснить про префиксы, поскольку это указание на установку в нестандартное место, а значит вам придется добавлять путь /opt/gnustep в переменные окружения: либо в LD_LIBRARY_PATH либо в PATH.

Вообщем для продолжения сборки я делаю:

export PATH=/opt/gnustep/bin:$PATH

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

libs-base

Следующим шагом собираем местный commons — общую библиотеку:

The GNUstep Base Library is a library of general-purpose, non-graphical Objective C objects. For example, it includes classes for strings, object collections, byte streams, typed coders, invocations, notifications, notification dispatchers, moments in time, network ports, remote object messaging support (distributed objects), and event loops.

Да да, как видно любой большой и сложный проект содержит свою библиотеку с такими примитивами.

Вот что будет нужно иметь в системе из библиотек:

   * ffi (HIGHLY RECOMMENDED)
   * icu (HIGHLY RECOMMENDED)
   * gnutls (HIGHLY RECOMMENDED)
   * libxml2 (RECOMMENDED)
   * libcurl (RECOMMENDED)
   * libdispatch (RECOMMENDED)
   * libavahi (RECOMMENDED for NSNetServices)
   * libxslt (RECOMMENDED)
   * zlib (RECOMMENDED)
   * iconv (OPTIONAL, not needed if you have glibc)
   * openssl (OPTIONAL, not needed if you have gnutls)

Не забывайте что речь про development пакеты, а не сами библиотеки, т. е. нужны заголовочные .h файлы.

Забираем:

git clone https://github.com/gnustep/libs-base.git

Собираем:

./configure --prefix=/opt/gnustep
make
make install

libs-gui

Следующий шаг это сборка еще одной библиотеки, в этот раз графической, отвечающей за интерфейс:

The GNUstep gui library is a library of graphical user interface classes written completely in the Objective-C language; the classes are based upon Apple's Cocoa framwork (which came from the OpenStep specification). These classes include graphical objects such as buttons, text fields, popup lists, browser lists, and windows; there are also many associated classes for handling events, colors, fonts, pasteboards and images.

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

   * tiff (REQUIRED)
   * jpeg (RECOMMENDED)
   * png (RECOMMENDED)
   * gif or ungif (OPTIONAL)
   * aspell (OPTIONAL)
   * cups (OPTIONAL)
   * audiofile (OPTIONAL)
   * portaudio, v19 which has several API changes previous version
     (OPTIONAL)

В принципе каких-то проблем с этими библиотеками не было, оно все очень старое и сверхстабильное.

Забираем:

git clone https://github.com/gnustep/libs-gui.git

Собираем:

./configure --prefix=/opt/gnustep
make
make install

libs-back

Это еще одна библиотека, в этот раз — с реализацией «бекэнда» графического рендера.

Забираем:

git clone https://github.com/gnustep/libs-back.git

Собираем:

./configure --prefix=/opt/gnustep
make
make install

Перед запуском конечных приложений еще нужно выполнить:

defaults write NSGlobalDomain GSBackend libgnustep-xlib

Это установит бекэнд по-умолчанию, сама настройка сохранится в папке ~/GNUstep

На этом сборка самого GNUstep завершена и можно наконец начинать играться с примерами.

Примеры

Выложены на github, вместе с самим проектом GNUstep, все собирается и запускается.

Забираем:

git clone https://github.com/gnustep/tests-examples.git

Каждый тестовый пример — отдельный проект, собираемый через make, для сборки надо иметь путь /opt/gnustep в пути.

Вот так выглядит собранный пример с графическим редактором:

Уолтер Вайт и Патрик Бейтман c неодобрением смотрят на твои коммиты

Под конец расскажу еще про две интересные штуки из могилы мира древних и усопших.

Gorm

Это тот самый interface builder, которым я восхищался в начале статьи:

Gorm (Graphical Object Relationship Modeller) is a graphical user interface builder application. It is part of the developer tools of GNUstep. Gorm is the equivalent of Interface Builder that was originally found on NeXTSTEP, then OPENSTEP, and finally on Mac OS X. It supports the old .nib files as well as its own .gorm file format.

Выглядит он как п#здец вот так:

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

Забираем:

git clone https://github.com/gnustep/apps-gorm.git

Собирается все точно также через gnu make, поэтому путь к /opt/gnustep все также должен быть указан в окружении:

make

Но вот autotools для этого проекта нет, поэтому попытка использования make install приведет к залезанию в системный /usr/lib, что мне не надо.

Поэтому я для теста решил запускать этот gorm сразу из места сборки:

export LD_LIBRARY_PATH=/opt/gnustep/lib:./InterfaceBuilder/obj:./GormObjCHeaderParser/obj:./GormCore/GormCore.framework/Versions/0 
./Applications/Gorm/Gorm.app/Gorm 

За что я конечно буду гореть в аду для программистов.

Но потом.

Projectcenter: IDE для GNUstep

Да, у них в могиле есть свой IDE:

IDE - Integrated Development Environment. PC manages code and builds applications, frameworks and libraries, use GORM for Interface editing.

И его я смог собрать и запустить.

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

Забираем:

git clone https://github.com/gnustep/apps-projectcenter.git

Сборка аналогична описанной выше сборке проекта Gorm, по тем же причинам я снова сделал «закат солнца вручную»:

export LD_LIBRARY_PATH=/opt/gnustep/lib:./Framework/ProjectCenter.framework/Versions/0.7.0
./ProjectCenter.app/ProjectCenter

Код

Думаю будет интересно увидеть как выглядит код на Objective-C, я взял для примера тестовый проект калькулятора, который как раз и запущен на первом скриншоте в статье.

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

/* CalcFace.m: Front-end of Calculator.app
(тут был длинный копирайт)
 */
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#include "CalcFace.h"
#include "CalcBrain.h"

//
// Thanks to Andrew Lindesay for drawing the app icon,
// and for suggestions on the window layout.
//

@implementation CalcFace: NSWindow

-(id)init
{
  int i;

  // Display 
  display = [[NSTextField alloc] initWithFrame: NSMakeRect (40, 84, 182, 24)];
  [display setEditable: NO];
  // [display setScrollable: YES];
  [display setBezeled: YES];
  [display setDrawsBackground: YES];
  [display setAlignment: NSRightTextAlignment];
  
  // Numbers
  buttons[0] = [[NSButton alloc] initWithFrame: NSMakeRect (77, 3, 34, 24)];
  [buttons[0] setButtonType: NSToggleButton];
  [buttons[0] setTitle: @"0"];
  [buttons[0] setTag: 0];
  [buttons[0] setState: NO];
  [buttons[0] setAction: @selector(digit:)];
  [buttons[0] setKeyEquivalent: @"0"];
  
  buttons[1] = [[NSButton alloc] initWithFrame: NSMakeRect (114, 3, 34, 24)];
  [buttons[1] setButtonType: NSToggleButton];
  [buttons[1] setTitle: @"1"];
  [buttons[1] setTag: 1];
  [buttons[1] setState: NO];
  [buttons[1] setAction: @selector(digit:)];
  [buttons[1] setKeyEquivalent: @"1"];


..

  buttons[12] = [[NSButton alloc] initWithFrame: NSMakeRect (3, 30, 34, 24)];
  [buttons[12] setButtonType: NSToggleButton];
  [buttons[12] setTitle: @"+"];
  [buttons[12] setTag: addition];
  [buttons[12] setState: NO];
  [buttons[12] setAction: @selector(operation:)];
  [buttons[12] setKeyEquivalent: @"+"];
...
  
  buttons[17] = [[NSButton alloc] initWithFrame: NSMakeRect (3, 3, 71, 24)];
  [buttons[17] setButtonType: NSToggleButton];
  [buttons[17] setTitle: @"="];
  [buttons[17] setState: NO];
  [buttons[17] setAction: @selector(equal:)];
  [buttons[17] setKeyEquivalent: @"="];

  // Window
  [self initWithContentRect: NSMakeRect (100, 100, 225, 111)
	styleMask: (NSTitledWindowMask | NSMiniaturizableWindowMask)
	backing: NSBackingStoreBuffered
	defer: NO];

  [self setInitialFirstResponder: buttons[17]];
  [self setDefaultButtonCell: [buttons[17] cell]];
  
  for (i = 0; i < 18; i++)
    {
      [[self contentView] addSubview: buttons[i]];
      [buttons[i] release];
    }
  [[self contentView] addSubview: display];
  [display release];

  [self setTitle: @"Calculator.app"];
  [self center];

  return self;
}

-(void) dealloc
{
  [super dealloc];
}

-(void) setBrain: (CalcBrain *)aBrain
{
  int i;

  for (i = 0; i < 18; i++)
    [buttons[i] setTarget: aBrain];
}

-(void) setDisplayedNumber: (double)aNumber 
	     withSeparator: (BOOL)displayDecimalSeparator
	  fractionalDigits: (int)fractionalDigits
{
  
  if (displayDecimalSeparator)
    {
      [display setStringValue: 
		 [NSString stringWithFormat: 
			     [NSString stringWithFormat: 
					 @"%%#.%df", fractionalDigits], 
			   aNumber]];
    }
  else // !displayDecimalSeparator
    [display setStringValue: [NSString stringWithFormat: @"%.0f", aNumber]];
}

-(void) setError
{
  [display setStringValue: @"Error"];
}

- (void)applicationDidFinishLaunching: (NSNotification *)aNotification
{
  CalcBrain *brain;
  NSMenu *mainMenu;
  NSMenu *menu;
  NSMenuItem *menuItem;

    mainMenu = AUTORELEASE ([NSMenu new]);
  // Info
  [mainMenu addItemWithTitle: @"Info..." 
	    action: @selector (orderFrontStandardInfoPanel:) 
	    keyEquivalent: @""];
  // Edit SubMenu
  menuItem = [mainMenu addItemWithTitle: @"Edit" 	
		       action: NULL 
		       keyEquivalent: @""];
  menu = AUTORELEASE ([NSMenu new]);
  [mainMenu setSubmenu: menu forItem: menuItem];
  /*
  [menu addItemWithTitle: @"Cut" 
	action: @selector (cut:) 
	keyEquivalent: @"x"];
  */
  [menu addItemWithTitle: @"Copy" 
	action: @selector (copy:) 
	keyEquivalent: @"c"];
  /* 
  [menu addItemWithTitle: @"Paste" 
	action: @selector (paste:) 
	keyEquivalent: @"v"];
  */
  [menu addItemWithTitle: @"SelectAll" 
	action: @selector (selectAll:) 
	keyEquivalent: @"a"];

  [mainMenu addItemWithTitle: @"Hide" 
	action: @selector (hide:) 
	keyEquivalent: @"h"];  
  [mainMenu addItemWithTitle: @"Quit" 
	    action: @selector (terminate:)
	    keyEquivalent: @"q"];	
  
  [NSApp setMainMenu: mainMenu];
  
  brain = [CalcBrain new];
  [brain setFace: self];
  [self setBrain: brain];

  [self orderFront: self];
}
@end

А вот главный метод, отвечающий за запуск:

/* main.m: Main Body of Calculator.app
..
  */
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "CalcFace.h"

int main (void)
{ 
  ENTER_POOL
  CalcFace *face;
  NSApplication *app;

  app = [NSApplication sharedApplication];
  face = [CalcFace new]; 
  [app setDelegate: face];
  
  [app run];
  LEAVE_POOL
  return 0;
}

Совместимость

После размещения статьи задали интересный вопрос по поводу совместимости с оригинальной и современной Cocoa.

Был взят минимальный пример:

#import <Cocoa/Cocoa.h>
int main()
{
  [NSAutoreleasePool new];
  [NSApplication sharedApplication];
  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  id menubar = [[NSMenu new] autorelease];
  id appMenuItem = [[NSMenuItem new] autorelease];
  [menubar addItem:appMenuItem];
  [NSApp setMainMenu:menubar];
  id appMenu = [[NSMenu new] autorelease];
  id appName = [[NSProcessInfo processInfo] processName];
  id quitTitle = [@"Quit " stringByAppendingString:appName];
  id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
  action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
  [appMenu addItem:quitMenuItem];
  [appMenuItem setSubmenu:appMenu];
  id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(10, 10, 200, 200)
  styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
  autorelease];
  [window setTitle:appName];
  [window makeKeyAndOrderFront:nil];
  [NSApp activateIgnoringOtherApps:YES];
  [NSApp run];
  return 0;
}

И.. оно заработало! Пусть и с минимальными переделками:

Да это тот самый minimal Cocoa example

Теперь расскажу как собирать.

Во-первых был сделан GNUmakefile, по аналогии с тестовым примером калькулятора:

include $(GNUSTEP_MAKEFILES)/common.make

APP_NAME = TestApp
TestApp_OBJC_FILES = main.m
include $(GNUSTEP_MAKEFILES)/application.make

Также пришлось немного поменять заголовки, вместо:

#import <Cocoa/Cocoa.h>

необходимо указать:

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

Еще нужно закомментировать строку:

[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];

поскольку этого функционала нет в GNUstep.

Вот полный код main.m - модифицированный пример Minimal Cocoa Application:

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
int main()
{
  [NSAutoreleasePool new];
  [NSApplication sharedApplication];
//  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  id menubar = [[NSMenu new] autorelease];
  id appMenuItem = [[NSMenuItem new] autorelease];
  [menubar addItem:appMenuItem];
  [NSApp setMainMenu:menubar];
  id appMenu = [[NSMenu new] autorelease];
  id appName = [[NSProcessInfo processInfo] processName];
  id quitTitle = [@"Quit " stringByAppendingString:appName];
  id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
  action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
  [appMenu addItem:quitMenuItem];
  [appMenuItem setSubmenu:appMenu];
  id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(10, 10, 200, 200)
  styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
  autorelease];
  [window setTitle:appName];
  [window makeKeyAndOrderFront:nil];
  [NSApp activateIgnoringOtherApps:YES];
  [NSApp run];
  return 0;
}

Современность

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

Обратите внимание на даты в почте - почти 10 лет назад

Но сами графические примитивы и паттерны использования, заложенные в GNUstep - абсолютно точно устарели, слабо представляю как это можно соотнести с современным использованием десктопа, даже профессионального.

Windows

Ну и наконец последний подрыв на сегодня:

вы можете легко и просто поставить GNUstep на Windows

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

Даже инсталлятор есть, наслаждайтесь.

Итого

Конечно NEXTstep это уже история и даже сами подходы к построению UI/UX концептуально устарели, настолько что пользоваться этим откровенно сложно.

Практическое использование GNUstep малоосмысленно, поэтому остается только для фанатов.

Но рабочие станции конечно были очень красивые для тех лех: