Цель работы — сократить накладные расходы на хранение большого количества утилит, написанных на golang.
Один из побочных эффектов статической компиляции golang — относительно большие накладные расходы на хранение рантайма и всех используемых библиотек внутри каждого исполняемого файла. Например небольшая утилитка, которая только и делает что обращается через сеть к серверу и выполняет простые полученные команды — весит 5.5Мб.
Когда такая утилитка одна — это в современных условиях это еще не страшно. Когда утилиты накапливаются и их становится уже несколько десятков или сотен — чисто по-человечески становится жалко сотен мегабайтов, утекающих «вникуда».
Для решения этой проблемы я написал библиотеку multiex, которой и делюсь с сообществом. С его помощью можно объединить несколько программ в один исполняемый файл с минимальными изменениями внутри кода программ и без изменения внешнего поведения. Мысль была взята у busybox — все программы компилируются в один файл, а выполняемый код выбирается при запуске, исходя из имени запускаемого файла.
Для подключения программ нужно вызвать функцию-регистратор, например вот так:
func f1(){
if os.Args[1] == "asdf" {
println("ok")
}
}
multiex.Register(multiex.ExecutorDescribe{Name: "test", Function: f1})
В структуре передается имя программы — если исполняемый файл вызывается с именем test, то будет вызвана функция f1. Дальше идет обычная работа функции без изменений, в т.ч. можно обычным образом разбираться переданные аргументы командной строки.
./test asdf
./test может быть именем исполняемого файла, жесткой или символьной ссылкой.
Кроме этого можно вызвать функцию f1 если первым параметром исполняемого файла будет --multiex-command=test. При этом параметр --multiex-command=test будет удален и функция f1 так же сможет выполнять разбор своих аргументов как обычно.
./any --multiex-command=test asdf
В библиотеке уже реализована одноименная программа с названием multiex, на данный момент она умеет:
1. Выводить краткую справку о том что это за файл и какие программы в него включены — если файл вызван с непредусмотренным именем
2. Создавать символьные ссылки на все программы, входящие в исполняемый файл. Ссылки создаются в той же папке, где лежит сам исполняемый файл.
./any --multiex-command=multiex install
При этом ссылка на сам multiex не создается.
В github.com/rekby/multiex-example показан пример подключения программ:
1. Регистрация функций непосредственно в основном компилируемом модуле. При этом способе главный сборочный модуль знает о конкретных включаемых функциях и при добавлении новой функции — нужно добавлять её перечисление в список регистрации. В сами включаемые программы при этом нет необходимости добавлять зависимость от multiex — достаточно иметь экспортируемую функцию Main.
2. В сборочном модуле импортируются подключаемые модули, а каждый модуль сам регистрирует свои функции в процессе инициализации. Тут сборочный модуль знает только список включаемых модулей, а не каждую функцию. Каждый модуль сам определяет какие функции он будет экспортировать и для экспорта новой функции достаточно поправить только модуль с этой функцией.
3. Естественно оба способа можно комбинировать (в примере как раз показан комбинированный вариант).
Автор: rekby