fury

Concepts

Fury introduces a number of concepts which it is useful to know about before using it, some of which will be new.

Layers

Fury always runs inside a layer. A layer represents a collection of related projects you are working on, and is associated with a directory on your filesystem which Fury has initialized. It is also a git repository.

The root directory of a layer will contain a file called layer.fury, which stores all the information relating to the layer. The file is kept under git’s version control, and is modified directly by Fury.

Source files for the Fury build may reside in the same repository, or may be included from an external repository.

The file format is designed to be human-readable, particularly when viewing diffs between different versions, but not human-writable. While it may be possible to make changes to the file manually, the format is sensitive to whitespace and structural changes, and it is not recommended.

Normal git commands can be used to manage changes to the layer.fury file, for example,

git reset --hard

will return the Fury layer to its last commited state.

Projects

A project should correspond to most people’s intuition of a project: it is the same as a project in sbt or on GitHub. A project has a name, is composed of a number of modules (see below), and is associated with a single license. A typical Fury project will be associated with a single git source repository (though it may depend on several).

Example projects might be, magnolia, shapeless, monix or web-microservice.

Modules

A module is a single phase of compilation; a single invocation of the Scala compiler, or less often, running something other than a compiler (such as code-generation). A module typically takes some source input and produces some binary output. Fury manages a graph of dependencies between modules so that In sbt, a module is called a “submodule”.

Four different module types are supported, and more may be added in the future. These are library (the default), compiler, plugin and application.

Example modules may be, core, frontend or macros.

Schemas

A schema is Fury’s approach to handling builds of the same collection of projects (or mostly the same) for several different target versions of Scala, or different platforms (e.g. Scala Native). A project which only targets a single version of Scala should not need to worry about schemas. Variations between schemas are typically small, and Fury commands which change the build are designed to make global changes to all schemas the default, and introducing new variations the exceptions.

Fury makes it very easy to view the differences between pairs of schemas.

In future, schemas may also handle some other scenarios, for example, variants of a library with different dependencies, or creating variations of a build which have only binary dependencies, to make publishing to Maven Central possible.

Example schemas may be, scala-2.11, scalajs-2.12, scala-native-2.11, scala-2.12-bin, scala-2.12-cats or even scala-2.12.3 for projects which build differently between different minor versions of Scala.

Source repositories (repos)

Source repositories, being a particular commit hash of a git repository, may be associated with a schema, which makes source code contained with that repository available for modules to compile, and layers available to import from.

The layer itself is in a git repository, so it is always available alongside other repositories, and is called local.

Imports

An import allows an entire schema of projects defined in a different layer (which has been published online) to be made available in a schema in your layer, for use as dependencies to modules in that layer.

Imports are the means by which schemas within layers compose, and are specified recursively, forming a tree of dependencies.

Sources

Every module which invokes a compiler will need sources to compile. These are specified as a list of directories within source repositories.

Sources may come from the local repository (the one containing the layer), from an external repository which has been defined within the layer, or from a shared space where generated files may be placed.

Dependencies

A dependency is a link between two modules, and requires one module to be compiled (or run) as a prerequisite to the other. The binary output of the prerequisite will be passed to the classpath of the module which depends upon it.

Dependencies may be marked as “intransitive”, which means that its output will be passed to the classpath of the module which depends directly upon it, but not to downstream modules.

Additionally, a module may depend on specific binaries, which Fury will download automatically from Maven Central using coursier.

Linking

Naming

Most entities, such as modules, projects, schemas and repositories have names. These identifiers permit only lower-case letters, digits and hyphens, and must start with a letter and cannot contain two or more adjacent hyphens.

Consistency

At any time, a layer may or may not be consistent, which means that all its references resolve to valid entities. This is a requirement to run a build, or publish a layer. But it is possible for a layer to be in an inconsistent state while modifications are being made.

Users will be notified of any inconsistencies in the state of the layer when these prevent a certain operation, such as running the build, from completing.

Dynamic linking

The references between entities in a Fury layer must ultimately be consistent, but the same reference can point to different entities in different schemas.

For example, a reference to some sources in the repository with the name magnolia will resolve to the repository that is specified for the current schema (say, scala-2.12, which may be (for example) commit hash 2776a4b. However, the scala-2.11 schema may define the magnolia repository to use a different branch, with the commit hash afb2860 instead. All the references to magnolia:src/core would be unchanged across the two schemas, but would point to a different set of files.

Another very common example may be references to the compiler called scala/compiler. In your scala-2.12 and scala-2.11 schemas, this would be interpreted in the context of the the projects imported from the published layer, and each import specifies not only a repository corresponding to a published layer, but also a schema within that layer.

So if the published layer had schemas corresponding to every minor published version of Scala, we might choose to import from scala-2.12.6 to our scala-2.12 schema, and from scala-2.11.11 to our scala-2.11 schema. Each external schema would contain a project called scala, and each scala project would contain a module called compiler, defined differently for each.

What this means is that many modules could be defined in the same schema, all with indentical references to scala/compiler, but by making just a single change to an import in other schemas, a different choice of compiler can be swapped in.

User Interface

Interaction with Fury is always through a command-line interface. This interface has a number of operations whose primary purpose is to manage the build file, and run builds.

No guarantees are made about the stability of the command-line API, and although it would be possible to distribute builds as scripts of fury commands to be run, there are no guarantees about their portability.

Limitations

The current private beta release of Fury lacks certain features which will need to be implemented before a full public release can be made. These are documented on the issue tracker on GitHub.