Рубрики
Без рубрики

Интеграция обратного инжиниринга J2CL–Bazel

J2CL – это инструмент Google, который преобразует Java-код в JavaScript, совместимый с Closure. Это уже началось… С тегами j2cl, gwt, bazel, java.

J2CL – это инструмент Google, который преобразует Java-код в JavaScript, совместимый с Closure. Он был запущен в 2015 году, чтобы в конечном итоге заменить GWT в Google, по разным причинам, объясненным в project README , и будет использоваться в качестве основы для GWT 3. Как проект Google, он глубоко интегрирован с их инструментом сборки: Базель . Мы рассмотрим, как все это работает, с целью создания эквивалентного плагина Gradle .

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

Отправные точки

С точки зрения пользователя, основными отправными точками являются правила Bazel j2cl_library и j2cl_application .

Правило j2cl_application на самом деле является просто макросом вокруг правил rules_closure . Он принимает в качестве входных данных closure_js_library и j2cl_library зависимостей и пространств имен закрытия точки входа (которые должны присутствовать в зависимостях), и создает оптимизированный JavaScript с помощью правила closure_js_binary с несколькими параметрами конфигурации. Он также генерирует правило web_library для запуска вашего кода во время разработки, мы вернемся к нему позже.

Как мы только что видели, a j2cl_application напрямую не имеет источников, но принимает их как зависимости. Таким образом, весь код вашего приложения фактически будет находиться в j2cl_library .

A j2cl_library правило – это то, что на самом деле переводит ваш исходный код Java в JS. Он принимает исходные файлы Java в качестве входных данных и создает JAR-файл скомпилированных классов Java и ZIP-файл транспилированного JS (известный как JSZip).

Наконец, существуют правила для тестов (мы вернемся к ним позже) и для импорта сторонних библиотек, в том числе из репозиториев Maven.

j2cl_library

Давайте подробнее рассмотрим, что делает j2cl_library .

A j2cl_library каким-то образом является одновременно java_library и closure_js_library . Он принимает в качестве входных данных набор исходных файлов Java и JS вместе с зависимостями, которые могут быть либо closure_js_library , либо другими правилами j2cl_library . Обратите внимание, что в Bazel код Java, который также используется, например, на сервере, будет также использоваться java_library .

A j2cl_library начнется с удаления всего кода из исходных файлов Java, который помечен @GwtIncompatible (технически, он заменяет этот код пробелами, так что номера строк сохраняются). Затем этот урезанный код будет скомпилирован с помощью явак ; этот шаг также может привести к созданию новых исходных текстов Java с помощью обработки аннотаций (технически, он может даже генерировать JS-файлы). Исходные файлы Java и JS, а также файлы, созданные во время компиляции, затем будут переданы в JCL для создания библиотеки Closure JS.

Для переноса кода Java J2 CL нужны зависимости в виде скомпилированных классов Java для разрешения API-интерфейсов Java (в частности, перегрузки методов, неявные приведения или даже вывод типа с помощью ключевого слова Java 10 var ); это должны быть разделенные варианты зависимостей, вот почему j2cl_library правила зависят от других j2cl_library правил, а не от standard/ | java_library правила. J2CL также разрешает .native.js файлы родственник .java files, который обычно содержит JS-код, реализующий собственные методы (каким-то образом эквивалентный JSNI GWT), и объединяет их содержимое в сгенерированные .js файлы. Следует отметить, что шаг javac заменяет путь к классу начальной загрузки библиотекой эмуляции Java Runtime (которая почти на 100% используется совместно с GWT).

Итак, j2cl_library выводит как JAR, подобный java_library , за исключением того, что исходные тексты сначала были удалены из любого @GwtIncompatible кода, так и ZIP-архив JS-файлов, таких как closure_js_library .

Технически, в Bazel j2cl_library , как и closure_js_library , также проверяет тип JS (это, вероятно, немного избыточно в случае J2CL, хотя есть .native.js files тоже) и выводит файлы метаданных о библиотеке в помогите ускорить нижестоящие библиотеки Closure JS (не перестраивая/повторно проверяя их, когда их вышестоящий API не изменился; это похоже на то, как Bazel и Gradle извлекают JAR/info только для API из классов Java) и передают диагностические подавления вплоть до компиляции закрытия. Это довольно специфично для Bazel и rules_closure , хотя , возможно, может быть реализован и в Gradle (хотя способ, которым Gradle извлекает и проверяет информацию только для API из классов Java, полностью отличается от того, как это делает Bazel: Gradle, по-видимому, снимает отпечатки пальцев со всего пути к классу компиляции Java, в то время как Bazel генерирует ijara для каждого java_library а затем сравнивает контрольные суммы этих файлов с любыми другими входными данными).

