Я хочу изменить файлы pom.xml на лету, чтобы Maven использовал локально установленные JAR-файлы для определенного groupId. Итак, мне нужно:
system.systemPath к каждой соответствующей зависимости, указав на файл, имя которого будет функцией artifactId.Например,
<dependency>
<groupId>myGroup</groupId>
<artifactId>myGroup-agent-api</artifactId>
<version>3.1.38.13</version>
<scope>provided</scope>
</dependency>
необходимо стать:
<dependency>
<groupId>myGroup</groupId>
<artifactId>myGroup-agent-api</artifactId>
<version>3.1.38.13</version>
<scope>system</scope>
<systemPath>${application_path}/jar/agent-api.jar</systemPath>
</dependency>
С первой частью я разобрался. Эта задача Ansible с использованием модуля xml должна изменять/устанавливать область действия:
- name: Set scope to system for myGroup if {{ module.name }} uses any
xml:
path: '{{ app_path }}/{{ module.name }}/pom.xml'
namespaces:
x: http://maven.apache.org/POM/4.0.0
xpath: '//x:dependency/x:groupId[text() = "myGroup"]/../x:scope'
value: system
register: xmlfound
when: java_files.matched
failed_when:
- xmlfound is failed
- >-
'in order to spawn nodes' not in xmlfound.msg
(Возиться с failed_when необходимо, потому что lxml бросает вызов xpath, если не может найти соответствующие зависимости.)
Но как мне достичь второй части — создания systemPath на основе artifactId каждой соответствующей зависимости?
Обновление: отвечая на вопросы в комментариях:
pom.xml и вызывает Maven — локально, на каждом сервере, предназначенном для запуска этих программ.add_children, но какой будет аргумент? systemPath каждой зависимости различен и является производным от artifactId.xpath. К сожалению, он по-прежнему вызывает ошибки для POM без единой соответствующей зависимости, поэтому мне все еще нужен хакерский подход failed_when...Используйте тот же модуль. См. add_children. Но прежде чем продолжить, обязательно прочитайте комментарий Александра выше.
Небольшой совет по поводу xpath: поместите предикат в x:dependent, чтобы вам не приходилось снова перемещаться вверх и вниз, вот так: //x:dependency[x:groupId[text() = "myGroup"]]/x:scope
> «Да, я совершенно уверен, что хочу использовать Ansible — изменение «механическое», добавление нового профиля не имеет большого смысла, так как это просто увеличит нагрузку на разработчиков, которые создают свои собственные рабочие столы — а не сервер" - желаемое поведение буквально является целью профилей Maven. Будучи определены и/или включены в settings.xml, они предоставят это автоматически. Я не уверен, что внедрение другого инструмента в процесс сборки для изменения источников (с настраиваемой обработкой ошибок) проще в обслуживании, чем встроенное стандартное решение.
pom.xml здесь находится в компетенции разработчиков. Я просто автоматизирую сборки...





