= Scala Dialect for Polyglot Maven
:polyglot-scala-version: 0.4.6
:polyglot-translate-version: 0.4.6
:toc:
:toc-placement: preamble

Enables Maven to read POMs (`pom.scala`) written in Scala Language.

== Enabling the Polyglot Scala Extension

In your project extension configuration `.mvn/extensions.xml` add the following:

[source,xml,subs="verbatim,attributes"]
----
<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
  <extension>
    <groupId>io.takari.polyglot</groupId>
    <artifactId>polyglot-scala</artifactId>
    <version>{polyglot-scala-version}</version>
  </extension>
</extensions>
----

== Converting existing `pom.xml` files

You can (initially) convert existing projects with the `polyglot-translate-plugin`.

[source,subs="attributes"]
----
mvn io.takari.polyglot:polyglot-translate-plugin:{polyglot-translate-version}:translate -Dinput=pom.xml -Doutput=pom.scala
----

== Example

As an example, the `pom.xml` of this project pom (in version {polyglot-scala-version}) written as `pom.scala` would look like:

[source,scala]
----
import org.sonatype.maven.polyglot.scala.model._
import scala.collection.immutable.Seq

Model(
  "" % "polyglot-scala",
  packaging = "takari-jar",
  name = "Polyglot :: Scala",
  contributors = Seq(
    Contributor(
      name = "Christopher Hunt",
      organization = "Typesafe",
      organizationUrl = "http://typesafe.com"
    ),
    Contributor(
      name = "Tobias Roeser",
      url = "https://github.com/lefou"
    )
  ),
  parent = Parent(
    gav = "io.takari.polyglot" % "polyglot" % "0.4.7-SNAPSHOT"
  ),
  repositories = Seq(
    Repository(
      snapshots = RepositoryPolicy(
        enabled = false
      ),
      id = "sonatype-public-grid",
      url = "http://repository.sonatype.org/content/groups/sonatype-public-grid/"
    )
  ),
  dependencies = Seq(
    "io.takari.polyglot" % "polyglot-common" % "",
    "com.twitter" % "util-eval_2.11" % "6.43.0",
    "com.googlecode.kiama" % "kiama_2.11" % "1.8.0",
    "org.scala-lang" % "scala-library" % "${scala.version}",
    "org.scala-lang" % "scala-reflect" % "${scala.version}",
    "org.scala-lang" % "scala-compiler" % "${scala.version}",
    "org.specs2" % "specs2-junit_2.11" % "3.9.5" % "test"
  ),
  properties = Map(
    "scala.version" -> "2.11.12"
  ),
  build = Build(
    sourceDirectory = "src/main/scala",
    testSourceDirectory = "src/test/scala",
    pluginManagement = PluginManagement(
      plugins = Seq(
        Plugin(
          "org.eclipse.m2e" % "lifecycle-mapping" % "1.0.0",
          configuration = Config(
            lifecycleMappingMetadata = Config(
              pluginExecutions = Config(
                pluginExecution = Config(
                  pluginExecutionFilter = Config(
                    groupId = "net.alchim31.maven",
                    artifactId = "scala-maven-plugin",
                    versionRange = "[3.3.0,)",
                    goals = Config(
                      goal = "add-source",
                      goal = "compile",
                      goal = "testCompile"
                    )
                  ),
                  action = Config(
                    ignore = None
                  )
                )
              )
            )
          )
        )
      )
    ),
    plugins = Seq(
      Plugin(
        "org.apache.maven.plugins" % "maven-compiler-plugin",
        executions = Seq(
          Execution(
            id = "default-compile",
            phase = "none"
          )
        )
      ),
      Plugin(
        "net.alchim31.maven" % "scala-maven-plugin" % "4.3.1",
        executions = Seq(
          Execution(
            goals = Seq(
              "compile",
              "testCompile"
            ),
            configuration = Config(
              args = Config(
                arg = "-deprecation",
                arg = "-feature"
              ),
              checkMultipleScalaVersions = "false",
              recompileMode = "incremental",
              useZincServer = "true"
            )
          )
        )
      ),
      Plugin(
        "org.apache.maven.plugins" % "maven-surefire-plugin",
        configuration = Config(
          includes = Config(
            include = "%regex[.*Spec.*]"
          )
        )
      )
    )
  ),
  modelVersion = "4.0.0"
)
----