Как мы видели, a j2cl_library использует три инструмента:

  • Gwt-совместимый стриптизерш
  • явак
  • и, наконец, J2clTranspiler

И последнее, но не менее важное: все эти инструменты выполняются в постоянных рабочих процессах, поэтому вам не нужно каждый раз создавать JVM (что при разработке Bazel с правилами почти для каждого пакета Java означало бы много раз).

Импорт банок

Существует три правила Базеля для импорта JAR в J2CL.

Правило j2cl_import представляет собой простой мост для случаев, когда вам нужен j2cl_library зависит от java_library ; это следует использовать только для JAR-файлов только для аннотаций (J2CL не нужны исходные файлы для аннотаций, и они не генерируют JS-код; они полезны только для запуска процессоров аннотаций или настройки инструментов статического анализа, таких как ErrorProne ).

Правило j2cl_import_external принимает набор альтернативных URL-адресов для JAR (и его контрольной суммы SHA256) и генерирует либо j2cl_import для JAR только с аннотациями, либо j2cl_library . В последнем случае JAR должен содержать исходные тексты Java. На самом деле ожидается, что это будет библиотека GWT с суперисточниками в подпакетах super . Затем j2cl_library будет использовать суперисточники (и игнорировать эквивалентные файлы с суперисточниками, которые в этом случае несовместимы с GWT), а также игнорировать любой файл * _CustomFieldSerializer * (поскольку Google больше не использует GWT-RPC и, следовательно, не портировал его к J2CL).

Наконец, правило j2cl_maven_import_external представляет собой оболочку вокруг j2cl_import_external , просто генерируя URL-адреса из координат Maven и набора URL-адресов репозитория Maven. Следует отметить, что этот макрос использует sources JAR, т.е. он заменяет классификатор и упаковку на sources и jar соответственно.

Да, вы правильно поняли: эти последние два правила будут учитывать только исходные файлы в JAR (если только они не являются JAR только для аннотаций) и, следовательно, удалят их @ GwtIncompatible код и (повторно) скомпилируют их с помощью javac , прежде чем окончательно перевести их в JS. Таким образом, ожидается, что банки либо включают в себя весь исходный код (что наиболее важно, включая источники, сгенерированные обработкой аннотаций), либо, возможно, их зависимости настроены таким образом, что javac step сможет обрабатывать аннотации и генерировать недостающие файлы. Библиотеки GWT попали бы в первую корзину, но их исходные файлы JAR, развернутые в репозиториях Maven, могут и не попасть, поэтому при воспроизведении этого за пределами Bazel мы, возможно, сделали бы другой выбор.

На самом деле, если вы посмотрите на репозиторий JCL, вы увидите, что он должен использовать другую технику при импорте библиотеки jbox2d, поскольку она помещает суперисточники в подпакет gwtemul (и включает несовместимый с GWT код в другой пакет). В этом конкретном случае он извлекает исходные файлы из GitHub, отфильтровывает файлы с супер-исходным кодом или файлы, несовместимые с J2CL, а затем объявляет j2cl_library для этих исходных файлов (поэтому на самом деле перестраиваем библиотеку из исходных текстов).

j2cl_приложение

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

Входными данными для компилятора закрытия являются транзитивное закрытие всех closure_js_library и вывод JS из j2cl_library зависимости. Это единственное место, где используются выходные данные JS правил j2cl_library ; когда j2cl_library зависит от другого j2cl_library , как мы видели выше, он использует только свой вывод JAR.

Однако интересно посмотреть, как пользователи будут запускать и отлаживать свой код: j2cl_application сгенерирует неоптимизированную версию JS (в основном в виде closure_js_binary с другой конфигурацией) и HTML-файл, который его загрузит, и запустит сервер разработки для обслуживания их всех. HTML-страница и сервер разработки также поддерживают ibazel и livereload, так что при запуске через |/ibazel страница будет загружаться всякий раз, когда изменяется исходный файл.

В коде есть примечания о приложениях с custom dev servers , можно было бы представить серверы, которые защищают страницу за аутентификацией, или нужно каким-то образом вводить динамические вещи на страницу. Однако пока это остается недокументированным. В любом случае, для тех, кто знает как работает режим super dev в GWT , это сильно отличается, гораздо больше похоже на parcel serve чем parcel watch для тех, кто знаком с разработкой JS с помощью Parcel и без горячей замены модуля (HMR); хотя мы на самом деле не знаем, как работает разработка GWT в Google с помощью Bazel, rules_gwt разрабатывается и поддерживается гуглером, который не входит в команду GWT.

