ci

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 16, 2026 License: MIT Imports: 2 Imported by: 0

README

F-Droid Client CI/CD Pipeline

This is the F-Droid Android client's CI/CD pipeline, imported from fdroid/fdroidclient.

Overview

F-Droid Client is the Android application for browsing and installing apps from the F-Droid repository. This pipeline demonstrates Android application development and testing.

Pipeline Characteristics

  • Project ID: 36189
  • Lines of Code: ~267 lines
  • Language: Java (Android)
  • Features Demonstrated:
    • Android build automation
    • APK building and signing
    • Multiple build flavors
    • Unit and instrumentation testing
    • F-Droid repository integration

Key Features

Android Build Pipeline

Complete Android application build process with Gradle.

Multi-Flavor Builds

Supports different app flavors (full, basic, etc.).

Testing

Unit tests, instrumentation tests, and UI tests for Android.

APK Distribution

Automated APK building and distribution to F-Droid repository.

What You Can Learn

  1. Android CI/CD - Building and testing Android applications
  2. Gradle automation - Using Gradle in CI/CD pipelines
  3. App flavors - Managing multiple build variants
  4. Mobile testing - Android testing strategies
  5. F-Droid integration - Publishing to F-Droid

Source

Building This Example

cat pipeline.go
mkdir my-android-pipeline && cp pipeline.go my-android-pipeline/
cd my-android-pipeline && go mod init example/my-android-pipeline && go mod tidy

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AppAssembleReleaseTest = pipeline.Job{
	Name:        "app assembleRelease test",
	Stage:       "test",
	Script:      List("./gradlew :app:assembleRelease :app:assembleDebug :app:testFullDebugUnitTest"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("app/**/*", "libs/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths:    List("app/build/reports", "app/build/outputs/apk", "libs/*/build/reports"),
		ExpireIn: "1 week",
		When:     "always",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
}
View Source
var AppCheckstyle = pipeline.Job{
	Name:        "app checkstyle",
	Stage:       "lint",
	Script:      List("./gradlew :app:checkstyle", "python3 tools/checkstyle-to-codeclimate.py --input app/build/reports/checkstyle/checkstyle.xml --output gl-checkstyle.json"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("app/**/*"),
		},
	),
}
View Source
var AppErrorprone = pipeline.Job{
	Name:   "app errorprone",
	Stage:  "lint",
	Script: List("sed -i \"s@plugins {@plugins{\\nid 'net.ltgt.errorprone' version '3.1.0'@\" app/build.gradle", "cat config/errorprone.gradle >> app/build.gradle", "./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("app/**/*"),
		},
	),
}
View Source
var AppLint = pipeline.Job{
	Name:        "app lint",
	Stage:       "lint",
	Script:      List("sed -i -e 's,textReport .*,textReport true,' app/build.gradle", "./gradlew :app:lint :app:ktlintCheck"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("app/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths:    List("kernel.log", "logcat.txt", "app/core*", "app/*.log", "app/build/reports", "app/build/outputs/*ml", "app/build/outputs/apk", "libs/*/build/reports", "build/reports"),
		ExpireIn: "1 week",
		When:     "on_failure",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
}
View Source
var AppLintPmd = pipeline.Job{
	Name:         "app lint pmd",
	Stage:        "lint",
	Script:       List("find ${PMD_FILE_PATHS[@]} -type f -name '*.java' ! -path '/vendored/*' > .pmd-files.txt", "pmd check --file-list .pmd-files.txt -R ${PMD_RULESETS} -f codeclimate -r gl-code-quality-not-formatted.json --no-fail-on-violation"),
	BeforeScript: List("apt-get update", "apt-get -qy install --no-install-recommends jq"),
	AfterScript:  List("sed 's/\\\\/\\\\\\\\/g' gl-code-quality-not-formatted.json | while IFS= read -r line; do\nfingerprint=$(echo -n \"$line\" | md5sum | awk '{print $1}');\necho \"$line\" | jq -c --arg fp \"$fingerprint\" '. + {fingerprint: $fp}' | sed 's/$/,/';\ndone > gl-pmd-${PMD_VARIANT}-report.json || true\n", "sed -i '1s/^/[/' gl-pmd-${PMD_VARIANT}-report.json", "sed -i '$s/,$/]/' gl-pmd-${PMD_VARIANT}-report.json"),
	Image: pipeline.Image{
		Name:       "registry.gitlab.com/gitlab-ci-utils/gitlab-pmd-cpd:latest",
		Entrypoint: List(""),
	},
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("app/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths: List("gl-pmd-${PMD_VARIANT}-report.json"),
		When:  "always",
	},
}
View Source
var AppToolsScripts = pipeline.Job{
	Name:   "app tools scripts",
	Stage:  "lint",
	Script: List("apt-get update", "apt-get -qy install --no-install-recommends git python3", "./tools/check-format-strings.py", "./tools/check-fastlane-whitespace.py", "./tools/remove-unused-and-blank-translations.py", "echo \"These are unused or blank translations that should be removed:\"", "git --no-pager diff --ignore-all-space --name-only --exit-code app/src/*/res/values*/strings.xml"),
	Image:  pipeline.Image{Name: "debian:bookworm-slim"},
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("app/**/*"),
		},
	),
}
View Source
var AppWeblateMergeConflict = pipeline.Job{
	Name:   "app weblate merge conflict",
	Stage:  "lint",
	Script: List("apt-get update", "apt-get -qy install --no-install-recommends ca-certificates git", "git config user.name \"$CI_PIPELINE_ID/$CI_JOB_ID\"", "git config user.email $CI_PROJECT_PATH@f-droid.org", "git fetch https://hosted.weblate.org/git/f-droid/f-droid", "git checkout -B weblate FETCH_HEAD", "export EXITVALUE=0", "if ! git rebase $CI_COMMIT_SHA; then export EXITVALUE=1; set -x; while git rebase --skip; do echo; done; set +x; fi", "git diff --exit-code", "exit $EXITVALUE"),
	Image:  pipeline.Image{Name: "debian:bookworm-slim"},
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "app/src/*/res/values*/strings.xml", "metadata/*"),
		},
	),
}
View Source
var DeployNightly = pipeline.Job{
	Name:   "deploy_nightly",
	Stage:  "deploy",
	Script: List("test -z \"$DEBUG_KEYSTORE\" && exit 0", "apt-get install -t bookworm-backports androguard fdroidserver", "sed -i 's,<string name=\"app_name\">.*</string>,<string name=\"app_name\">F-Nightly</string>,' app/src/main/res/values*/strings.xml", "sed -i -e '/<\\/string-array>/d' -e '/<\\/resources>/d' app/src/main/res/values/default_repos.xml", "echo \"<item>${CI_PROJECT_PATH}-nightly</item>\" >> app/src/main/res/values/default_repos.xml", "echo \"<item>${CI_PROJECT_URL}-nightly/-/raw/master/fdroid/repo</item>\" >> app/src/main/res/values/default_repos.xml", "cat config/nightly-repo/repo.xml >> app/src/main/res/values/default_repos.xml", "export DB=`sed -n 's,.*version *= *\\([0-9][0-9]*\\).*,\\1,p' libs/database/src/main/java/org/fdroid/database/FDroidDatabase.kt`", "export versionCode=`printf '%d%05d' $DB $(date '+%s'| cut -b1-8)`", "sed -i \"s,^\\(\\s*versionCode\\)  *[0-9].*,\\1 $versionCode,\" app/build.gradle", "./gradlew assembleDebug", "rm -rf $fdroidserver", "mkdir $fdroidserver", "git ls-remote https://gitlab.com/fdroid/fdroidserver.git master", "curl --silent https://gitlab.com/fdroid/fdroidserver/-/archive/master/fdroidserver-master.tar.gz | tar -xz --directory=$fdroidserver --strip-components=1", "export PATH=\"$fdroidserver:$PATH\"", "export PYTHONPATH=\"$fdroidserver:$fdroidserver/examples\"", "export PYTHONUNBUFFERED=true", "fdroid nightly -v"),
	Variables: Json{
		"JAVA_HOME": "/usr/lib/jvm/java-17-openjdk-amd64",
	},
}
View Source
var Kvm24DefaultX86 = pipeline.Job{
	Name:        "kvm 24 default x86",
	Stage:       "test",
	Script:      List("./gradlew assembleFullDebug", "export AVD_SDK=`echo $CI_JOB_NAME | awk '{print $2}'`", "export AVD_TAG=`echo $CI_JOB_NAME | awk '{print $3}'`", "export AVD_ARCH=`echo $CI_JOB_NAME | awk '{print $4}'`", "export AVD_PACKAGE=\"system-images;android-${AVD_SDK};${AVD_TAG};${AVD_ARCH}\"", "echo $AVD_PACKAGE", "$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager --verbose delete avd --name \"$NAME_AVD\"", "export AVD=\"$AVD_PACKAGE\"", "echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install \"$AVD\"", "echo no | $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager --verbose create avd --name \"$NAME_AVD\" --package \"$AVD\" --device \"pixel\"", "df -h", "start-emulator.sh", "./gradlew installFullDebug", "adb shell am start -n org.fdroid.fdroid.debug/org.fdroid.fdroid.views.main.MainActivity", "export FLAG=\"-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest,androidx.test.filters.FlakyTest\"", "./gradlew $FLAG :app:connectedFullDebugAndroidTest :libs:database:connectedCheck :libs:download:connectedCheck :libs:index:connectedCheck", "export FLAG=\"-Pandroid.testInstrumentationRunnerArguments.annotation=androidx.test.filters.FlakyTest\"", "for i in {1..5}; do echo \"$i\" && ./gradlew $FLAG :app:connectedFullDebugAndroidTest :libs:database:connectedCheck :libs:download:connectedCheck :libs:index:connectedCheck && break; done || exit 137"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Image:       pipeline.Image{Name: "briar/ci-image-android-emulator:latest"},
	Artifacts: pipeline.Artifacts{
		Paths:    List("kernel.log", "logcat.txt", "app/core*", "app/*.log", "app/build/reports", "app/build/outputs/*ml", "app/build/outputs/apk", "libs/*/build/reports", "build/reports"),
		ExpireIn: "1 week",
		When:     "on_failure",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
	Tags: List("kvm"),
}
View Source
var LibsDatabaseSchema = pipeline.Job{
	Name:   "libs database schema",
	Stage:  "lint",
	Script: List("apt-get update", "apt-get -qy --no-install-recommends install openjdk-17-jdk-headless git sdkmanager", "export ANDROID_HOME=/opt/android-sdk", "export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdk = \"\\([0-9][0-9]*\\)\".*,\\1,p' gradle/libs.versions.toml`", "sdkmanager \"platforms;android-$ANDROID_COMPILE_SDK\" \"build-tools;$ANDROID_COMPILE_SDK.0.0\"", "sdkmanager \"build-tools;34.0.0\"", "sdkmanager \"build-tools;35.0.0\"", "./gradlew :libs:database:kspDebugKotlin", "git --no-pager diff --exit-code"),
	Image:  pipeline.Image{Name: "debian:bookworm-backports"},
	Variables: Json{
		"JAVA_HOME": "/usr/lib/jvm/java-17-openjdk-amd64",
	},
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("libs/database/**/*"),
		},
	),
}
View Source
var LibsDbTest = pipeline.Job{
	Name:        "libs db test",
	Stage:       "test",
	Script:      List("./gradlew :libs:database:testDebugUnitTest"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("libs/database/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths:    List("kernel.log", "logcat.txt", "app/core*", "app/*.log", "app/build/reports", "app/build/outputs/*ml", "app/build/outputs/apk", "libs/*/build/reports", "build/reports"),
		ExpireIn: "1 week",
		When:     "on_failure",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
}
View Source
var LibsDownloadTest = pipeline.Job{
	Name:        "libs download test",
	Stage:       "test",
	Script:      List("./gradlew :libs:download:testDebugUnitTest"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("libs/download/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths:    List("kernel.log", "logcat.txt", "app/core*", "app/*.log", "app/build/reports", "app/build/outputs/*ml", "app/build/outputs/apk", "libs/*/build/reports", "build/reports"),
		ExpireIn: "1 week",
		When:     "on_failure",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
}
View Source
var LibsIndexTest = pipeline.Job{
	Name:        "libs index test",
	Stage:       "test",
	Script:      List("./gradlew :libs:index:testDebugUnitTest"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("libs/index/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths:    List("kernel.log", "logcat.txt", "app/core*", "app/*.log", "app/build/reports", "app/build/outputs/*ml", "app/build/outputs/apk", "libs/*/build/reports", "build/reports"),
		ExpireIn: "1 week",
		When:     "on_failure",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
}
View Source
var LibsLintKtlintCheck = pipeline.Job{
	Name:        "libs lint ktlintCheck",
	Stage:       "lint",
	Script:      List("sed -i -e 's,textReport .*,textReport true,' app/build.gradle", "./gradlew :libs:database:lint :libs:download:lint :libs:index:lint ktlintCheck checkLegacyAbi"),
	AfterScript: List("echo \"Download debug artifacts from https://gitlab.com/${CI_PROJECT_PATH}/-/jobs\"", "rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock", "rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/"),
	Rules: List(
		pipeline.Rule{
			Changes: List(".gitlab-ci.yml", "build.gradle", "settings.gradle", "gradle/libs.versions.toml"),
		},
		pipeline.Rule{
			Changes: List("libs/**/*"),
		},
	),
	Artifacts: pipeline.Artifacts{
		Paths:    List("kernel.log", "logcat.txt", "app/core*", "app/*.log", "app/build/reports", "app/build/outputs/*ml", "app/build/outputs/apk", "libs/*/build/reports", "build/reports"),
		ExpireIn: "1 week",
		When:     "on_failure",
		Name:     "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}",
	},
}
View Source
var MainPipeline = pipeline.Pipeline{
	Stages: List("lint", "test", "deploy"),
}
View Source
var Pages = pipeline.Job{
	Name:   "pages",
	Stage:  "deploy",
	Script: List("./gradlew :libs:core:dokkaGenerateHtml :libs:download:dokkaGenerateHtml :libs:index:dokkaGenerateHtml :libs:database:dokkaGenerateHtml", "mkdir -p public/libs", "touch public/index.html public/libs/index.html", "cp -r libs/core/build/dokka/html public/libs/core", "cp -r libs/download/build/dokka/html public/libs/download", "cp -r libs/index/build/dokka/html public/libs/index", "cp -r libs/database/build/dokka/html public/libs/database"),
	Artifacts: pipeline.Artifacts{
		Paths: List("public"),
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL