Binary distributions

Эти заметки предназначены для тех, кто хочет собрать бинарное распределение Julia для распространения на различных платформах. Мы любим, когда пользователи распространяют Julia как можно шире, пробуя её на как можно большем количестве операционных систем и аппаратных конфигураций. Поскольку каждая платформа имеет свои специфические нюансы и процессы, которые необходимо соблюдать для создания портативного, работающего распределения Julia, мы разделили большинство заметок по ОС.

Обратите внимание, что хотя код для Julia равен MIT-licensed, with a few exceptions, дистрибутив, созданный с помощью описанных здесь техник, будет лицензирован по GPL, так как различные зависимые библиотеки, такие как SuiteSparse, лицензированы по GPL. Мы надеемся в будущем иметь дистрибутив Julia, не относящийся к GPL.

Versioning and Git

Makefile использует как файл VERSION, так и хеши коммитов и теги из репозитория git для генерации base/version_git.jl с информацией, которую мы используем для заполнения заставки и вывода versioninfo(). Если по какой-то причине вы не хотите, чтобы репозиторий git был доступен при сборке, вы должны предварительно сгенерировать файл base/version_git.jl с:

make -C base version_git.jl.phony

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

make full-source-dist

который создает архив julia-version-commit.tar.gz со всеми необходимыми зависимостями.

При компиляции помеченного релиза в репозитории git мы не отображаем информацию о ветке/хэше коммита на экране приветствия. Вы можете использовать эту строку, чтобы показать описание релиза длиной до 45 символов. Чтобы установить эту строку, вам нужно создать файл Make.user, содержащий:

override TAGGED_RELEASE_BANNER = "my-package-repository build"

Target Architectures

По умолчанию Julia оптимизирует свой системный образ под нативную архитектуру машины сборки. Обычно это не то, что вы хотите при сборке пакетов, так как это приведет к сбою Julia при запуске на любой машине с несовместимыми процессорами (в частности, на более старых с более ограниченными наборами инструкций).

Мы поэтому рекомендуем передать переменную MARCH при вызове make, установив ее на целевую платформу, которую вы намерены поддерживать. Это определит целевой процессор для как исполняемого файла Julia, так и библиотек, а также системного образа (последний также может быть установлен с помощью JULIA_CPU_TARGET). Обычно полезные значения для процессоров x86 - это x86-64 и core2 (для 64-битных сборок) и pentium4 (для 32-битных сборок). К сожалению, процессоры старше Pentium 4 в настоящее время не поддерживаются (см. this issue).

Полный список поддерживаемых целевых процессоров CPU в LLVM можно получить, выполнив llc -mattr=help.

Linux

На Linux команда make binary-dist создает архив tar, который содержит полностью функциональную установку Julia. Если вы хотите создать дистрибутивный пакет, такой как .deb или .rpm, потребуется приложить дополнительные усилия. См. репозиторий julia-debian для примера того, какие метаданные необходимы для создания пакетов .deb для систем на базе Debian и Ubuntu. См. Fedora package для дистрибутивов на базе RPM. Хотя мы еще не экспериментировали с этим, Alien может быть использован для генерации пакетов Julia для различных дистрибутивов Linux.

Julia поддерживает переопределение стандартных каталогов установки с помощью prefix и других переменных окружения, которые вы можете передать при вызове make и make install. Смотрите Make.inc для их списка. DESTDIR также может быть использован для принудительной установки в временный каталог.

По умолчанию Julia загружает $prefix/etc/julia/startup.jl в качестве файла инициализации для всей установки. Этот файл может использоваться менеджерами дистрибутивов для настройки пользовательских путей или кода инициализации. Для пакетов дистрибутивов Linux, если $prefix установлен на /usr, нет /usr/etc, чтобы заглянуть внутрь. Это требует изменения пути к частному каталогу etc Julia. Это можно сделать с помощью переменной sysconfdir при сборке. Просто передайте sysconfdir=/etc в make при сборке, и Julia сначала проверит /etc/julia/startup.jl, прежде чем пытаться загрузить $prefix/etc/julia/startup.jl.