Тесты

Тесты определяются правилами j2cl_test , реализация которых на самом деле не была с открытым исходным кодом ( пока ). Эти правила могут быть сгенерированы с помощью правила gen_j2cl_tests , где мы можем узнать об этом немного больше, хотя на самом деле мы узнаем больше, посмотрев, как оно используется в собственных тестах J2CL.

Каждое правило j2cl_test соответствует только одному тестовому классу, который может быть тестовым примером или набором тестов. Следует отметить, что JCL поддерживает как тесты JUnit 3, так и JUnit 4, но поддерживает только наборы тестов JUnit 4.

Тесты могут выполняться в двух вариантах: скомпилированные или нет; Я полагаю это означает, следует ли использовать оптимизированный или неоптимизированный вывод закрытия. И они могут быть запущены в нескольких конкретных браузерах или, я полагаю, в наборе глобально определенных браузеров.

В репозитории JCL Git мы также можем найти неиспользуемый макрос j2cl_generate_jsunit_suite , который, вероятно, используется внутри Google правилом j2cl_test . Он принимает в качестве входных данных имя тестового класса, генерирует фиктивный Java-файл, который ссылается на него в аннотации @J2clTestInput , и компилирует его с помощью процессора аннотаций, который будет генерировать файлы поддержки. Таким образом, фиктивный Java-файл используется только для запуска процессора аннотаций, но в остальном он совершенно бесполезен. Процессор сгенерирует файл test_summary.json , описывающий тесты, файл JS для каждого тестового примера и файл Java, который должен быть обработан J2CL и используется файлами JS. Компиляция выполняется через j2cl_library , поэтому файлы JS на самом деле имеют расширение .test suite , так что J2CL их не улавливает. Технически фиктивный Java-файл может быть обработан с помощью javac -proc:только , а затем генерируемый Java-файл обрабатывается J2 CL без необходимости предварительной компиляции в класс Java. В макросе Bazel выходные данные j2cl_library затем обрабатываются для создания ZIP-файла, содержащего только JS (с .testsuite переименован в .js ) и test_summary.json . Правило jsunit_test , на которое ссылается макрос, вероятно, аналогично правилу closure_js_test из rules_closure , но мы не можем точно знать.

Чтобы на самом деле узнать больше о том, как работает тестирование с J2 CL, мы должны взглянуть на текущую работу j2cl-maven-plugin , команда разработчиков которой спросила Google, как это сделать на самом деле.

Ожидается, что в плагине Maven тестовые примеры (или наборы тестов) будут аннотироваться с помощью @J2clTestInput прямая ссылка на аннотированный класс (тогда как в Bazel аннотация фактически полностью сохраняется как деталь реализации). Плагин сам скомпилирует классы (несмотря на то, что они уже были скомпилированы maven-compiler-plugin ), потому что is также, как и Bazel, сначала удалит @GwtIncompatible код и добавит обработчик аннотаций в процесс компиляции. Затем он прочитает сгенерированный test_summary.json и для каждого теста скопируйте сгенерированный .testsuite файл в .js , запустите на нем компилятор закрытия, аналогичный j2cl_application используя этот скрипт в качестве точки входа, создайте простой HTML-файл, загружающий результирующий скрипт, и, наконец, загрузите эту страницу в браузере, чтобы фактически запустить тесты. Чтобы определить, что тесты завершены, плагин опросит страницу на предмет определенного состояния JS; на самом деле это то же самое, что и phantomjs_test plumbing в rules_closure . AFAICT, основное отличие от rules_closure заключается в том, что HTML-страница загружается как URL-адрес file://| в плагине Maven, в то время как на самом деле она обслуживается через HTTP в тестовом жгуте rules_closure , и это может иметь реальные последствия при работе с файлами cookie, ресурсами или HTTP-запросами.

Заключительные мысли

Gradle должен иметь все необходимое, чтобы сделать его производительным для создания аналогичного инструментария, как интеграция J2CL–Bazel, описанная здесь: встроенные актуальные проверки для производительных инкрементных сборок, варианты (классы JS против Java для библиотеки J2CL), рабочие процессы для лучшей производительности, преобразования артефактов для J2CLING внешних зависимостей (со встроенным кэшированием), непрерывные сборки для повторного запуска всего конвейера сборки при изменении файла и т.д.

Я попытаюсь разработать такой плагин Gradle в следующем посте. Быть в курсе!

Оригинал: “https://dev.to/tbroyer/reverse-engineering-j2cl-bazel-integration-1o1f”