Пакеты сборки

Содержание

6.10. Пакеты сборки#

Модуль в платформе представляет собой логически неделимую совокупность функциональности, которая может быть включена в различные прикладные проекты. Каждый модуль физически располагается в отдельной директории и может быть разделён на несколько пакетов сборки (build packets). Пакеты сборки позволяют более гибко управлять зависимостями и артефактами внутри модуля.

6.10.1. Назначение пакетов сборки#

Пакет сборки определяет набор артефактов (исходный код, ресурсы) и зависимостей, которые группируются вместе. Каждый пакет сборки выпускается с версией модуля, в состав которого он входит. Разделение на пакеты позволяет:

  • изолировать контракты от реализации;

  • управлять областью видимости типов (например, core-пакет может содержать классы и трейты, доступные для наследования в других модулях);

  • уменьшить перекомпиляцию при изменениях (контракты меняются реже, чем реализация).

6.10.2. Базовые пакеты сборки#

Для большинства модулей рекомендуется использовать три стандартных пакета:

  • core - содержит классы и трейты, от которых может зависеть реализация других модулей (например, базовые абстрактные классы, case-классы, общие утилиты). Код в этом пакете компилируется обычным образом и может использоваться в других модулях как обычная библиотека.

  • contracts - содержит только контракты (интерфейсы на Api, Pkg, Lib), которые будут использоваться для разрыва бинарной зависимости между модулями.

  • src - так же является пакетом сборки, который содержит внутренние классы модуля, которые не используются в других пакетах сборки или модулях.

6.10.3. Структура пакетов сборки#

btk/                      # корень модуля
├── contracts/            # пакет btk_contracts
│   ├── src/main/scala/   # исходники контрактов
│   └── build.sbt         # настройки пакета
├── core/                 # пакет btk_core
│   ├── src/main/scala/   # реализация, core-классы
│   └── build.sbt
├── src/                  # базовый код модуля
│   └── main/scala/       # реализация
├── build.sbt             # сборка всего модуля (агрегация пакетов)
└── buildPackets.yaml     # конфигурация пакетов сборки

6.10.4. Настройка пакетов сборки модуля#

В файле конфигурации пакетов сборки (buildPackets.yaml) указывается список пакетов и флаг генерации контрактов:

# Флаг о необходимости генерировать контракты для данного модуля.
isGenerateContract: true

# Пакеты сборки модуля
packets:
  - name: btk_contracts
  - name: btk_core

6.10.4.1. Описание сборки в build.sbt#

Для корректной сборки модуля используется агрегация пакетов сборки в корневом build.sbt:

import ru.bitec.app.gtk.sbt.{Build, CommonSetting, ScalaVersionNames}

// пакеты сборки
lazy val btk_contracts = project in file("contracts")
lazy val btk_core = project in file("core")

// Модуль – агрегирует пакеты сборки и задаёт общие зависимости
lazy val btk = (project in file("."))
  .aggregate(btk_contracts, btk_core)     // Агрегация пакетов сборки
  .dependsOnGsfModules("gtk")             // Зависимость от модуля gtk
  .dependsOnBuildPacket("btk_contracts")  // Зависимость модуля от пакетов сборки
  .settings(CommonSetting.setting *)
  .settings(
    name := "btk"
  )

build.sbt для пакета btk_core (btk/core/build.sbt):

import ru.bitec.app.gtk.sbt.{Build, CommonSetting, ScalaVersionNames}

lazy val btk_core = (project in file("."))
  .dependsOnGsfModules("gtk")
  .settings(CommonSetting.setting *)
  .settings(
    name := "btk_core",     // Наименование пакета сборки
    publish / skip := true
  )

build.sbt для пакета btk_contracts (btk/contracts/build.sbt)

import ru.bitec.app.gtk.sbt.{Build, CommonSetting, ScalaVersionNames}

lazy val btk_contracts = (project in file("."))
  .dependsOnGsfModules("gtk")
  .dependsOnBuildPacket("btk_core")   // contracts зависит от пакета сборки core
  .settings(CommonSetting.setting *)
  .settings(
    name := "btk_contracts",          // Наименование пакета сборки
    publish / skip := true
  )

6.10.5. Проектные модули#

