Компиляция Julia с помощью C! (Это было не весело.)

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

Хотя это не означает, что создание исполняемых файлов в Julia невозможно, определенно требуется гораздо больше усилий, чтобы получить надлежащий исполняемый файл из Julia. Я подошел к этой самой проблеме где-то в начале этого года (или в конце прошлого года, я не помню) и помню, как потратил много часов, пытаясь получить хоть какой-то результат. В конце концов, я сдался и положил клавиатуру, и это было очень удручающим поражением для меня. С тех пор в компилятор Джулии было внесено множество обновлений, и я очень оптимистичен, что теперь может быть способ получить скомпилированный исполняемый файл. Судя по тому, что мне сказали, это, конечно, еще не оптимально, но теперь это возможно.

Наше приложение

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

  • Я знаком с GTK в Python, Vala и C.
  • Я использую Gnome.
  • У Юлии налаженный порт GTK.
  • Приложения GTK довольно легко и быстро создавать.
  • GTK использует общесистемную зависимость от Linux, gtklib3, что означает, что будет очень легко протестировать упаковку зависимостей.

Все это огромные преимущества, которые сделают фактическую письменную часть этого опыта довольно простой. Хотя я знаком с Gtk, а также с его классами и функциями, перенос библиотеки в Julia, безусловно, интересен.

Первым шагом к созданию графического пользовательского интерфейса с помощью GTK является создание окна.

(предсказуемо)

Мы можем сделать это так же, как в Python или Vala. Хотя в Python классы, конечно, не экспортируются, поэтому вам придется вызывать Gtk, например:

# Python 
import Gtk
win = Gtk.GtkWindow(title, width, height)
---------
# Julia
using Gtk
win = GtkWindow(title, width, height)
---------
# Vala
using Gtk
var window = new Gtk.ApplicationWindow (title, width, height);

Очевидно, что все они имеют общие различия в синтаксисе, но где код действительно становится юлианизированным, так это с удалением таких функций, как add (), и заменой этих функций классическими функциями Julia Base, которые мы все знаем и любим.

win = GtkWindow("this program is literally just an audio icon", 1280, 720)
b = GtkVolumeButton()
push!(win,b)

Кроме того, мы можем использовать обычный метод showall (), чтобы показать наше творение !:

showall(win)

Я никогда не видел лучшего дизайна UI / UX.

Компиляция

Чтобы скомпилировать наш код, нам нужно сохранить его как файл .jl. Я открыл G-edit и записал код, который только что продемонстрировал, однако теперь запуск этого файла через bash дает следующий результат:

ничего такого

Что ж, может быть, мы ничего не видим, но, возможно, нам стоит спросить саму Юлию, есть ли у нее какой-либо вклад в эту ситуацию?

Вроде пришли к взаимопониманию, ничего не произошло. Конечно, я понятия не имею, что делать на самом деле, но, к счастью, мы можем проконсультироваться с моим хорошим другом и соучастником преступления:

Google.

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



Помня об этом, я понял, что, возможно, пришло время создать виртуальную среду.

А теперь я собираюсь добавить Gtk в эту новую среду:

На этот раз я также решил вместо этого попробовать запустить код, включив его из REPL.

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

НА САМОМ ДЕЛЕ компиляции

Что касается упаковки и компиляции двоичных файлов с помощью Julia, есть два основных подхода к зависимостям. Наш первый вариант включает в себя сохранение загруженных пакетов и скомпилированных функций в файл, который мы можем заставить Джулию выполнить при запуске. Второй и более жизнеспособный для этого варианта использования вариант - скомпилировать весь проект в перемещаемое скомпилированное приложение. Это генерирует набор других файлов вместе с исполняемым файлом, который может быть запущен на компьютере, на котором может даже не быть установлена ​​Julia (и мы это проверим).

Чтобы компиляция пакета действительно работала, нам сначала нужно реструктурировать эти файлы в «стиле Джулии», что включает создание как каталога src, так и Project.toml. Начнем с файла Project.toml.

Теперь нам нужно создать новый каталог с именем src и переместить туда наш исходный файл.

Теперь, конечно, нам нужно будет получить UUID Джулии Gtk и добавить его в раздел [deps] в нашем файле проекта (да, я почти забыл, что у нас была зависимость). После этого нам также нужно будет добавить «основная» функция для нашего файла Julia, для окончательного результата, который будет выглядеть примерно так:

using Gtk
function julia_main()
 win = GtkWindow("this is definitely not a window title", 400, 200)
 b = GtkButton("DO NOT click this button.")
 push!(win,b)
 showall(win)
end

На следующем этапе мы повторно активируем нашу среду Pkg и добавим в нее компилятор пакетов Джулии.