OS X

Чтобы создать бинарное распределение на OSX, сначала соберите Julia, затем перейдите в contrib/mac/app и выполните make с теми же makevars, которые использовались с make при сборке самой Julia. Это создаст файл .dmg в директории contrib/mac/app, содержащий полностью автономное приложение Julia.app.

В качестве альтернативы, Julia может быть собрана как фреймворк, вызвав make с целью darwinframework и установленным DARWIN_FRAMEWORK=1. Например, make DARWIN_FRAMEWORK=1 darwinframework.

Windows

Инструкции по созданию дистрибутива Julia на Windows описаны в build devdocs for Windows.

Notes on BLAS and LAPACK

Julia по умолчанию собирает OpenBLAS, который включает библиотеки BLAS и LAPACK. На 32-битных архитектурах Julia собирает OpenBLAS с использованием 32-битных целых чисел, в то время как на 64-битных архитектурах Julia собирает OpenBLAS с использованием 64-битных целых чисел (ILP64). Важно, чтобы все функции Julia, которые вызывают API-рутины BLAS и LAPACK, использовали целые числа правильной ширины.

Большинство дистрибутивов BLAS и LAPACK, предоставляемых в дистрибутивах linux, а также даже коммерческие реализации, поставляют библиотеки, которые используют 32-битные API. В многих случаях 64-битный API предоставляется в виде отдельной библиотеки.

При использовании библиотек, предоставленных поставщиком или операционной системой, доступен параметр make, называемый USE_BLAS64, как часть сборки Julia. При выполнении make USE_BLAS64=0 Julia будет вызывать BLAS и LAPACK, предполагая 32-битный API, где все целые числа имеют ширину 32 бита, даже на 64-битной архитектуре.

Другие библиотеки, которые использует Julia, такие как SuiteSparse, также используют BLAS и LAPACK внутри. API должны быть согласованными во всех библиотеках, которые зависят от BLAS и LAPACK. Процесс сборки Julia будет правильно собирать все эти библиотеки, но при переопределении значений по умолчанию и использовании библиотек, предоставленных системой, необходимо обеспечить эту согласованность.

Также обратите внимание, что дистрибутивы Linux иногда поставляют несколько версий OpenBLAS, некоторые из которых поддерживают многопоточность, а другие работают только в последовательном режиме. Например, в Fedora libopenblasp.so является многопоточным, а libopenblas.so — нет. Мы рекомендуем использовать первый для оптимальной производительности. Чтобы выбрать библиотеку OpenBLAS, имя которой отличается от стандартного libopenblas.so, передайте LIBBLAS=-l$(YOURBLAS) и LIBBLASNAME=lib$(YOURBLAS) в make, заменив $(YOURBLAS) на имя вашей библиотеки. Вы также можете добавить .so.0 к имени библиотеки, если хотите, чтобы ваш пакет работал без необходимости в символической ссылке .so без версии.

Наконец, OpenBLAS включает свою собственную оптимизированную версию LAPACK. Если вы установите USE_SYSTEM_BLAS=1 и USE_SYSTEM_LAPACK=1, вам также следует установить LIBLAPACK=-l$(YOURBLAS) и LIBLAPACKNAME=lib$(YOURBLAS). В противном случае будет использоваться стандартный LAPACK, и производительность обычно будет значительно ниже.

Начиная с Julia 1.7, Julia использует libblastrampoline для выбора другой BLAS во время выполнения.

Point releasing 101

Создание точечного/патч-релиза состоит из нескольких отдельных этапов.

Backporting commits

Некоторые запросы на внесение изменений помечены как "backport pending x.y", например, "backport pending 0.6". Это обозначает, что следующий выпуск, помеченный из ветки release-x.y, должен включать коммит(ы) из этого запроса на внесение изменений. После того как запрос на внесение изменений будет объединен с основной веткой, каждый из коммитов должен быть cherry picked в отдельной ветке, которая в конечном итоге будет объединена с release-x.y.

Creating a backports branch

