Я думаю примерно двумя разными способами.
Первый - это когда я достаточно формально определяю какие-то свойства интересным мне объектов, свойства решения, которое нужно получить, и, практически переборно, начинаю в голове по разному комбинировать исходные объекты. Можно какую-то интересную комбинацию исходных объектов зафиксировать, обдумать со всех сторон, и добавить в пул исходных объектов.
Способ хорошо подходит для всякого рода олимпиадных задачек, но думать так побряд больше 15 минут у меня не получается - мозг устает.
Второй - я загружаю условия задачи куда-то в "фон", а потом, через какой-то неизвестный мне заранее промежуток времени достаю готовый результат. Чаще в виде грубой идеи, которую потом надо допилить напильником.
Вот, я так несколько месяцев носил в голове задачу "научиться делать зависимости команд сборки от таргетов сборки".
Звучит немного странно, поясню.
"Таргеты" сборки, это практически пакеты в дистрибутиве - у них есть зависимости друг от друга, зависимости времени сборки и времени выполнения. Зависимости транзитивны.
"Команды" - это мешок команд, у которых есть input, output, но нет явно прописанных между собой зависимостей.
"Таргет" порождет множество "команд", нужных для его изготовления.
Зачем эта задача вообще нужна?
Ну мне вот нужно уметь делать зависимость команды, которая готовит дерево исходников, от таргета bin/git, в случае, если нам нужно качать не через curl/https, а из git репозитория.
Сложно объяснить, почему сложно "нарисовать" зависимость от команды к таргету, но я попробую.
Это две очень разные сущности. Таргет, по сути, это интерфейс, реализованный кодом, а команда - кусок json. Как кусок кода может породить json довольно понятно, а вот как в json сделать указатель на вызов фабрики таргетов - не очень, там уже теряется часть нужной информации для вызова этой фабрики.
Вот, вчера моя неонка выдала мне ажно целых 3(!) решения этой задачи.
Они довольно специальные, но попробую объяснить!
* Первое решение - зависимости команд наследуются таргетом, породившим эти команды, и тогда в том месте, которое выстраивает зависимости команд по их input/output, есть и команды нужных нам таргетов инструментов.
* Второе решение - а пусть у нас появится не команда, а таргет, который делает закачку через git, и скачанный артефакт - просто выхлоп этого таргета. А дальше код, который обрабатывает пакет, специальным образом обрабатывает зависимость от git репозитория - он делает не еще одну команду, а добавляет зависимость от сгенерированного таргета.
* Про третий способ не буду, там совсем непонятно.
Я в таких случаях стараюсь реализовать все способы, посмотреть, какой из них лучше всего fit, и остановлюсь на нем.
Вот, например, реализация первого способа для кастомного вычисления хеша от скачанных данных(я как-то писал, что некоторые негодяи отдают tgz, md5 от корого плавает со временем) - https://git.sr.ht/~pg/ix/commit/7d0db02da7c1abdb8a5d6d855baae6fc9522598b
Первый - это когда я достаточно формально определяю какие-то свойства интересным мне объектов, свойства решения, которое нужно получить, и, практически переборно, начинаю в голове по разному комбинировать исходные объекты. Можно какую-то интересную комбинацию исходных объектов зафиксировать, обдумать со всех сторон, и добавить в пул исходных объектов.
Способ хорошо подходит для всякого рода олимпиадных задачек, но думать так побряд больше 15 минут у меня не получается - мозг устает.
Второй - я загружаю условия задачи куда-то в "фон", а потом, через какой-то неизвестный мне заранее промежуток времени достаю готовый результат. Чаще в виде грубой идеи, которую потом надо допилить напильником.
Вот, я так несколько месяцев носил в голове задачу "научиться делать зависимости команд сборки от таргетов сборки".
Звучит немного странно, поясню.
"Таргеты" сборки, это практически пакеты в дистрибутиве - у них есть зависимости друг от друга, зависимости времени сборки и времени выполнения. Зависимости транзитивны.
"Команды" - это мешок команд, у которых есть input, output, но нет явно прописанных между собой зависимостей.
"Таргет" порождет множество "команд", нужных для его изготовления.
Зачем эта задача вообще нужна?
Ну мне вот нужно уметь делать зависимость команды, которая готовит дерево исходников, от таргета bin/git, в случае, если нам нужно качать не через curl/https, а из git репозитория.
Сложно объяснить, почему сложно "нарисовать" зависимость от команды к таргету, но я попробую.
Это две очень разные сущности. Таргет, по сути, это интерфейс, реализованный кодом, а команда - кусок json. Как кусок кода может породить json довольно понятно, а вот как в json сделать указатель на вызов фабрики таргетов - не очень, там уже теряется часть нужной информации для вызова этой фабрики.
Вот, вчера моя неонка выдала мне ажно целых 3(!) решения этой задачи.
Они довольно специальные, но попробую объяснить!
* Первое решение - зависимости команд наследуются таргетом, породившим эти команды, и тогда в том месте, которое выстраивает зависимости команд по их input/output, есть и команды нужных нам таргетов инструментов.
* Второе решение - а пусть у нас появится не команда, а таргет, который делает закачку через git, и скачанный артефакт - просто выхлоп этого таргета. А дальше код, который обрабатывает пакет, специальным образом обрабатывает зависимость от git репозитория - он делает не еще одну команду, а добавляет зависимость от сгенерированного таргета.
* Про третий способ не буду, там совсем непонятно.
Я в таких случаях стараюсь реализовать все способы, посмотреть, какой из них лучше всего fit, и остановлюсь на нем.
Вот, например, реализация первого способа для кастомного вычисления хеша от скачанных данных(я как-то писал, что некоторые негодяи отдают tgz, md5 от корого плавает со временем) - https://git.sr.ht/~pg/ix/commit/7d0db02da7c1abdb8a5d6d855baae6fc9522598b
🔥5🤔3🤯2
Ну и в продолжение предыдущей темы.
Зависимость команды загрузки от git - штука довольно опасная.
Потому что в content addressable store у нас получается, что все, что идет выше по цепочке, будет зависеть от git, и пересобираться почем зря, хотя, на самом деле, от версии git ничего(в общем случае) не зависит, данные на выходе будут те же самые.
Поэтому пришлось изобрести способ "восстанавливать чистоту". Вернее, распространить его с команд по загрузке исходников на прочие команды.
Каждая команда может сказать: "'эй, чувак, у меня на выходе будет 2 файла, и md5 от них будет такой-то и такой-то".
Тогда:
* executor графа проверит это при выполнении команды, и, если информация не верна, то остановит сборку с ошибкой.
* графопостроитель при вычислении хеша ноды будет учитывать только эти (заранее известные ему!) данные, которые не будут плавать при изменении версии git.
Тут, конечно, есть опасность, что свежесобранный git не сможет скачать репозиторий правильно, а мы об этом узнаем сильно позже, потому что будем использовать закешированный результат.
Это, кажется, решается только тестами, и регулярной пересборкой без кеша.
Зависимость команды загрузки от git - штука довольно опасная.
Потому что в content addressable store у нас получается, что все, что идет выше по цепочке, будет зависеть от git, и пересобираться почем зря, хотя, на самом деле, от версии git ничего(в общем случае) не зависит, данные на выходе будут те же самые.
Поэтому пришлось изобрести способ "восстанавливать чистоту". Вернее, распространить его с команд по загрузке исходников на прочие команды.
Каждая команда может сказать: "'эй, чувак, у меня на выходе будет 2 файла, и md5 от них будет такой-то и такой-то".
Тогда:
* executor графа проверит это при выполнении команды, и, если информация не верна, то остановит сборку с ошибкой.
* графопостроитель при вычислении хеша ноды будет учитывать только эти (заранее известные ему!) данные, которые не будут плавать при изменении версии git.
Тут, конечно, есть опасность, что свежесобранный git не сможет скачать репозиторий правильно, а мы об этом узнаем сильно позже, потому что будем использовать закешированный результат.
Это, кажется, решается только тестами, и регулярной пересборкой без кеша.
👍6
commit -m "better"
Завершил большой и важный рефакторинг, один из 2 оставшихся, после выполнения которых я бы считал свою пакетную систему завершенной. Я перенес построение realm/environment из пакетного менеджера в граф построения пакетов. Звучит странно, и непонятно, зачем…
Хочу более подробно написать про модель безопасности #ix, благодаря которой возможно разделение одного и того же store с пакетами между разными пользователями. #sec_model
Как выглядит описание сборки пакета, с упущением не важных для понимания деталей:
Что output, и команды параметризуются некоторым параметром uid. То есть, это не просто набор команд, а набор команд, параметризованный путем его out_dir.
Так вот, я утверждаю, что, при соблюдении следующих инвариантов, получившиеся артефакты(артефакт == содержимое папки "/sore/{{uid}}") можно безопасно в read only виде разделять между пользователями системы, и более того, вообще между ВСЕМИ пользователями IX:
* PKG["out_dir"] == "/store/{{PKG["uid"]}}"
* Код выполняется в изолированном окружении, в котором фиксировано время, доступны только пути по PKG["in_dirs"], и не доступны другие источники энтропии, типа, /dev/random.
* PKG["uid"] == sha256(PKG без поля "uid")
* В момент выполнения такой ноды мы производим подстановку шаблона значением поля "uid".
Почему так?
Я не умею это строго доказывать, но "на пальцах", это довольно понятная вещь - в uid мы положили значение односторонней функции от шаблона по этому uid.
То есть, путь, по которому будет лежать результат, содержит в себе в качестве элемента хеш от всего шаблона.
Кстати, оцените, как изящно и эффективно я делаю такую подстановку!
https://git.sr.ht/~pg/ix/tree/main/item/core/gen_cmds.py#L194
Я тут упустил несколько моментов, которые не очень важны для понимания, но сути они не меняют:
* uid я считаю не как хеш от шаблона, а как хеш от шаблона с подставленным uid == sentinel, для удобства проверки.
* Для некоторых узлов я беру не весь dict для вычисления шаблона, а некоторый его срез, надежность которого, в будущем, проверит выполнитель(писал вчера про такие специальные ноды)
* Я думаю, вдумчивый читатель теперь без труда поймет смысл этих двух сумасшедших строк кода: https://git.sr.ht/~pg/ix/tree/main/item/core/package.py#L142-143
Как выглядит описание сборки пакета, с упущением не важных для понимания деталей:
PKG = {
"uid": "XYZ",
"in_dirs": ["/store/A", "/store/B"],
"out_dir": "/store/{{uid}}",
"cmd": "/bin/cp /store/A/f1 /store/{{uid}}/",
}
Что мы тут видим?Что output, и команды параметризуются некоторым параметром uid. То есть, это не просто набор команд, а набор команд, параметризованный путем его out_dir.
Так вот, я утверждаю, что, при соблюдении следующих инвариантов, получившиеся артефакты(артефакт == содержимое папки "/sore/{{uid}}") можно безопасно в read only виде разделять между пользователями системы, и более того, вообще между ВСЕМИ пользователями IX:
* PKG["out_dir"] == "/store/{{PKG["uid"]}}"
* Код выполняется в изолированном окружении, в котором фиксировано время, доступны только пути по PKG["in_dirs"], и не доступны другие источники энтропии, типа, /dev/random.
* PKG["uid"] == sha256(PKG без поля "uid")
* В момент выполнения такой ноды мы производим подстановку шаблона значением поля "uid".
Почему так?
Я не умею это строго доказывать, но "на пальцах", это довольно понятная вещь - в uid мы положили значение односторонней функции от шаблона по этому uid.
То есть, путь, по которому будет лежать результат, содержит в себе в качестве элемента хеш от всего шаблона.
Кстати, оцените, как изящно и эффективно я делаю такую подстановку!
https://git.sr.ht/~pg/ix/tree/main/item/core/gen_cmds.py#L194
Я тут упустил несколько моментов, которые не очень важны для понимания, но сути они не меняют:
* uid я считаю не как хеш от шаблона, а как хеш от шаблона с подставленным uid == sentinel, для удобства проверки.
* Для некоторых узлов я беру не весь dict для вычисления шаблона, а некоторый его срез, надежность которого, в будущем, проверит выполнитель(писал вчера про такие специальные ноды)
* Я думаю, вдумчивый читатель теперь без труда поймет смысл этих двух сумасшедших строк кода: https://git.sr.ht/~pg/ix/tree/main/item/core/package.py#L142-143
🔥4
https://portal.mozz.us/gemini/arcanesciences.com/gemlog/22-07-28/
Размер одного и того же кода в байтах, скомпилированного под разные архитектуры.
Метрика важная, потому что чем меньше кода, тем меньше tlb miss / cache miss / memory loads при его исполнении.
* Удивлен, насколько плох RISC-V. Прямо сильно хуже, чем ARM, а это значит, что, при прочих равных, код он исполнять будет медленнее. Хуже как и в обычном варианте, так и в пожатом.
* Удивлен, насколько плох x86_64(вполне сравним с ARM64LE, значит, средняя длина инструкции чуть меньше 4 байт). Казалось бы, CISC архитектура, наиболее полезные инструкции самые короткие. Но нет, видимо, эти инструкции были актуальны на время появления 8086, а сейчас компиляторы выбирают инструкции, которые уже не так плотно упакованы. Ну и, видимо, 64-битное расширение не очень плотно упаковано, надо посмотреть, как оно вообще кодируется.
Размер одного и того же кода в байтах, скомпилированного под разные архитектуры.
Метрика важная, потому что чем меньше кода, тем меньше tlb miss / cache miss / memory loads при его исполнении.
* Удивлен, насколько плох RISC-V. Прямо сильно хуже, чем ARM, а это значит, что, при прочих равных, код он исполнять будет медленнее. Хуже как и в обычном варианте, так и в пожатом.
* Удивлен, насколько плох x86_64(вполне сравним с ARM64LE, значит, средняя длина инструкции чуть меньше 4 байт). Казалось бы, CISC архитектура, наиболее полезные инструкции самые короткие. Но нет, видимо, эти инструкции были актуальны на время появления 8086, а сейчас компиляторы выбирают инструкции, которые уже не так плотно упакованы. Ну и, видимо, 64-битное расширение не очень плотно упаковано, надо посмотреть, как оно вообще кодируется.
👍7🤔2
Forwarded from Метаверсище и ИИще (Sergey Tsyptsyn ️️)
Интернетик приподвзорвалсо ИИ-апокалипсисом.
А началось все с того, что dailymail кликбейтнул и бахнул статью с заголовком "ИИ попросили подумать, как будет выглядеть последнее селфи, сделанное на Земле, и он начал создавать кошмарные изображения".
В статье указано, что весь этот адъ создан DaLLI-2. Вчера, когда я пытался испортить всем настроение, внимательные подписчики засомневались, что это Далли - уж больно картинки не похожи на его стиль (чувствуете, как последняя сентенция равноприменима как к ИИ, так и кожаному художнику?). Действительно, похоже мало.
Большинство склоняется, что это Мидджорни, ибо если кинуть в него "последнее селфи, сделанное на Земле", то получится что-то очень близкое. А парни из канала robotoverloards просто потроллили всех, указав в тегах и Далли и Мидджорни - они так делают в каждом посте.
Но журналюжки подхватили истерику и повесили апокалипсис на Далли.
На Реддите есть огромный тред, где народ разбирает этот кейс и приводит примеры своих генераций в разных ИИ, включая Dalle-2 и Dalle-Mini.
Я выбрал оттуда кое-что и подписал, чтобы дать понять, что каждый ИИ - это художник и "он так видит" свой/наш апокалипсис.
Подробности читайте по ссылкам. И не верьте журналистам, особенно про ИИ.
А началось все с того, что dailymail кликбейтнул и бахнул статью с заголовком "ИИ попросили подумать, как будет выглядеть последнее селфи, сделанное на Земле, и он начал создавать кошмарные изображения".
В статье указано, что весь этот адъ создан DaLLI-2. Вчера, когда я пытался испортить всем настроение, внимательные подписчики засомневались, что это Далли - уж больно картинки не похожи на его стиль (чувствуете, как последняя сентенция равноприменима как к ИИ, так и кожаному художнику?). Действительно, похоже мало.
Большинство склоняется, что это Мидджорни, ибо если кинуть в него "последнее селфи, сделанное на Земле", то получится что-то очень близкое. А парни из канала robotoverloards просто потроллили всех, указав в тегах и Далли и Мидджорни - они так делают в каждом посте.
Но журналюжки подхватили истерику и повесили апокалипсис на Далли.
На Реддите есть огромный тред, где народ разбирает этот кейс и приводит примеры своих генераций в разных ИИ, включая Dalle-2 и Dalle-Mini.
Я выбрал оттуда кое-что и подписал, чтобы дать понять, что каждый ИИ - это художник и "он так видит" свой/наш апокалипсис.
Подробности читайте по ссылкам. И не верьте журналистам, особенно про ИИ.
👍4👎1
TIL в unix есть не только богомерзкий fts для обхода дерева файлов, но и божественный nftw()/ftw()/ftw.h - https://stackoverflow.com/questions/5467725/how-to-delete-a-directory-and-its-contents-in-posix-c
Stack Overflow
How to delete a directory and its contents in (POSIX) C?
I am most interested in the non-recursive case, but I am guessing others who might track this question would prefer seeing the recursive case.
Basically, we are aiming to accomplish:
rm -rf <...
Basically, we are aiming to accomplish:
rm -rf <...
👍8
commit -m "better"
Я думаю примерно двумя разными способами. Первый - это когда я достаточно формально определяю какие-то свойства интересным мне объектов, свойства решения, которое нужно получить, и, практически переборно, начинаю в голове по разному комбинировать исходные…
Решил рассказать про второй способ описания закачки кастомных зависимостей.
Потому что звучит сложно, а на деле довольно интересно, ну и получилось неплохо.
Я разделил changeset на 3 части:
* Первая часть - очень грубо прорубить дорожку. https://git.sr.ht/~pg/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53 Тут, на самом деле, все очень просто - для сборочного таргета bin/emptty я завел еще один таргет, bin/emptty/src, который тупо на скачанную папку с github зовет go vendor, и потом прикапывает выхлоп в tgz файл - https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-4f24bc72d343374a778107da01dbbdec4922ff6ece3064de9bebc53ffadd0a6dR17-R19 (важно отметить, какие я использовал опции для tgz, чтобы получить повторяемый результат). https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-4f24bc72d343374a778107da01dbbdec4922ff6ece3064de9bebc53ffadd0a6dR23-R24 - в install просто копируем получившийся tgz в ${out} https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-4f24bc72d343374a778107da01dbbdec4922ff6ece3064de9bebc53ffadd0a6dR32 - вот тут интересная "хитрость". Моя сборка пакетов предполагает, что блок fetch, если он есть, кладет исходники в ${src}, а если блока fetch нет - то он не выставляет эту переменную среды. Ну я и воспользовался этим фактом, теперь в bin/emptty нет блока fetch, но зато эту переменную среды выставит зависимый пакет, ровно туда, куда положит скачанный исходник, и все магически заработает "из коробки". https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-ba13220bef6450320bd58efdcf2632da60599c28a7f84d477350711c6e7736bcR16 - флажочек для go build, чтобы он использовал завендоренные зависимости.
* Вторая часть марлезонского балета - https://github.com/pg83/ix/commit/a984214c3b43b36702da09a648a311530731e223 Очень простая идея - а давайте сделаем таргет на закачку исходников параметризуемым по урлу, в котором лежит seed для go vendor, чтобы не кажому проекту на go нужно было копипастить этот волшебный таргет на закачку, а просто воспользоваться готовым шаблоном - https://github.com/pg83/ix/commit/a984214c3b43b36702da09a648a311530731e223#diff-ba13220bef6450320bd58efdcf2632da60599c28a7f84d477350711c6e7736bcR6
* И третья часть - а давайте уберем всю эту некрасивую машинерию(указание флагов для таргета, дополнительные go build флаги) под капот нового шаблона сборки? https://github.com/pg83/ix/commit/0a42b0c0634c265278c90eed900e09f97483e1fc#diff-3f8ca95fe0cd560dcf72bdf4e6dfb833490fde8d741142c4300d61dc3f93c1c5R5
Собственно, "чистая" сборка go кода выглядит вот так - https://github.com/pg83/ix/blob/main/pkgs/bin/emptty/stock/ix.sh Отличие от предыдущего варианта - вместо блока fetch появилось два блока go_url + go_sum.
Пока не сделано:
* Возможность фиксации md5 для получившегося git.tgz
* Навести немного красоты, сейчас эта дополнительная зависимость типа bin(я ее подцепил просто как еще одну тулзу для сборки), а надо тип aux/data.
Я так подробно про это рассказываю, потому что меня лично очень прет, что выразительная мощь построителя графов оказалась достаточна для решения этой задачи без заведения дополнительных сущностей в коде.
Потому что звучит сложно, а на деле довольно интересно, ну и получилось неплохо.
Я разделил changeset на 3 части:
* Первая часть - очень грубо прорубить дорожку. https://git.sr.ht/~pg/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53 Тут, на самом деле, все очень просто - для сборочного таргета bin/emptty я завел еще один таргет, bin/emptty/src, который тупо на скачанную папку с github зовет go vendor, и потом прикапывает выхлоп в tgz файл - https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-4f24bc72d343374a778107da01dbbdec4922ff6ece3064de9bebc53ffadd0a6dR17-R19 (важно отметить, какие я использовал опции для tgz, чтобы получить повторяемый результат). https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-4f24bc72d343374a778107da01dbbdec4922ff6ece3064de9bebc53ffadd0a6dR23-R24 - в install просто копируем получившийся tgz в ${out} https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-4f24bc72d343374a778107da01dbbdec4922ff6ece3064de9bebc53ffadd0a6dR32 - вот тут интересная "хитрость". Моя сборка пакетов предполагает, что блок fetch, если он есть, кладет исходники в ${src}, а если блока fetch нет - то он не выставляет эту переменную среды. Ну я и воспользовался этим фактом, теперь в bin/emptty нет блока fetch, но зато эту переменную среды выставит зависимый пакет, ровно туда, куда положит скачанный исходник, и все магически заработает "из коробки". https://github.com/pg83/ix/commit/1a08ffd42c9b82ac913bc85212b980af5542fa53#diff-ba13220bef6450320bd58efdcf2632da60599c28a7f84d477350711c6e7736bcR16 - флажочек для go build, чтобы он использовал завендоренные зависимости.
* Вторая часть марлезонского балета - https://github.com/pg83/ix/commit/a984214c3b43b36702da09a648a311530731e223 Очень простая идея - а давайте сделаем таргет на закачку исходников параметризуемым по урлу, в котором лежит seed для go vendor, чтобы не кажому проекту на go нужно было копипастить этот волшебный таргет на закачку, а просто воспользоваться готовым шаблоном - https://github.com/pg83/ix/commit/a984214c3b43b36702da09a648a311530731e223#diff-ba13220bef6450320bd58efdcf2632da60599c28a7f84d477350711c6e7736bcR6
* И третья часть - а давайте уберем всю эту некрасивую машинерию(указание флагов для таргета, дополнительные go build флаги) под капот нового шаблона сборки? https://github.com/pg83/ix/commit/0a42b0c0634c265278c90eed900e09f97483e1fc#diff-3f8ca95fe0cd560dcf72bdf4e6dfb833490fde8d741142c4300d61dc3f93c1c5R5
Собственно, "чистая" сборка go кода выглядит вот так - https://github.com/pg83/ix/blob/main/pkgs/bin/emptty/stock/ix.sh Отличие от предыдущего варианта - вместо блока fetch появилось два блока go_url + go_sum.
Пока не сделано:
* Возможность фиксации md5 для получившегося git.tgz
* Навести немного красоты, сейчас эта дополнительная зависимость типа bin(я ее подцепил просто как еще одну тулзу для сборки), а надо тип aux/data.
Я так подробно про это рассказываю, потому что меня лично очень прет, что выразительная мощь построителя графов оказалась достаточна для решения этой задачи без заведения дополнительных сущностей в коде.
GitHub
new idea · pg83/ix@1a08ffd
ix package manager. Contribute to pg83/ix development by creating an account on GitHub.
🔥5
Девочка Антон познает #rust:
https://recursion.wtf/posts/rust_schemes/
Новый язык - это всегда хорошо, можно переизобретать одни и те же штуки в сотый раз, и оно снова будет к месту!
TL;DR - рекурсивный обход дерева в куче тормозит, давайте мы ноды положим в вектор, вместо указателей у нас будет индекс, и, чтобы это не выглядело всрато, накрутим поверх какого-то синтаксического сахара.
Я, как С++ программист с опытом, при чтении этой статьи испытал неиллюзорный butthurt:
* Почему бы не воспользоваться стандартным советом - "положи все это в memory pool, и не выебывайся"?
* По мне так страшный, но straightforward, код был понятнее, а вот ее перверсии с преобразованием кода я не понял от слова совсем, наверное, потому что пока плохо понимаю Rust.
После прочтения пошел гуглить про Rust memory pool, https://docs.rs/mempool/0.3.1/mempool/, увидел там красивое:
"Note that the pool returns an immutable reference. If you need a mutable reference, then use a RefCell. (Which is guaranteed safe by the pool.)"
Тут, меня, конечно, торкнуло задать наконец-то правильные вопросы:
* А как вообще Vec возвращает &mut? Он же должен вернуть нечто, что одолжит ссылку и на сам Vec? https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#2586-2593 Yep.
* А что такое RefCell? https://doc.rust-lang.org/src/core/cell.rs.html#947 Классная штука! Позволяет перенести проверку "максимум 1 мутабельная ссылка на объект" в runtime.
После этого знания, конечно, передо мной вновь замаячил вопрос "а нафига оно все это нужно", если в самых сложных и интересных случаях мы эту проверку таки перенесли в runtime(возможен panic при неверном использовании), и компилятор нам ничего не гарантирует.
https://recursion.wtf/posts/rust_schemes/
Новый язык - это всегда хорошо, можно переизобретать одни и те же штуки в сотый раз, и оно снова будет к месту!
TL;DR - рекурсивный обход дерева в куче тормозит, давайте мы ноды положим в вектор, вместо указателей у нас будет индекс, и, чтобы это не выглядело всрато, накрутим поверх какого-то синтаксического сахара.
Я, как С++ программист с опытом, при чтении этой статьи испытал неиллюзорный butthurt:
* Почему бы не воспользоваться стандартным советом - "положи все это в memory pool
* По мне так страшный, но straightforward, код был понятнее, а вот ее перверсии с преобразованием кода я не понял от слова совсем, наверное, потому что пока плохо понимаю Rust.
После прочтения пошел гуглить про Rust memory pool, https://docs.rs/mempool/0.3.1/mempool/, увидел там красивое:
"Note that the pool returns an immutable reference. If you need a mutable reference, then use a RefCell. (Which is guaranteed safe by the pool.)"
Тут, меня, конечно, торкнуло задать наконец-то правильные вопросы:
* А как вообще Vec возвращает &mut? Он же должен вернуть нечто, что одолжит ссылку и на сам Vec? https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#2586-2593 Yep.
* А что такое RefCell? https://doc.rust-lang.org/src/core/cell.rs.html#947 Классная штука! Позволяет перенести проверку "максимум 1 мутабельная ссылка на объект" в runtime.
После этого знания, конечно, передо мной вновь замаячил вопрос "а нафига оно все это нужно", если в самых сложных и интересных случаях мы эту проверку таки перенесли в runtime(возможен panic при неверном использовании), и компилятор нам ничего не гарантирует.
Inanna Malick
Elegant and performant recursion in Rust
This is a post about writing elegant and performant recursive algorithms in Rust. It makes heavy use of a pattern from Haskell called recursion schemes, but you don’t need to know anything about …
🔥4👍2😁1🤔1
commit -m "better"
В одном там рабочем чате с коллегами зашла речь, кто как смотрит порно. Мне пришлось признаться, что я для этого использую рабочий macbook, потому что в моем браузере все еще не работает поддержка видео. Собственно, у меня там 3 заметных проблемы: 1) Поддержка…
А я продолжаю пользоваться epiphany, потому что уже как-то привык, ну и хорошее - враг лучшего, как известно.
К chromium я постепенно подбираюсь, вот, собрал nodejs, который, за каким-то хером, нужен для сборки хрома.
А пока решил посмотреть, какие там еще браузеры есть на webkit, и чтобы были вертикальные вкладки.
Вот, пожалуйста:
* https://wiki.gnome.org/Apps/Eolie, между прочим, тоже часть Gnome, так же как и epiphany. Вот у людей ресурсов, столько никому не нужных браузеров развивать? К сожалению, он написан на python.
* А вот еще один браузер на основе webkit, тоже с вертикальными вкладками - https://github.com/sonnyp/Tangram К сожалению, он написано на java script, причем, накакой-то очень всратой его разновидности - https://gitlab.gnome.org/GNOME/gjs Видимо, сейчас очень модно плодить JS рантаймы - https://deno.land/ - на Rust, https://bun.sh/ - на zig, да на любой вкус. Вот, гномовцы себе запилили на spidermonkey от Мозиллы, прекрасный выбор, что тут можно сказать.
Я стараюсь держаться подальше от скриптовых приложений для десктопа.
Очень, очень уважаю соответствующие языки, сам много пишу на Python, но, часто, вкупе с такими языками, идут соответствующие погромисты.
Ну его нахер, посижу еще на epiphany.
К chromium я постепенно подбираюсь, вот, собрал nodejs, который, за каким-то хером, нужен для сборки хрома.
А пока решил посмотреть, какие там еще браузеры есть на webkit, и чтобы были вертикальные вкладки.
Вот, пожалуйста:
* https://wiki.gnome.org/Apps/Eolie, между прочим, тоже часть Gnome, так же как и epiphany. Вот у людей ресурсов, столько никому не нужных браузеров развивать? К сожалению, он написан на python.
* А вот еще один браузер на основе webkit, тоже с вертикальными вкладками - https://github.com/sonnyp/Tangram К сожалению, он написано на java script, причем, накакой-то очень всратой его разновидности - https://gitlab.gnome.org/GNOME/gjs Видимо, сейчас очень модно плодить JS рантаймы - https://deno.land/ - на Rust, https://bun.sh/ - на zig, да на любой вкус. Вот, гномовцы себе запилили на spidermonkey от Мозиллы, прекрасный выбор, что тут можно сказать.
Я стараюсь держаться подальше от скриптовых приложений для десктопа.
Очень, очень уважаю соответствующие языки, сам много пишу на Python, но, часто, вкупе с такими языками, идут соответствующие погромисты.
Ну его нахер, посижу еще на epiphany.
GitHub
GitHub - sonnyp/Tangram: Browser for your pinned tabs
Browser for your pinned tabs. Contribute to sonnyp/Tangram development by creating an account on GitHub.
😁3
https://lwn.net/Articles/903033/
"On a personal note, the most interesting part here is that I did the release (and am writing this) on an arm64 laptop. It's something I've been waiting for for a _loong_ time, and it's finally reality, thanks to the Asahi team. We've had arm64 hardware around running Linux for a long time, but none of it has really been usable as a development platform until now.
It's the third time I'm using Apple hardware for Linux development - I did it many years ago for powerpc development on a ppc970 machine. And then a decade+ ago when the Macbook Air was the only real thin-and-lite around. And now as an arm64 platform."
Хренасе, Линус пользуется Asahi.
"On a personal note, the most interesting part here is that I did the release (and am writing this) on an arm64 laptop. It's something I've been waiting for for a _loong_ time, and it's finally reality, thanks to the Asahi team. We've had arm64 hardware around running Linux for a long time, but none of it has really been usable as a development platform until now.
It's the third time I'm using Apple hardware for Linux development - I did it many years ago for powerpc development on a ppc970 machine. And then a decade+ ago when the Macbook Air was the only real thin-and-lite around. And now as an arm64 platform."
Хренасе, Линус пользуется Asahi.
👍6🔥3🤔3
https://old.reddit.com/r/rust/comments/us328s/can_someone_from_the_rust_community_share_their/i91o4wj/
Забавный текст.
Коллега описывает проблемы C-style linking model, которая используется в дистрибутивах, для распространения С++/Rust/Go кода.
Причем описывает хорошо, со знанием дела, а вот выводы делает совершенно неверные.
Пишет, что надо при сборке пакетов через go/cargo/etc фиксировать список зависимостей, и при их изменении инициировать пересборку зависимостей.
Херь какая-то.
Правильно так - каждая версия каждого пакета привязывает к себе конкретный набор конкретных версий(без <=, >=), и для rebuild нужно поменять этот список, и апнуть версию пакета.
Что делать с security багфиксами?
Владелец пакета должен бампнуть версию зависимости у себя, и сделать новый релиз.
Что делать, если пакет стал бесхозный, или владелец игнорирует баг?
* Форкните пакет, бампните версии, выложите в репозиторий.
* А вы точно хотите использовать такой код? Подыщите альтернативу.
* Можно вообще ничего не делать. Не понимаю этого безумия, когда надо пересобрать все, что зависит от zlib/openssl, когда там находят баг. Почти наверное, баг затрагивает мало кого.
Забавный текст.
Коллега описывает проблемы C-style linking model, которая используется в дистрибутивах, для распространения С++/Rust/Go кода.
Причем описывает хорошо, со знанием дела, а вот выводы делает совершенно неверные.
Пишет, что надо при сборке пакетов через go/cargo/etc фиксировать список зависимостей, и при их изменении инициировать пересборку зависимостей.
Херь какая-то.
Правильно так - каждая версия каждого пакета привязывает к себе конкретный набор конкретных версий(без <=, >=), и для rebuild нужно поменять этот список, и апнуть версию пакета.
Что делать с security багфиксами?
Владелец пакета должен бампнуть версию зависимости у себя, и сделать новый релиз.
Что делать, если пакет стал бесхозный, или владелец игнорирует баг?
* Форкните пакет, бампните версии, выложите в репозиторий.
* А вы точно хотите использовать такой код? Подыщите альтернативу.
* Можно вообще ничего не делать. Не понимаю этого безумия, когда надо пересобрать все, что зависит от zlib/openssl, когда там находят баг. Почти наверное, баг затрагивает мало кого.
Reddit
From the rust community on Reddit: Can someone from the Rust community share their views on this? Why can't we have dynamic linking…
Posted by codeandfire - 72 votes and 60 comments
👍4
Я в первой серии про #bootstrap рассказывал вот про этот вот проект - https://github.com/fosslinux/live-bootstrap/blob/master/parts.rst
Их задача - полностью исключить машинногенерируемый(закоммиченный) код из процесса сборки, все нужно собрать исходя из ground truth. Ну, то есть, борются за то, чтобы исключить из сборки 10 configure скриптов по 10000 строк кода каждый(на bash).
Интересно, что они делают с ядром Linux:
https://www.opennet.ru/opennews/art.shtml?num=57449
"Добавлено более 420 тысяч строк кода, связанных с драйвером amdgpu, из которых около 400 тысяч строк приходится на автоматически сгенерированные заголовочные файлы с данными для регистров ASIC в драйвере для GPU AMD, а ещё 22.5 тысяч строк обеспечивают начальную реализацию поддержки AMD SoC21. Общий размер драйвера для GPU AMD превысил 4 млн строк кода".
Я так понимаю, что и там 2-4 миллиона строк кода - автоматически сгенерированные.
В принципе, AMD там может прикопать вообще все, что угодно. Интересно, почему оно не генерируется в процессе сборки.
Их задача - полностью исключить машинногенерируемый(закоммиченный) код из процесса сборки, все нужно собрать исходя из ground truth. Ну, то есть, борются за то, чтобы исключить из сборки 10 configure скриптов по 10000 строк кода каждый(на bash).
Интересно, что они делают с ядром Linux:
https://www.opennet.ru/opennews/art.shtml?num=57449
"Добавлено более 420 тысяч строк кода, связанных с драйвером amdgpu, из которых около 400 тысяч строк приходится на автоматически сгенерированные заголовочные файлы с данными для регистров ASIC в драйвере для GPU AMD, а ещё 22.5 тысяч строк обеспечивают начальную реализацию поддержки AMD SoC21. Общий размер драйвера для GPU AMD превысил 4 млн строк кода".
Я так понимаю, что и там 2-4 миллиона строк кода - автоматически сгенерированные.
В принципе, AMD там может прикопать вообще все, что угодно. Интересно, почему оно не генерируется в процессе сборки.
GitHub
live-bootstrap/parts.rst at master · fosslinux/live-bootstrap
Use of a Linux initramfs to fully automate the bootstrapping process - fosslinux/live-bootstrap
👍6🤔2
В целом, я доделал вендоринг для #go.
Some highlights:
* Добавил в графогенератор возможность выставить факт того, что нода будет использовать сеть, и факто того, что нода будет производить файлы с заранее известными sha. https://git.sr.ht/~pg/ix/tree/main/item/pkgs/die/go/vendor.sh#L13-17
* Сделал так, что нода, которая хочет сеть, обязана выставить sha для своих output - https://git.sr.ht/~pg/ix/tree/main/item/core/gg.py#L48-49 Тем самым, я гарантирую "чистоту" выхлопа.
* Нода, которая сеть не заказала, сеть не получит - https://git.sr.ht/~pg/ix/tree/main/item/pkgs/bin/assemble/as.go#L196-199! (в сторону - мне прямо нравится писать на Go(ну, на моей модификации Go, я автор, я так вижу), надо про это написать отдельно)
* Я не очень понял, как правильно завендорить все проекты, находящиеся в одном дереве исходников за раз. Пока получилось как-то так - https://git.sr.ht/~pg/ix/tree/main/item/pkgs/die/go/vendor.sh#L24-27, если знаете способ лучше - расскажите!
* Ну и немного магии с tar, https://git.sr.ht/~pg/ix/tree/main/item/pkgs/die/go/vendor.sh#L31, чтобы получить bit identical tar.gz файл. C bsdtar повторить пока не получилось, поэтому пока тут используется gnu tar.
Правило "хочешь unpure input, гарантируй pure output" мне очень нравится, с его помощью можно сделать несколько приятных штук, например, сделать так, чтобы нода, генерирующая сертификаты для всей системы, зависела только от sha своего output, а не от curl + openssl.
Some highlights:
* Добавил в графогенератор возможность выставить факт того, что нода будет использовать сеть, и факто того, что нода будет производить файлы с заранее известными sha. https://git.sr.ht/~pg/ix/tree/main/item/pkgs/die/go/vendor.sh#L13-17
* Сделал так, что нода, которая хочет сеть, обязана выставить sha для своих output - https://git.sr.ht/~pg/ix/tree/main/item/core/gg.py#L48-49 Тем самым, я гарантирую "чистоту" выхлопа.
* Нода, которая сеть не заказала, сеть не получит - https://git.sr.ht/~pg/ix/tree/main/item/pkgs/bin/assemble/as.go#L196-199! (в сторону - мне прямо нравится писать на Go(ну, на моей модификации Go, я автор, я так вижу), надо про это написать отдельно)
* Я не очень понял, как правильно завендорить все проекты, находящиеся в одном дереве исходников за раз. Пока получилось как-то так - https://git.sr.ht/~pg/ix/tree/main/item/pkgs/die/go/vendor.sh#L24-27, если знаете способ лучше - расскажите!
* Ну и немного магии с tar, https://git.sr.ht/~pg/ix/tree/main/item/pkgs/die/go/vendor.sh#L31, чтобы получить bit identical tar.gz файл. C bsdtar повторить пока не получилось, поэтому пока тут используется gnu tar.
Правило "хочешь unpure input, гарантируй pure output" мне очень нравится, с его помощью можно сделать несколько приятных штук, например, сделать так, чтобы нода, генерирующая сертификаты для всей системы, зависела только от sha своего output, а не от curl + openssl.
👍7
https://mort.coffee/home/tar/
https://www.opennet.ru/opennews/art.shtml?num=57587 #CVE
Я тут подумал, что было бы очень круто, если бы всякого рода распаковщики можно было запускать в своем mount namespace + chroot, ну, то есть, чтобы / был той папкой, куда мы распаковываем.
Например, вот так:
Тогда они бы все перестали лазить по fs куда ни попадя, и не надо было бы реализовывать квадратичные алгоритмы в tar.
Но, конечно, у этого решения есть фатальный недостаток - один раз решишь проблему на корню, а что потом?
https://www.opennet.ru/opennews/art.shtml?num=57587 #CVE
Я тут подумал, что было бы очень круто, если бы всякого рода распаковщики можно было запускать в своем mount namespace + chroot, ну, то есть, чтобы / был той папкой, куда мы распаковываем.
Например, вот так:
pg-> unshare -r -m sh(папочку bin я скопировал, чтобы внутри chroot можно было бы показать, где я)
pg-> mkdir -p new_root/bin
pg-> cp /bin/* new_root/bin/
cp: omitting directory '/bin/bin_dhcpcd_sys'
cp: omitting directory '/bin/bin_ix'
cp: omitting directory '/bin/bin_openresolv'
pg-> chroot new_root /bin/sh
pg-> ls -la /
total 16
drwxr-xr-x 3 0 0 17 Aug 3 02:32 .
drwxr-xr-x 3 0 0 17 Aug 3 02:32 ..
drwxr-xr-x 2 0 0 12288 Aug 3 02:32 bin
Тогда они бы все перестали лазить по fs куда ни попадя, и не надо было бы реализовывать квадратичные алгоритмы в tar.
Но, конечно, у этого решения есть фатальный недостаток - один раз решишь проблему на корню, а что потом?
www.opennet.ru
Уязвимость в Rsync, позволяющая перезаписать файлы на стороне клиента
В rsync, утилите для синхронизации файлов и резервного копирования, выявлена уязвимость (CVE-2022-29154), позволяющая при обращении к rsync-серверу, подконтрольному злоумышленнику, записать или перезаписать произвольные файлы в целевом каталоге на стороне…
👍4
https://theevilskeleton.gitlab.io/2022/07/28/libadwaita-fixing-usability-problems-on-the-linux-desktop.html
SJW текст, полный двоемыслия.
"GNOME developers typically use copyleft licenses, specifically GNU licenses like GPL. GPL prevents entities from creating proprietary and closed source forks. For example, if I fork (grab the code) a GNOME project that is licensed as GPL, I legally cannot modify and redistribute the software without disclosing the source code, because GPL prohibits forkers from doing so, thereby literally protecting user freedom."
"While “locking” down theming may worsen user freedom, it prevents distributions from breaking applications, as a result protecting user experience. Every decision taken has to have some compromises. GNOME developers are slightly compromising user freedom to protect user experience, as it prevents users from getting a subpar experience out of the box with GNOME applications. In my opinion, this is absolutely worth the compromise."
Два абзаца подряд, человек не понимает, что противоречит сам себе(IMHO).
Как в одной голове укладывается что "gpl это хорошо, потому что защищает свободу пользователя" и "ну, местами на эту свободу пофиг, главное - UX".
"In conclusion, theming is very sensitive to change, even at the slightest. While users knowingly theming applications were not a problem for GNOME developers, the problem lied in distributions shipping custom themes despite explicitly being asked not to."
"This is also a very important lesson to every one: having the freedom to do something doesn’t mean it should be done. While it is neat idea to be able to do whatever you want, there is a risk that you can affect people around you, or worse, yourself."
"Just because you can, doesn’t mean you should."
Я так понимаю, это про
"With these problems in mind, this lead to GNOME contributors to write an open letter in 2019, to politely ask distributions to stop shipping custom themes by default, and let users manually apply themes if they ever choose to do so. However, within a couple of years after the letter, nothing had changed: distributions continued to ship custom themes by default, which caused them to break many applications"
А это вообще прекрасно. Неэтично модифицировать код, когда тебя просят этого не делать!
Лицензия? Не, не слышали, зачем какая-то лицензия, если можно в какой-то момент заявить, что конкретное изменение в коде - плохо, и все тут?
SJW текст, полный двоемыслия.
"GNOME developers typically use copyleft licenses, specifically GNU licenses like GPL. GPL prevents entities from creating proprietary and closed source forks. For example, if I fork (grab the code) a GNOME project that is licensed as GPL, I legally cannot modify and redistribute the software without disclosing the source code, because GPL prohibits forkers from doing so, thereby literally protecting user freedom."
"While “locking” down theming may worsen user freedom, it prevents distributions from breaking applications, as a result protecting user experience. Every decision taken has to have some compromises. GNOME developers are slightly compromising user freedom to protect user experience, as it prevents users from getting a subpar experience out of the box with GNOME applications. In my opinion, this is absolutely worth the compromise."
Два абзаца подряд, человек не понимает, что противоречит сам себе(IMHO).
Как в одной голове укладывается что "gpl это хорошо, потому что защищает свободу пользователя" и "ну, местами на эту свободу пофиг, главное - UX".
"In conclusion, theming is very sensitive to change, even at the slightest. While users knowingly theming applications were not a problem for GNOME developers, the problem lied in distributions shipping custom themes despite explicitly being asked not to."
"This is also a very important lesson to every one: having the freedom to do something doesn’t mean it should be done. While it is neat idea to be able to do whatever you want, there is a risk that you can affect people around you, or worse, yourself."
"Just because you can, doesn’t mean you should."
Я так понимаю, это про
"With these problems in mind, this lead to GNOME contributors to write an open letter in 2019, to politely ask distributions to stop shipping custom themes by default, and let users manually apply themes if they ever choose to do so. However, within a couple of years after the letter, nothing had changed: distributions continued to ship custom themes by default, which caused them to break many applications"
А это вообще прекрасно. Неэтично модифицировать код, когда тебя просят этого не делать!
Лицензия? Не, не слышали, зачем какая-то лицензия, если можно в какой-то момент заявить, что конкретное изменение в коде - плохо, и все тут?
❤6🤡6
Есть такой проект - http://www.oilshell.org/, https://github.com/oilshell/oil
Я на него регулярно наталкиваюсь, когда читаю "сегодняшний" список на version upgrade для моих пакетов.
Мне, конечно, очень интересно, что это такое, только я пока так и не сумел понять.
https://www.oilshell.org/cross-ref.html
У автора примерно 100500 неконсистентных идей, от текста к тексту разных:
https://www.oilshell.org/blog/2018/01/28.html
https://www.oilshell.org/blog/2021/01/why-a-new-shell.html#more-ambitious-ideas
Шелл сначала был написан на Python, а потом его автора торкнуло, и он решил все ускорить в 100500 раз.
Нет чтобы переписать там на go, или на C++, он начал придумывать какую-то систему непрерывного "морфинга" кода с Python на С++, со своим runtime и GC:
http://www.oilshell.org/blog/2022/05/mycpp.html
http://www.oilshell.org/blog/2022/05/gc-heap.html
http://www.oilshell.org/blog/2022/03/middle-out.html
Там уже 3 каких-то разных языка:
https://www.oilshell.org/cross-ref.html?tag=osh-language#osh-language
https://www.oilshell.org/cross-ref.html?tag=oil-language#oil-language
https://www.oilshell.org/blog/2020/10/big-changes.html#appendix-the-tea-language
Какие-то дикие кросс-референсы между отдельными статьями, без конкретики в каждой из них.
Короче, это написал или сумасшедший с биполяркой(очередной TempleOS?), или какой-то сумрачный гений, и я просто не могу его понять(или и то, и другое, вместе). Но откуда тогда 2к звезд на github?
Я на него регулярно наталкиваюсь, когда читаю "сегодняшний" список на version upgrade для моих пакетов.
Мне, конечно, очень интересно, что это такое, только я пока так и не сумел понять.
https://www.oilshell.org/cross-ref.html
У автора примерно 100500 неконсистентных идей, от текста к тексту разных:
https://www.oilshell.org/blog/2018/01/28.html
https://www.oilshell.org/blog/2021/01/why-a-new-shell.html#more-ambitious-ideas
Шелл сначала был написан на Python, а потом его автора торкнуло, и он решил все ускорить в 100500 раз.
Нет чтобы переписать там на go, или на C++, он начал придумывать какую-то систему непрерывного "морфинга" кода с Python на С++, со своим runtime и GC:
http://www.oilshell.org/blog/2022/05/mycpp.html
http://www.oilshell.org/blog/2022/05/gc-heap.html
http://www.oilshell.org/blog/2022/03/middle-out.html
Там уже 3 каких-то разных языка:
https://www.oilshell.org/cross-ref.html?tag=osh-language#osh-language
https://www.oilshell.org/cross-ref.html?tag=oil-language#oil-language
https://www.oilshell.org/blog/2020/10/big-changes.html#appendix-the-tea-language
Какие-то дикие кросс-референсы между отдельными статьями, без конкретики в каждой из них.
Короче, это написал или сумасшедший с биполяркой(очередной TempleOS?), или какой-то сумрачный гений, и я просто не могу его понять(или и то, и другое, вместе). Но откуда тогда 2к звезд на github?
GitHub
GitHub - oils-for-unix/oils: Oils is our upgrade path from bash to a better language and runtime. It's also for Python and JavaScript…
Oils is our upgrade path from bash to a better language and runtime. It's also for Python and JavaScript users who avoid shell! - oils-for-unix/oils
🤔13👍3