foundry¶
This repository contains much of Slack’s Android/Kotlin/JVM tooling for Gradle, IntelliJ, CLIs, and more.
This repo is effectively for Slack’s own used but publishes to Maven Central. We develop these in the open to knowledge-share with the community.
As such, we don’t normally accept external PRs, but we welcome your questions in the discussions section of the project!
Highlights¶
Common project configuration¶
The foundry.base
plugin offers common configuration for all projects implementing it, covering a
wide spectrum of Android, Kotlin, and Java configurations.
This includes a whole host of things!
- Common Android configuration (single variant libraries, disabling unused features, compose, etc).
- Common Kotlin configuration (freeCompilerArgs, JVM target, etc).
- Common Java configuration (toolchains, release versions, etc).
- Common annotation processors.
FoundryExtension
(see next section).- Formatting (via Spotless).
- Platforms and BOM dependencies (see “Platform plugins” section below).
- Common lint checks (both on Android and plain JVM projects).
Full docs can be found in the architecture docs.
Feature DSL¶
To ease use and configuration of common features in projects, we expose a foundry
DSL in projects
that allows for configuration of these in a semantically easy and boilerplate-free way. This is
controlled via FoundryExtension
.
foundry {
features {
dagger()
moshi(codegen = true)
}
android {
features {
robolectric()
}
}
}
A major benefit of this is that we can intelligently configure features and avoid applying costly plugins like Kapt unless they’re actually needed for a specific feature, such as Java injections in projects using Dagger. Since this is pure code, we can also propagate deprecated behavior by deprecating the corresponding functions in the DSL.
Platform plugins¶
Platforms.kt
contains an implementation that sources a VersionCatalog
and applies it to a
Gradle platform project. This allows us to effectively treat our versions catalog as a BOM and apply
it to all projects in the consuming repo and reduce dependency version stratification.
Thermal throttling capture¶
MacBooks can suffer thermal throttling and result in poor build performance. We built
instrumentation for this to capture these and include them in our build scans to better understand
their impact. We support both Intel and Apple Silicon macs now and contain this implementation in
ThermalsWatcher.kt
. This also includes helpful charting APIs for visualizing the data (courtesy
of our friends at Square).
Better Properties¶
Gradle’s built-in property support is limited and has surprising behavior, so we have our own
system on top of it that has consistent precedence, local.properties
support, project-local
gradle.properties
, and configuration caching support. Check out PropertyResolver
.
Dependency Rake¶
DependencyRake.kt
contains an extension to the gradle-dependency-analysis-plugin
that applies
its advice to a project to automatically optimize it and rake dependencies.
Module Stats (aka “Mod Score”)¶
As a part of our modularization efforts, we developed a scoring mechanism for modules that we could
use as a measure of their “modularization”. This includes a number of metrics and weighs them in a
formula to compute a score. This includes LoC, language mixtures, and build graph centrality. This
logic is under the foundry.stats
package.
Robolectric Jars Management¶
Robolectric uses preinstrumented Android API jars that live on maven central. While it can handle
downloading of these automatically, we found this implementation to be brittle and unreliable, so we
built our own version of it that handles downloading these into a local .cache
directory. This
implementation lives in UpdateRobolectricJarsTask.kt
and that task is configured to be a
dependency of all Test
tasks.
Bootstrap¶
We try to simplify and streamline the bootstrap process for both local development and on CI. This
involves computing optimized JVM arguments for the Gradle and Kotlin daemons (which differ between
CI and local) as well as toe-holds for future customizations. This logic lives in BootstrapTask.kt
.
Permission Checks¶
To avoid accidentally checking in any new, unexpected manifest permissions, we have a
CheckManifestPermissionsTask
that compares the final merged manifest’s permissions to an
allow list of known permissions. This is allow list is checked in and expected to be guarded by a
CODEOWNERS
watch and will fail the build if they differ.
slack {
android {
app {
permissionAllowlist {
if (name == "externalRelease") {
setAllowlistFile(file("permissionsAllowlist.txt"))
}
}
}
}
}
APK Versioning Computers¶
AGP offers new property-based APIs for computing APK version codes and version names. We use this
to compute information from different inputs (CI build number, git state, etc) and control this
logic in ApkVersioningPlugin.kt
.
Check dependency versions¶
Sometimes a dependency update may bring with it a surprise update to a transitive dependency that
we also declare. In order to avoid this happening unexpectedly, the CheckDependencyVersionsTask
checks that any transitive dependency versions that also correspond to a version declared in our
VersionCatalog
match the version there. It’s ok if they don’t, but the author just need to update
the version in the catalog too to be explicit (or investigate further if it’s an unwanted
surprise!).
AGP Handlers¶
AGP occasionally contains new or breaking API changes that we want to handle gracefully in early
testing. We regularly test against newer preview versions of AGP so we can’t just
hardcode in new APIs and expect them to work everywhere. To handle this, we have an AgpHandler
interface that can be used to abstract these new APIs in a backward-compatible way. Then we ship
implementations of this as different artifacts that are built against different AGP versions. Then,
at runtime, we pick the appropriate instance (via service loading) to use for the current AGP
version being used in that build.
Detekt baselining¶
Detekt is a static analysis tool that we use to check for common issues in our code. We use one
global baseline file for baselined issues (when introducing new checks or updates), but Detekt
doesn’t currently support this easily. So, we built MergeDetektBaselinesTask
to merge all the
generated baselines from each subproject into a single global baseline.
Misc tools¶
There are a ton of miscellaneous tools, utilities, and glue code for Gradle (and various plugins) sprinkled throughout this project.
Usage requirements¶
SGP expects there to be a libs
version catalog.
The following versions are required to be set the above catalog.
Their docs can be found in FoundryVersions.kt
.
For Android projects, some extra definitions need to be defined
libs.versions.toml
librariesgoogle-coreLibraryDesugaring
- the core library desugaring libraries to use with L8.
gradle.properties
propertiesfoundry.compileSdkVersion
foundry.targetSdkVersion
foundry.minSdkVersion
The following plugins are applied by default but can be disabled if you don’t need them.
- Gradle’s test retry –
foundry.auto-apply.test-retry
- By default, this uses the Gradle test-retry plugin, but can be configured to use the Gradle Enterprise plugin’s implementation instead by setting the
foundry.test.retry.pluginType
property toGE
.
- By default, this uses the Gradle test-retry plugin, but can be configured to use the Gradle Enterprise plugin’s implementation instead by setting the
- Spotless –
foundry.auto-apply.spotless
- Detekt –
foundry.auto-apply.detekt
- NullAway –
foundry.auto-apply.nullaway
- Android Cache Fix –
foundry.auto-apply.cache-fix
Commit hooks¶
The installCommitHooks
task can install commit hooks for formatting kotlin and java files. They expect binaries to be in <root>/config/git/hooks
and it’s highly recommended to enable git-lfs for these objects. These binaries can be downloaded via the updateKtfmt
, updateGjf
, etc tasks.
License¶
Copyright 2022 Slack Technologies, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.