Сначала создайте новую ветку на основе release-x.y. Типичная конвенция для веток Julia заключается в том, чтобы добавлять префикс с вашими инициалами к имени ветки, если она предназначена для личной работы. Для примера скажем, что автором ветки является Джейн Смит.

git fetch origin
git checkout release-x.y
git rebase origin/release-x.y
git checkout -b js/backport-x.y

Это гарантирует, что ваша локальная копия release-x.y актуальна с origin перед тем, как вы создадите новую ветку от нее.

Cherry picking commits

Теперь мы выполняем фактический бэктортинг. Найдите все объединенные запросы на извлечение с меткой "backport pending x.y" в веб-интерфейсе GitHub. Для каждого из них прокрутите вниз, где написано "someperson объединил коммит 123abc в master XX минут назад". Обратите внимание, что имя коммита является ссылкой; если вы на него кликнете, вам будут показаны содержимое коммита. Если эта страница показывает, что 123abc является коммитом слияния, вернитесь на страницу PR — нам не нужны коммиты слияния, нам нужны фактические коммиты. Однако, если это не показывает коммит слияния, это означает, что PR был объединен с помощью squash. В этом случае используйте git SHA коммита, указанный рядом с коммитом на этой странице.

Как только у вас есть SHA коммита, выполните cherry-pick на ветку бэкторинга:

git cherry-pick -x -e <sha>

Могут возникнуть конфликты, которые необходимо разрешить вручную. После разрешения конфликтов (если это применимо) добавьте ссылку на запрос на вытягивание GitHub, который ввел коммит, в текст сообщения коммита.

После того как все соответствующие коммиты будут в ветке backports, отправьте ветку на GitHub.

Checking for performance regressions

Точечные релизы никогда не должны вводить регрессии производительности. К счастью, бот бенчмаркинга Julia, Nanosoldier, может запускать бенчмарки против любой ветки, а не только master. В этом случае мы хотим проверить результаты бенчмарков js/backport-x.y против release-x.y. Для этого разбудите Nanosoldier из его роботизированного сна, оставив комментарий к вашему запросу на слияние по бэктпорту:

@nanosoldier `runbenchmarks(ALL, vs=":release-x.y")`

Это запустит все зарегистрированные бенчмарки на release-x.y и js/backport-x.y и создаст сводку результатов, отмечая все улучшения и регрессии.

Если Nanosoldier обнаружит какие-либо регрессии, попробуйте проверить локально и повторно запустите Nanosoldier, если это необходимо. Если регрессии окажутся реальными, а не просто шумом, вам нужно будет найти коммит в мастер-ветке, чтобы сделать бэктпорт, который это исправляет, если такой существует, в противном случае вам следует определить, что вызвало регрессию, и отправить патч (или попросить кого-то, кто знает код, отправить патч) в мастер, а затем сделать бэктпорт коммита, как только он будет объединен. (Или отправьте патч напрямую в ветку бэктпорта, если это уместно.)

Building test binaries

После того как PR с обратной совместимостью будет объединен в ветку release-x.y, обновите свою локальную копию Julia, затем получите SHA ветки, используя

git rev-parse origin/release-x.y

Держите это под рукой, так как это то, что вы введете в поле "Revision" в интерфейсе buildbot.

На данный момент вам нужны только бинарные файлы для Linux x86-64, так как именно это используется для запуска PackageEvaluator. Перейдите на https://buildog.julialang.org, отправьте задание для nuke_linux64, затем поставьте в очередь задание для package_linux64, указав SHA в качестве ревизии. Когда задание по упаковке завершится, бинарный файл будет загружен в корзину julialang2 на AWS. Получите URL, так как он будет использоваться для PackageEvaluator.

Checking for package breakages

Точечные релизы никогда не должны ломать пакеты, за исключением случаев, когда пакеты используют серьезно сомнительные хаки с использованием внутренних механизмов Base, которые не предназначены для пользователей. (В таких случаях, возможно, стоит поговорить с автором пакета.)