Проектные модули не разделяются на пакеты сборки и не используют контракты, поэтому правки в них вносить не надо. Все проектные модули зависят от модуля целиком, то есть тянут за собой все его пакеты сборки.

В build.sbt при наследовании от модуля dependsOnGsfModules наследуются и все пакеты сборки. Пример:

import ru.bitec.app.gtk.sbt.{Build, CommonSetting, ScalaVersionNames}

lazy val zbtk = (project in file("."))
  .dependsOnGsfModules("gtk", "btk")
  .settings(CommonSetting.setting *)

Модуль zbtk наследуется от модуля btk, поэтому может использовать код из пакетов сборки core, contract и всех остальных.

6.10.6. Примечание#

  • Пакет contracts не должен содержать реализацию, только автоматически генерируемые контракты;

  • Пакет contracts зависит от core, так как последний содержит общие классы;

  • Все внешние библиотеки должны быть добавлены в модуль, либо в соответствующий пакет сборки.

6.11. Контракты#

Контракты — это декларации интерфейсов, которые используются для разрыва бинарной зависимости между модулями. Вместо прямой зависимости от реализации модуля A модуль B зависит только от контрактов модуля A. Это позволяет изменять реализацию модуля A без перекомпиляции модуля B;

Контракты содержат все методы из Dpi и аннотированные программистом методы из Api.

6.11.1. Адаптеры#

Адаптеры - связующее звено, которая позволяет контрактам работать с Api, Pkg, Lib. Генерируются вместе с контрактами и имеют следующее расширения: Apa, Pka, Lia.

6.11.2. Генерация контрактов#

Генерация контрактов выполняется аналогично генерации исходного кода (source generator). Для этого необходимо:

  1. Установить новую версию плагина для IDEA и обновить external-tools на главном экране IDEA;

  2. Включить флаг isGenerateContract: true в конфигурации пакетов сборки модуля;

  3. Проставить аннотации на Api, Pkg, Lib;

  4. Сгенерировать контракты по необходимым файлам;

  5. Сгенерировать по этим же файлам исходный код.

6.11.3. Аннотации#

Для декларации контрактов используются аннотации:

  • ExternalApi - для апи;

  • ExternalLib - для библиотек;

  • ExternalPkg - для пакетов.

Аннотации для методов:

  • CntInclude - сообщает, что для этого метода необходимо сгенерировать контракт;

  • CntExclude - сообщает, что для этого метода не нужно генерировать контракт.

Аннотация декларации контрактов имеет правило defaultRule, от которого зависит генерация методов в контракте (значение по умолчанию Include):

  • MethodScope.Include - контракты генерируется для всех публичных методов, которые не отмечены CntExclude;

  • MethodScope.Exclude - контракты генерируется только для публичных методов, которые отмечены аннотацией CntInclude;

Пример декларации контрактов:

@ExternalApi(defaultRule = MethodScope.Include)
class Btk_FileApi {
  // Приватный метод не будет сгенерирован в любом сценарии
  private def doSomething(): Unit = {

  }

  // Не будет сгенерирован из-за аннотации
  @CntExclude
  def withFile(fileStorage: FileStorage, fullFileName: NString)(f: File => Unit): Unit = {
    val fileSrc = fileStorage.FileFactory(fullFileName)
    if (fileSrc.exists()) {
      f(fileSrc)
    } else {
      throw AppException(s"Файл $fullFileName был перемещен или удален из хранилища")
    }
  }

  // Будет сгенерирован из-за аннотации
  @CntInclude
  def getFileStorageByFile(idpFile: NLong): NString = {
    if (idpFile.isNotNull) {
      val ropFile = Btk_FileApi().load(idpFile)
      val idFileStorage = ropFile.get(_.idFileStorage)
      Btk_FileStorageApi().getMnemoCode(idFileStorage).getOrElse("Default")
    } else {
      throw AppException("Не передан идентификатор файла")
    }
  }
}

6.11.3.1. Результат генерации по Api#

Пример сгенерированного контракта Btk_FileApc:

Внимание

Все методы объявленные только в Dpi автоматически попадают в контракт, их не нужно аннотировать

