Amper - Project

Introduction

An Amper project is defined by a project.yaml file. This file contains the list of modules and the project-wide configuration. The folder with the project.yaml file is the project root. Modules can only be located under the project root (at any depth). If there is only one module in the project, the project.yaml file is not required.

Project layout

A single-module Amper project doesn’t need a project.yaml file. Just create a single valid module, and it is also a valid project.

If there are multiple modules, the project.yaml file specifies the list of modules:

project.yaml
modules:
- android-app
- ios-app
- jvm-app
- shared

Sources and resources can’t be defined as part of multiple modules — they must belong to a single module, which other modules can depend on. This ensures that the IDE always knows how to analyze and refactor the code, as it always has a single well-defined set of settings and dependencies.

Getting started

Create a new “Compose Multiplatform application” with Amper - Compose

Specific module to run (run the show modules command to get the modules list)

Terminal window
.\amper run --module jvm-app

Dependencies

Module dependencies

To depend on another module of your project, use the path to that module, relative to the current module’s root directory. The path must start either with ./ or ../

For example, on the created project the jvm-app module declares a dependency on the shared module:

jvm-app/module.yaml
dependencies:
- ../shared
Note

Dependencies between modules are only allowed within the project scope. That is, they must be listed in the project.yaml file and cannot be outside the project root directory.

Project Catalog

The project catalog is the user-defined catalog for the project.

It is defined in a file named libs.versions.toml, and is written in the TOML format of Gradle version catalogs.

Note

Only [versions] and [libraries] sections are supported from the Gradle format, not [bundles] and [plugins].

To use dependencies from the project catalog, use the syntax $libs.<key> instead of the coordinates, where $libs is the catalog name of the project catalog, and <key> is defined according to the Gradle name mapping rules.

Example:

libs.versions.toml
[versions]
ktor = "3.3.2"
[libraries]
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
jvm-app/module.yaml
dependencies:
- $libs.ktor-client-auth
- $libs.ktor-client-cio
- $libs.ktor-client-contentNegotiation

Transitivity and scope

By default, dependencies are available during both compilation and execution. However, you can change this behavior by specifying a scope or marking the dependency as exported.

Dependency scopes

There are three dependency scopes:

ScopeDescription
all(Default) The dependency is available during compilation and execution.
compile-onlyThe dependency is only available during compilation.
runtime-onlyThe dependency is not available during compilation, but is available during testing and execution.

You can declare the scope using either a shorthand or a long-form syntax:

module.yaml
dependencies:
# Shorthand syntax
- io.ktor:ktor-client-core:3.4.0: compile-only
- ../ui/utils: runtime-only
# Long-form syntax
- io.ktor:ktor-client-cio:3.4.0:
scope: compile-only

Exported dependencies

By default, dependencies of your module are not added to the compilation of dependent modules. In the following setup, app-jvm cannot directly use Ktor classes in its code:

shared/module.yaml
dependencies:
- io.ktor:ktor-client-core:3.4.0
app-jvm/module.yaml
dependencies:
- ../shared

To make a dependency accessible to all dependent modules during their compilation, you need to explicitly mark it as exported.

shared/module.yaml
dependencies:
# Shorthand syntax
- io.ktor:ktor-client-core:3.4.0: exported

You can also use the long-form syntax, which is useful when combining exported with a specific scope:

shared/module.yaml
dependencies:
- io.ktor:ktor-client-core:3.4.0:
scope: compile-only
exported: true

Ideally, you should use exported dependencies as little as possible. The rule of thumb is that, if your module uses some types from the dependency in its public API, you should mark it as exported. If not, you should probably avoid it.

For example, if you depend on ktor-client-core in your module, and you have the following class:

class MyApi(private val client: HttpClient) {
// ...
}

The HttpClient type is used in your public constructor, so your consumers will need to see it at compile time. You should therefore mark ktor-client-core as exported.

Templates

In modularized projects, there is often a need to have a certain common configuration for all or some modules. Typical examples could be a testing framework used in all modules or a Kotlin language version.

Amper offers a way to extract whole sections or their parts into reusable template files. These files are named <name>.module-template.yaml and have the same structure as module.yaml files.

A template is applied to a module.yaml file by adding it to the apply: section:

jvm-app/module.yaml
product: jvm/app
apply:
- ../common.module-template.yaml
common.module-template.yaml
test-dependencies:
- org.jetbrains.kotlin:kotlin-test:1.8.10
settings:
kotlin:
languageVersion: 2.2

Sections in the template can also have @platform-qualifiers.

Note

Template files can’t have product: and apply: sections (they can’t be recursive).

Templates are applied one by one, using the same rules as platform-specific dependencies and settings:

  • Scalar values (strings, numbers etc.) are overridden.
  • Mappings and lists are appended.

Settings and dependencies from the module.yaml file are applied last. The position of the apply: section doesn’t matter, the module.yaml file content always has precedence. E.g.

common.module-template.yaml
dependencies:
- ../shared
settings:
kotlin:
languageVersion: 2.2
compose: enabled
jvm-app/module.yaml
product: jvm/app
apply:
- ./common.module-template.yaml
dependencies:
- ../jvm-util
settings:
kotlin:
languageVersion: 1.9
jvm:
release: 8

After applying the template the resulting effective module is:

jvm-app/module.yaml
product: jvm/app
dependencies: # lists appended
- ../shared
- ../jvm-util
settings: # objects merged
kotlin:
languageVersion: 1.9 # module.yaml overwrites value
compose: enabled # from the template
jvm:
release: 8 # from the module.yaml