Проверка того, приведут ли изменения в предстоящей новой версии к поломке пакетов, может быть выполнена с помощью PackageEvaluator, часто называемого "PkgEval" для краткости. PkgEval заполняет значки статуса на репозиториях GitHub и на pkg.julialang.org. Обычно он работает на одном из узлов, не занимающихся бенчмаркингом, Nanosoldier и использует Vagrant для выполнения своих задач в отдельных, параллельных виртуальных машинах VirtualBox.

Setting up PackageEvaluator

Клонируйте PackageEvaluator и создайте ветку с именем backport-x.y.z, а затем переключитесь на нее. Обратите внимание, что необходимые изменения немного хакерские и запутанные, и, надеюсь, это будет исправлено в будущей версии PackageEvaluator. Изменения, которые нужно внести, будут основаны на this commit.

Скрипт настройки принимает своим первым аргументом версию Julia для запуска и вторым - диапазон имен пакетов (AK для пакетов с именами от A до K, LZ для L-Z). Основная идея заключается в том, что мы немного изменим это, чтобы запустить только две версии Julia: текущую версию x.y и нашу версию с обратной совместимостью, каждая из которых будет иметь три диапазона пакетов.

В связанном диффе мы говорим, что если второй аргумент - это LZ, используйте бинарные файлы, собранные из нашей ветки backport, в противном случае (AK) используйте бинарные файлы релиза. Затем мы используем первый аргумент для запуска раздела списка пакетов: A-F для ввода 0.4, G-N для 0.5 и O-Z для 0.6.

Running PackageEvaluator

Чтобы запустить PkgEval, найдите достаточно мощную машину (например, узел Nanosoldier 1), затем выполните

git clone https://github.com/JuliaCI/PackageEvaluator.jl.git
cd PackageEvaluator.jl/scripts
git checkout backport-x.y.z
./runvagrant.sh

Это создает несколько папок в директории scripts/. Имена папок и их содержимое расшифрованы ниже:

Folder nameJulia versionPackage range
0.4AKReleaseA-F
0.4LZBackportA-F
0.5AKReleaseG-N
0.5LZBackportG-N
0.6AKReleaseO-Z
0.6LZBackportO-Z

Investigating results

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

