Статьи

Путь ленивого разработчика к обновленному списку библиотек

В прошлый раз я поделился некоторыми советами о том, как правильно использовать библиотеки. Теперь я хочу углубиться в один из них: знать, какие библиотеки вы используете . На прошлой неделе я решил создать такой список встроенных компонентов для нашего продукта. Это требование для нашего жизненного цикла разработки безопасности (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 Ремона Синнема в блоге по разработке безопасного программного обеспечения .