Create alpha and beta releases

This commit is contained in:
Oscar Mira 2022-01-06 12:04:12 +01:00
parent 305157cc46
commit 2acdc04c2c
20 changed files with 210 additions and 224 deletions

View file

@ -1,36 +0,0 @@
name: Android CI
on:
pull_request:
push:
paths-ignore:
- '**/README*.md'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Remove Android 31 (S)
run: $ANDROID_HOME/tools/bin/sdkmanager --uninstall "platforms;android-31"
- name: Build with Gradle
run: ./gradlew build
- name: Archive reports for failed build
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: reports
path: '*/build/reports'

View file

@ -1,109 +0,0 @@
name: Docker
on:
push:
tags:
- 'v*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create staging keystore
run: echo "$STAGING_KEYSTORE" | base64 -d > app/staging.keystore
env:
STAGING_KEYSTORE: ${{ secrets.STAGING_KEYSTORE }}
- name: Build Docker image
run: docker-compose build
working-directory: reproducible-builds
- name: Build APKs
run: docker-compose run reproducible-molly
working-directory: reproducible-builds
env:
BUILD_SCAN: 1
STAGING_KEYSTORE_PATH: staging.keystore
STAGING_KEYSTORE_PASSWORD: ${{ secrets.STAGING_KEYSTORE_PASSWORD }}
- name: Log checksums
run: find reproducible-builds/outputs/apk -name "*.apk" -exec sha256sum '{}' \;
- name: Upload APKs
uses: actions/upload-artifact@v2
with:
name: molly-apk-docker
path: reproducible-builds/outputs/apk/**/release/*.apk
if-no-files-found: error
release:
name: Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v2
- name: Get the version
id: get-version
run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- name: Create release
id: create-release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Molly Android ${{ steps.get-version.outputs.VERSION }}
draft: true
prerelease: ${{ contains(steps.get-version.outputs.VERSION, 'rc') }}
- name: Upload APK staging
if: contains(steps.get-version.outputs.VERSION, 'rc')
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./molly-apk-docker/stagingNonFree/release/Molly-staging-release-unsigned-${{ steps.get-version.outputs.VERSION }}.apk
asset_name: Molly-staging-unsigned-${{ steps.get-version.outputs.VERSION }}.apk
asset_content_type: application/zip
- name: Upload APK production
if: "!contains(steps.get-version.outputs.VERSION, 'rc')"
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./molly-apk-docker/prodNonFree/release/Molly-prod-release-unsigned-${{ steps.get-version.outputs.VERSION }}.apk
asset_name: Molly-unsigned-${{ steps.get-version.outputs.VERSION }}.apk
asset_content_type: application/zip
- name: Upload APK staging FOSS
if: contains(steps.get-version.outputs.VERSION, 'rc')
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./molly-apk-docker/stagingFree/release/Molly-staging-release-unsigned-${{ steps.get-version.outputs.VERSION }}-FOSS.apk
asset_name: Molly-staging-unsigned-${{ steps.get-version.outputs.VERSION }}-FOSS.apk
asset_content_type: application/zip
- name: Upload APK production FOSS
if: "!contains(steps.get-version.outputs.VERSION, 'rc')"
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./molly-apk-docker/prodFree/release/Molly-prod-release-unsigned-${{ steps.get-version.outputs.VERSION }}-FOSS.apk
asset_name: Molly-unsigned-${{ steps.get-version.outputs.VERSION }}-FOSS.apk
asset_content_type: application/zip

81
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,81 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker-compose build
working-directory: reproducible-builds
- name: Build release APKs
if: "!contains(github.ref_name, 'alpha')"
run: docker-compose run assemble
working-directory: reproducible-builds
- name: Extract alpha keystore
if: "contains(github.ref_name, 'alpha')"
run: echo "$ALPHA_KEYSTORE" | base64 -d > reproducible-builds/certs/alpha.jks
env:
ALPHA_KEYSTORE: ${{ secrets.ALPHA_KEYSTORE }}
- name: Build alpha release APKs
if: "contains(github.ref_name, 'alpha')"
run: docker-compose run assemble-alpha
working-directory: reproducible-builds
env:
CI_KEYSTORE_PATH: certs/alpha.jks
CI_KEYSTORE_ALIAS: alpha
CI_KEYSTORE_PASSWORD: ${{ secrets.ALPHA_KEYSTORE_PASSWORD }}
- name: Clean up keystores
if: "always()"
run: rm -f reproducible-builds/certs/alpha.jks
- name: Log checksums
run: find reproducible-builds/outputs/apk -name "*.apk" -exec sha256sum '{}' \;
- name: Upload APKs
uses: actions/upload-artifact@v2
with:
name: molly-apk-docker
path: reproducible-builds/outputs/apk/**/release/*.apk
if-no-files-found: error
publish:
name: Publish
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
- name: Create stable release draft
if: "!contains(github.ref_name, 'alpha') && !contains(github.ref_name, 'beta')"
run: gh release create -d -t "Molly $GITHUB_REF_NAME" "$GITHUB_REF_NAME"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create beta release draft
if: "contains(github.ref_name, 'beta')"
run: gh release create -d -p -t "Molly $GITHUB_REF_NAME" "$GITHUB_REF_NAME"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create alpha release
if: "contains(github.ref_name, 'alpha')"
run: gh release create -p -t "Molly Insider $GITHUB_REF_NAME" "$GITHUB_REF_NAME" ./molly-apk-docker/*/release/*.apk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

38
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: Test
on:
pull_request:
push:
paths-ignore:
- '**/README*.md'
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@master
- name: Build Docker image
run: docker-compose build
working-directory: reproducible-builds
- name: Run tests
run: docker-compose run test
working-directory: reproducible-builds
- name: Archive reports for failed build
if: "failure()"
uses: actions/upload-artifact@v2
with:
name: test-reports
path: "reproducible-builds/test-reports"

