Pull to refresh

Comments 27

Это что же, они придумали Maven? А вообще, интересно было бы попробовать. В императивном грейдле слишком просто выстрелить себе в ногу.

Ну по сути да, maven с автодополнением

Нет. Разница между Maven и Gradle не в императивности. Кто юзал gradle правильно, и так не писал императивных штук в скриптах, а переносил их в плагины. Разница в том, что плагин в Maven - это по сути одна таска, а в Gradle плагин способен конфигурировать проект целиком - например, подсовывать зависимости, таски, модифицировать имеющиеся и т.д. Это очень полезно, когда в проекте есть модули, имеющие некоторый общий функционал. Например, у нас в проекте мы предоставляем пользователям писать скрипты для нашего приложения на TypeScript, при этом есть некоторые модули, которые экспортируют части этого скриптового API. Для них нужны определённые действия, плюс определённые зависимости. Причём, действия - это не просто одна таска. Например, хочется запускать валидатор сгенерированных d.ts файлов, для чего нужен node.js плагин. Почему не написать свой mojo, который бы запускал node.js? Ну потому что такой плагин уже есть и не хочется его велосипедить. До переезда на Gradle мы просто тащили из модуля в модуль определённый бойлерплейт (который ещё изредка менялся и приходилось его апдейтить в каждом модуле, где он есть). В Gradle это выглядит как-то так:

plugins {
id "tsApiProvider"
}

Кроме того, в Gradle в принципе очень многие вещи сделаны грамотнее:

  • вместо прибитого гвоздям набора фаз есть граф тасок

  • концепция конфигураций намного гибче скоупов в maven; идущие из коробки с java плагином конфигурации лучше отражают реалии многомодульных проектов (api/implementation)

  • продумана инкрементальность на уровне тасок, можно чётко определять для каждой таски, откуда она получает данные на вход, куда пишет вывод.

  • gradle любят ругать за отсутствие документации. Так вот, для Maven она в тысячи раз хуже. Есть только базовая инструкция, как написать плагин (mojo), но как сделать продвинутые вещи (например, динамически порезолвить зависимость) можно узнать, только залезая в код каких-нибудь плагинов, которые делают более-менее то же самое.

Но ведь "прибитый гвоздями набор фаз" - это попытка упорядочить бардак. Стандартизация. Система. Сколько можно изобретать? Каждый проект - велосипед желающих почесать свое эго разрабов. Особенный проект. Гибкость. Очередная куча наваленных друг на друга костылей. Каждый раз черт ногу сломит.

Ну а что поделать, если на практике реально возникла ситуация, когда стандартных фаз не хватает? Вот возник кейс, о котором создатели Maven просто не подумали. Приходится шаманить с порядком применения mojo внутри одной фазы, чтобы всё сошлось. И "почесать своё эго" вообще не при чём. По моему опыту, если делаешь типовой Java проект со Spring/Hibernate/что-то такое, собираешь это в war, то Maven отлично работает. А если проект: собрать из (почти) одних сорцов десктопное приложение в виде fat jar, Android-приложение, iOS приложение с помощью Graal Native Image и веб-приложение с помощью какого-нибудь транслятора Java->JS/WebAssembly, да ещё сервер рядом с ними положить в докер-образ вместе с нативными либами, то использование Maven превращается в АД. Кстати, да, часть исходников при этом генерится с помощью какого-нибудь ANTLR, а другая часть - Protobuf.

И, кстати, у организации тасок в граф есть ещё пара преимуществ. Во-первых, так проще сделать инкрементальную сборку. Тогда просто таски не зависят друг от друга напрямую, а output одной таски направляется в input другой. Во-вторых, граф тасок проще распараллелить.

Первым делом, для antlr и protobuf есть maven плагины.

Во-вторых, насчет создания приложений из одной кодовой базы для разных платформ. У меня сомнения, что эту задачу надо решать инструментами gradle, но я не настолько специалист, и интересно было бы почитать мнение специалистов. Это, имхо, архитектурная задача, как распределить код по проектам.

Но вообще, согласен с вами. Желание простоты разбивается о практику.

