摘要:本文根据 sbt-native-packager
官方文档详细介绍其基本理论基础,重点在理解一些基础知识,便于理解复杂的配置,并且可以灵活自定义打包。参考文档
概述
sbt-native-packager
的初衷是:
“Code once, deploy anywhere”
也就是一次编码,在所有地方部署,可见其功能能够是比较强大,但是功能强大带来的问题是设计必须巧妙,能够兼容各种环境,所以如果希望灵活的使用该插件,就需要对其基本原理有较为深刻的理解,才能将其应用到各种环境中。
介绍
SBT native packager允许您以原始格式构建应用程序包,并为常见配置(如简单Java应用程序或服务器应用程序)提供不同的模型(archetypes)。
本节将介绍 native packager 概述及其核心概念。
Goals 目标
首先我们需要了解该插件的目标,这样我们才能合理的使用它:
- Native formats should build on their respective platform
This allows native packager to support a wide range of formats as the packaging plugin serves as a wrapper around the actual packaging tool. However, alternative packaging plugins maybe provided if a java/scala implementation exists. As an example debian packages should always build on debian systems, however native packager provides an additional plugin that integrates JDeb for a platform independent packaging strategy.
原生格式应该构建与对应的平台,比如 debian 的安装包就应该在 debian 系统下构建。
- Provide archetypes for zero configuration builds
While packaging plugins provide the how a package is created, archetypes provide the configuration for what gets packaged. Archetypes configure your build to create a package for a certain purpose. While an archetype may not support all packaging formats, it should work without configuration for the supported formats.
能够提供零配置的 archetypes。
- Enforce best-practices
There is no single way to create a package. Native packager tries to create packages following best practices, e.g. for file names, installation paths or script layouts.
提供打包的最佳实践
Scope 范围
While native packager provides a wide range of formats and archetype configurations, its scope is relatively narrow. Native packager only takes care of packaging, the act of putting a list of mappings (source file to install target path) into a distinct package format (zip, rpm, etc.).
实际上该打包工具的核心功能很简单:打包,也就是将一系列的文件打包到对应的格式中。
我们还要知道哪些事情该插件是不负责的:
- 程序生命周期管理
- 部署配置
核心概念
职责分离
这里有两种插件
- format plugins: 如何创建 package (how)
- archetype plugins:将哪些内容放入 package 中 (what)
mappings 映射
这个概念是用于表示如何将构建的文件组织到目标系统中。
Mappings are a Seq[(File, String)], which translates to “a list of tuples, where each tuple defines a source file that gets mapped to a path on the target system”.
例如:Seq[(File, String)]
,表示一个 tuples 的序列,每个 tuple 定义一组源文件映射到目标系统的路径。
该理念是核心,需要重点理解。
1 | mappings: TaskKey[Seq[(File, String)]] |
The file part of the tuple must be available during the packaging phase. The String part represents the path inside the installation directory.
- 其中 file 部分是只需要被打包的文件,需要在打包阶段已经生成完毕
- 其中 String 部分表示安装目录的路径
进一步理解 mappings,我们可以通过 Universal Plugin
来学习。
Universal Plugin
The Universal Plugin creates a generic, or “universal” distribution package. This is called “universal packaging.” Universal packaging just takes a plain mappings configuration and generates various package files in the output format specified. Because it creates a distribution that is not tied to any particular platform it may require manual labor (more work from your users) to correctly install and set up.
简单的说就是将项目打包为扩平台的 JVM 程序,用户需要在不同平台上手动执行命令来运行,比如在 Windows 上需要执行 bat 脚本,Linux 上需要执行 sh 脚本。
原理
参考 Getting Started with Universal Packaging
By default, all files found in the
src/universal
directory are included in the distribution. So, the first step in creating a distribution is to place files in this directory and organize them as you’d like in them to be in the distributed package. If your output format is a zip file, for example, although the distribution will consist of just one zip file, the files and directories within that zip file will reflect the same organization and structure assrc/universal
.To add files generated by the build task to a distribution, simply add a mapping to the
mappings in Universal
setting. Let’s look at an example where we add the packaged jar of a project to the lib folder of a distribution:
我们需要通过 mapping
来想目标目录添加文件
1 | mappings in Universal <+= (packageBin in Compile) map { jar => |
解释:
- 依赖于
Compile
中的packageBin
,因为这个task
会在target
目录下生成jar
,这些jar
是打包必备的核心内容。 - 创建一个
mapping
是一个(Tuple2[File, String]
),表示添加的文件和目标路径的字符串。
进一步理解 (packageBin in Compile)
会返回一个 jar
的序列,然后通过 map 操作,将每个 jar
映射为一个 Tuple2
, 两个元素分别是 jar
文件本身,后一个元素是一个字符串,表示打包后目标路径,及 ("lib/" + jar.getName)
,也就是放置于 lib
目录下,文件名就是 jar
的名字。
关于 Mapping 的各种方法总结
关于 setting 的操作符
1 | def +=[U](v: U)(implicit a: Append.Value[T, U]): Setting[Task[T]] = |
see https://stackoverflow.com/a/23693921/2000468
You cannot find documentation, because as @JacekLaskowski correctly pointed out all operators except for +=, ++= and := are deprecated.
You can however find the Documentation if you switch to older version of sbt.
If you however are stuck to older version, this is their meaning (via documentation):
+= and ++= append to previous value, where first appends single element and next appends a Seq
~= transforms value, e.g. you want to use value stored in a setting to get a new setting.
<<= depends on another key, for example if you call organization <<= name, then organization value is equal to name value. You can depend on multiple values, e.g. organization <<= (name, version) { (n, v) => / do something with values / }
<+= and <++= are appending with dependencies, like the append, but you can use another’s setting value to compute new value
Said that, @JacekLaskowski is right, and if you are using sbt 13.x or greater you should not have to use those operators in favours of macros.
使用方法案例
:= will replace the previous value
+= will append a single element to the sequence.
++= will concatenate another sequence.
1 | sourceDirectories in Compile += new File("source") |
~= applies a function to the setting’s previous value, producing a new value of the same type. 也就是将之前的值经过一些处理得到新的值,再赋值。
1 | // filter out src/main/scala |
Unlike <<= of course, <+= and <++= will append to the previous value of the key on the left, rather than replacing it.
For example, say you have a coverage report named after the project, and you want to add it to the files removed by clean:
1 | cleanFiles <+= (name) { n => file("coverage-report-" + n + ".txt") } |
关于路径的获取方法
参考链接 :Mapping Examples
SBT provides the IO and Path APIs, which help make defining custom mappings easy. The files will appear in the generate universal zip, but also in your debian/rpm/msi/dmg builds as described above in the conventions.
https://www.scala-sbt.org/0.13.1/docs/Detailed-Topics/Paths.html
The packageBin in Compile dependency is only needed if your files get generated during the packageBin command or before. For static files you can remove it.
注意这里是一个旧的文档
这里需要实际案例测试各个功能。
案例解析
1 | mappings in Universal <+= (packageBin in Compile) map { jar => |
1 | mappings in Windows ++= { |