6
.gitignore vendored
View file

@ -1,8 +1,6 @@
.classpath
captures/
project.properties
keystore.debug.properties
keystore.staging.properties
.project
.settings
bin/
@ -25,6 +23,6 @@ ffpr
test/androidTestEspresso/res/values/arrays.xml
obj/
.cxx
reproducible-builds/certs/
reproducible-builds/outputs/
pkcs11.password
dev.keystore
reproducible-builds/test-reports/

View file

@ -80,31 +80,9 @@ protobuf {
def canonicalVersionCode = 979
def canonicalVersionName = "5.28.6"
def mollyRevision = 0
def postFixSize = 100
def abiPostFix = ['universal' : 0,
'armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4]
def selectableVariants = [
'nightlyProdFlipper',
'nightlyProdPerf',
'nightlyProdRelease',
'playProdDebug',
'playProdFlipper',
'playProdPerf',
'playProdRelease',
'playStagingDebug',
'playStagingFlipper',
'playStagingPerf',
'playStagingRelease',
'studyProdMock',
'studyProdPerf',
'websiteProdFlipper',
'websiteProdRelease',
]
android {
buildToolsVersion BUILD_TOOL_VERSION
@ -123,19 +101,23 @@ android {
}
signingConfigs {
staging {
if (System.getenv('STAGING_KEYSTORE_PATH')) {
storeFile file("${System.env.STAGING_KEYSTORE_PATH}")
storePassword "${System.env.STAGING_KEYSTORE_PASSWORD}"
keyAlias "staging"
keyPassword "${System.env.STAGING_KEYSTORE_PASSWORD}"
ci {
if (System.getenv('CI_KEYSTORE_PATH')) {
storeFile file("${System.env.CI_KEYSTORE_PATH}")
storePassword "${System.env.CI_KEYSTORE_PASSWORD}"
keyAlias "${System.env.CI_KEYSTORE_ALIAS}"
keyPassword "${System.env.CI_KEYSTORE_PASSWORD}"
enableV4Signing false
}
}
}
if (mollyRevision < 0 || mollyRevision >= postFixSize) {
throw new GradleException("Molly revision $mollyRevision out of range")
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize + abiPostFix['universal']
versionCode canonicalVersionCode * postFixSize + mollyRevision
versionName project.hasProperty('ci') ? getCommitTag() : canonicalVersionName
minSdkVersion MINIMUM_SDK
@ -146,9 +128,10 @@ android {
applicationId basePackageId
buildConfigField "String", "SIGNAL_PACKAGE_NAME", "\"org.thoughtcrime.securesms\""
buildConfigField "String", "CANONICAL_VERSION_NAME", "\"$canonicalVersionName\""
buildConfigField "String", "SIGNAL_CANONICAL_VERSION_NAME", "\"$canonicalVersionName\""
buildConfigField "int", "SIGNAL_CANONICAL_VERSION_CODE", "$canonicalVersionCode"
buildConfigField "String", "FDROID_UPDATE_URL", "\"https://molly.im/fdroid/repo\""
buildConfigField "String", "BACKUP_FILENAME", "\"" + baseAppName.toLowerCase() + "\""
buildConfigField "String", "BACKUP_FILENAME", "\"" + baseApkFileName.toLowerCase() + "\""
vectorDrawables.useSupportLibrary = true
@ -178,7 +161,6 @@ android {
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXQ==\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
buildConfigField "int[]", "MOBILE_COIN_BLACKLIST", "new int[]{98,963,53,850,7}"
buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\""
@ -308,6 +290,7 @@ android {
}
release {
signingConfig signingConfigs.ci.storeFile ? signingConfigs.ci : null
minifyEnabled true
shrinkResources true
proguardFiles = buildTypes.debug.proguardFiles
@ -319,12 +302,12 @@ android {
def isStaging = variant.productFlavors*.name.contains("staging")
def hasSigningConfig = buildType.signingConfig || variant.signingConfig
variant.resValue 'string', 'app_name', baseAppName + (isStaging ? " Staging" : "")
variant.resValue 'string', 'app_name', baseAppTitle + (isStaging ? " Staging" : "")
variant.resValue "string", 'package_name', variant.applicationId
variant.outputs.all {
def flavors = variant.baseName - ~/(free|nonFree)-/ + (hasSigningConfig ? "" : "-unsigned")
outputFileName = "${baseAppName}-${flavors}-${versionName}.apk"
def flavors = variant.baseName - ~/-(free|nonFree)/ - ~/-release/ + (hasSigningConfig ? "" : "-unsigned")
outputFileName = "${baseApkFileName}-${flavors}-${versionName}.apk"
}
}

View file

@ -1,3 +1,4 @@
# Set the app title and package ID
baseAppName=Molly
baseAppTitle=Molly
baseApkFileName=Molly
basePackageId=im.molly.app

View file

@ -404,8 +404,8 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
IdentityKeyUtil.generateIdentityKeys(this);
}
Log.i(TAG, "Setting first install version to " + Util.getCanonicalVersionCode());
TextSecurePreferences.setFirstInstallVersion(this, Util.getCanonicalVersionCode());
Log.i(TAG, "Setting first install version to " + Util.getSignalCanonicalVersionCode());
TextSecurePreferences.setFirstInstallVersion(this, Util.getSignalCanonicalVersionCode());
}
}

View file

@ -93,7 +93,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
var fcmToken: String?
get() {
val tokenVersion: Int = TextSecurePreferences.getIntegerPreference(context, "pref_gcm_registration_id_version", 0)
return if (tokenVersion == Util.getCanonicalVersionCode()) {
return if (tokenVersion == Util.getSignalCanonicalVersionCode()) {
TextSecurePreferences.getStringPreference(context, "pref_gcm_registration_id", null)
} else {
null
@ -101,7 +101,7 @@ internal class AccountValues internal constructor(store: KeyValueStore) : Signal
}
set(value) {
TextSecurePreferences.setStringPreference(context, "pref_gcm_registration_id", value)
TextSecurePreferences.setIntegerPrefrence(context, "pref_gcm_registration_id_version", Util.getCanonicalVersionCode())
TextSecurePreferences.setIntegerPrefrence(context, "pref_gcm_registration_id_version", Util.getSignalCanonicalVersionCode())
TextSecurePreferences.setLongPreference(context, "pref_gcm_registration_id_last_set_time", System.currentTimeMillis())
}

View file

@ -81,7 +81,7 @@ public class LogSectionSystemInfo implements LogSection {
.append(" ")
.append(pm.getPackageInfo(context.getPackageName(), 0).versionName)
.append(" (")
.append(BuildConfig.CANONICAL_VERSION_CODE)
.append(Util.getSignalCanonicalVersionCode())
.append(", ")
.append(Util.getManifestApkVersion(context))
.append(") (")

View file

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.util.Util;
public class StandardUserAgentInterceptor extends UserAgentInterceptor {
public StandardUserAgentInterceptor() {
// MOLLY: Replace BuildConfig.VERSION_NAME by Util.getCanonicalVersionName()
super("Signal-Android/" + Util.getCanonicalVersionName() + " Android/" + Build.VERSION.SDK_INT);
// MOLLY: Replace BuildConfig.VERSION_NAME by Util.getSignalCanonicalVersionName()
super("Signal-Android/" + Util.getSignalCanonicalVersionName() + " Android/" + Build.VERSION.SDK_INT);
}
}

View file

@ -11,7 +11,6 @@ import com.annimon.stream.Stream;
import org.json.JSONException;
import org.json.JSONObject;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
@ -531,7 +530,7 @@ public final class FeatureFlags {
return VersionFlag.OFF;
}
if (BuildConfig.CANONICAL_VERSION_CODE >= versionFromKey) {
if (Util.getSignalCanonicalVersionCode() >= versionFromKey) {
return VersionFlag.ON;
} else {
return VersionFlag.ON_IN_FUTURE_VERSION;

View file

@ -24,7 +24,7 @@ public final class RemoteDeprecation {
* there's no pending expiration.
*/
public static long getTimeUntilDeprecation() {
return getTimeUntilDeprecation(FeatureFlags.clientExpiration(), System.currentTimeMillis(), BuildConfig.VERSION_NAME);
return getTimeUntilDeprecation(FeatureFlags.clientExpiration(), System.currentTimeMillis(), Util.getSignalCanonicalVersionName());
}
/**

View file

@ -763,11 +763,11 @@ public class TextSecurePreferences {
return getBooleanPreference(context, SCREEN_SECURITY_PREF, true);
}
public static int getLastVersionCode(Context context) {
return getIntegerPreference(context, LAST_VERSION_CODE_PREF, Util.getCanonicalVersionCode());
public static int getSignalLastVersionCode(Context context) {
return getIntegerPreference(context, LAST_VERSION_CODE_PREF, Util.getSignalCanonicalVersionCode());
}
public static void setLastVersionCode(Context context, int versionCode) throws IOException {
public static void setSignalLastVersionCode(Context context, int versionCode) throws IOException {
if (!setIntegerPrefrenceBlocking(context, LAST_VERSION_CODE_PREF, versionCode)) {
throw new IOException("couldn't write version code to sharedpreferences");
}

View file

@ -338,25 +338,16 @@ public class Util {
/**
* The app version.
* <p>
* This code should be used in all places that compare app versions rather than
* {@link #getManifestApkVersion(Context)} or {@link BuildConfig#VERSION_CODE}.
*/
public static int getCanonicalVersionCode() {
return BuildConfig.CANONICAL_VERSION_CODE;
public static int getMollyVersionCode() {
return BuildConfig.VERSION_CODE;
}
public static String getCanonicalVersionName() {
return BuildConfig.CANONICAL_VERSION_NAME;
public static int getSignalCanonicalVersionCode() {
return BuildConfig.SIGNAL_CANONICAL_VERSION_CODE;
}
/**
* {@link BuildConfig#VERSION_CODE} may not be the actual version due to ABI split code adding a
* postfix after BuildConfig is generated.
* <p>
* However, in most cases you want to use {@link BuildConfig#CANONICAL_VERSION_CODE} via
* {@link #getCanonicalVersionCode()}
*/
// MOLLY: No ABI splits. APK version matches Molly version code
public static int getManifestApkVersion(Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
@ -365,6 +356,14 @@ public class Util {
}
}
public static String getMollyVersionName() {
return BuildConfig.VERSION_NAME;
}
public static String getSignalCanonicalVersionName() {
return BuildConfig.SIGNAL_CANONICAL_VERSION_NAME;
}
public static String getSecret(int size) {
byte[] secret = getSecretBytes(size);
return Base64.encodeBytes(secret);

View file

@ -18,18 +18,18 @@ public class VersionTracker {
private static final String TAG = Log.tag(VersionTracker.class);
public static int getLastSeenVersion(@NonNull Context context) {
return TextSecurePreferences.getLastVersionCode(context);
return TextSecurePreferences.getSignalLastVersionCode(context);
}
public static void updateLastSeenVersion(@NonNull Context context) {
try {
int currentVersionCode = Util.getCanonicalVersionCode();
int lastVersionCode = TextSecurePreferences.getLastVersionCode(context);
int currentVersionCode = Util.getSignalCanonicalVersionCode();
int lastVersionCode = TextSecurePreferences.getSignalLastVersionCode(context);
if (currentVersionCode != lastVersionCode) {
Log.i(TAG, "Upgraded from " + lastVersionCode + " to " + currentVersionCode);
SignalStore.misc().clearClientDeprecated();
TextSecurePreferences.setLastVersionCode(context, currentVersionCode);
TextSecurePreferences.setSignalLastVersionCode(context, currentVersionCode);
ApplicationDependencies.getJobManager().add(new RemoteConfigRefreshJob());
LocalMetrics.getInstance().clear();
}

View file

@ -41,12 +41,12 @@ wget https://github.com/mollyim/mollyim-android/releases/download/$VERSION/Molly
# Run the diff script to compare the APKs
python apkdiff/apkdiff.py \
Molly-$VERSION.apk \
outputs/apk/prodNonFree/release/Molly-prod-release-unsigned-$VERSION.apk
outputs/apk/prodNonFree/release/Molly-prod-unsigned-$VERSION.apk
# Run the diff script to compare the APKs (FOSS)
python apkdiff/apkdiff.py \
Molly-$VERSION-FOSS.apk \
outputs/apk/prodFree/release/Molly-prod-release-unsigned-$VERSION-FOSS.apk
outputs/apk/prodFree/release/Molly-prod-unsigned-$VERSION-FOSS.apk
# Clean up the Docker environment
docker-compose down

View file

View file

@ -1,13 +1,45 @@
version: '3.2'
services:
reproducible-molly:
assemble:
image: reproducible-molly
build:
context: ..
dockerfile: reproducible-builds/Dockerfile
command: ./gradlew -Pci :mollyim-android:assembleRelease
command: ./gradlew -Pci :app:assembleProdNonFreeRelease :app:assembleProdFreeRelease
volumes:
- ./certs:/molly/app/certs:ro
- ./outputs:/molly/app/build/outputs
environment:
- BUILD_SCAN
- STAGING_KEYSTORE_PATH
- STAGING_KEYSTORE_PASSWORD
- CI_KEYSTORE_PATH
- CI_KEYSTORE_PASSWORD
- CI_KEYSTORE_ALIAS
assemble-alpha:
image: reproducible-molly
build:
context: ..
dockerfile: reproducible-builds/Dockerfile
command: ./gradlew -Pci :app:assembleRelease
volumes:
- ./certs:/molly/app/certs:ro
- ./outputs:/molly/app/build/outputs
environment:
- CI_KEYSTORE_PATH
- CI_KEYSTORE_PASSWORD
- CI_KEYSTORE_ALIAS
- ORG_GRADLE_PROJECT_baseAppTitle=Molly Insider
- ORG_GRADLE_PROJECT_baseApkFileName=Molly-Insider
- ORG_GRADLE_PROJECT_basePackageId=im.molly.insider
profiles:
- alpha
test:
image: reproducible-molly
build:
context: ..
dockerfile: reproducible-builds/Dockerfile
command: ./gradlew build
volumes:
- ./test-reports:/molly/app/build/reports
environment:
- BUILD_SCAN=1
profiles:
- test

View file