Теперь создадим образ системы с помощью этой команды:

julia --startup-file=no --trace-compile=app_precompile.jl src/test.jl

Это создаст прекомпилированный файл с именем app_precompile.jl в нашем рабочем каталоге:

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

Этот код Julia предварительно компилирует образ нашей системы для создания исполняемого файла. Следует отметить, что нужно будет добавить несколько параметров для компиляции для Windows. Конечно, я использую GNU + Linux, поэтому я просто настроил его для компиляции для X11. Вот параметры, которые вы бы добавили для Windows:

-Wl,--export-all-symbols

Наконец, нам нужно будет написать немного C, чтобы создать небольшой «компилятор» для нашего конкретного пакета Julia. Итак, давайте быстро коснемся файла C:

touch compile.c

Теперь давайте откроем этот файл в любом текстовом редакторе, мне нравится Nano.

nano compile.c

Первое, что нам нужно сделать, это включить некоторые стандартные заголовки C, которые понадобятся компилятору Julia, это строка и stdin.

#include <string.h>
#include <stdint.h>

Далее мы также добавим заголовки, необходимые Джулии. Есть два заголовка, которые жизненно важны для использования Julia, но если вы используете пакет, перенесенный на C, вы можете также включить его заголовки. Поскольку это случай с GTK, я пошел дальше и включил его также в Julia.h и uv.h:

#include "uv.h"
#include "julia.h"
#include <gtk/gtk.h>

Для окончательного результата, который выглядит примерно так:

Далее мы запустим это из заголовка Julia:

JULIA_DEFINE_FAST_TLS()

Затем объявите прототип точки входа C в нашем приложении:

int julia_main();

Затем мы создадим новый класс с именем main, который будет принимать два параметра, предоставленных основной операцией Julia. Мы также вызовем uv_setup_args (), который сообщит UV аргументы, предоставленные Джулией:

int main(int argc, char *argv[]) {
uv_setup_args(argc, argv);

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

libsupport_init();

Затем мы настроим параметры Джулии, предоставленные заголовком Julia.h:

jl_options.image_file = JULIAC_PROGRAM_LIBNAME;     julia_init(JL_IMAGE_JULIA_HOME);

Обратите внимание, что JULIAC_PROGRAM_LIBNAME будет параметром, который мы можем добавить в компилятор C после завершения создания этого файла. Затем мы установим аргументы Julia так же, как и с uv:

jl_set_ARGS(argc, argv);

Теперь нам нужно установить для параметра PROGRAM_FILE значение argv [0]. Это, конечно же, будет параметром, чтобы мы могли повторно использовать этот компилятор с любым файлом Julia.

jl_set_global(jl_base_module,         jl_symbol("PROGRAM_FILE"), (jl_value_t*)jl_cstr_to_string(argv[0]));

Теперь мы собираемся установить аргументы Джулии в качестве основы.

jl_array_t *ARGS = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("ARGS"));     jl_array_grow_end(ARGS, argc - 1);     
for (int i = 1; i < argc; i++) {         
jl_value_t *s = (jl_value_t*)jl_cstr_to_string(argv[i]);         jl_arrayset(ARGS, s, i - 1);     
}

И с этим мы можем закрыть этот класс и вызвать рабочую функцию:

int ret = julia_main();

Затем мы добавим хук выхода с заголовком Julia:

jl_atexit_hook(ret);     
return ret;

Вот мой последний файл:

(Так что не слишком много C)

Вот моя последняя команда компиляции:

gcc -DJULIAC_PROGRAM_LIBNAME=\"sys.so\" -o MyApp compile.c sys.so -O2 -fPIE     -I'/home/kc/julia/include/julia'     -L'/home/kc/julia/lib'     -ljulia     -Wl,-rpath,'/home/kc/julia/lib:$ORIGIN'

И вот мы это сделали!

У нас есть скомпилированный двоичный файл, и при нажатии мы получаем:

Заключение

Я даже не могу солгать тебе,

это была своего рода боль.

Сборник Джулии определенно еще не совсем готов. Разобраться в этом, конечно, было неинтересно, и борьба усугублялась отсутствием документации о том, как это сделать, но, судя по тому, что я видел, на самом деле не так далеко. Я с нетерпением жду потенциального будущего, которое может иметь компиляция файлов Julia! Я думаю, что у Julia так много применений как универсального языка программирования высокого уровня, который еще предстоит изучить из-за отсутствия удобной компиляции. Разумеется, понятно, почему что-то подобное может быть настолько сложным, но я надеюсь, что в моем прекрасном прекрасном будущем у нас будет опыт, более похожий на компиляцию исполняемого файла с другого языка.