Статьи

Полноценные проекты SBT

Akka Extras использует полный  .scala синтаксис SBT. Это отличается от  .sbt синтаксиса, поэтому давайте рассмотрим, как создать синтаксическую сборку Scala, включая публикацию в Sonatype. Мы исследуем это на реальном  EigengoBuild объекте .

Строительный объект

EigengoBuild Расширяет  Build объект, который является настройкой проекта. В этом файле мы определим модули, а также  root модуль, который объединяет дочерние модули. Давайте рассмотрим общую структуру.

object EigengoBuild extends Build {
  // contains the modules and build settings
}

object Publish {
  // contains settings for publishing
}

object Dependencies {
  // contains dependencies
}

Давайте рассмотрим  EigengoBuildосновную структуру России. Начнем с настроек и модулей. settings И  defaultSettings провести глобальные и модуль конкретных параметров SBT. Затем мы ссылаемся и используем эти последовательности настроек.

override val settings Имеет глобальные параметры проекта. Мы устанавливаем  organisation, version и  scalaVersion настройки SBT. defaultSettings Содержит настройки , которые будут использоваться в каждом модуле. К ним относятся параметры компилятора (scalac и javac), поведение потоков и средства разрешения артефактов. Если у вас есть локальный / корпоративный репозиторий, вам нужно будет добавить его в resolversпоследовательность. Обратите внимание, что некоторые ключи в настройках извлекаются из нашего  Publish объекта, а также из объектов, которые определяют плагины. ( Ex gratia graphSettings , определено в net.virtualvoid.sbt.graph.Plugin.)

object EigengoBuild extends Build {

  override val settings = super.settings ++ Seq(
    organization := "org.eigengo.akka-extras",
    version := "0.1.0",
    scalaVersion := "2.10.1"
  )

  lazy val defaultSettings = 
    Defaults.defaultSettings ++ 
    Publish.settings ++ 
    graphSettings ++ 
    Seq(
      scalacOptions in Compile ++= Seq("-encoding", "UTF-8", 
                                       "-target:jvm-1.6", 
                                       "-deprecation", 
                                       "-unchecked"),
      javacOptions in Compile ++= Seq("-source", "1.6", 
                                      "-target", "1.6", 
                                      "-Xlint:unchecked", 
                                      "-Xlint:deprecation", 
                                      "-Xlint:-options"),
    // https://github.com/sbt/sbt/issues/702
    javaOptions += "-Djava.util.logging.config.file=logging.properties",
    javaOptions += "-Xmx2G",
    outputStrategy := Some(StdoutOutput),
    fork := true,
    maxErrors := 1,
    resolvers ++= Seq(
      Resolver.mavenLocal,
      Resolver.sonatypeRepo("releases"),
      Resolver.typesafeRepo("releases"),
      "Spray Releases" at "http://repo.spray.io",
      Resolver.typesafeRepo("snapshots"),
      Resolver.sonatypeRepo("snapshots")
    ),
    parallelExecution in Test := false
  ) ++ ScctPlugin.instrumentSettings // ++ ScalastylePlugin.Settings

  ...
}

Теперь, когда у нас есть настройки, мы можем определить модули и получить настройки, которые мы определили выше. Здесь у нас есть все модули, которые составляют Akka Extras. Мы используем подчеркивание для имени переменной и тире для имени каталога. Итак,  apple_push переменная определяет модуль, который живет в  apple-pushкаталоге.

Модули включают  settings, которые обычно определяют  libraryDependencies. Переменные, в которых хранятся зависимости, определяются ниже в  Dependencies объекте. (Таким образом,  import Dependencies._ выше.) В каждом каталоге, мы имеем обычную Maven-эск структуру src/main/scala,
src/main/resourcessrc/test/scala и так далее.

object EigengoBuild extends Build {

  override val settings = ...

  lazy val defaultSettings = Defaults.defaultSettings ++ 
                             Publish.settings ++ 
                             graphSettings ++ ...

  def module(dir: String) = 
    Project(id = dir, base = file(dir), settings = defaultSettings)

  import Dependencies._

  lazy val apple_push = module("apple-push") settings(
    libraryDependencies += akka,
    libraryDependencies += specs2 % "test"
  )

  lazy val freemarker_templating = module("freemarker-templating") settings (
    libraryDependencies += freemarker,
    libraryDependencies += specs2 % "test"
  )

