В прошлый раз я поделился некоторыми советами о том, как правильно использовать библиотеки. Теперь я хочу углубиться в один из них: знать, какие библиотеки вы используете . На прошлой неделе я решил создать такой список встроенных компонентов для нашего продукта. Это требование для нашего жизненного цикла разработки безопасности (SDL). Тем не менее, это не веселая задача. Как разработчик, я хочу писать код, а не обновлять документы! Поэтому я обратился к своим друзьям Грэдлу и Груви с небольшой помощью Дженкинса и Слияния .
Зависимости Gradle
Мы используем Gradle для создания нашего продукта, и Gradle поддерживает зависимости, которые мы имеем от сторонних компонентов.
Наша сборка определяет список имен конфигураций для встроенных компонентов, copyBundleConfigurations , для их копирования в каталог распространения. Оттуда я добираюсь до внешних зависимостей, используя методы коллекции Groovy :
|
1
2
3
4
5
6
7
8
|
def externalDependencies() { copyBundleConfigurations.collectMany { configurations[it].allDependencies }.findAll { !(it instanceof ProjectDependency) && it.group && !it.group.startsWith('com.emc') }} |
Добавление необходимой информации
Однако зависимости Gradle не содержат всей необходимой информации. Например, нам нужна лицензия, по которой распространяется библиотека, чтобы мы могли запросить разрешение Юридического отдела на ее использование. Поэтому я добавил простой XML-файл для хранения дополнительной информации. Объединить эту информацию с зависимостями, которые поддерживает Gradle, легко с помощью XML-поддержки Groovy :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
ext.embeddedComponentsInfo = 'embeddedComponents.xml'def externalDependencyInfos() { def result = new TreeMap() def componentInfo = new XmlSlurper() .parse(embeddedComponentsInfo) externalDependencies().each { dependency -> def info = componentInfo.component.find { it.id == '$dependency.group:$dependency.name' && it.friendlyName?.text() } if (!info.isEmpty()) { def component = [ 'id': info.id, 'friendlyName': info.friendlyName.text(), 'version': dependency.version, 'latestVersion': info.latestVersion.text(), 'license': info.license.text(), 'licenseUrl': info.licenseUrl.text(), 'comment': info.comment.text() ] result.put component.friendlyName, component } } result.values()} |
Затем я создал задачу Gradle для записи информации в файл HTML. Наша сборка Jenkins выполняет эту задачу, поэтому у нас всегда есть актуальный список. Я использовал макрос html-include Confluence, чтобы включить HTML-файл в нашу вики. Теперь наша вики всегда актуальна.
Автоматический поиск недостающей информации
Следующая проблема заключалась в заполнении XML-файла дополнительной информацией. Если бы у нас был этот файл с самого начала, добавление этой информации вручную не имело бы большого значения. В нашем случае у нас уже было более ста зависимостей, поэтому автоматизация была в порядке. Сначала я определил компоненты, которые пропускают необходимую информацию:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
def missingExternalDependencies() { def componentInfo = new XmlSlurper() .parse(embeddedComponentsInfo) externalDependencies().findAll { dependency -> componentInfo.component.find { it.id == '$dependency.group:$dependency.name' && it.friendlyName?.text() }.isEmpty() }.collect { '$it.group:$it.name' }.sort()} |
Затем я хотел автоматически найти недостающую информацию и добавить ее в файл XML (используя Groovy’s MarkupBuilder ). В случае, если требуемая информация не может быть найдена, сборка должна завершиться неудачно
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
project.afterEvaluate { def missingComponents = missingExternalDependencies() if (!missingComponents.isEmpty()) { def manualComponents = [] def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.expandEmptyElements = true println 'Looking up information on new dependencies:' xml.components { externalDependencyInfos().each { existingComponent -> component { id(existingComponent.id) friendlyName(existingComponent.friendlyName) latestVersion(existingComponent.latestVersion) license(existingComponent.license) licenseUrl(existingComponent.licenseUrl) approved(existingComponent.approved) comment(existingComponent.comment) } } missingComponents.each { missingComponent -> def lookedUpComponent = collectInfo(missingComponent) component { id(missingComponent) friendlyName(lookedUpComponent.friendlyName) latestVersion(lookedUpComponent.latestVersion) license(lookedUpComponent.license) licenseUrl(lookedUpComponent.licenseUrl) approved('?') comment(lookedUpComponent.comment) } if (!lookedUpComponent.friendlyName || !lookedUpComponent.latestVersion || !lookedUpComponent.license) { manualComponents.add lookedUpComponent.id println ' => Please enter information manually' } } } writer.close() def embeddedComponentsFile = project.file(embeddedComponentsInfo) embeddedComponentsFile.text = writer.toString() if (!manualComponents.isEmpty()) { throw new GradleException('Missing library information') } }} |
Любой, кто добавляет зависимость в будущем, теперь вынужден добавить необходимую информацию. Таким образом, все, что осталось реализовать — это collectInfo() . Для поиска необходимой информации я использовал два основных источника: Spring Bundle Enterprise Bundle Repository содержит OSGi- версии общих библиотек, а Maven Central — обычные jar-файлы.
Извлечение информации из этих источников — это загрузка и анализ файлов XML и HTML. Это достаточно просто с помощью String.toURL() Groovy String.toURL() и URL.eachLine() и поддержки регулярных выражений .
Вывод
Все это заняло у меня несколько дней, но я чувствую, что инвестиции того стоят, так как мне больше не нужно беспокоиться о том, что список используемых библиотек устарел.
Ссылка: путь ленивого разработчика к обновленному списку библиотек от нашего партнера по JCG Ремона Синнема в блоге по разработке безопасного программного обеспечения .