Я бы предпочел решение Maven Profiles, упомянутое Александр Плетнев в комментарии к вопросу , поскольку это делает ваши сборки более независимыми от внешних инструментов/конфигураций:
Прежде всего, я бы сохранил вашу зависимость myGroup-agent-api в вашем:
например, возможность извлечь выгоду из обычного разрешения зависимостей Maven.
Во-вторых, в вашем POM:
<properties>
<myGroup-agent-api.version>3.1.38.13</myGroup-agent-api.version>
<properties>
...
<profiles>
<profile>
<id>agent-provided</id>
<!-- See https://maven.apache.org/guides/introduction/introduction-to-profiles.html#details-on-profile-activation
"All profiles that are active by default are automatically
deactivated when a profile in the POM is activated on the
command line or through its activation config."
-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>myGroup</groupId>
<artifactId>myGroup-agent-api</artifactId>
<version>${myGroup-agent-api.version}</version>
<scope>provided</scope>
</dependency>
<dependencies>
</profile>
<profile>
<id>agent-repo</id>
<dependencies>
<dependency>
<groupId>myGroup</groupId>
<artifactId>myGroup-agent-api</artifactId>
<version>${myGroup-agent-api.version}</version>
</dependency>
<dependencies>
</profile>
</profiles>
...
Запустите mvn <phase> для сборки с зависимостями <scope>provided.
Запустите mvn <phase> -P agent-repo для сборки с разрешенной зависимостью из вашего репозитория. При этом профиль agent-provided будет деактивирован; см. комментарий в декларации POM выше.
Спасибо. Я знаю, как пользоваться профилями — просто не хочу. Я подумал, что может быть плагин Maven, который может выполнять замену автоматически, но если его нет, я с радостью помассирую pom.xml. Рассматриваемые файлы JAR поступают от поставщика и используются самим приложением. Хранение копии каждого где-либо приводит к потере как памяти, так и оперативной памяти времени выполнения.
@МихаилТ. Ну, вы знаете, но потенциальные будущие читатели/искатели здесь, возможно, этого не делают. ;) Касательно «Хранения... где-то»: это не где-то, а там, где должны находиться зависимости Maven: в репозитории Maven. Насколько велики эти зависимости и как при этом тратится дополнительная «оперативная память»?
Поставщик приложения предоставляет 44 JAR-файла размером от нескольких сотен КБ до десятков МБ. Поскольку в самом приложении каждый файл открыт, открытие одного и того же пути позволяет ядру обмениваться данными между несколькими процессами. Но если каждый из разных процессов открывает свою копию, оперативная память тратится впустую. Это также аргумент против объединения всех зависимостей в один большой JAR-файл для каждой программы, как это, похоже, в наши дни принято у начинающих разработчиков.
Отказ от ответственности: это только ответ на исходный вопрос. Чтобы решить эту проблему, используйте вместо этого профили Maven.
Рассмотрим следующий pom.xml пример:
<project xmlns = "http://maven.apache.org/POM/4.0.0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>your-group</groupId>
<artifactId>your-artifact</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>myGroup</groupId>
<artifactId>myGroup-agent-api</artifactId>
<version>3.1.38.13</version>
<classifier>yaml</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>my-artifact</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>myGroup</groupId>
<artifactId>myGroup-another-api</artifactId>
<version>3.5.79.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Но как мне достичь второй части — создания
systemPathна основеartifactIdкаждой соответствующей зависимости?
К сожалению, этот вариант использования кажется слишком сложным для модуля xml.
Сначала вам нужно получить список этих зависимостей, и вот в чем проблема: список совпадений плоский. Таким образом, вам придется правильно воссоздать структуру, что является непростой задачей, особенно если ваши узлы dependency имеют нечетное количество вложенных узлов. Поскольку я не использовал XPath в течение многих лет, возможно, я использую неправильный, но вот о чем я говорю (учитывая простейший pom.xml, где 2 из 3 зависимостей находятся на артефактах с «myGroup» groupId):
# playbook.yaml
---
- name: Modify the pom.xml
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Find the dependencies on myGroup modules
xml:
path: 'pom.xml'
namespaces:
x: http://maven.apache.org/POM/4.0.0
xpath: '//x:dependency/x:groupId[text() = "myGroup"]/..//*'
content: text
register: xmlfound
- name: Show the results
debug:
var: xmlfound
TASK [debug] **************************************************************************
ok: [localhost] =>
xmlfound:
actions:
namespaces:
x: http://maven.apache.org/POM/4.0.0
state: present
xpath: //x:dependency/x:groupId[text() = "myGroup"]/..//*
changed: false
count: 8
failed: false
matches:
- '{http://maven.apache.org/POM/4.0.0}groupId': myGroup
- '{http://maven.apache.org/POM/4.0.0}artifactId': myGroup-agent-api
- '{http://maven.apache.org/POM/4.0.0}version': 3.1.38.13
- '{http://maven.apache.org/POM/4.0.0}scope': provided
- '{http://maven.apache.org/POM/4.0.0}groupId': myGroup
- '{http://maven.apache.org/POM/4.0.0}artifactId': myGroup-another-api
- '{http://maven.apache.org/POM/4.0.0}version': 3.5.79.0
- '{http://maven.apache.org/POM/4.0.0}scope': provided
msg: 8
И я даже не говорю об переборе всех POM в проектах, вложенных циклах и о том, что вам нужно использовать модуль win_xml, если у вас Windows-машины.
Вместо этого вы можете использовать фильтр ansible.utils.from_xml для загрузки всего файла, а затем найти зависимости для обработки с помощью фильтра selectattr Jinja:
# playbook.yaml
- name: Modify the pom.xml
hosts: localhost
connection: local
gather_facts: false
vars:
vendor_group_id: myGroup
tasks:
- name: Read the pom.xml
set_fact:
current_pom: "{{ lookup('file', 'pom.xml') | ansible.utils.from_xml }}"
- name: Detect the dependencies to process
set_fact:
dependencies_to_process: >-
{{
current_pom.project.dependencies.dependency
| selectattr('groupId', 'equalto', vendor_group_id)
}}
Следующий шаг — использовать add_elements. Сложные моменты здесь заключаются в следующем:
pretty_print: true помогает, но меняет отступ с 4 пробелов (типично для POM) на 2 пробела;systemPath.Чтобы преодолеть это, вам необходимо:
artifactId вместо узлов groupId, перебирая список, построенный в предыдущей задаче;systemPath только в том случае, если элемент списка его еще не содержит;xmllint. Конечно, это можно (и вообще нужно) сделать с помощью встроенных модулей Ansible, таких как replace, но в данном случае это была бы слишком сложная задача. Самым простым решением было бы использовать фильтр ansible.utils.to_xml, но при этом комментарии, если они были, будут потеряны.Вот почти (см. примечания ниже) полный идемпотентный пример (обратите внимание, что я использовал lstrip(vendor_group_id + "-") для определения пути — в вашем случае он может отличаться):
- name: Modify the pom.xml
hosts: localhost
connection: local
gather_facts: false
vars:
vendor_group_id: myGroup
tasks:
- name: Read the pom.xml
set_fact:
current_pom: "{{ lookup('file', 'pom.xml') | ansible.utils.from_xml }}"
- name: Replace the scope
set_fact:
dependencies_to_process: >-
{{
current_pom.project.dependencies.dependency
| selectattr('groupId', 'equalto', vendor_group_id)
}}
- name: Set scope to system for myGroup if uses any
xml:
path: 'pom.xml'
namespaces:
x: http://maven.apache.org/POM/4.0.0
xpath: '//x:dependency[x:groupId[text() = "{{ vendor_group_id }}"]]/x:scope'
value: system
- name: Set systemPath for myGroup
xml:
path: 'pom.xml'
namespaces:
x: http://maven.apache.org/POM/4.0.0
xpath: '//x:dependency[x:artifactId[text() = "{{ item.artifactId }}"]]'
pretty_print: true
add_children:
- systemPath: >-
${application_path}/jar/{{ item.artifactId.lstrip(vendor_group_id + "-") }}.jar
loop: '{{ dependencies_to_process }}'
register: add_children_result
when: item.systemPath is not defined
- name: Restore the indentation
environment:
XMLLINT_INDENT: ' '
command: 'xmllint pom.xml --output pom.xml --format'
when: add_children_result is defined and add_children_result.changed
ПРИМЕЧАНИЯ:
Возиться с
failed_whenнеобходимо, потому чтоlxmlвызывает раздражение у xpath, если он не может найти соответствующие зависимости.
Я бы не сказал «необходимо», поскольку вы можете обработать список соответствующих зависимостей, как я предложил. Более того, вы наверняка захотите добавить scope, если ни одного набора не было.
Важно: ради простоты я даже не учел dependencyManagement. Как вы можете видеть, это решение уже слишком сложно даже для простого случая.
Спасибо, это очень похоже на то, что я реализовал. Я говорю «необходимо», когда речь идет о failed_when гимнастике, потому что задачи не выполняются для тех POM-файлов, которые не имеют ни одной зависимости с использованием mygroup...
Поскольку мы уже перебираем список обнаруженных зависимостей, чтобы добавить элемент systemPath, мы можем применить тот же подход для scope. Это приводит к тому, что мы можем переместить оба действия в block и запускать их только тогда, когда список не пуст.
Я не знаю, как найти совпадения, не получив ошибки, если совпадений не найдено... Относительно небольшая проблема, но все же открытая...
Это то, что я делаю с переменной dependencies_to_process — беру все зависимости и выбираю только те, которые имеют «myGroup» в качестве groupId. Я не стал добавлять проверку длины списка, чтобы упростить код, но ее можно легко добавить.
Понятно... Да, это способ сделать это. Я до сих пор не понимаю, почему xml-модуль не может этого сделать. Меня также беспокоит пространство имен: что, если в следующем году Maven изменится на .../POM/4.0.1?
> «Что, если в следующем году Maven перейдет на .../POM/4.0.1?» - Это еще одна веская причина придерживаться стандартного решения. Кстати, есть вероятность, что у вас даже не будет доступа к определению схемы в средах с ограниченным доступом, таких как серверы CI, поэтому вам все равно придется настроить xpath. Что касается «почему xml-модуль не может этого сделать» - в документации есть особое примечание: «Этот модуль не обрабатывает сложные выражения xpath, поэтому ограничьте селекторы xpath простыми выражениями». Если упростить — это всего лишь универсальный модуль, не предназначенный для выполнения сложных операций над Maven POM.
Ограниченные CI-серверы — это место, где используется существующий pom.xml. Мы делаем это в первую очередь по инерции. Я строю на реальных серверах приложений, на которых будут работать программы: использую Ansible для извлечения кода из git, а затем собираю его локально.
Какой тогда смысл CI в вашей организации? Согласно вашему комментарию, среды CI, а также любые среды тестирования со всеми усилиями группы(ов) контроля качества кажутся напрасными, поскольку вы запускаете не тот код, который был протестирован.
Большинство Java-приложений развертываются «обычно» — в контейнерах Docker или Tomcat. Только не эти...
Вы уверены, что хотите сделать это с помощью Ansible, а не профилей Maven, предназначенных для таких задач? И что вы подразумеваете под «на лету»?