собрать из (почти) одних сорцов десктопное приложение в виде fat jar, Android-приложение, iOS приложение с помощью Graal Native Image и веб-приложение с помощью какого-нибудь транслятора Java->JS/WebAssembly, да ещё сервер рядом с ними положить в докер-образ вместе с нативными либами, то использование Maven превращается в АД.

Только звучит страшно, а на практике вполне решаемо, если, конечно, не упереться рогом в требование всю эту вакханалию в рамках одного артефакта собирать. Да, будет много модулей, вероятно, собираться будет долго. Но озвученная задача вполне решаема в рамках стандартных мавеновских фаз.

Кстати, да, часть исходников при этом генерится с помощью какого-нибудь ANTLR, а другая часть - Protobuf.

Кстати, для генерации исходников у мавена таки из коробки имеется отдельная фаза.

на практике вполне решаемо

Конечно, решаемо. Мы как раз и собирали весь этот ад Maven, пока пару лет назад не смигрировали на Gradle. И это был глоток свежего воздуха. Там где раньше были обильно расставлены костыли и нагорожена куча бойлерплейта, сейчас короткие и ясные декларативные описания. Там, где приходилось сражаться с ограничениями инструмента, сейчас просто берёшь и используешь его.

Кстати, для генерации исходников у мавена таки из коробки имеется отдельная фаза.

Да-да, как и плагин. Но тут, конечно, не совсем в тему мой коммент. Просто если проект держат на Maven, то собственно Maven его достаточно редко собирают, ибо каждый раз ждать 5 минут просто чтобы запустить и проверить изменения - это невиданная расточительность. Так вот, мы всегда полагались на то, что IDEA как-то спарсит pom.xml и как-то свой проект сконфигурирует, а потом собирать идеей, т.к. она умеет в инкремениальную сборку, и там, где Maven работал несколько минут, IDEA умудрялась за пару секунд пересобирать. Ну и, конечно, туда не входил protobuf. Поэтому когда кто-то накоммитил изменений в proto-файлы, приходилось брать вручную запускать какие-то goal-ы каких-то проектов. Ну и т.к. это было не одно место, была целая россыпь run configuration на все случаи жизни - что запустить, если при очередном pull проект сломался. Это уж не говоря о том, что при переключении между feature branch и master приходилось всё то же самое проделывать. C gradle в принципе иначе поступают - там собирают всегда им, а не делегируют IDE, и он сам хорошо умеет в инкрементальную компиляцию и способен проект собрать за 2 секунды. Так что как только мы переехали на gradle, необходимость в каких-либо манипуляцих отпала - можно просто запустить проект оттуда, где вы сейчас находитесь, и он гарантированно запустится.

Кстати, ещё забыл. Проект частично на Kotlin, частично на Java, причём есть модули, где есть код и на Kotlin и на Java. Я помню, нужно было отдельно настраивать оба компилятора так, чтобы они правильно совместно работали, т.е. писать кучу бойлерплейта. С Gradle всё не так - нужно всего лишь подключить плагин Java и плагин Kotlin и оно всё само.

Вообще не понимаю зачем в файлах проектов императивность. Все что там нужно - это список файлов исходников и опции компилятора.

Я достаточно далек от Java/Kotlin, но вот что интересно. Если в интернете найти какие-то старые проекты для Visual С++, современные студии их открывают (да, там происходит преобразование форматов, но даже новейшая студия умеет открывать файлы dsp образца 1998 года). С проектами для Android у меня это не получилось вообще. Я как-то попытался поизучать программинг под Android и нашел на гитхабе интересующие меня примеры. При попытке их собрать из Android Studio всякий раз получал совершенно невразумительные ошибки (разумеется невразумительные они для меня, новичка - но это совершенно точно были не ошибки в коде Java/Kotlin).

И условно старый gradle процентов на 80 декларативный.

Дефолтный конфиг обычно выглядит как список плагинов, список репозитариев и список зависимостей. Это в чистом виде декларативность.