trait Btk_FileDpc[
  ARC <: Btk_FileDrc,
  ATC <: Btk_FileDtc[ARC, Api[java.lang.Long, ARC], ATC],
  APC <: Btk_FileApc
] extends RootApc[java.lang.Long, ARC, APC]
  with JIdExtApc[EntityAbst, java.lang.Long, ARC]
  with AttrApc[java.lang.Long, ARC]


trait Btk_FileApc extends Btk_FileDpc[Btk_FileDrc, Btk_FileAtc, Btk_FileApc] {

  
  def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong, params: Btk_CopyObjectParam): NLong

  
  def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong): NLong

  
  def setgidSrc(rop: ApiRop, value: NGid): Unit

  
  def setsFileName(rop: ApiRop, value: NString): Unit

  
  def setsExt(rop: ApiRop, value: NString): Unit

  
  def setsFullFileName(rop: ApiRop, value: NString): Unit

  
  def setidFileStorage(rop: ApiRop, value: NLong): Unit

  
  def setsSHA2(rop: ApiRop, value: NString): Unit

  
  def setsMD5(rop: ApiRop, value: NString): Unit

  
  def setnSize(rop: ApiRop, value: NNumber): Unit

  
  def setidAttachType(rop: ApiRop, value: NLong): Unit

  
  def setidSrcObjectType(rop: ApiRop, value: NLong): Unit

  
  def setbManualMoved(rop: ApiRop, value: NNumber): Unit

  
  def setbDeleted(rop: ApiRop, value: NNumber): Unit

  
  def setjParams(rop: ApiRop, value: NString): Unit

  
  def parsejParams(rop: ApiRop): JESegment

  def getFileStorageByFile(idpFile: NLong): NString
}

object Btk_FileApc extends ApcFactory[Btk_FileDrc, Btk_FileApc] {
  override protected val sApiClassPath: String = "ru.bitec.app.btk.Btk_FileApi$"
  override protected val sApaClassPath: String = "ru.bitec.app.btk.Btk_FileApa"
}

trait Btk_FileAtc extends Btk_FileDtc[Btk_FileDrc, Api[java.lang.Long, Btk_FileDrc], Btk_FileAtc]

object Btk_FileAtc extends AtcFactory[java.lang.Long, Btk_FileDrc, Api[java.lang.Long, Btk_FileDrc], Btk_FileAtc] {
  override protected val sAtaClassPath: String = "ru.bitec.app.btk.Btk_FileAta$"
}

trait Btk_FileDrc extends SEntityArc[java.lang.Long] {
  def id: NLong
  def id_=(value: NLong): Unit
  def idClass: NLong
  def idClass_=(value: NLong): Unit
  def gid: NGid
  def gid_=(value: NGid): Unit
  def gidSrc: NGid
  def gidSrc_=(value: NGid): Unit
  def sFileName: NString
  def sFileName_=(value: NString): Unit
  def sExt: NString
  def sExt_=(value: NString): Unit
  def sFullFileName: NString
  def sFullFileName_=(value: NString): Unit
  def idFileStorage: NLong
  def idFileStorage_=(value: NLong): Unit
  def sSHA2: NString
  def sSHA2_=(value: NString): Unit
  def sMD5: NString
  def sMD5_=(value: NString): Unit
  def nSize: NNumber
  def nSize_=(value: NNumber): Unit
  def idAttachType: NLong
  def idAttachType_=(value: NLong): Unit
  def idSrcObjectType: NLong
  def idSrcObjectType_=(value: NLong): Unit
  def bManualMoved: NNumber
  def bManualMoved_=(value: NNumber): Unit
  def bDeleted: NNumber
  def bDeleted_=(value: NNumber): Unit
  def jParams: NString
  def jParams_=(value: NString): Unit

}

trait Btk_FileDtc[
  ARO <: Arc[java.lang.Long],
  API <: Api[java.lang.Long, ARO],
  ATA <: BaseAtaImpl[java.lang.Long, ARO, API]
] extends BaseAta[java.lang.Long, ARO, API, ATA] {

  val id: Column
  val idClass: Column
  val gid: Column
  val gidSrc: Column
  val sFileName: Column
  val sExt: Column
  val sFullFileName: Column
  val idFileStorage: Column
  val sSHA2: Column
  val sMD5: Column
  val nSize: Column
  val idAttachType: Column
  val idSrcObjectType: Column
  val bManualMoved: Column
  val bDeleted: Column
  val jParams: Column
}

