0 概述
在Chisel入门(一)------搭建Chisel开发环境 Chisel入门(一)------搭建Chisel开发环境_努力学习的小英的博客-CSDN博客中我们运行了第一个Chisel程序并将Chisel代码转为了Verilog代码。转化的过程中我们主要是使用SBT(Scala Build Tool)构建工具,后续的所有学习都将基于SBT进行搭建,所以有效的一下SBT的构建定义等是很有必要的。有助于帮助我们更好的搭建自己的Chisel工程,下边就开始吧。
1 BT入门
1.1 使用SBT运行一个Scala程序
新建一个文件夹,新建一个后缀为.scala的文件并输入:
object HelloScala extends App{
println("Hello World!")
}
然后在当前目录下执行
sbt
进入SBT交互界面后之后执行
run
即可看到成功输出了“Hello World”,注意第一次执行需要下载一些东西,速度会比较慢,等构建一次之后再次执行速度就会快很多。(我这里新建的文件名文Scala_test,这个无所谓)
正常情况下SBT会按照约定执行工作,会自动寻找以下内容:
- 项目根目录下的源文件
- src/main/scala或src/main/java中的源文件
- src/test/scala或src/test/java中的测试文件
- src/main/resources或src/test/resources中的数据文件
- lib中的jar文件
1.2 构建定义
在SBT启动过程中,会读取该项目根目录下的build.sbt文件,该文件存储了项目的基本构建设置,恶意进行手动设置,一个简单的build.sbt文件如下:
lazy val root = (project in file("."))
.settings(
name := "hello",
version := "1.0",
scalaVersion := "2.12.16"
)
具体关于如何编写build.sbt将在后边介绍。
1.3 设置SBT版本
SBT的版本设置在/project/build.properties文件下,通过编写可以指定版本
sbt.version=1.8.2
2 SBT目录结构
2.1 SBT的源文件目录
SBT和Maven默认的源文件目录结构是一样的,在实际工程中,也不会向上边的hello world程序一样将源代码放在根目录下,因为这样太混乱了,更好的做法是按照下述目录结构管理源代码:
src/
main/
resources/
<files to include in main jar here>
scala/
<main Scala sources>
scala-2.12/
<main Scala 2.12 specific sources>
java/
<main Java sources>
test/
resources
<files to include in test jar here>
scala/
<test Scala sources>
scala-2.12/
<test Scala 2.12 specific sources>
java/
<test Java sources>
除了上述目录外,src/中的其他目录都会被忽略。
2.2 SBT构建定义文件
除了项目根目录下的build.sbt文件外,其他的sbt文件在project目录下,也可能是.scala文件,这些文件最终会和.sbt文件合并共同构成完整的构建定义,将在后边详细介绍
build.sbt
project/
Build.scala
2.3 构建产品
构建出来的文件,包括编译的classes,打包的jars,托管文件,caches和文档,都默认写在target目录下。
2.4 配置版本管理
在.gitignore文件(或其他版本控制系统等同的文件)应该包含:
target/
这里的/不能省略,表示除了匹配普通的target/外还匹配project/target/
3 运行SBT
3.1 交互模式
在项目目录下运行sbt即可进入:
$ sbt
交互模式具有tab自动补全和历史记录功能,退出需要输入exit或者Ctrl+D(Unix)或Ctrl+Z(Windows)。
3.2 批处理模式
可以以空格作为分隔符指定参数,然后用批处理模式来运行sbt。对于接受参数的sbt命令,将命令和参数用一号引起来一起传给sbt,如:
$ sbt clean compile "testOnly TestA TestB"
其中,tesOnly有两个参数TestA和TestB,这个命令会安顺西执行(clean,compile,然后testOnly)。
3.3 持续构建和测试
为了加快编辑-编译-测试循环,可以让sbt在保存源文件的同时自动重新编译或跑测试。在命令前面加上前缀~后,每当有一个或多个源文件发生变化时就会自动运行该命令。
> ~ compile
按回车键即可停止监视变化。
3.4 常用命令
clean | 删除所有生成的文件 (在 target 目录下)。 |
compile | 编译源文件(在 src/main/scala 和 src/main/java 目录下)。 |
test | 编译和运行所有测试。 |
console | 进入到一个包含所有编译的文件和所有依赖的 classpath 的 Scala 解析器。输入 :quit, Ctrl+D (Unix),或者 Ctrl+Z (Windows) 返回到 sbt。 |
run <参数>* | 在和 sbt 所处的同一个虚拟机上执行项目的 main class。 |
package | 将 src/main/resources 下的文件和 src/main/scala 以及 src/main/java 中编译出来的 class 文件打包成一个 jar 文件。 |
help <命令> | 显示指定的命令的详细帮助信息。如果没有指定命令,会显示所有命令的简介。 |
reload | 重新加载构建定义(build.sbt, project/*.scala, project/*.sbt 这些文件中定义的内容)。在修改了构建定义文件之后需要重新加载。 |
3.5 Tab自动补全
Tab键按一次的时候可能只会显示所有命令中最有可能的自动补全子集,当按多次时才会显示详细的选项。
3.6 命令历史记录
即使退出sbt后重新进入,也会存在历史记录,除了上方向键外,还有以下命令:
! | 显示历史记录命令帮助。 |
!! | 重新执行前一条命令。 |
!: | 显示所有之前的命令。 |
!:n | 显示之前的最后 n 条命令。 |
!n | 执行 !: 命令显示的结果中下标为 n 的命令。 |
!-n | 执行从该命令往前数第 n 条命令。 |
!string | 执行最近执行过的以 string 打头的命令。 |
!?string | 执行最近执行过的包含 string 的命令。 |
4 IDE集成
如果单纯的通过文本编编辑器的形式来修改SBT中的配置文件和源文件,会显得特别麻烦,所以这里选择使用IDEA作为项目管理工具。另外VSCODE上的Metals插件同样可以使用,可以在官网上查看其使用方法sbt Reference Manual — IDE Integration (scala-sbt.org)
1)打开IDEA
2)安装Scala插件
3)打开带有.sbt的项目
4)等待构建完成即可。
5 构建定义
5.1 指定sbt版本
建立一个名为project/build.properties的文件,指定sbt版本语句如下:
sbt.version=1.8.2
如果指定的版本本地没有安装,那么sbt构建器将会去下载该版本,如果这个文件不存在,那sbt将会使用一个任意的版本,这种方式比较不推荐。
5.2 什么是构建定义
构建定义被定义在当前项目下的build.sbt文件下,并且它由一系列的子项目组成,比如:
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
其中定义了一个键值key,name映射到一个字符串。
5.3 build.sbt如何定义设置
build.sbt使用build.sbt 领域特定语言DSL(domain-specific-language)来设置一个键值对序列,称为setting表达式。
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.16"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "hello"
)
仔细看一下在build.sbt中的DSL:
每个条目可以被称作setting表达式,其中有一些也被称为task表达式。
一个设置表达式由以下三部分组成:
- 左侧是一个键值(key)
- 操作符为:=
- 右侧右侧被称为实体,或者设置主体
build.sbt左侧的name,version和scalaVersion都是键值。键(key)是SettingKey[T]、TaskKey[T]或InputKey的实例,其中T是期望的值类型。
因为键name的类型时SettingKey[String],操作符:=输入给name的值便被指定为String,如果使用了错误的类型,将编译不通过。
lazy val root = (project in file("."))
.settings(
name := 42 // will not compile
)
build.sbt也可能穿插着val、lazy val和def。顶层的object和class则不被允许出现。这些应该在project路径下的Scala源文件中。
5.4 键
5.4.1 类型
有三种类型的key:
- SettingLey[T]:只计算一次值的key(在加载子项目时被计算,之后一直保留)
- TaskKey[T]:一个被称为Task的key值,类似于Scala中的函数,在每次被引用的都厚都会计算,可能会有副作用
- InputKey[T]:一个有命令行形参作为输入的任务key。
5.4.2 内建key
内置键是一个名为Keys的对象的字段,build.sbt文件中隐含会导入sbt.Keys._,所以sbt.Keys.name可以引用为name。
5.4.3 自定义键
自定义键可以被他们不同的创建方法所定义:seetingKey,taskKey和inputKey。每个方法都要求值的类型要与键的类型所匹配。键的名称取决于该键所对应的val。例如,定义一个名为hello的tsak键:
lazy val hello = taskKey[Unit]("An example task")
这里.sbt文件除了包含setting外还包含了val和def,所有的类似定义应该在在setting之前定义。
注意:一般情况下,都使用lazy' val来替代val从而避免初始化的一些问题。
5.4.4 Task vs setting键
TaskKey[T]通常被定义为task,可能是如compile或package这样的操作符。他们的返回值可能是空Unit,或者返回与task相关的值,比如pakage是一个TaskKey[File]并且他的值是穿件的jar文件。
每次执行一个任务,比如在sbt交互界面下输入compile,sbt就会重新运行一次所有涉及到的任务。
sbt的key-value对描述一个子项目可以像name一样将值固定为一个字符串,但是也必须保持一些像compile一样的可执行代码。
5.4.5 列出所有可用的setting键和task键
可以在sbt命令行中输入setting或setting -v来获取当前构建定义下的所有设置键。同样的,输入task或task -v可以获取所有task键,
如果满足以下条件之一,键将会打印出来:
- 是内建sbt(比如name、scalaVersion)
- 是自定义键
- 导入的插件所带来的定义
可以使用help<key>命令来获取更多信息。
5.5 定义tasks和settings
使用:=即可连接一个值到setting键或一个task的计算中。对于setting来说,项目加载时,值会被计算一次。对于task来说,每次task被执行整个计算都会执行一次。
例如,执行一个名为hello的task:
lazy val hello = taskKey[Unit]("An example task")
lazy val root = (project in file("."))
.settings(
hello := { println("Hello!") }
)
同样的,一个setting:
lazy val root = (project in file("."))
.settings(
name := "hello"
)
5.6 在sbt shell中输入key
输入task key则执行该任务,但是不返回结果,想要返回结果需输入 show <task name>,setting key则打印出其值。
5.7 在build.sbt中导入
默认导入;
import sbt._
import Keys._
5.8 单个.sbt构建定义
设置可以直接写入build.sbt文件中,而不需要放在.settings(...)里。称这种形式为“裸风格”
ThisBuild / version := "1.0"
ThisBuild / scalaVersion := "2.12.16"
这种语法推荐用于ThisBuild作用域设置和添加插件。
5.9 增加库依赖
要依赖第三方库,有两种方式。第一种是在lib/(非托管依赖项)下增加jar,另一个是添加托管依赖项,在build中是这样的:
val derby = "org.apache.derby" % "derby" % "10.4.1.3"
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.16"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += derby
)
这是在Apache Derby library库10.4.13版本上添加依赖的方法。+=表示附加到键的基础上增加,而=是替换,%用于从字符串中构造一个Ivy模块的ID,
6 多项目构建
该节介绍了如何在一个构建文件中建立多个子项目,
6.1 多个子项目
如果有多个子项目之间相互依赖,并且你希望可以同时修改他们,这种方式是很有用的。构建的每个子项目都有自己的源目录,在运行pakage时会生成自己的jar文件,并且像其他项目一样工作。
通过声明一个Project类型的lazy val即可定义一个项目,比如:
lazy val util = (project in file("util"))
lazy val core = (project in file("core"))
val的名字被用作子项目的ID,在sbt shell中指定子项目的时候会用到。
如果基目录的名字和val一直,那么可以忽略基目录:
lazy val util = project
lazy val core = project
6.2 构建大范围的setting
要想设置公共的setting跨越多个子项目,需要定义setting的范围为ThisBuild。ThisBuild是一个默认的子项目,可以使用它来定义build中的默认值。当定义一个或多个子项目时,而且子项目中没有定义scalaVersion键,那么会默认使用 ThisBuild / scalaVersion的值。
限制在于右侧需要一个纯净的值或者设置范围为全局或ThisBuild,并且子项目中没有默认设置。
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.16"
lazy val core = (project in file("core"))
.settings(
// other settings
)
lazy val util = (project in file("util"))
.settings(
// other settings
)
现在我们可以在一个地方提升版本,并且当重新构建加载时,会在其他子项目中反映出来。
6.3 常规设置
另外一种跨越多个项目进行公共色值的方法是新建一个名为commonSettings的序列,然后在每个项目中调用setting方法。
lazy val commonSettings = Seq(
target := { baseDirectory.value / "target2" }
)
lazy val core = (project in file("core"))
.settings(
commonSettings,
// other settings
)
lazy val util = (project in file("util"))
.settings(
commonSettings,
// other settings
)
6.4 依赖
构建中的项目之间可以完全独立,但是通常他们会以某种依赖关系相关关联。依赖关系有两种:聚合(aggregate)和类路径(classpath)
6.4.1 聚合
聚合意味着在聚合项目上运行任务也将在聚合的其他项目上运行该项目,例如:
lazy val root = (project in file("."))
.aggregate(util, core)
lazy val util = (project in file("util"))
lazy val core = (project in file("core"))
上述例子子,根项目聚合了util和core两个项目。启动例子中带有两个子项目的sbt,尝试compile,将会发现,三个项目都进行了编译。
在聚合项目中,可以控制每个任务的聚合,例如,避免聚合update任务:
lazy val root = (project in file("."))
.aggregate(util, core)
.settings(
update / aggregate := false
)
[...]
其中,update / aggregate是改变upodate聚合范围的关键词。
注意:聚合任务是并行执行,,没有顺序关系。
6.4.2 类路径依赖
一个项目可能依赖于另一个项目中的代码,这可以通过添加一个dependsOn来实现,如果core在它的类路径上需要util,则可以这样定义core:
lazy val core = project.dependsOn(util)
现在core中的代码就可以使用util的类了。这也建立了它们的编译顺序,util必须在core之前编译和更新。
要依赖多个项目,可以使用dependsOn的多个参数,如depensOn(bar, baz)。
core dependsOn(util)意味着core的编译配置依赖于util的编译配置,你可以将其显示的表示为dependsOn(util % "compile->compile")。
其中->意味着“依赖”,所以“test->compile”意味着core中的test配置依赖于util中的compile配置。
如果忽略->config部分则意味着“->compile”,所以depensOn(util % "test")意味着core中的tes配置依赖于util中的compile配置。
一个有用的设置是“test->test”,这意味着test依赖于test。可以将有用的代码放在“util/src/test/scala”中然后在“core/src/test/scala”中去使用它。
同样可以配置多个依赖,由分号隔开,比如:
dependsOn(util % "test->test;compile->compile")
6.4.3 项目间依赖
在具有许多文件和子项目的超大性项目上,sbt在持续监视已更改并使用大量磁盘和系统I/O时执行的不太理想。
sbt具有trackInternalDependencies和exportToInternal设置,这些可以用于控制在调用compile是是否触发依赖子项母的编译。他们两个都汇取3个值之一:TrackLevel.NoTracking、TrackLevel.TrackIfMissing和TrackLevel,TrackAlways。默认情况下两者都设置为TrackLevel.TrackAlways。
当trackInternalDependencies设置为TrackLevel.TrackIfMissing时,sbt将不在尝试自动编译内部(项目间)依赖项,除非在输出目录下没有*.class文件(或JAR文件,如果exportJar设置为true)。
当trackInternalDependencies设置为TrackLevel.NoTracking时,内部依赖的编译将被调跳过。注意,这时的类路径仍会被追加,依赖关系图仍然会将它们显示为依赖关系。这样做的目的是节省I/O开销,以便在开发过程中检查带有许多子项目的项目在构建上的更改。下面是将所有的子项目设置为TrackIfMissing:
ThisBuild / trackInternalDependencies := TrackLevel.TrackIfMissing
ThisBuild / exportJars := true
lazy val root = (project in file("."))
.aggregate(....)
expoerToInternal设置允许设置以来子项目去退出内部追踪,如果你想要追踪少数子项目以外的大部分子项目的话,这是很有用的。trackInternalDependencies和expoertToInternal设置的交集将被用于确定实际的追踪效果。下边是一个子项目退出追踪的示例;
lazy val dontTrackMe = (project in file("dontTrackMe"))
.settings(
exportToInternal := TrackLevel.NoTracking
)
6.5 默认根项目
如果没有在根目录的构建中没有定义一个项目,那么sbt将创建一个聚合build中所有子项目的项目。
因为项目hello-foo是用base=file(“foo”)定义的,所以它将包含在foo中。它的源代码可以直接在foo目录下,比如foo/Foo.scala。或者是foo/src.main/scala。除了构建文件外,通常的sbt目录结构应该位于foo下面。
6.6 交互式的导航项目
在sbt交互式命令行下,输入project将列出你的所有项目,输入project <projectname>去选择一个当前项目。当运行类似于compile这样的任务时,它只运行在当前项目。所以并需要在根项目下进行编译,而可以只编译子项目。
还可以通过现实的指定项目ID在其他项目上运行任务,比如:
subProjectID/compile
6.7 公共代码
.sbt文件中的定义在其他.sbt文件中并不可见。为了共享不同.sbt文件,可以在project/目录下定义一个或多个Scala文件。
6.8 附录:子项目构建定义文件
任何在foo下的.sbt文件,称为foo/build.sbt,将于整个构建定义合并,但是仅限于hello-foo项目。
如果你的整个项目是在hello里,在hello/build.sbt、hello/foo/build.sbt和hello/bar/build.sbt中定义一个不同的版本(version := “0.6”)。然后查看他们的版本,将可以看到:
> show version
[info] hello-foo/*:version
[info] 0.7
[info] hello-bar/*:version
[info] 0.9
[info] hello/*:version
[info] 0.5
风格建议:
- 每个子项目的设置可以在该项目根目录下的*.sbt文件下设置,然而根目录下的build.sbt仅在没有设置的情况下以“lazy val foo = (project in file(“foo”))”的格式声明最小项目
- 更加推荐将所有项目的声明放在根目录的build.sbt文件下,以便于在单个文件中更好的管理项目。
注意:不能在子目录下有project子路径或者project/*.scala。这些将被忽视。
7 任务图
7.1 声明对其他任务的依赖
在build.sbt中,可以使用.value方法来表示对其他任务和设置的依赖。value方法是特殊的,只能:=(+=或++=)的参数中调用。
8 作用域
8.1 keys的故事
前边假设一个name键对应sbt中的一条键值对映射,这其实是一个简化。实际上每个key在多个环境中有一个相关联的值,称之为作用域。
一些具体的例子:
- 如果在构建定义中有多个子项目,那么每个项目中的键可以有不同的值;
- 如果你想以不同的方式编译源代码和测试代码,compile键可以有不同的值;
- packageOptions键(包含创建jar包的选项)在打包类文件(packageBin)或打包源代码(packageSrc)时可能有不同的值。
给定的一个键名的值并不单一,这是因为值可能会因为作用域不同而不同。然而对于给定作用域的键,它的值只有一个。
如果你像之前一样使用sbt处理一个setting列表来生成键值对描述项目,那么映射的键值对便是scoped keys。
通常,作用域是隐含的具有一个默认值,但如果默认值是错误的,则需要在build.sbt中修改。
8.2 scope axes作用域轴
scope axis是类似于Option[A]的类型构造,用于在作用域中形成组件。