Getting Started with Chisel (2)------Beginner SBT

0 Overview

    In Getting Started with Chisel (1)---Building a Chisel Development Environment  Getting Started with Chisel (1)------Building a Chisel Development Environment_We ran the first Chisel in Program and convert Chisel code to Verilog code. During the conversion process, we mainly use the SBT (Scala Build Tool) construction tool, and all subsequent studies will be built based on SBT, so it is necessary to effectively understand the construction definition of SBT. Help us to better build our own Chisel project, let's start below.

1 BT entry

1.1 Running a Scala program with SBT

    Create a new folder, create a new file with the suffix .scala and enter:

object HelloScala extends App{
    println("Hello World!")
}

Then execute in the current directory

sbt

Execute after entering the SBT interactive interface

run

You can see that "Hello World" has been successfully output. Note that some things need to be downloaded for the first execution, and the speed will be relatively slow. After a build, the execution speed will be much faster again. (The name of the newly created file here is Scala_test, it doesn't matter)

     Under normal circumstances SBT will perform the work as agreed, and will automatically look for the following:

  • Source files in the project root directory
  • Source files in src/main/scala or src/main/java
  • Test files in src/test/scala or src/test/java
  • Data files in src/main/resources or src/test/resources
  • jar files in lib

1.2 Build Definition

    During the startup process of SBT, the build.sbt file in the root directory of the project will be read. This file stores the basic build settings of the project. Malicious manual settings are made. A simple build.sbt file is as follows:

lazy val root = (project in file("."))
  .settings(
    name := "hello",
    version := "1.0",
    scalaVersion := "2.12.16"
  )

The details on how to write build.sbt will be introduced later.

1.3 Set SBT version

    The version of SBT is set under the /project/build.properties file, and the version can be specified by writing

sbt.version=1.8.2

2 SBT directory structure

2.1 SBT source file directory

    The default source file directory structure of SBT and Maven is the same. In actual projects, the source code will not be placed in the root directory like the hello world program above, because this is too confusing. A better way is to follow the steps below The above directory structure management source code:

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>

In addition to the above directories, other directories in src/ are ignored.

2.2 SBT build definition file

    In addition to the build.sbt file in the project root directory, other sbt files are in the project directory, and may also be .scala files. These files will eventually be merged with the .sbt file to form a complete build definition, which will be introduced in detail later

build.sbt
project/
  Build.scala

2.3 Build the product

    The built files, including compiled classes, packaged jars, managed files, caches and documents, are written in the target directory by default.

2.4 Configuration version management

    The .gitignore file (or equivalent in other version control systems) should contain:

target/

The / here cannot be omitted, which means that it matches project/target/ in addition to matching ordinary target/

3 Run SBT

3.1 Interactive mode

    Run sbt in the project directory to enter:

$ sbt

    The interactive mode has the functions of tab auto-completion and history recording. To exit, you need to enter exit or Ctrl+D (Unix) or Ctrl+Z (Windows).

3.2 Batch mode

    You can specify arguments with spaces as separators and run sbt in batch mode. For sbt commands that accept parameters, pass the command and parameters to sbt together with a quote, such as:

$ sbt clean compile "testOnly TestA TestB"

Among them, tesOnly has two parameters TestA and TestB, and this command will be executed (clean, compile, and then testOnly).

3.3 Continuous build and test

    To speed up the edit-compile-test cycle, you can have sbt automatically recompile or run tests while saving source files. Prefixing a command with ~ will automatically run the command whenever one or more source files change.

> ~ compile

Press Enter to stop monitoring changes.

3.4 Common commands

clean Delete all generated files (under the target directory).
compile Compile the source files (under the src/main/scala and src/main/java directories).
test Compile and run all tests.
console into a Scala parser that includes all compiled files and the classpath of all dependencies. Type :quit, Ctrl+D (Unix), or Ctrl+Z (Windows) to return to sbt.
run <parameters>* Execute the project's main class on the same virtual machine as sbt.
package Package the files under src/main/resources and the class files compiled in src/main/scala and src/main/java into a jar file.
help <command> Display detailed help information for the specified command. If no command is specified, brief descriptions of all commands are displayed.
reload Reload build definitions (build.sbt, project/*.scala, project/*.sbt defined in these files). A reload is required after modifying the build definition file.

3.5 Tab auto-completion

    When the Tab key is pressed once, only the most likely autocompletion subset of all commands may be displayed, and when the Tab key is pressed multiple times, detailed options will be displayed.

3.6 Command History

    Even if you re-enter after exiting sbt, there will be history records. In addition to the up arrow keys, there are the following commands:

! Show history command help.
!! Re-executes the previous command.
!: Show all previous commands.
!:n Display the last n previous commands.
!n Execute the command with subscript n in the result displayed by the !: command.
!-n Execute the nth command from this command to the front.
!string Execute the most recently executed command starting with string.
!?string Execute the most recently executed command containing string.

4 IDE integration

    If you simply modify the configuration file and source file in SBT through a text editor, it will be particularly troublesome, so here we choose to use IDEA as a project management tool. In addition, the Metals plug-in on VSCODE can also be used. You can check its usage method on the official website sbt Reference Manual — IDE Integration (scala-sbt.org)

    1) Open IDEA

    2) Install the Scala plugin

    3) Open the project with .sbt

    4) Wait for the build to complete.

5 Build Definition

5.1 specify sbt version

    Create a file named project/build.properties, and specify the sbt version statement as follows:

sbt.version=1.8.2

If the specified version is not installed locally, the sbt builder will download the version. If this file does not exist, sbt will use an arbitrary version. This method is not recommended.

5.2 What is a build definition

    The build definition is defined in the build.sbt file of the current project, and it consists of a series of sub-projects, such as:

lazy val root = (project in file("."))
  .settings(
    name := "Hello",
    scalaVersion := "2.12.7"
  )

Which defines a key value key, name is mapped to a string.

5.3 How build.sbt defines settings

    build.sbt uses the build.sbt domain-specific language DSL (domain-specific-language) to set a sequence of key-value pairs, called a setting expression.

ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.16"
ThisBuild / version      := "0.1.0-SNAPSHOT"

lazy val root = (project in file("."))
  .settings(
    name := "hello"
  )

Take a closer look at the DSL in build.sbt:

 Each entry can be called a setting expression, some of which are also called task expressions.

A set expression consists of the following three parts:

  • On the left is a key value (key)
  • The operator is: =
  • The right side is called the entity, or set subject

    The name, version and scalaVersion on the left side of build.sbt are all key values. A key is an instance of SettingKey[T], TaskKey[T], or InputKey, where T is the expected value type.

    Because the type of the key name is SettingKey[String], the value input to name by the operator := is specified as String. If the wrong type is used, the compilation will fail.

lazy val root = (project in file("."))
  .settings(
    name := 42  // will not compile
  )

    build.sbt may also be interspersed with val, lazy val and def. Top-level object and class are not allowed to appear. These should be in the Scala source files under the project path.

5.4 keys

5.4.1 Type

    There are three types of keys:

  • SettingLey[T]: the key that only calculates the value once (calculated when the sub-item is loaded, and then kept)
  • TaskKey[T]: A key value called Task, similar to functions in Scala, will be calculated every time it is referenced, and may have side effects
  • InputKey[T]: A task key that has command-line parameters as input.

5.4.2 Built-in key

     The built-in key is a field of an object named Keys, and sbt.Keys._ is implicitly imported in the build.sbt file, so sbt.Keys.name can be referenced as name.

5.4.3 Custom keys

    Custom keys can be defined by their different creation methods: seeingKey, taskKey and inputKey. Each method requires the type of the value to match the type of the key. The name of a key depends on the val that the key corresponds to. For example, to define a tsak key called hello:

lazy val hello = taskKey[Unit]("An example task")

Here the .sbt file contains val and def in addition to setting, all similar definitions should be defined before setting.

    Note: In general, lazy' val is used instead of val to avoid some initialization problems.

5.4.4 Task vs setting键

    TaskKey[T] is usually defined as task, possibly an operator such as compile or package. Their return value may be an empty Unit, or return a value related to the task, such as pakage is a TaskKey[File] and its value is the installed jar file.

    Every time a task is executed, such as entering compile in the sbt interface, sbt will re-run all the involved tasks.

    The key-value pair of sbt describes that a sub-item can fix the value as a string like name, but must also maintain some executable code like compile.

5.4.5 List all available setting keys and task keys

    You can enter setting or setting -v on the sbt command line to get all the setting keys under the current build definition. Similarly, enter task or task -v to get all task keys,

    A key will be printed if one of the following conditions is true:

  • are built-in sbt (such as name, scalaVersion)
  • is a custom key
  • Definitions brought in by imported plugins

You can use the help <key> command to get more information.

5.5 Define tasks and settings

    Use := to link a value to a setting key or a task's calculation. For setting, when the project is loaded, the value will be calculated once. For a task, the entire calculation is performed once each time the task is executed.

For example, to execute a task named hello:

lazy val hello = taskKey[Unit]("An example task")

lazy val root = (project in file("."))
  .settings(
    hello := { println("Hello!") }
  )

Similarly, a setting:

lazy val root = (project in file("."))
  .settings(
    name := "hello"
  )

5.6 Enter key in sbt shell

    Enter the task key to execute the task, but not return the result. If you want to return the result, you need to enter show <task name>, and the setting key will print out its value.

5.7 Import in build.sbt

    import by default;

import sbt._
import Keys._

5.8 Single .sbt build definition

    Settings can be written directly into the build.sbt file instead of in .settings(...) . Call this form "naked style"

ThisBuild / version := "1.0"
ThisBuild / scalaVersion := "2.12.16"

This syntax is recommended for ThisBuild scope settings and adding plugins.

5.9 Add library dependencies

    To rely on third-party libraries, there are two ways. The first is to add jar under lib/ (unmanaged dependencies), and the other is to add managed dependencies, which looks like this in 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
  )

This is how to add dependencies on version 10.4.13 of the Apache Derby library. += means to add to the base of the key, and = is to replace, % is used to construct an Ivy module ID from a string,

6 Multi-project build

    This section describes how to build multiple subprojects in one build file,

6.1 Multiple subprojects

    This is useful if you have multiple subprojects that depend on each other and you want to be able to modify them at the same time. Each subproject built has its own source directory, generates its own jar file when pakage is run, and works like any other project.

    A project can be defined by declaring a lazy val of Project type, for example:

lazy val util = (project in file("util"))

lazy val core = (project in file("core"))

The name of the val is used as the subproject ID, which is used when specifying subprojects in the sbt shell.

    If the name of the base directory is the same as val, then the base directory can be ignored:

lazy val util = project

lazy val core = project

6.2 Building a wide range of settings

    To set a common setting across multiple sub-projects, you need to define the scope of the setting as ThisBuild. ThisBuild is a default subproject that can be used to define default values ​​in the build. When defining one or more subprojects, and the scalaVersion key is not defined in the subproject, the value of ThisBuild / scalaVersion will be used by default.

    The limitation is that the right requires a clean value or setting scope to Global or ThisBuild, and there is no default setting in subprojects.

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
  )

    Now we can boost the version in one place, and when a rebuild loads, it will be reflected in other subprojects.

6.3 General Settings

    Another way to make common color values ​​across multiple projects is to create a new sequence named commonSettings, and then call the setting method in each project.

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 Dependencies

    Projects in a build can be completely independent of each other, but usually they are related by some kind of dependency. There are two types of dependencies: aggregate and classpath

6.4.1 Aggregation

    Aggregation means that running a task on the aggregated item will also run that item on other items of the aggregate, for example:

lazy val root = (project in file("."))
  .aggregate(util, core)

lazy val util = (project in file("util"))

lazy val core = (project in file("core"))

In the above example, the root project aggregates two projects, util and core. Start sbt with two subprojects in the example, try to compile, and you will find that all three projects are compiled.

    In aggregation projects, it is possible to control the aggregation of each task, for example, to avoid aggregation of update tasks:

lazy val root = (project in file("."))
  .aggregate(util, core)
  .settings(
    update / aggregate := false
  )

[...]

Among them, update/aggregate is a keyword to change the aggregation scope of upodate.

Note: Aggregation tasks are executed in parallel, and there is no order relationship.

6.4.2 Classpath dependencies

    A project may depend on code in another project, this can be achieved by adding a dependsOn, if core requires util on its classpath, you can define core like this:

lazy val core = project.dependsOn(util)

Now the code in the core can use the util class. This also establishes their compilation order, util must be compiled and updated before core.

To depend on multiple items, you can use multiple parameters of dependsOn, such as dependsOn(bar, baz).

    core dependsOn(util) means that the compilation configuration of core depends on the compilation configuration of util, and you can express it as dependsOn(util % "compile->compile").

    Among them -> means "dependence", so "test->compile" means that the test configuration in core depends on the compile configuration in util.

    If the ->config part is ignored, it means "->compile", so dependsOn(util % "test") means that the tes configuration in core depends on the compile configuration in util.

    A useful setting is "test->test", which means that test depends on test. You can put useful code in "util/src/test/scala" and use it in "core/src/test/scala".

    You can also configure multiple dependencies, separated by semicolons, such as:

dependsOn(util % "test->test;compile->compile")

6.4.3 Inter-project dependencies

    On very large projects with many files and subprojects, sbt does not perform well when it is constantly monitoring what has changed and uses a lot of disk and system I/O.

    sbt has trackInternalDependencies and exportToInternal settings, these can be used to control whether to trigger the compilation of the dependent child parent when calling compile. Both of them take one of 3 values: TrackLevel.NoTracking, TrackLevel.TrackIfMissing and TrackLevel,TrackAlways. Both are set to TrackLevel.TrackAlways by default.

    When trackInternalDependencies is set to TrackLevel.TrackIfMissing, sbt will no longer attempt to automatically compile internal (inter-project) dependencies unless there are no *.class files (or JAR files, if exportJar is set to true ) in the output directory.

    When trackInternalDependencies is set to TrackLevel.NoTracking, the compilation of internal dependencies will be skipped. Note that the classpath at this point will still be appended, and the dependency graph will still show them as dependencies. The purpose of this is to save I/O overhead for checking changes on builds of projects with many subprojects during development. Here's how to set all subitems to TrackIfMissing:

ThisBuild / trackInternalDependencies := TrackLevel.TrackIfMissing
ThisBuild / exportJars := true

lazy val root = (project in file("."))
  .aggregate(....)

    The expoerToInternal setting allows setting subprojects to opt out of internal tracking, which is useful if you want to trace most subprojects except a few. The intersection of trackInternalDependencies and expoertToInternal settings will be used to determine the actual tracking performance. Below is an example of a sub-item exiting tracking;

lazy val dontTrackMe = (project in file("dontTrackMe"))
  .settings(
    exportToInternal := TrackLevel.NoTracking
  )

6.5 Default root project

    If there is no project defined in the build at the root, sbt will create a project that aggregates all subprojects in the build.

    Since the project hello-foo is defined with base=file("foo"), it will be included in foo. Its source code can be directly in the foo directory, such as foo/Foo.scala. Or foo/src.main/scala. The usual sbt directory structure should be under foo, except for the build files.

6.6 Interactive navigation items

    In the sbt interactive command line, typing project will list all your projects, typing project <projectname> to select a current project. When running a task like compile, it only runs on the current project. So it does not need to be compiled under the root project, but only subprojects can be compiled.

    You can also run tasks on other projects by actually specifying the project ID, such as:

subProjectID/compile

6.7 Common Code

    Definitions in .sbt files are not visible in other .sbt files. In order to share different .sbt files, one or more Scala files can be defined in the project/ directory.

6.8 Appendix: Subproject Build Definition Files

    Any .sbt files under foo, called foo/build.sbt, will be merged for the entire build definition, but only for the hello-foo project.

    If your whole project is in hello, define a different version (version :="0.6") in hello/build.sbt, hello/foo/build.sbt and hello/bar/build.sbt. Then check their version, you will see:

> show version
[info] hello-foo/*:version
[info]  0.7
[info] hello-bar/*:version
[info]  0.9
[info] hello/*:version
[info]  0.5

    Style suggestions:

  • The settings of each sub-project can be set in the *.sbt file in the root directory of the project, but the build.sbt in the root directory only uses "lazy val foo = (project in file("foo") )" to declare the minimum item
  • It is more recommended to put the declarations of all projects under the build.sbt file in the root directory, so as to better manage projects in a single file.

    Note: There cannot be project subpaths or project/*.scala in subdirectories. These will be ignored.

7 Task Map

7.1 Declare dependencies on other tasks

    In build.sbt, you can use the .value method to express dependencies on other tasks and settings. The value method is special and can only be called on arguments of := (+= or ++=).

8 scope

8.1 The story of keys

    It is assumed that a name key corresponds to a key-value pair mapping in sbt, which is actually a simplification. In fact each key has an associated value in multiple environments, called scopes.

    Some concrete examples:

  • If there are multiple subprojects in the build definition, then the key in each project can have a different value;
  • The compile key can have different values ​​if you want to compile source and test code differently;
  • The packageOptions key (which contains options for creating jar packages) may have different values ​​when packaging class files (packageBin) or packaging source code (packageSrc).

There is no single value for a given key, because the value may vary depending on the scope. However, there is only one value for a key of a given scope.

    If you use sbt to process a setting list as before to generate key-value pairs describing items, then the mapped key-value pairs are scoped keys.

Normally, scopes are implicitly given a default value, but if the default value is wrong, it needs to be changed in build.sbt.

8.2 scope axes scope axis

    The scope axis is a type construct similar to Option[A] used to form components in scope.

Guess you like

Origin blog.csdn.net/qq_38798111/article/details/129294191