Пример сгенерированного адаптера Btk_FileApa:

class Btk_FileApa extends Btk_FileApc
  with RootApa[java.lang.Long, Btk_FileDrc, Btk_FileApc]
  with JIdExtApa[EntityAbst, java.lang.Long, Btk_FileDrc]
  with AttrApa[java.lang.Long, Btk_FileDrc] {

  private type ApiType = Btk_FileApi

  private def apiInstance: ApiType = _apiInstance.asInstanceOf[ApiType]

  override def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong, params: Btk_CopyObjectParam): NLong = {
    apiInstance.copyObject(idFrom, idTo, idParent, params)
  }

  override def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong): NLong = {
    apiInstance.copyObject(idFrom, idTo, idParent)
  }

  override def setgidSrc(rop: ApiRop, value: NGid): Unit = {
    apiInstance.setgidSrc(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsFileName(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsFileName(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsExt(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsExt(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsFullFileName(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsFullFileName(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setidFileStorage(rop: ApiRop, value: NLong): Unit = {
    apiInstance.setidFileStorage(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsSHA2(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsSHA2(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsMD5(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsMD5(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setnSize(rop: ApiRop, value: NNumber): Unit = {
    apiInstance.setnSize(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setidAttachType(rop: ApiRop, value: NLong): Unit = {
    apiInstance.setidAttachType(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setidSrcObjectType(rop: ApiRop, value: NLong): Unit = {
    apiInstance.setidSrcObjectType(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setbManualMoved(rop: ApiRop, value: NNumber): Unit = {
    apiInstance.setbManualMoved(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setbDeleted(rop: ApiRop, value: NNumber): Unit = {
    apiInstance.setbDeleted(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setjParams(rop: ApiRop, value: NString): Unit = {
    apiInstance.setjParams(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def parsejParams(rop: ApiRop): JESegment = {
    apiInstance.parsejParams(rop.asInstanceOf[ApiType#ApiRop])
  }

  override def getFileStorageByFile(idpFile: NLong): NString = {
    apiInstance.getFileStorageByFile(idpFile)
  }
}

Использование в прикладном коде осуществляется точно так же, как с обычным Api:

val rop = Btk_FileApc().load(123.nl)
val sFileName = rop.get(_.sFileName)
Btk_FileApc().setidAttachType(rop, Btk_AttachTypeApc().getDefault())

6.11.3.2. Результат генерации по Lib#

Пример сгенерированного контракта Btk_FileLic:

trait Btk_FileLic extends Lic {

  /** Имя переменной выборки для контекста подбора файлового хранилища */
  def uploadFile(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): NLong

  /** Имя переменной выборки для контекста подбора файлового хранилища */
  def uploadFiles(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): List[NLong]
}

object Btk_FileLic extends AviLicFactory[Btk_FileLic] {
  override protected val sLibClassPath: String = "ru.bitec.app.btk.Btk_FileLib$"
  override protected val sLiaClassPath: String = "ru.bitec.app.btk.Btk_FileLia"
}

Пример сгенерированного адаптера Btk_FileLia:

class Btk_FileLia extends Btk_FileLic {
  private type LibType = Btk_FileLib

  private def libInstance: LibType = _libInstance.asInstanceOf[LibType]

  override def uploadFile(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): NLong = {
    libInstance.uploadFile(idpFileStorage, gidpSrc, bpManualMoved, idpAttachFileType, idpSrcObjectType, idpClassDoc, bpTemp)
  }

  override def uploadFiles(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): List[NLong] = {
    libInstance.uploadFiles(idpFileStorage, gidpSrc, bpManualMoved, idpAttachFileType, idpSrcObjectType, idpClassDoc, bpTemp)
  }
}

Использование в прикладном коде осуществляется точно так же, как с обычной Lib:

Btk_FileLic().uploadFile(...)

6.11.3.3. Результат генерации по Pkg#

Пример сгенерированного контракта Btk_FilePkc:

trait Btk_FilePkc extends Pkc {

  /**
   * Проверка наличия файла в хранилище
   * @param idpFile id файла
   * @return boolean
   */
  def checkExist(idpFile: NLong): Boolean
}

object Btk_FilePkc extends PkcFactory[Btk_FilePkc] {
  override protected val sPkgClassPath: String = "ru.bitec.app.btk.Btk_FilePkg$"
  override protected val sPkaClassPath: String = "ru.bitec.app.btk.Btk_FilePka"
}

Пример сгенерированного адаптера Btk_FilePka:

class Btk_FilePka extends Btk_FilePkc {
  private type PkgType = Btk_FilePkg

  private def pkgInstance: PkgType = _pkgInstance.asInstanceOf[PkgType]

  override def checkExist(idpFile: NLong): Boolean = {
    pkgInstance.checkExist(idpFile)
  }
}

Использование в прикладном коде осуществляется точно так же, как с обычным Pkg:

new OQuery(Btk_FileAtc.Type) {
  where(t.sExt === "png".ns)
}.foreach { rop =>
  Btk_FilePkc().checkExist(rop.get(_.id))
}

6.12. Подготовка модуля к пакетам сборки#

В корне модуля создайте файл с конфигурацией пакетов сборки buildPackets.yaml и заполните его следующим содержимым:

# Флаг о необходимости генерировать контракты для данного модуля.
isGenerateContract: true

# Пакеты сборки модуля
packets:
  - name: <module_name>_contracts
  - name: <module_name>_core

Где <module_name> - наименование модуля в нижнем регистре.

Подготовьте корневой 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")  // Зависимость от пакета сборки с контрактами
  // Остальные настройки оставьте как в оригинальном файле

В корне модуля создайте две папки: 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
  )

Если в 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")
      }
    }
  )

После чего необходимо выполнить reload sbt и задачу publishDevDependencies.

6.13. Генерация контрактов#

Генерация контрактов происходит так же, как генерация исходного файла. Для этого в дереве проекта кликните ПКМ по файлу или директории, зайдите в раздел External Tools и выберете Generate Contracts, после чего так же сгенерируйте исходный код по данному файлу/директории.

6.13.1. Подготовка 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
  }

  <...>
}

После чего через External Tools -> Generate Contracts сгенерируйте контракт и исходный код (Generate Source) по вашему файлу. Таким образом создастся контракт (в примере для Btk_FileApi) Btk_FileApc и адаптер Btk_FileApa с одним методом copyObject.

6.13.2. Подготовка 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)
  }

  <...>

}

После чего через External Tools сгенерируйте контракт и исходный код по вашему файлу. Таким образом создастся контракт (в примеру для Btk_FileLib) Btk_FileLic и адаптер Btk_FileLia с двумя методами uploadFile и uploadFiles.

6.13.3. Подготовка 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()
  }

  <...>

}

После чего через External Tools сгенерируйте контракт и исходный код по вашему файлу. Таким образом создастся контракт (в примеру для Btk_FilePkg) Btk_FilePkc и адаптер Btk_FilePka с методом checkExist.

6.13.4. Как определить методы и классы для которых необходимо сгенерировать контракт#

Контракты необходимо генерировать на классы и методы которые используются в других модулях. Например Btk_FileApi часто используется, поэтому для него необходимо сгенерировать контракт. В нём есть методы для создания, удаления, изменения файлов, которые так же нужно добавить в контракт, но так же есть методы, к примеру updateFileStorageFromObjectTypes, который хоть и являются публичными, но нужны для работы с файлами внутри модуля Btk, для такого метода генерировать контракт не нужно.

6.13.5. Частые проблемы и их решения#

6.13.5.1. Отсутствие case class#

При генерации контракта, если ваш метод возвращал case class, то для контракта он будет не доступен, так как лежит внутри пакета сборки src или внутри другого Api. Такие классы необходимо выносить в пакет сборки core, что бы они были доступны и для контрактов и для пакета src.

6.13.5.2. Метод возвращает провайдер строки от другого класса#

Если ваш метод, например в Pkg возвращает провайдер строки (rop) из другого Api, то для него так же необходимо сгенерировать контракт, иначе проект не будет собираться.

6.13.5.3. Ошибка компиляции после переноса 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 в вашем модуле

6.13.5.4. При генерации отсутствует import или не правильно сгенерировалась структура параметров метода#

В таких случаях временно исправьте структуру руками и напишите с примером в чат поддержки Gtk, Btk что у вас не получается сгенерировать контракт.