Возможно. Но мне как человеку со стороны, тем ни менее имеющему разнообразный опыт в программировании на других языках, использование Gradle... не понравилось. Это очень сложно сформулировать, возможно этой теме следует посвятить отдельное исследование... но впечатление какой-то громоздкой переусложненности всего этого. Сгенерированная средой разработки программа уровня Hello World, состоящая из единственного файла с исходным кодом, а проект (еще нескомпилированный!) содержит 120 файлов и 45 директорий, назначение 99% которых мне неизвестно.

Одновременно с экспериментами для Android я начал также же эксперименты на Go - и там оказалось все просто, понятно и приятно. Среда разработки кстати похожая, от той же компании.

содержит 120 файлов и 45 директорий, назначение 99% которых мне неизвестно

Это не вина Gradle.

Ну я вот вам так скажу - у меня были проекты на gradle. Не я его туда притащил, но мне пришлось сопровождать. В итоге после изучения я его выпилил и заменил на maven, потому что императивного кода там не было. И не было ничего, что бы требовало писать код для сборки. То есть, то что тут чуть ранее написали про 80% - по моим оценкам очень близко к истине.

Но есть случаи, когда нужно собирать один и тот же проект по-разному, скажем, для разных клиентов. И тогда в сборке появляется логика, отличная от той, что умеет даже make - т.е. от описания зависимостей и списка простых последовательных шагов. И тогда код в сборке нужен.

Когда один проект нужно собирать по-разному, то делается отдельный артефакт a-la my-project-dist, в котором assembly-плагином описываешь, что нужно получить в каждом дистрибутиве.

Ну, assembly это все же не совсем про логику. Или это частный случай логики, один из возможных.

Вообще не понимаю зачем в файлах проектов императивность. Все что там нужно - это список файлов исходников и опции компилятора.

Для простых проектов - это так. Но есть случаи, когда нужно что-то сделать в процессе билда. Например скачать 3rd-party ресурсы с сервера и докинуть их в дистрибутив. Или вот не так давно я собирал native image для graalvm/js приложения, и мне пришлось написать логику на полсотки строк кода, потому что не было варианта из коробки.

По итогу главная проблема чисто декларативных языков - как только задача выходит за рамки стандартного и нет в наличии плагина для нужной цели, приходится самому писать эти плагины, и как правило это намного геморнее, чем просто вызвать код в том же gradle.

В мавене, если не ошибаюсь, есть команды для скопировать, скачать, удалить и так далее (прошу поправить, если ошибаюсь).

Это философский вопрос. Описание последовательности действий - на самом деле то-же самое, что просто декларативное объявление, какие шаги нужно сделать для сборки.

Если в скрипте нет условий, циклов, то он ничем не отличается от декларативного объявления.

Аналогично в gradle есть стандартные задачки для копирования, скачивания файлов и со вполне декларативным описанием.

Но иногда хочется чего-то нестандартного.

Например, существует плагин для компиляции скалы 2.11 под android - он ждёт, когда андроид плагин соберёт граф зависимостей между задачами и добавляет новых задач, в которых вместо компилятора java начинает вызываться компилятор scala. Задачки со сборкой java остаются, но у них аккуратно отключают (заменяют список входных файлов на пустой и добавляют зависимость от компиляции scala)

Сам андроид плагин в зависимости от конфигурации создаёт целую кучу задачек компиляции и именно гибкость и динамичность старого gradle позволяла их модифицировать без больших мучений.

Судя по вашим словам, какой-то феерический костыль.

Да, костыль. Но иногда бывает "надо".
Альтернативое решение - взять исходники андроид плагина, поменять их, а потом поддерживать все-все изменения, которые делает гугл. Это тоже ни разу не просто и не красиво.

Судя по тому что я бегло прочитал, то это аналог Amper, теперь самый главный вопрос, если Gradle теперь умеет так же как Amper зачем он нужен.

Понимаю, если кто-то напишет: "но ведь Amper для Kotlin Multiplatform, а Gradle для JVM", но Gradle умеет не только JVM собирать, но и даже C++.

Таким образом, зачем нужен Amper - снова непонятно.

Например, у нас на backend проекте вообще котлин с перемешку с Java файлами собирается Maven'ом.