More examples...


== Features

* Supports the complete Maven POM model
* compact sbt-like notation for dependencies: `group % artiact % version % scope`
* Dynamic notation of plugin configurations with `Config`
* Provides full Scala Language features to write poms
* Conversion to and from `pom.xml` via the `polyglot-translate-plugin`
* Powerful include feature, to share configuration code between poms
* Works out of the box with Maven 3.3.1+


=== Scala-specific features

==== Scala binary version specific dependencies

Polyglot-Scala allows you to write sbt-like scala-binary-version specific dependencies with `%%`.

[source,scala]
----
import org.sonatype.maven.polyglot.scala.model._
import scala.collection.immutable.Seq

implicit val scalaVersion = ScalaVersion("2.12.6")

Model(
  dependencies = Seq(
    "com.typesafe.akka" %% "akka-actor" % "2.5.9"
  )
  // ...
)
----

which is preferred over

[source,scala]
----
"com.typesafe.akka" % s"akka-actor_${scalaVersion.binaryVersion}" % "2.5.9"
----

or even

[source,scala]
----
"com.typesafe.akka" % "akka-actor_2.12" % "2.5.9"
----

==== Scala Projects

== Best Practices

=== Prefer variables over Maven's property resolution mechanism

For example, consider the following snippet, which uses a Maven property to define the Scala version

[source,scala]
----
Model(
  properties = Map(
    "scala.version" -> "2.12.6"
  ),
  dependencies = Seq(
    "org.scala-lang" % "scala-library" % "${scala.version}",
    "org.scala-lang" % "scala-reflect" % "${scala.version}"
  )
  // ...
)
----

Instead, you can use a Scala immutable variable to hold the Scala version.

[source,scala]
----
val scalaVersion = "2.12.6"

Model(
  dependencies = Seq(
    "org.scala-lang" % "scala-library" % scalaVersion,
    "org.scala-lang" % "scala-reflect" % scalaVersion
  )
  // ...
)
----

Benefits:

* Variables are checked at compile-time (parse-time of pom), thus incomplete removals or renaming will fail early before Maven start building the project.
* Maven will only see the actual value, thus no variables will be in the resulting pom which gets installed into the artifact repository.

=== Use shared include files for versions and other settings

One unique feature of the Polyglot Scala Extension is the include macro.

.`pom.scala`: Include code from the shared file `mvn-shared.scala`
[source,scala]
----
//#include mvn-shared.scala
----

By placing the project version number in a shared scala file and including it into each project, you have to only edit one central place when the version number needs to be modified.

Also, the include process is completely transparent to Maven.
When your artifact gets installed and deployed, the version is no longer a variable, which makes later resolve processes more robust.

TIP: Avoid Maven parent poms completely, by using include files for dependency and plugin definitions.

=== Replace Maven parent poms with include files

Instead of defining and referencing parent poms, you should consider to define an object which produces the desired project model with all its settings and potential defaults.
This not only makes the reasoning about your project easier, it also avoid various issues Maven has with parent poms.
It also speeds up the build and produces less artifacts to download.

== Known Issues

=== Converting/Translating existing `pom.xml` fails when there is no XML Header

In case your `pom.xml` does not have an proper XML header (aka XML Declaration), conversion with the `polyglot-translate-plugin` will fail.
You can work around this issue by adding the missing XML header to the pom file.
E.g. just add to following line as to the top of the `pom.xml`.

[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
----

=== Eclipse IDE does not directly support Polyglot-Scala

There is a proof-of-concept Eclipse Plug-in that integrates some polyglot support into M2E, but it isn't integrated as smooth as `pom.xml`.
