Overview
Artifact Swap is a Gradle and IntelliJ plugin that dramatically accelerates IDE sync times for large Gradle builds. It works by swapping local Gradle projects with eagerly fetched, pre-compiled artifacts when appropriate, minimizing the number of projects Gradle needs to configure. This can result in a 50% reduction in sync times for large projects. See our blog post and this Droidcon NYC talk for more details.
How It Works
Artifact Swap operates through three main components:
- Gradle Plugin: Analyzes your project structure, determines which modules to include based on local changes and dependencies, and configures Gradle to use artifacts instead of project references
- CLI Tool: Downloads artifacts from your artifact repository and manages a local Bill of Materials (BOM) that tracks artifact versions
- IDE Plugin: Helps navigate between project source code and the swapped artifacts
Prerequisites
- A Gradle build using Spotlight and at least Gradle 8.12
- Java 17 or higher for running the CLI tool
- Spotlight IDE Plugin
Getting Started
1. Apply the Plugin
In your settings.gradle(.kts), apply the Artifact Swap settings plugin:
pluginManagement {
repositories {
mavenCentral()
}
plugins {
id("com.fueledbycaffeine.spotlight") version "<latest>"
id("xyz.block.artifactswap.settings") version "<latest>"
}
}
plugins {
// Required to be declared here, but artifact swap will apply it for you
id("com.fueledbycaffeine.spotlight") apply false
id("xyz.block.artifactswap.settings")
}2. Configure Required Properties
Create or update gradle.properties in your project root with the required configuration:
# Primary repository name in your artifact management system artifactswap.primaryRepositoryName=my-company-artifacts # Maven group ID for your internal artifacts artifactswap.primaryArtifactsMavenGroup=com.mycompany.artifactswap.artifacts # Git branch name where BOM versions are published artifactswap.bomSourceBranchName=origin/artifact-swap-green-main # Base URL for your Artifactory instance artifactswap.artifactoryBaseUrl=https://artifactory.mycompany.com # Token file name for Artifactory authentication (used in CI) artifactswap.artifactoryPublisherTokenFileName=artifactory-token.txt
3. Optional: Configure the Extension
You can programmatically control Artifact Swap behavior in settings.gradle(.kts):
artifactSwap {
// Enable/disable artifact swap
enabled(true)
// Or use a provider for dynamic configuration
enabled(providers.gradleProperty("artifactswap.enabled").map { it.toBoolean() })
}4. Install the CLI Tool
The CLI tool manages artifact downloads and BOM versions. Download and install it:
Option A: From Published Release
# Download the CLI distribution VERSION="<latest>" curl -L "https://repo.maven.apache.org/maven2/xyz/block/artifactswap/cli/$VERSION/cli-$VERSION.zip" -o artifactswap.zip # Extract to your tools directory mkdir -p tools/artifactswap unzip artifactswap.zip -d tools/artifactswap
Option B: Build From Source
# Clone and build git clone https://github.com/block/artifact-swap.git cd artifact-swap ./gradlew :cli:publishToMavenLocal # Extract from maven local VERSION=$(grep "^version=" gradle.properties | cut -d'=' -f2) cd ~/.m2/repository/xyz/block/artifactswap/cli/$VERSION unzip cli-$VERSION.zip -d ~/Development/your-project/tools/artifactswap
Create a Wrapper Script
Create a script at scripts/artifact-swap/download-artifacts.sh:
#!/bin/bash set -o errexit set -o pipefail set -o nounset git_root=$(git rev-parse --show-toplevel) tools_root="$git_root/tools" branch=artifact-swap-green-main # Your BOM branch name # Attempt to fetch BOM branch if ! git fetch origin $branch --quiet 2>/dev/null; then git update-ref -d refs/remotes/origin/$branch 2>/dev/null || true git fetch origin $branch --quiet 2>/dev/null || true fi # Ensure the reference exists after fetch if ! git show-ref --verify --quiet refs/remotes/origin/$branch; then git update-ref refs/remotes/origin/$branch FETCH_HEAD fi echo "Syncing artifacts for Artifact Swap" "$tools_root/artifactswap/artifactswap-$VERSION/bin/artifactswap" download-artifacts "$@"
Make it executable:
chmod +x scripts/artifact-swap/download-artifacts.sh
5. Set Up Git Hooks
To automatically download artifacts when switching branches, configure Git hooks:
# In your project root cat > .git/hooks/post-checkout << 'EOF' #!/bin/bash ./scripts/artifact-swap/download-artifacts.sh > /dev/null 2>&1 & disown EOF # Or just add the above command to an existing post-checkout hook chmod +x .git/hooks/post-checkout
Usage
Controlling Which Modules Are Included
Use the Spotlight IDE Plugin to choose the projects you want to load in the IDE.
Artifact Swap automatically determines which additional modules to include based on:
- Git Changes: Modules that have changes (both committed or not) compared to the branch specified by the
artifactswap.bomSourceBranchNameproperty - Missing artifacts: Modules without available published artifacts or ones that have not been downloaded
- Always included modules: Some projects cannot be swapped due to various limitations and can be listed in
gradle/artifact-swap-always-keep.txt
Only projects that are transitive dependencies of your requested modules are analyzed for inclusion based on the additional critera above.
Manual Artifact Download
Download artifacts for the current branch:
./scripts/artifact-swap/download-artifacts.sh --logging debug
Disabling Artifact Swap
Disable via gradle.properties:
artifactswap.enabled=falseHow the BOM (Bill of Materials) Works
Artifact Swap uses a BOM to track which version of each module should be used:
- BOM Generation: Your CI pipeline publishes a BOM file to a specific Git branch (
artifact-swap-green-main) containing module versions - BOM Resolution: The plugin finds the most recent commit on that branch that's also in your current branch's history
- Artifact Download: The CLI downloads all artifacts for that BOM version from Artifactory to Maven Local
- Dependency Resolution: Gradle resolves swapped dependencies from Maven Local
CI Publishing Configuration
How It Works
- Hashing: The
hashingcommand computes content hashes for all modules to create deterministic version identifiers - Task Finding: The
task-findercommand analyzes the build to determine which publish tasks are available - Artifact Checking: The
artifact-checkercommand queries Artifactory to filter out tasks for artifacts that already exist - Task Running: The
task-runnercommand executes only the necessary publish tasks - BOM Publishing: The
bom-publishercommand creates and publishes a BOM file mapping each module to its content-based version - Branch Update: The BOM tracking branch (e.g.,
artifact-swap-green-main) is updated to point to the current commit, allowing developers to discover the latest available BOM
To enable Artifact Swap in your CI pipeline, you need to set up two separate publishing steps: one for artifacts and one for the BOM.
Prerequisites
1. Apply the Publish Plugin
The publish plugin configures Maven publishing for your modules. Choose one of these options:
Option A: Auto-apply via Settings Plugin (Recommended)
Enable the artifactswap.publishingEnabled property in gradle.properties and the settings plugin will automatically apply the publish plugin to all library projects:
artifactswap.publishingEnabled=trueThe plugin is automatically applied to projects with the java, java-library, org.jetbrains.kotlin.jvm, or com.android.library plugins.
Important
All your build plugins must be loaded in the settings classpath for this to work
Option B: Manual Application via Build Logic
If you prefer more control, apply the publish plugin manually through your convention plugins (in build-logic/ or similar):
// build-logic build.gradle(.kts) dependencies { implementation("xyz.block.artifactswap:gradle-publish-plugin:<latest>") }
// Your build-logic library plugin(s) class MyLibraryPlugin : Plugin<Project> { override fun apply(target: Project) = target.run { // ... pluginManager.apply(ArtifactSwapProjectPublishPlugin::class.java) } }
This approach allows you to selectively apply publishing to specific modules or customize the configuration. It also may help in situations where you have classpath issues in your build that prevent the auto-apply from working.
2. Configure Publishing Properties
Add these properties to your gradle.properties:
# Artifact repository URL for publishing artifactswap.artifactRepo.url=https://artifactory.mycompany.com/artifactory/my-repo # Credentials for publishing (typically set via environment variables in CI) # artifactswap.artifactRepo.username=publisher # artifactswap.artifactRepo.password=secret
Note: Credentials can also be provided via a token file. Set the SECRETS_PATH environment variable to a directory containing the token file specified by artifactswap.artifactoryPublisherTokenFileName (defaults to artifactory-token.txt).
Step 1: Publishing Artifacts
The artifact publishing process uses the CLI tool to intelligently publish only changed modules. This should be run in your main branch builds:
#!/usr/bin/env bash set -o errexit set -o pipefail set -o nounset # Allocate memory to prevent OOM crashes export ARTIFACTSWAP_TOOL_OPTS="-Xmx3072M" # Configure your CI pipeline to save this artifact for use in the BOM publishing stage export ARTIFACTSWAP_HASHING_FILE=artifactswapHashes/artifactswapHashing.out export ARTIFACTSWAP_GRADLE_MAX_MEMORY=17408 # 1. Hash all files to determine which modules have changed echo "===== Hashing Files =====" tools/artifactswap/artifactswap hashing \ --logging debug \ --hashing-output-file "${ARTIFACTSWAP_HASHING_FILE}" \ --gradle-max-memory ${ARTIFACTSWAP_GRADLE_MAX_MEMORY} \ --gradle-jvm-args "-XX:MaxMetaspaceSize=1g" \ --log-gradle # 2. Find the publish tasks that need to run echo "===== Finding Tasks to Publish =====" tools/artifactswap/artifactswap task-finder \ --logging debug \ --gradle-max-memory ${ARTIFACTSWAP_GRADLE_MAX_MEMORY} \ --gradle-args "-Partifactswap.publishingEnabled=true -Partifactswap.artifactVersionFile=${ARTIFACTSWAP_HASHING_FILE}" \ --gradle-jvm-args "-XX:MaxMetaspaceSize=1g" \ --task-list-output-directory "taskOutputs" \ --task publishToArtifactSwapRepository \ --log-gradle \ --output-mode "SINGLE_TASK_LIST" # 3. Check which artifacts already exist in Artifactory echo "===== Checking Artifacts =====" tools/artifactswap/artifactswap artifact-checker \ --logging debug \ --hash-file "${ARTIFACTSWAP_HASHING_FILE}" \ --input-file "taskOutputs/task-output.out" \ --output-file "taskOutputs/task-output-filtered.out" # 4. Publish only the artifacts that don't already exist echo "===== Publishing Artifacts =====" tools/artifactswap/artifactswap task-runner \ --logging debug \ --gradle-max-memory ${ARTIFACTSWAP_GRADLE_MAX_MEMORY} \ --gradle-args "-Partifactswap.publishingEnabled=true -Partifactswap.artifactVersionFile=${ARTIFACTSWAP_HASHING_FILE}" \ --gradle-jvm-args "-XX:MaxMetaspaceSize=1g" \ --task-list-file "taskOutputs/task-output-filtered.out" \ --log-gradle
Step 2: Publishing the BOM
After artifacts are published, publish the BOM to Artifactory and update the BOM tracking branch:
#!/usr/bin/env bash set -o errexit set -o pipefail set -o nounset export ARTIFACTSWAP_TOOL_OPTS="-Xmx3072M" # This file should be consumed as an artifact from the previous pipeline stage export ARTIFACTSWAP_HASHING_FILE=artifactswapHashes/artifactswapHashing.out export BOM_BRANCH=artifact-swap-green-main # Your BOM branch name from gradle.properties echo "===== Publishing BOM =====" tools/artifactswap/artifactswap bom-publisher \ --logging debug \ --bom-version "${GIT_COMMIT}" \ --hash-file-location "${ARTIFACTSWAP_HASHING_FILE}" echo "===== Updating BOM Branch =====" # Update the BOM tracking branch to point to the current commit # This allows developers to find the most recent BOM for their branch git fetch origin "${BOM_BRANCH}" || true git branch -f "${BOM_BRANCH}" "${GIT_COMMIT}" git push origin "${BOM_BRANCH}"
Tip
Configure your CI pipeline to only run the BOM publishing job after all your other check tasks have completed successfully. This will prevent compilation issues from "poisoning" the IDE artifacts.
Contributing
See CONTRIBUTING.md for development setup and contribution guidelines.
License
See LICENSE for license information.