Architecture¶
Foundry contains three Gradle plugins and some associated helper artifacts.
FoundryRootPlugin¶
This is the root plugin that is applied to the root project of a multi-project build.
plugins {
id("foundry.root")
}
Its responsibilities include:
- Registering the
FoundryToolsbuild service. - Setting up global configuration (i.e. global lifecycle tasks, download tasks, etc).
- Validating the JDK matches the expected JDK defined in
libs.versions.toml. - Configure git (hooks, ignored revisions, etc).
- (If running on macOS) Validating the build isn’t running in Rosetta mode.
FoundryBasePlugin¶
This is the base plugin that is applied to all projects (including the root project).
plugins {
id("foundry.base")
}
Its responsibilities include:
- Configuring formatters for Spotless.
- Configuring the buildscript and dependency classpaths. This includes “fixing” any known bad dependencies (Hamcrest and CheckerFramework are a mess), failing on non-reproducible dependency declarations.
- Running standard subproject configurations via
StandardProjectConfigurations. - Configuring unit tests via
UnitTests. This also includes configuring the Gradle test retry plugin, if enabled. - Configuring NullAway, if enabled.
- Configuring Skippy.
- Configuring Mod Score tasks.
StandardProjectConfigurations¶
This class warrants special mention as it is responsible for the bulk of the configuration applied to projects Foundry manages.
- Creates and exposes the
foundryextension DSL. - Applies common configurations.
- This largely just sets up the dependency sorter plugin.
- Applies common JVM configurations.
- Applies common Kotlin configurations.
JVM Configurations¶
All JVM projects (Android, Java, Kotlin) receive some common configuration for their JVM tasks.
Common¶
- Applies the repo’s platform project, if any.
- Applies any BOM dependencies to platform-configurable configurations.
- Configures the dependency analysis gradle plugin and
DependencyRake. - Applies common annotations and common test bundles from version catalogs.
- Fails on non-androidx support library dependencies.
- Configure common annotations processors.
- This logic is largely in
StandardProjectConfigurations.configureAnnotationProcessors()andStandardProjectConfigurations.APT_OPTION_CONFIGS, which seeks to apply common configs for known processors like Dagger and Moshi.
Java¶
Java projects are fairly simple. Note that these are applied on all projects that apply the java plugin, which is most of them!
- They are just configured to ensure their source/target compatibility is set to the repo’s JVM target.
- In non-android projects,
JavaCompiletasks have theiroptions.releaseproperty set to this as well. - Gradle toolchains are used to manage the JDK used for
JavaCompiletasks in non-android projects to ensure consistency. - All
JavaCompiletasks have-parametersadded tooptions.compilerArgsfor better static analysis and annotation processing support. - If opted-in, error-prone and nullaway are applied and set up with the project with common configurations (configured checks, ignoring build dirs, etc).
- Foundry supports Error-Prone’s auto-patching mode via enabling the
foundry.epAutoPatchproperty.
Android¶
- Configures AndroidTest APK aggregation with Skippy support.
- Applies the Android cache fix plugin, if enabled.
- Configures common AGP extensions (both legacy extensions and new Component extensions).
- Disables unused/irrelevant variants. Foundry is single-variant for library projects by default.
- Disables android tests on projects unless opted in.
- Disables unit tests on app projects by default (we use the app project as just a shell project for producing an APK).
- Configures
compileOptions,defaultConfig, compileSdk/targetSdk/minSdk/ndkVersion, etc.- Enables
vectorDrawables.useSupportLibrary.
- Enables
- Enables core library desugaring.
- Configures common
testOptionslike orchestrator,unitTests, etc.unitTests.isReturnDefaultValuesis always enabled for convenience.unitTests.isIncludeAndroidResourcesis only enabled if robolectric is enabled on the project, as this is expensive to enable.- Set up unit test
Testtasks to depend on theUpdateRobolectricJarsTaskif robolectric is enabled.
- Patches objenesis dependency versions due to weird transitive dependencies in android test classpaths.
- Configures
com.android.libraryandcom.android.applicationprojects.com.android.testis supported but somewhat limited. - Application projects…
- have their packaging config set up with some convenience common exclusions and handling common
jniLibshandling. - have v3 and v4 signing enabled by default.
- are configured with
PermissionChecks. - are configured with the Bugsnag gradle plugin, if enabled.
- Library projects…
- are configured with an automatic
android.namespace, if none is manually specified in the buildscript. The namespace is inferred from the project’s Gradle path. - are single-variant by default, set to
release.
Kotlin Configurations¶
Kotlin projects are configured with KGP and Detekt in mind. Foundry supports configuring Android, JVM, Multiplatform, and Compose Multiplatform projects. Multiplatform for targets other than JVM/android is limited at the moment.
Common configurations include:
- Setting computed kotlin daemon JVM args.
- Setting the
jvmToolchainto align with the repo’s JDK target. - Configuring
KotlinCompilationtasks with common configurations. - Enabling
allWarningsAsErrors. - Adding free compiler args to
freeCompilerArgs.- If a JVM compilation, add extra JVM args + set
jvmTargetandjavaParameters. - Dynamic, dependency-based compiler args are also set via
StandardProjectConfigurations.configureFreeKotlinCompilerArgs(). This is an annoying thing to have to do, but necessary because kotlinc will complain if you add opt-ins that are not recognized by any dependencies on that classpath.
- If a JVM compilation, add extra JVM args + set
- Enabling K2 testing.
- Configure Detekt, if enabled, via
DetektTasks. - Configure Lint if not an Android project via the
com.android.lintplugin andLintTasks. - Configure the
src/{variant}/kotlinsource set in android projects, as these are still not automatically enabled. - Prevent use of the deprecated
android.extensionsextension. - Configure kapt (if requested) with common configuration
correctErrorTypesis set to true for better error messages.mapDiagnosticLocationsis set to false because it’s broken.- Unless opted-in, disables kapt in tests due to this.
ApkVersioningPlugin¶
plugins {
id("com.slack.gradle.apk-versioning")
}
This plugin is applied in Android application projects and is solely to configure the versionCode and versionName of APKs based on git and Gradle property inputs.
The following properties are sourced
versionMajor=...
versionMinor=...
versionPatch=...
This also adds a generateVersionProperties task that is more or less only relevant for Slack’s internal CI.
AGP Handlers¶
Foundry is designed to work with multiple versions of AGP at a time, albeit only for forward compatibility and testing reasons. Generally Foundry will only be tested against the latest stable version of AGP. To support multiple beta/canary versions of upcoming AGP versions, Foundry has an API called AgpHandler, which is intended to be an AGP-agnostic common interface for configuring AGP projects across breaking API (source or binary) changes. When a new such change is introduced, we create an AgpHandler{version} artifact and implementation with that AGP version as its minimum. At runtime, Foundry loads the relevant AgpHandler instance for the AGP version it is running against and relevant APIs use this instance via FoundryTools to interact with them in a version-agnostic way. These aren’t always needed so there may be times when there are no implementations needed for the current suite of AGP versions.
An example handler for AGP 8.0 looks like this.
// AutoService makes it available via ServiceLoader
// The factory should always be AGP-api agnostic.
class AgpHandler80 : AgpHandler {
@Suppress("DEPRECATION")
override val agpVersion: String
get() = com.android.builder.model.Version.ANDROID_GRADLE_PLUGIN_VERSION
@AutoService(AgpHandlerFactory::class)
class Factory : AgpHandlerFactory {
override val minVersion: VersionNumber = VersionNumber.parse("8.0.0")
@Suppress("DEPRECATION")
override fun currentVersion(): String =
com.android.builder.model.Version.ANDROID_GRADLE_PLUGIN_VERSION
override fun create(): AgpHandler {
return AgpHandler80()
}
}
}