415 lines
13 KiB
Bash
Executable File
415 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Release script for MnnLlmChat
|
|
# This script builds and publishes:
|
|
# 1. Standard flavor debug APK for CDN upload
|
|
# 2. Google Play flavor release AAB for Google Play Store
|
|
|
|
set -e
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Configuration
|
|
PROJECT_NAME="MnnLlmChat"
|
|
VERSION_NAME=$(awk '
|
|
/defaultConfig[[:space:]]*\{/ { in_default=1; next }
|
|
in_default && /versionName[[:space:]]*"/ {
|
|
match($0, /versionName[[:space:]]*"[^"]+"/)
|
|
print substr($0, RSTART + 13, RLENGTH - 14)
|
|
exit
|
|
}
|
|
in_default && /\}/ { in_default=0 }
|
|
' app/build.gradle)
|
|
VERSION_CODE=$(awk '
|
|
/defaultConfig[[:space:]]*\{/ { in_default=1; next }
|
|
in_default && /versionCode[[:space:]]*[0-9]+/ {
|
|
match($0, /versionCode[[:space:]]*[0-9]+/)
|
|
value=substr($0, RSTART, RLENGTH)
|
|
sub(/versionCode[[:space:]]*/, "", value)
|
|
print value
|
|
exit
|
|
}
|
|
in_default && /\}/ { in_default=0 }
|
|
' app/build.gradle)
|
|
BUILD_DATE=$(date +"%Y%m%d_%H%M%S")
|
|
RELEASE_HIGHLIGHTS="${RELEASE_HIGHLIGHTS:-}"
|
|
PREVIOUS_VERSION="${PREVIOUS_VERSION:-}"
|
|
|
|
# Directories
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
BUILD_DIR="$PROJECT_DIR/app/build"
|
|
OUTPUT_DIR="$PROJECT_DIR/release_outputs"
|
|
CDN_UPLOAD_DIR="$OUTPUT_DIR/cdn"
|
|
GOOGLE_PLAY_DIR="$OUTPUT_DIR/googleplay"
|
|
|
|
# Environment variables for signing (should be set in CI/CD or locally)
|
|
KEYSTORE_FILE="${KEYSTORE_FILE:-}"
|
|
KEYSTORE_PASSWORD="${KEYSTORE_PASSWORD:-}"
|
|
KEY_ALIAS="${KEY_ALIAS:-}"
|
|
KEY_PASSWORD="${KEY_PASSWORD:-}"
|
|
|
|
# CDN configuration
|
|
CDN_ENDPOINT="${CDN_ENDPOINT:-}"
|
|
CDN_ACCESS_KEY="${CDN_ACCESS_KEY:-}"
|
|
CDN_SECRET_KEY="${CDN_SECRET_KEY:-}"
|
|
CDN_BUCKET="${CDN_BUCKET:-}"
|
|
|
|
# Google Play configuration
|
|
GOOGLE_PLAY_SERVICE_ACCOUNT="${GOOGLE_PLAY_SERVICE_ACCOUNT:-}"
|
|
GOOGLE_PLAY_PACKAGE_NAME="${GOOGLE_PLAY_PACKAGE_NAME:-com.alibaba.mnnllm.android.googleplay}"
|
|
ENABLE_FIREBASE="${ENABLE_FIREBASE:-true}"
|
|
MNN_FIREBASE_CONFIG_DIR="${MNN_FIREBASE_CONFIG_DIR:-$HOME/.mnn}"
|
|
MNN_FIREBASE_DEV_CONFIG="${MNN_FIREBASE_DEV_CONFIG:-$MNN_FIREBASE_CONFIG_DIR/google-services-dev.json}"
|
|
MNN_FIREBASE_RELEASE_CONFIG="${MNN_FIREBASE_RELEASE_CONFIG:-$MNN_FIREBASE_CONFIG_DIR/google-services-release.json}"
|
|
|
|
STANDARD_GRADLE_ARGS=()
|
|
GOOGLEPLAY_GRADLE_ARGS=()
|
|
|
|
# Functions
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
has_google_services_config() {
|
|
[[ -f "app/google-services.json" ]] || [[ -f "app/src/googleplay/google-services.json" ]] || [[ -f "app/src/standard/google-services.json" ]]
|
|
}
|
|
|
|
detect_previous_version() {
|
|
local readme_file="README.md"
|
|
if [[ ! -f "$readme_file" ]]; then
|
|
return
|
|
fi
|
|
|
|
awk -v current="$VERSION_NAME" '
|
|
/^## Version / {
|
|
version=$3
|
|
if (seen_current && version != current) {
|
|
print version
|
|
exit
|
|
}
|
|
if (version == current) {
|
|
seen_current=1
|
|
}
|
|
}
|
|
' "$readme_file"
|
|
}
|
|
|
|
prepare_google_services_config() {
|
|
if [[ "$ENABLE_FIREBASE" != "true" ]]; then
|
|
return
|
|
fi
|
|
|
|
if [[ ! -f "$MNN_FIREBASE_DEV_CONFIG" ]]; then
|
|
log_error "Missing standard/debug Firebase config: $MNN_FIREBASE_DEV_CONFIG"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$MNN_FIREBASE_RELEASE_CONFIG" ]]; then
|
|
log_error "Missing Google Play/release Firebase config: $MNN_FIREBASE_RELEASE_CONFIG"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "app/src/standard" "app/src/googleplay"
|
|
cp "$MNN_FIREBASE_DEV_CONFIG" "app/src/standard/google-services.json"
|
|
cp "$MNN_FIREBASE_RELEASE_CONFIG" "app/src/googleplay/google-services.json"
|
|
log_info "Prepared Firebase configs for standard(debug) and googleplay(release) flavors."
|
|
}
|
|
|
|
check_requirements() {
|
|
log_info "Checking requirements..."
|
|
|
|
# Check if we're in the right directory
|
|
if [[ ! -f "app/build.gradle" ]]; then
|
|
log_error "This script must be run from the project root directory"
|
|
exit 1
|
|
fi
|
|
|
|
# Check Java version
|
|
java_version=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d'.' -f1)
|
|
if [[ "$java_version" -lt 11 ]]; then
|
|
log_error "Java 11 or higher is required. Current version: $java_version"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if Gradle wrapper exists
|
|
if [[ ! -f "gradlew" ]]; then
|
|
log_error "Gradle wrapper not found. Please run 'gradle wrapper' first."
|
|
exit 1
|
|
fi
|
|
|
|
# Check signing configuration for Google Play upload.
|
|
# Build of Google Play AAB can still proceed without signing env vars.
|
|
if [[ -z "$KEYSTORE_FILE" || -z "$KEYSTORE_PASSWORD" || -z "$KEY_ALIAS" || -z "$KEY_PASSWORD" ]]; then
|
|
log_warning "Signing configuration not found. Google Play upload will be skipped."
|
|
SKIP_GOOGLE_PLAY_UPLOAD=true
|
|
unset KEYSTORE_FILE KEYSTORE_PASSWORD KEY_ALIAS KEY_PASSWORD
|
|
elif [[ ! -f "$KEYSTORE_FILE" ]]; then
|
|
log_warning "Signing keystore not found at $KEYSTORE_FILE. Building local AAB without signing and skipping Google Play upload."
|
|
SKIP_GOOGLE_PLAY_UPLOAD=true
|
|
unset KEYSTORE_FILE KEYSTORE_PASSWORD KEY_ALIAS KEY_PASSWORD
|
|
else
|
|
SKIP_GOOGLE_PLAY_UPLOAD=false
|
|
fi
|
|
|
|
if [[ "$ENABLE_FIREBASE" == "true" ]]; then
|
|
prepare_google_services_config
|
|
if has_google_services_config; then
|
|
STANDARD_GRADLE_ARGS+=("-PENABLE_FIREBASE=true")
|
|
GOOGLEPLAY_GRADLE_ARGS+=("-PENABLE_FIREBASE=true")
|
|
log_info "Firebase enabled for standardDebug and Google Play bundle builds."
|
|
else
|
|
log_error "ENABLE_FIREBASE=true but no google-services.json found."
|
|
log_error "Add app/google-services.json (or flavor-specific config) before release."
|
|
exit 1
|
|
fi
|
|
else
|
|
log_info "Firebase disabled by configuration for local release build."
|
|
fi
|
|
|
|
log_success "Requirements check completed"
|
|
}
|
|
|
|
clean_build() {
|
|
log_info "Cleaning previous builds..."
|
|
./gradlew clean
|
|
rm -rf "$OUTPUT_DIR"
|
|
mkdir -p "$OUTPUT_DIR" "$CDN_UPLOAD_DIR" "$GOOGLE_PLAY_DIR"
|
|
log_success "Clean completed"
|
|
}
|
|
|
|
build_standard_debug() {
|
|
log_info "Building standard flavor debug version..."
|
|
|
|
./gradlew "${STANDARD_GRADLE_ARGS[@]}" assembleStandardDebug
|
|
|
|
# Generate version-based filename (replace dots with underscores)
|
|
VERSION_FILENAME=$(echo "$VERSION_NAME" | sed 's/\./_/g')
|
|
APK_FILENAME="mnn_chat_${VERSION_FILENAME}.apk"
|
|
|
|
# Copy APK to output directory with version-based name
|
|
APK_PATH="$BUILD_DIR/outputs/apk/standard/debug/app-standard-debug.apk"
|
|
if [[ -f "$APK_PATH" ]]; then
|
|
cp "$APK_PATH" "$CDN_UPLOAD_DIR/$APK_FILENAME"
|
|
log_success "Standard debug APK built: $CDN_UPLOAD_DIR/$APK_FILENAME"
|
|
else
|
|
log_error "Standard debug APK not found at $APK_PATH"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
build_googleplay_release_bundle() {
|
|
log_info "Building Google Play flavor release AAB..."
|
|
./gradlew "${GOOGLEPLAY_GRADLE_ARGS[@]}" bundleGoogleplayRelease
|
|
|
|
# Copy AAB to output directory
|
|
AAB_PATH="$BUILD_DIR/outputs/bundle/googleplayRelease/app-googleplay-release.aab"
|
|
if [[ -f "$AAB_PATH" ]]; then
|
|
cp "$AAB_PATH" "$GOOGLE_PLAY_DIR/"
|
|
log_success "Google Play release AAB built: $GOOGLE_PLAY_DIR/app-googleplay-release.aab"
|
|
else
|
|
log_warning "Google Play release AAB not found at $AAB_PATH"
|
|
fi
|
|
}
|
|
|
|
upload_to_cdn() {
|
|
if [[ -z "$CDN_ENDPOINT" || -z "$CDN_ACCESS_KEY" || -z "$CDN_SECRET_KEY" || -z "$CDN_BUCKET" ]]; then
|
|
log_warning "CDN configuration not found. Skipping CDN upload."
|
|
return
|
|
fi
|
|
|
|
log_info "Uploading to CDN (ali-oss SDK)..."
|
|
|
|
# Ensure Node.js and dependencies are available
|
|
if ! command -v node &> /dev/null; then
|
|
log_warning "Node.js not found. Install Node.js to enable automatic CDN upload."
|
|
return
|
|
fi
|
|
|
|
if [[ ! -d "node_modules/ali-oss" ]]; then
|
|
log_info "Installing ali-oss for CDN upload..."
|
|
npm install --no-save ali-oss
|
|
fi
|
|
|
|
VERSION_FILENAME=$(echo "$VERSION_NAME" | sed 's/\./_/g')
|
|
APK_FILENAME="mnn_chat_${VERSION_FILENAME}.apk"
|
|
APK_FILE="$CDN_UPLOAD_DIR/$APK_FILENAME"
|
|
|
|
if [[ ! -f "$APK_FILE" ]]; then
|
|
log_error "APK file not found for CDN upload: $APK_FILE"
|
|
return
|
|
fi
|
|
|
|
if node scripts/upload-cdn.mjs --apk "$APK_FILE" --version "$VERSION_NAME"; then
|
|
OSS_PREFIX="${CDN_OSS_PREFIX:-data/mnn/apks}"
|
|
log_success "APK uploaded to CDN: oss://$CDN_BUCKET/$OSS_PREFIX/$APK_FILENAME"
|
|
else
|
|
log_error "CDN upload failed"
|
|
fi
|
|
}
|
|
|
|
upload_to_google_play() {
|
|
if [[ "$SKIP_GOOGLE_PLAY_UPLOAD" == "true" ]]; then
|
|
log_warning "Skipping Google Play upload due to missing signing configuration"
|
|
return
|
|
fi
|
|
|
|
if [[ -z "$GOOGLE_PLAY_SERVICE_ACCOUNT" ]]; then
|
|
log_warning "Google Play service account not configured. Skipping Google Play upload."
|
|
return
|
|
fi
|
|
|
|
log_info "Uploading to Google Play..."
|
|
|
|
# Check if fastlane is available
|
|
if ! command -v fastlane &> /dev/null; then
|
|
log_warning "fastlane not found. Please install it to upload to Google Play."
|
|
log_info "You can install fastlane with: gem install fastlane"
|
|
return
|
|
fi
|
|
|
|
# Create fastlane configuration if it doesn't exist
|
|
FASTLANE_DIR="$PROJECT_DIR/fastlane"
|
|
if [[ ! -d "$FASTLANE_DIR" ]]; then
|
|
mkdir -p "$FASTLANE_DIR"
|
|
cat > "$FASTLANE_DIR/Appfile" << EOF
|
|
json_key_file("$GOOGLE_PLAY_SERVICE_ACCOUNT")
|
|
package_name("$GOOGLE_PLAY_PACKAGE_NAME")
|
|
EOF
|
|
|
|
cat > "$FASTLANE_DIR/Fastfile" << EOF
|
|
default_platform(:android)
|
|
|
|
platform :android do
|
|
desc "Upload to Google Play"
|
|
lane :upload do
|
|
upload_to_play_store(
|
|
track: 'internal',
|
|
aab: '../release_outputs/googleplay/app-googleplay-release.aab'
|
|
)
|
|
end
|
|
end
|
|
EOF
|
|
fi
|
|
|
|
# Upload to Google Play
|
|
cd "$FASTLANE_DIR"
|
|
fastlane upload
|
|
cd "$PROJECT_DIR"
|
|
|
|
log_success "App uploaded to Google Play"
|
|
}
|
|
|
|
generate_release_notes() {
|
|
log_info "Generating release notes..."
|
|
|
|
# Generate version-based filename for documentation
|
|
VERSION_FILENAME=$(echo "$VERSION_NAME" | sed 's/\./_/g')
|
|
APK_FILENAME="mnn_chat_${VERSION_FILENAME}.apk"
|
|
|
|
RELEASE_NOTES_FILE="$OUTPUT_DIR/release_notes.md"
|
|
local previous_version_resolved="${PREVIOUS_VERSION:-$(detect_previous_version)}"
|
|
|
|
cat > "$RELEASE_NOTES_FILE" << EOF
|
|
# Release Notes - $PROJECT_NAME v$VERSION_NAME
|
|
|
|
## Version Information
|
|
- **Version Name**: $VERSION_NAME
|
|
- **Version Code**: $VERSION_CODE
|
|
- **Build Date**: $BUILD_DATE
|
|
- **Build Type**: Release
|
|
|
|
## Build Outputs
|
|
|
|
### Standard Flavor (Debug)
|
|
- **APK**: \`$APK_FILENAME\`
|
|
- **Purpose**: CDN distribution
|
|
- **Location**: \`$CDN_UPLOAD_DIR/\`
|
|
|
|
### Google Play Flavor (Release)
|
|
- **AAB**: \`app-googleplay-release.aab\`
|
|
- **Purpose**: Google Play Store distribution
|
|
- **Location**: \`$GOOGLE_PLAY_DIR/\`
|
|
|
|
## Build Configuration
|
|
- **Min SDK**: 26
|
|
- **Target SDK**: 35
|
|
- **Compile SDK**: 35
|
|
- **ABI**: arm64-v8a
|
|
|
|
## Notes
|
|
- Standard flavor includes debug features and is suitable for testing
|
|
- Google Play flavor is optimized for production and follows Google Play guidelines
|
|
- Local Google Play AAB build can run without signing env vars; store upload still requires signing + service account
|
|
|
|
EOF
|
|
|
|
echo "" >> "$RELEASE_NOTES_FILE"
|
|
echo "## Release Highlights" >> "$RELEASE_NOTES_FILE"
|
|
if [[ -n "$RELEASE_HIGHLIGHTS" ]]; then
|
|
IFS='|' read -r -a highlight_items <<< "$RELEASE_HIGHLIGHTS"
|
|
for item in "${highlight_items[@]}"; do
|
|
echo "- $item" >> "$RELEASE_NOTES_FILE"
|
|
done
|
|
else
|
|
echo "- Release highlights were not provided." >> "$RELEASE_NOTES_FILE"
|
|
if [[ -n "$previous_version_resolved" ]]; then
|
|
log_warning "RELEASE_HIGHLIGHTS is empty. Provide highlights that summarize the delta from v$previous_version_resolved."
|
|
else
|
|
log_warning "RELEASE_HIGHLIGHTS is empty. Release notes were generated without a curated change summary."
|
|
fi
|
|
fi
|
|
|
|
log_success "Release notes generated: $RELEASE_NOTES_FILE"
|
|
}
|
|
|
|
main() {
|
|
log_info "Starting release process for $PROJECT_NAME v$VERSION_NAME"
|
|
|
|
# Check requirements
|
|
check_requirements
|
|
|
|
# Clean previous builds
|
|
clean_build
|
|
|
|
# Build standard debug version
|
|
build_standard_debug
|
|
|
|
# Build Google Play release AAB
|
|
build_googleplay_release_bundle
|
|
|
|
# Upload to CDN
|
|
upload_to_cdn
|
|
|
|
# Upload to Google Play
|
|
upload_to_google_play
|
|
|
|
# Generate release notes
|
|
generate_release_notes
|
|
|
|
log_success "Release process completed successfully!"
|
|
log_info "Output directory: $OUTPUT_DIR"
|
|
log_info "CDN uploads: $CDN_UPLOAD_DIR"
|
|
log_info "Google Play uploads: $GOOGLE_PLAY_DIR"
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|