./summary.sh 0.4AK/*.json > summary_release.txt
./summary.sh 0.5AK/*.json >> summary_release.txt
./summary.sh 0.6AK/*.json >> summary_release.txt
./summary.sh 0.4LZ/*.json > summary_backport.txt
./summary.sh 0.5LZ/*.json >> summary_backport.txt
./summary.sh 0.6LZ/*.json >> summary_backport.txt

Теперь у нас есть два файла, summary_release.txt и summary_backport.txt, содержащие результаты тестов PackageEvaluator (успех/неудача) для каждого пакета для двух версий.

Чтобы упростить их восприятие в Julia, мы преобразуем их в файлы CSV, а затем используем пакет DataFrames для обработки результатов. Чтобы преобразовать в CSV, скопируйте каждый .txt файл в соответствующий .csv файл, затем войдите в Vim и выполните ggVGI"<esc> затем :%s/\.json /",/g. (Вы не обязаны использовать Vim; это просто один из способов сделать это.) Теперь обработайте результаты с помощью кода Julia, аналогичного следующему.

using DataFrames

release = readtable("summary_release.csv", header=false, names=[:package, :release])
backport = readtable("summary_backport.csv", header=false, names=[:package, :backport])

results = join(release, backport, on=:package, kind=:outer)

for result in eachrow(results)
    a = result[:release]
    b = result[:backport]
    if (isna(a) && !isna(b)) || (isna(b) && !isna(a))
        color = :yellow
    elseif a != b && occursin("pass", b)
        color = :green
    elseif a != b
        color = :red
    else
        continue
    end
    printstyled(result[:package], ": Release ", a, " -> Backport ", b, "\n", color=color)
end

Это будет записывать строки с цветовой кодировкой в stdout. Все строки в красном цвете должны быть исследованы, так как они указывают на потенциальные сбои, вызванные версией бэкторта. Строки в желтом цвете также следует проверить, так как это означает, что пакет работал на одной версии, но не на другой по какой-то причине. Если вы обнаружите, что ваша ветка бэкторта вызывает сбои, используйте git bisect, чтобы определить проблемные коммиты, git revert этих коммитов и повторите процесс.

Merging backports into the release branch

После того как вы убедились, что

  • заданные коммиты проходят все модульные тесты Julia,
  • нет никаких регрессий производительности, вызванных обратно портированными коммитами по сравнению с веткой релиза, и
  • обратные коммиты не нарушают работу никаких зарегистрированных пакетов,

тогда ветка backport готова к слиянию с release-x.y. Как только она будет слита, пройдите и удалите метку "backport pending x.y" со всех запросов на извлечение, содержащих коммиты, которые были перенесены. Не удаляйте метку из PR, которые не были перенесены.

Ветка release-x.y теперь должна содержать все новые коммиты. Последнее, что мы хотим сделать с веткой, это изменить номер версии. Для этого отправьте PR против release-x.y, который редактирует файл VERSION, чтобы удалить -pre из номера версии. Как только это будет объединено, мы готовы к тегированию.

Tagging the release

Пора! Проверьте ветку release-x.y и убедитесь, что ваша локальная копия ветки актуальна с удаленной веткой. В командной строке выполните

git tag v$(cat VERSION)
git push --tags

Это создает тег локально и отправляет его на GitHub.

После тегирования релиза отправьте еще один PR в release-x.y, чтобы увеличить номер патча и добавить -pre в конец. Это обозначает, что состояние ветки отражает версию предварительного релиза следующего точечного релиза в серии x.y.

Следуйте оставшимся указаниям в Makefile.

Signing binaries

Некоторые из этих шагов потребуют надежных паролей. Чтобы получить соответствующие пароли, свяжитесь с Эллиотом Сабой (staticfloat) или Алексом Арсланом (ararslan). Обратите внимание, что подписание кода для каждой платформы должно выполняться на этой платформе (например, подписание для Windows должно выполняться на Windows и т.д.).

Linux

Подпись кода должна выполняться вручную в Linux, но это довольно просто. Сначала получите файл julia.key из папки CodeSigning в AWS-ведре juliasecure. Добавьте его в свой ключевой кольцо GnuPG, используя

gpg --import julia.key

Это потребует ввода пароля, который вы должны получить у Эллиота или Алекса. Далее установите уровень доверия для ключа на максимальный. Начните с ввода сессии gpg:

gpg --edit-key julia

На подсказке введите trust, затем, когда вас спросят о уровне доверия, укажите максимальный доступный (вероятно, 5). Выйдите из GnuPG.

Теперь для каждого из tarball'ов Linux, которые были собраны на сборочных ботах, введите

gpg -u julia --armor --detach-sig julia-x.y.z-linux-<arch>.tar.gz

Это создаст соответствующий .asc файл для каждого tarball. И всё!

macOS

Подпись кода должна происходить автоматически на сборочных машинах macOS. Однако важно убедиться, что она прошла успешно. На системе или виртуальной машине с macOS загрузите .dmg файл, который был собран на сборочных машинах. Для примера предположим, что .dmg файл называется julia-x.y.z-osx.dmg. Запустите

mkdir ./jlmnt
hdiutil mount -readonly -mountpoint ./jlmnt julia-x.y.z-osx.dmg
codesign -v jlmnt/Julia-x.y.app

Обязательно обратите внимание на имя смонтированного диска, указанное при монтировании! Для примера предположим, что это disk3. Если проверка подписи кода завершилась успешно, то на этапе codesign не будет вывода. Если это действительно было успешно, вы можете отсоединить .dmg сейчас:

hdiutil eject /dev/disk3
rm -rf ./jlmnt

Если вы получите сообщение, подобное этому

Julia-x.y.app: объект кода вообще не подписан

тогда вам нужно будет подписать вручную.

Чтобы подписать вручную, сначала получите сертификаты OS X из папки CodeSigning в бакете juliasecure на AWS. Добавьте файл .p12 в свою связку ключей с помощью Keychain.app. Попросите Эллиота Саба (staticfloat) или Алекса Арслана (ararslan) о пароле для ключа. Теперь выполните

hdiutil convert julia-x.y.z-osx.dmg -format UDRW -o julia-x.y.z-osx_writable.dmg
mkdir ./jlmnt
hdiutil mount -mountpoint julia-x.y.z-osx_writable.dmg
codesign -s "AFB379C0B4CBD9DB9A762797FC2AB5460A2B0DBE" --deep jlmnt/Julia-x.y.app

Это может завершиться неудачей с сообщением, подобным

Julia-x.y.app: ресурсная ветка, информация Finder или подобные остатки не допускаются

Если это так, вам нужно удалить лишние атрибуты:

xattr -cr jlmnt/Julia-x.y.app

Затем повторите кодовую подпись. Если это не вызывает ошибок, повторите проверку. Если все в порядке, размонтируйте записываемый .dmg и преобразуйте его обратно в режим только для чтения:

hdiutil eject /dev/disk3
rm -rf ./jlmnt
hdiutil convert julia-x.y.z-osx_writable.dmg -format UDZO -o julia-x.y.z-osx_fixed.dmg

Проверьте, что полученный .dmg действительно исправлен, дважды щелкнув по нему. Если все выглядит хорошо, извлеките его, а затем уберите суффикс _fixed из имени. И всё!

Windows

Подпись должна выполняться вручную в Windows. Сначала получите Windows 10 SDK, который содержит необходимые утилиты для подписи, с сайта Microsoft. Нам нужна утилита SignTool, которая должна быть установлена где-то вроде C:\Program Files (x86)\Windows Kits\10\App Certification Kit. Получите файлы сертификатов Windows из CodeSigning на juliasecure и поместите их в ту же директорию, что и исполняемые файлы. Откройте окно CMD Windows, cd в ту папку, где находятся все файлы, и выполните

set PATH=%PATH%;C:\Program Files (x86)\Windows Kits\10\App Certification Kit;
signtool sign /f julia-windows-code-sign_2017.p12 /p "PASSWORD" ^
   /t http://timestamp.verisign.com/scripts/timstamp.dll ^
   /v julia-x.y.z-win32.exe

Обратите внимание, что ^ является символом продолжения строки в Windows CMD, а PASSWORD является заполнителем для пароля этого сертификата. Как обычно, свяжитесь с Эллиотом или Алексом для получения паролей. Если ошибок нет, значит, все в порядке!

Uploading binaries

Теперь, когда все подписано, нам нужно загрузить бинарные файлы в AWS. Вы можете использовать программу, такую как Cyberduck, или утилиту командной строки aws. Бинарные файлы должны быть помещены в бакет julialang2 в соответствующие папки. Например, Linux x86-64 помещается в julialang2/bin/linux/x.y. Обязательно удалите текущий файл julia-x.y-latest-linux-<arch>.tar.gz и замените его дубликатом julia-x.y.z-linux-<arch>.tar.gz.

Мы также должны загрузить контрольные суммы для всего, что мы собрали, включая исходные tarball'ы и все бинарные файлы релиза. Это просто:

shasum -a 256 julia-x.y.z* | grep -v -e sha256 -e md5 -e asc > julia-x.y.z.sha256
md5sum julia-x.y.z* | grep -v -e sha256 -e md5 -e asc > julia-x.y.z.md5

Обратите внимание, что если вы выполняете эти команды на macOS, вы получите немного другой вывод, который можно отформатировать, посмотрев на существующий файл. Пользователям Mac также нужно использовать md5 -r вместо md5sum. Загрузите файлы .md5 и .sha256 в julialang2/bin/checksums на AWS.

Убедитесь, что разрешения на AWS для всех загруженных файлов установлены на "Все: ЧТЕНИЕ."

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

curl -X PURGE https://julialang-s3.julialang.org/bin/checksums/julia-x.y.z.sha256

Иногда это не обязательно, но все равно хорошо это сделать.