Мое личное имхо: Kotlin Boot хочу, чтобы начать кодить на котлине можно было в два шага.

А не собирая солянку из Ktor + Koin + библиотеки для логов + библиотеки для тестов + test containers + ... до бесконечности, а потом тоже самое повторить для фронтенда и при этом заставить всё это нормально билдится и отлаживаться.

Но это не мое дело, т.к к сожалению я в котлин ничего за 6 лет в разработке не закоммитил.

Что DG, что Amper - есть результат последнего коллаба JB, Gradle и Google с целью: "... put most of our attention into declarative software definition".

Ну и standalone "движок" Amper'а - он же, практически, "вчера только" вышел. И чем оно закончится - подозреваю - и сами JB до конца пока не понимают. Весь MP "покрыть" - это не так просто, как кажется/хочется.

Так что, не буду сильно удивлен, если - в конце концов - у Amper под капотом окажется как раз DG...

В комментариях пошел спор о сравнении императивности и декларативности, Maven и Gradle.

А мне по сути статьи непонятно, чем же новый Gradle Declarative отличается от «классического» Gradle? То что tasks будут запрещены? Или что их теперь надо всегда в plagin реализовывать?

По приведённому примеру это как-то непонятно. Уж очень всё похоже на «классический» Gradle, поскольку задачка простая.

А мне по сути статьи непонятно, чем же новый Gradle Declarative отличается от «классического» Gradle?

Всё достаточно просто... основная мотивация в том, что бы отделить "скрипты" от "конфигураций". Идея в том, что будет круто, если в "большинстве случаев" разработчики будут оперировать "конфигурацией" - исключительно понятными им "концептами" (приложение, библиотека, версия, тестовое окружение и т.п.). А в "меньшинстве случаев" - они станут т.н. "build инженерами" - ну или позовут таковых на помощь - и допишут всё что "им надо" через "скрипты".

Про build инженеров...

Gradle - исторически - делит всех своих пользователей на две большие группы. Разработчиков ПО и т.н. build инженеров. Первых сильно больше, чем вторых. Но для вторых, ценность "гибкости" Gradle сильно выше всего остального.

Далее, "конфигурация" (как набор описаний тех самых "концептов") - по своей сути - декларативна. И это приводит нас к мысли, что это "будет круто" не только для разработчиков, но и для всякого рода интеграций... например, с IDE - которой, при наличии такой "конфигурации" не нужно будет "парсить в уме" 100500 "скриптов", чтоб построить, условно, содержимое classpath для "вот этого модуля". Как-то так.

Если говорить о текущей реализации, то - на данный момент - это всё выражается в том, что появился ещё один DSL (в добавок к .gradle и .gradle.kts) - .gradle.dcl. В нём - таки да - запрещены всякие "императивные штучки" типа циклов, ветвлений и т.п.

То что tasks будут запрещены? Или что их теперь надо всегда в plagin реализовывать?

Формально - да, в .dcl - "таски" и "всё вот это вот" - нельзя.

Но, как обычно - в рамках проекта - эти (.gradle, .gradle.kts и .gradle.dcl) DSL можно "миксовать". Например, вполне норм, когда settings.gradle.dcl и - при этом - build.gradle.kts. Или в "корне" .gradle.kts, а в модулях .gradle.dcl. Ну и т.д. до штатного использования buildScr и build-logic.

По поводу самого .dcl - он всё-таки пока ещё "сыроват" - что не удивительно - но "играцца" уже вполне себе модно. Хотя сама по себе идея, имхо, годная.

Если сильно интересно, то можно "поискать по блогам"... как раз под конец прошлого года (когда был анонс DG) было весьма много статей как раз от "build инженеров", основной посыл которых сводился к "руки прочь" и т.п. Но на данный момент - когда всё это более мнее прояснилось - особых возражений за "ещё один DSL" вроде как нет.

Спасибо за понятный ответ.
Посмотрим, что из этого выйдет. Больших улучшений я не жду, ибо, как сказано "любая продвинутая технология превращается в магию...".

Sign up to leave a comment.