  lazy val javamail = module("javamail") settings (
    libraryDependencies += mail,
    libraryDependencies += scalaz_core,
    libraryDependencies += typesafe_config,
    libraryDependencies += akka,
    libraryDependencies += specs2 % "test",
    libraryDependencies += dumbster % "test",
    libraryDependencies += akka_testkit % "test",

    publishArtifact in Compile := true
  )

  lazy val main = module("main") 
    dependsOn(apple_push, freemarker_templating, javamail)
  ...
}

Мы почти в конце. Последнее, что нам нужно сделать, это объединить все эти проекты в  rootпроект. Дальше нет  libraryDependencies, но включены все остальные модули.

object EigengoBuild extends Build {
  ...

  lazy val root = Project(
    id = "parent", 
    base = file("."), 
    settings = defaultSettings ++ 
               ScctPlugin.mergeReportSettings ++ 
               Seq(publishArtifact in Compile := false),
    aggregate = Seq(apple_push, freemarker_templating, javamail) 
  )
  
}

зависимости

В полноценных сборках Scala мы определяем индивидуальные зависимости как переменные; имеет смысл отделить их от их собственного объекта. И это именно то, что мы сделали в  Dependencies объекте

object Dependencies {
  val akka_version    = "2.1.2"

  val akka            = "com.typesafe.akka" %% "akka-actor"   % akka_version
  val scalaz_core     = "org.scalaz"        %% "scalaz-core"  % "7.0.0"
  val typesafe_config = "com.typesafe"       % "config"       % "1.0.0"
  val akka_testkit    = "com.typesafe.akka" %% "akka-testkit" % akka_version
  val specs2          = "org.specs2"        %% "specs2"       % "1.14"
  val mail            = "javax.mail"         % "mail"         % "1.4.2"
  val freemarker      = "org.freemarker"     % "freemarker"   % "2.3.19"
  val dumbster        = "dumbster"           % "dumbster"     % "1.6"
}

Публикация и Публикация объекта

Мы публикуем наши артефакты на  хостинге Sonatype OSS ; и мы используем  sbt-release плагин. Плагину нужны некоторые настройки, которые мы определяем в  Publish объекте. Код соответствует  руководству по  публикации , но использует .scala синтаксис вместо  полноценного  .sbt .

Мы определяем  settings переменную: последовательность настроек SBT, которая является Scala-синтаксическим эквивалентом написания

pomExtra  := ...
publishTo := ...

и другие в  .sbt синтаксисе. Чтобы все было читабельно, мы вытащили фактические значения вроде akkaExtrasPomExtra, что является правильной переменной в  Publish объекте; и мы обращаемся к нему, когда строим настройки SBT в этой последовательности. (А именно  pomExtra := akkaExtrasPomExtra)

akkaExtrasCredentials Загружается из  ~/.sonatype файла. (Вы не хотите публиковать свои учетные данные на GitHub — помните печальный эпизод, когда люди нажимали свои закрытые ключи без ключевой фразы?)

Этот файл должен содержать  realmhostusername и  password свойство типа линий.

realm=Sonatype Nexus Repository Manager
host=oss.sonatype.org
user=yeahright
password=likeidtellyou

Итак, вперед к  Publish объекту.

object Publish {

  lazy val settings = Seq(
    crossPaths := false,
    pomExtra := akkaExtrasPomExtra,
    publishTo < <= version { v: String =>
      val nexus = "https://oss.sonatype.org/"
      if (v.trim.endsWith("SNAPSHOT")) 
        Some("snapshots" at nexus + "content/repositories/snapshots")
      else                             
        Some("releases" at nexus + "service/local/staging/deploy/maven2")
    },
    credentials ++= akkaExtrasCredentials,
    organizationName := "Eigengo",
    organizationHomepage := Some(url("http://www.eigengo.com")),
    publishMavenStyle := true,
    // Maven central cannot allow other repos.  
    // TODO - Make sure all artifacts are on central.
    pomIncludeRepository := { x => false }
  )

  val akkaExtrasPomExtra = (
    <url>http://www.eigengo.org/akka-extras</url>
    ...
  )

  val akkaExtrasCredentials = Seq(Credentials(Path.userHome / ".sonatype"))

}

Резюме

На этом мы завершаем статью о том, как мы строим многомодульный проект Akka Extras. Я надеюсь, что вы найдете это полезным для ваших собственных проектов. Исходный код находится по адресу  https://github.com/eigengo/akka-extras .