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:
Вот снимок с еще ЭЛТ монитора тех лет:
Это не шутка и не прикол, самый настоящий конструктор интерфейсов был реализован еще в 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.
Было создано и работало задолго до рождения большинства читающих эту статью.
Вот вам еще скриншот с местной «косынкой»:
A preview release of NeXTSTEP (version 0.8) was shown with the launch of the NeXT Computer on October 12, 1988
Мне тогда было 5 лет а Советский Союз был крепок. Для сравнения, вот так примерно в те времена выглядел весь софт в СССР а затем в РФ:
Вообщем графический интерфейс — великая вещь, а основы могущества 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]
Причем проект очень даже живой. Но выглядит это как-то как п#здец так:
Немного про сборку
Как вы могли заметить, я запускал 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 в пути.
Вот так выглядит собранный пример с графическим редактором:
Под конец расскажу еще про две интересные штуки из могилы мира древних и усопших.
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; }
И.. оно заработало! Пусть и с минимальными переделками:
Во-первых был сделан 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 как ни странно до сих пор продолжается, вот так выглядит наверное самое популярное приложение на этом фреймворке:
Но сами графические примитивы и паттерны использования, заложенные в GNUstep - абсолютно точно устарели, слабо представляю как это можно соотнести с современным использованием десктопа, даже профессионального.
Windows
Ну и наконец последний подрыв на сегодня:
вы можете легко и просто поставить GNUstep на Windows
Даже инсталлятор есть, наслаждайтесь.
Итого
Конечно NEXTstep это уже история и даже сами подходы к построению UI/UX концептуально устарели, настолько что пользоваться этим откровенно сложно.
Практическое использование GNUstep малоосмысленно, поэтому остается только для фанатов.