Подготовка модуля к переходу на пакеты сборки#
С помощью изменения архитектуры прикладных модулей был внедрён механизм пакетов сборки (build packets). Цель перехода — кэшированная сборка для ускорения компиляции проектов. Модуль делится на изолированные части (core, contracts, src), чтобы при изменениях перекомпилировался только изменённый пакет, а не весь проект целиком.
Этот документ описывает пошаговый процесс подготовки модуля к использованию пакетов сборки и генерации контрактов.
Процесс состоит из нескольких этапов:
Настройка пакетов сборки.
Подготовка классов к генерации контрактов.
Генерация контрактов для публичных интерфейсов.
См. также:
Пакеты сборки: концепция — описание типов пакетов и их назначения
Контракты — детали генерации и использования контрактов
Настройка пакетов сборки модуля#
1. Создание конфигурации пакетов
В корне модуля создайте файл с конфигурацией пакетов сборки buildPackets.yaml и заполните его следующим содержимым:
# Флаг о необходимости генерировать контракты для данного модуля.
shouldGenerateContract: true
# Пакеты сборки модуля
packets:
- name: <module_name>_contracts
- name: <module_name>_core
Где <module_name> — наименование модуля в нижнем регистре.
2. Настройка корневого build.sbt
Подготовьте корневой build.sbt, добавив в него объявления пакетов сборки и укажите зависимость модуля от пакета с контрактами:
// Объявление пакетов сборки (<module_name> - наименование модуля)
lazy val <module_name>_contracts = project in file("contracts")
lazy val <module_name>_core = project in file("core")
lazy val <module_name> = (project in file("."))
.dependsOnGsfModules("btk") // Зависимости от других модулей, оставьте их как было указано в оригинальном файле
.settings(CommonSetting.setting *)
.dependsOnBuildPacket("<module_name>_contracts") // Зависимость от пакета сборки с контрактами
// Остальные настройки оставьте как в оригинальном файле
3. Создание папок и build.sbt для пакетов
В корне модуля создайте две папки: core и contracts с build.sbt файлами внутри.
Заполните build.sbt для core:
lazy val <module_name>_core = (project in file("."))
.dependsOnGsfModules("gtk")
.settings(CommonSetting.setting *)
.settings(
name := "<module_name>_core",
publish / skip := true
)
И build.sbt для contracts:
lazy val <module_name>_contracts = (project in file("."))
.dependsOnGsfModules("gtk")
.dependsOnBuildPacket("<module_name>_core") // Укажите зависимость от core пакета сборки
.settings(CommonSetting.setting *)
.settings(
name := "<module_name>_contracts",
publish / skip := true
)
4. Перенос настроек компилятора
Если в build.sbt для модуля была следующая настройка:
.settings(
scalacOptions ++= {
if ((Global / Build.gsfScalaFeatureRelease).value.equals(ScalaVersionNames.ver_3)) {
Seq.empty[String]
} else {
Seq("-Wconf:cat=scala3-migration:e", "-Xsource:3")
}
}
)
То её так же необходимо указать в build.sbt для пакетов сборки. Пример для контрактов:
lazy val <module_name>_contracts = (project in file("."))
.dependsOnGsfModules("gtk")
.dependsOnBuildPacket("<module_name>_core") // Укажите зависимость от core пакета сборки
.settings(CommonSetting.setting *)
.settings(
name := "<module_name>_contracts",
publish / skip := true
).settings(
scalacOptions ++= {
if ((Global / Build.gsfScalaFeatureRelease).value.equals(ScalaVersionNames.ver_3)) {
Seq.empty[String]
} else {
Seq("-Wconf:cat=scala3-migration:e", "-Xsource:3")
}
}
)
5. Применение настроек
После чего необходимо выполнить reload sbt и задачу publishDevDependencies.
Подготовка классов к генерации контрактов#
Как определить методы и классы для которых необходимо сгенерировать контракт#
Контракты необходимо генерировать на классы и методы, которые используются в других модулях. Например Btk_FileApi часто используется, поэтому для него необходимо сгенерировать контракт. В нём есть методы для создания, удаления, изменения файлов, которые также нужно добавить в контракт, но так же есть методы, к примеру updateFileStorageFromObjectTypes, которые хоть и являются публичными, но нужны для работы с файлами внутри модуля Btk, для такого метода генерировать контракт не нужно.
Подготовка Api для генерации контракта#
Для этого необходимо проставить аннотацию @ExternalApi(defaultRule = MethodScope.Exclude) на Api и аннотацию @CntInclude на методах, которые вы хотите видеть в контракте. Пример на Btk_FileApi:
import <...>
import ru.bitec.app.gtk.gl.contract.{CntInclude, ExternalApi, MethodScope}
@ExternalApi(defaultRule = MethodScope.Exclude)
class Btk_FileApi extends Btk_FileDpi[Btk_FileAro, Btk_FileApi, Btk_FileAta] {
override protected def entityAta: Btk_FileAta = Btk_FileAta
@CntInclude
def copyObject(idFrom: NLong, idTo: NLong, gidpSrc: NGid): NLong = {
val idvTo = super.copyObject(idFrom, idTo, None.nl)
//копируем сам файл по новому пути
val rvFrom = load(idFrom).copyAro()
val svFullPathNew = Btk_FilePkg().genFullFileName(idvTo)
val ropTo = load(idvTo)
setsFullFileName(ropTo, svFullPathNew)
setgidSrc(ropTo, gidpSrc.nvl(rvFrom.gidSrc))
session.scheduleBeforeFlush(Btk_FilePkg().copyFile(idFrom, idvTo))
// session.flush()
idvTo
}
<...>
}
После проставления аннотаций класс готов к генерации контракта. Вы можете продолжить подготовку других классов или сразу перейти к генерации. При генерации создастся контракт (в примере для Btk_FileApi) Btk_FileApc и адаптер Btk_FileApa с одним методом copyObject.
Подготовка Lib для генерации контракта#
Для этого необходимо проставить аннотацию @ExternalLib(defaultRule = MethodScope.Exclude) на Lib и аннотацию @CntInclude на методах, которые вы хотите видеть в контракте. Пример на Btk_FileLib:
import <...>
import ru.bitec.app.gtk.gl.contract.{CntInclude, ExternalLib, MethodScope}
@ExternalLib(defaultRule = MethodScope.Exclude)
class Btk_FileLib extends Btk_Lib {
@CntInclude
def uploadFile(
idpFileStorage: NLong,
gidpSrc: NGid = None.ng,
bpManualMoved: NNumber = 0.nr,
idpAttachFileType: NLong = None.nl,
idpSrcObjectType: NLong = None.nl,
idpClassDoc: NLong = None.nl,
bpTemp: NNumber = 0.nr
): NLong = {
uploadFiles(idpFileStorage, gidpSrc, bpManualMoved, idpAttachFileType = idpAttachFileType, idpSrcObjectType = idpSrcObjectType, idpClassDoc = idpClassDoc, bpTemp = bpTemp).headOption.getOrElse(None.nl)
}
@CntInclude
def uploadFiles(
idpFileStorage: NLong,
gidpSrc: NGid = None.ng,
bpManualMoved: NNumber = 0.nr,
idpAttachFileType: NLong = None.nl,
idpSrcObjectType: NLong = None.nl,
idpClassDoc: NLong = None.nl,
bpTemp: NNumber = 0.nr
): List[NLong] = {
uploadFilesWithValidator(idpFileStorage, gidpSrc, fileName => (), bpManualMoved, idpAttachFileType = idpAttachFileType, idpSrcObjectType = idpSrcObjectType, idpClassDoc = idpClassDoc, bpTemp = bpTemp)
}
<...>
}
После проставления аннотаций класс готов к генерации контракта. Вы можете продолжить подготовку других классов или сразу перейти к генерации. При генерации создастся контракт (в примере для Btk_FileLib) Btk_FileLic и адаптер Btk_FileLia с двумя методами uploadFile и uploadFiles.
Подготовка Pkg для генерации контракта#
Для этого необходимо проставить аннотацию @ExternalPkg(defaultRule = MethodScope.Exclude) на Pkg и аннотацию @CntInclude на методах, которые вы хотите видеть в контракте. Пример на Btk_FilePkg:
import <...>
import ru.bitec.app.gtk.gl.contract.{CntInclude, ExternalPkg, MethodScope}
@ExternalPkg(defaultRule = MethodScope.Exclude)
class Btk_FilePkg extends Pkg {
/**
* Проверка наличия файла в хранилище
* @param idpFile id файла
* @return boolean
*/
@CntInclude
def checkExist(idpFile: NLong): Boolean = {
if (idpFile.isNull) return false
val ropFile = Btk_FileApi().load(idpFile)
val svFileStorage = Btk_FileStorageApi().getFileStorageName(ropFile.get(_.idFileStorage))
val vFileStorage = session.fileStorage(svFileStorage)
val File = vFileStorage.FileFactory.apply(ropFile.get(_.sFullFileName))
File.exists()
}
<...>
}
После проставления аннотаций класс готов к генерации контракта. Вы можете продолжить подготовку других классов или сразу перейти к генерации. При генерации создастся контракт (в примеру для Btk_FilePkg) Btk_FilePkc и адаптер Btk_FilePka с методом checkExist.
Генерация контрактов#
После подготовки всех классов (Api, Lib, Pkg) выполните генерацию контрактов. Процесс одинаков для всех типов классов.
Генерация контрактов происходит так же, как генерация исходного файла. Для этого в дереве проекта кликните ПКМ по файлу или директории, зайдите в раздел External Tools и выберете Generate Contracts, после чего так же сгенерируйте исходный код по данному файлу/директории.

Частые проблемы и их решения#
Отсутствие case class#
При генерации контракта, если ваш метод возвращал case class, то для контракта он будет не доступен, так как лежит внутри пакета сборки src или внутри другого Api. Такие классы необходимо выносить в пакет сборки core, что бы они были доступны и для контрактов и для пакета src.
Метод возвращает провайдер строки от другого класса#
Если ваш метод, например в Pkg возвращает провайдер строки (rop) из другого Api, то для него так же необходимо сгенерировать контракт, иначе проект не будет собираться.
Ошибка компиляции после переноса case class в пакет сборки core#
При переносе case class в пакет сборки core вы оставляете пакет по умолчанию (к примеру ru.bitec.app.bs) из-за чего компилятор scala не видит изменения, но считает что класса нет, поэтому выдает ошибку:
not found class: ru.bitec.app.bs.MyCaseClass
maybe import: ru.bitec.app.bs.MyCaseClass
В данном случае необходимо очистить папку target в вашем модуле.
При генерации отсутствует import или не правильно сгенерировалась структура параметров метода#
В таких случаях временно исправьте структуру руками и напишите с примером в чат поддержки Gtk, Btk что у вас не получается сгенерировать контракт.