Windows 11에서 Android 앱 개발부터 Google Play Store 배포까지의 완전 자동화 가이드
opencode를 내pc(windows11) 에 설치하고 안드로이드 앱 시장수요조사 -> 앱개발 -> 빌드 및테스트 ->검수 및 구글스토어 필요 파일 생성 -> 구글스토어 업로드 까지 자동화 하기위한 방법을 상세히 하나한 알려줘
이제 정보가 충분하므로 상세한 가이드를 작성하겠습니다.
Windows 11에서 Android 앱 개발부터 Google Play Store 배포까지의 완전 자동화 가이드
📋 목차
- 환경 설정
- OpenCode 설치 및 구성
- Android 개발 환경 구축
- 시장수요조사 자동화
- Gradle 기반 빌드 자동화
- CI/CD 파이프라인 구성
- Google Play Store 배포 자동화
1. 환경 설정
1.1 필수 요구사항 설치
PowerShell 관리자 권한으로 실행:
# Chocolatey 설치 (Windows 패키지 관리자)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
# 필수 도구 설치
choco install jdk17 -y
choco install android-sdk -y
choco install android-studio -y
choco install git -y
choco install nodejs -y
choco install python -y
수동 설치 (권장):
- JDK 17 설치
- https://www.oracle.com/java/technologies/downloads/#java17
- 설치 후 환경변수 설정:
JAVA_HOME=C:\Program Files\Java\jdk-17.x.x
- Android SDK 설치
- Android Studio 설치: https://developer.android.com/studio
- SDK Manager에서 필요한 버전 설치
- Git 설치
2. OpenCode 설치 및 구성
2.1 OpenCode CLI 설치
# Node.js를 통한 설치
npm install -g opencode
# 버전 확인
opencode --version
# Windows용 OpenCode TUI (Terminal User Interface) 설치
npm install -g opencode-tui
2.2 OpenCode 초기 설정
# OpenCode 프로젝트 디렉토리 생성
mkdir my-android-project
cd my-android-project
# Git 저장소 초기화
git init
# OpenCode 초기화
opencode init
# AGENTS.md 파일이 생성됨 (프로젝트 구조 분석)
2.3 OpenCode 에이전트 구성
AGENTS.md 파일 커스터마이징:
# Android App Development Pipeline
## Project Structure
- /app: Android 애플리케이션 소스 코드
- /app/build.gradle: 앱 빌드 설정
- /app/src/main: 메인 소스 코드
- /scripts: 자동화 스크립트
## Build & Deploy Agents
### 1. Market Research Agent
- 역할: 앱스토어 데이터 분석, 경쟁사 분석
- 스크립트 위치: /scripts/market_research.py
### 2. Build Agent
- 역할: Gradle 빌드 자동화
- 명령어: ./gradlew build bundleRelease
### 3. Test & QA Agent
- 역할: 단위 테스트, 통합 테스트 실행
- 명령어: ./gradlew test connectedAndroidTest
### 4. Store Upload Agent
- 역할: Google Play Console에 자동 업로드
- 스크립트 위치: /scripts/upload_gplay.py
3. Android 개발 환경 구축
3.1 새 Android 프로젝트 생성
# Android Studio에서 또는 CLI로 프로젝트 생성
# 프로젝트 구조
my-android-project/
├── app/
│ ├── build.gradle
│ ├── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ └── res/
│ └── proguard-rules.pro
├── build.gradle (Top-level)
├── gradle.properties
├── settings.gradle
└── gradlew.bat (Windows용 Gradle 래퍼)
3.2 build.gradle 설정 (Module: app)
plugins {
id 'com.android.application'
}
android {
compileSdk 34
defaultConfig {
applicationId "com.example.myapp"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0.0"
}
signingConfigs {
release {
storeFile file("keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
debuggable true
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
}
4. 시장수요조사 자동화
4.1 Python 기반 시장분석 스크립트
scripts/market_research.py:
#!/usr/bin/env python3
"""
안드로이드 앱 시장수요조사 자동화 스크립트
- Google Play Store API를 통한 경쟁사 분석
- 앱 카테고리별 트렌드 분석
- 사용자 리뷰 감정 분석
"""
import os
import json
from datetime import datetime
from google.play import googleplay
import pandas as pd
from textblob import TextBlob
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class MarketResearchAgent:
def __init__(self, app_category, keywords):
self.app_category = app_category
self.keywords = keywords
self.research_data = {}
self.api = googleplay.GooglePlayAPI()
def analyze_competitor_apps(self, max_results=10):
"""경쟁사 앱 분석"""
logger.info(f"Analyzing top {max_results} competitors in '{self.app_category}'")
competitors = []
for keyword in self.keywords:
results = self.api.search(keyword, category=self.app_category, limit=max_results)
for app in results:
app_data = {
'package_name': app['packageName'],
'title': app['title'],
'version': app['versionString'],
'rating': app['aggregateRating'],
'review_count': app.get('reviewCount', 0),
'installs': app.get('installs', 'N/A'),
'last_update': app.get('lastUpdateTime', 'N/A'),
'size': app.get('size', 'N/A'),
'content_rating': app.get('contentRating', 'N/A')
}
competitors.append(app_data)
# 중복 제거 및 정렬
unique_competitors = list({c['package_name']: c for c in competitors}.values())
unique_competitors.sort(key=lambda x: x['rating'], reverse=True)
self.research_data['competitors'] = unique_competitors
return unique_competitors
def analyze_sentiment_from_reviews(self, package_name, limit=100):
"""사용자 리뷰 감정분석"""
logger.info(f"Analyzing reviews for {package_name}")
reviews = self.api.get_reviews(package_name, limit=limit)
sentiments = {
'positive': 0,
'neutral': 0,
'negative': 0,
'reviews': []
}
for review in reviews:
blob = TextBlob(review['text'])
polarity = blob.sentiment.polarity
if polarity > 0.1:
sentiments['positive'] += 1
elif polarity < -0.1:
sentiments['negative'] += 1
else:
sentiments['neutral'] += 1
sentiments['reviews'].append({
'rating': review['score'],
'text': review['text'],
'sentiment_score': polarity
})
return sentiments
def market_trend_analysis(self):
"""시장 트렌드 분석"""
logger.info("Analyzing market trends")
competitors = self.research_data.get('competitors', [])
trends = {
'avg_rating': sum(c['rating'] for c in competitors) / len(competitors) if competitors else 0,
'total_installs': sum(
int(str(c['installs']).replace('+', '').replace('M', '000000').replace('K', '000'))
for c in competitors if c['installs'] != 'N/A'
),
'categories': list(set(c.get('category', 'Unknown') for c in competitors)),
'recommendation': self._generate_recommendation(competitors)
}
return trends
def _generate_recommendation(self, competitors):
"""앱 개발 권장사항 생성"""
if not competitors:
return "데이터 부족으로 권장사항을 제공할 수 없습니다."
avg_rating = sum(c['rating'] for c in competitors) / len(competitors)
recommendations = []
if avg_rating > 4.0:
recommendations.append("높은 평가 경쟁사들이 있습니다. 차별화된 기능 개발 필요")
else:
recommendations.append("낮은 평가 앱들이 많습니다. 품질 중심의 개발 가능")
if any(c['review_count'] > 100000 for c in competitors):
recommendations.append("인기 앱들이 많습니다. 강력한 마케팅 전략 필요")
return "; ".join(recommendations)
def generate_report(self, output_file="market_research_report.json"):
"""최종 보고서 생성"""
logger.info("Generating market research report")
report = {
'timestamp': datetime.now().isoformat(),
'category': self.app_category,
'keywords': self.keywords,
'competitors': self.research_data.get('competitors', []),
'market_trends': self.market_trend_analysis(),
'recommendations': self._generate_recommendation(self.research_data.get('competitors', []))
}
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
logger.info(f"Report saved to {output_file}")
return report
# 사용 예시
if __name__ == "__main__":
agent = MarketResearchAgent(
app_category="PRODUCTIVITY",
keywords=["todo", "task manager", "productivity"]
)
# 1. 경쟁사 분석
competitors = agent.analyze_competitor_apps(max_results=15)
logger.info(f"Found {len(competitors)} competitor apps")
# 2. 감정분석
if competitors:
sentiment = agent.analyze_sentiment_from_reviews(competitors[0]['package_name'])
logger.info(f"Sentiment Analysis: Positive={sentiment['positive']}, Neutral={sentiment['neutral']}, Negative={sentiment['negative']}")
# 3. 보고서 생성
report = agent.generate_report()
print(json.dumps(report, ensure_ascii=False, indent=2))
실행 방법:
pip install google-play-scraper textblob pandas
python scripts/market_research.py
5. Gradle 기반 빌드 자동화
5.1 커스텀 Gradle 태스크 생성
build.gradle (Module: app)에 추가:
// 커스텀 빌드 타스크
task generateVersionCode {
doLast {
def versionCode = (int)(System.currentTimeMillis() / 1000)
println("Generated Version Code: $versionCode")
// 파일에 저장
file('version.properties').text = "versionCode=$versionCode\n"
}
}
// 자동 버전 증가
task incrementVersionCode {
doLast {
def versionProps = file('version.properties')
def properties = new Properties()
properties.load(new FileInputStream(versionProps))
def currentVersion = (properties.getProperty('versionCode', '1') as int) + 1
properties.setProperty('versionCode', currentVersion.toString())
properties.store(versionProps.newWriter(), null)
println("Version incremented to: $currentVersion")
}
}
// 릴리스 빌드 태스크
task buildReleaseBundle {
dependsOn 'incrementVersionCode'
dependsOn 'bundleRelease'
doLast {
println("Release bundle created successfully!")
println("Output: ${buildDir}/outputs/bundle/release/app-release.aab")
}
}
// 빌드 전 사전검사
task preBuildCheck {
doLast {
println("Checking build prerequisites...")
// SDK 버전 확인
println("✓ SDK Version: 34")
// Keystore 확인
if (file('keystore.jks').exists()) {
println("✓ Keystore found")
} else {
println("✗ Keystore not found - generating new keystore...")
exec {
commandLine 'keytool', '-genkey', '-v',
'-keystore', 'keystore.jks',
'-keyalg', 'RSA',
'-keysize', '2048',
'-validity', '10000'
}
}
}
}
// 통합 빌드 파이프라인
task automate {
dependsOn 'preBuildCheck'
dependsOn 'test'
dependsOn 'buildReleaseBundle'
doLast {
println("===== AUTOMATION COMPLETE =====")
println("✓ Pre-build checks passed")
println("✓ Tests completed")
println("✓ Release bundle created")
}
}
5.2 명령어 라인 빌드
# 디버그 APK 빌드
gradlew assembleDebug
# 릴리스 AAB(App Bundle) 빌드
gradlew bundleRelease
# 테스트 실행
gradlew test
# 커스텀 자동화 태스크 실행
gradlew automate
# 전체 빌드 및 정렬
gradlew clean build
6. CI/CD 파이프라인 구성
6.1 GitHub Actions 기반 자동화
.github/workflows/android-build-deploy.yml:
name: Android Build & Deploy to Google Play
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
JAVA_VERSION: 17
GRADLE_VERSION: 8.0
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Market Research Analysis
run: |
pip install google-play-scraper textblob pandas
python scripts/market_research.py > market_report.txt
cat market_report.txt
- name: Build Debug APK
run: ./gradlew assembleDebug
- name: Run unit tests
run: ./gradlew test
- name: Run connected tests (Android Emulator)
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
script: ./gradlew connectedAndroidTest
- name: Build Release Bundle (AAB)
run: ./gradlew bundleRelease
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Sign Release Bundle
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks
./gradlew bundleRelease --build-file=app/build.gradle
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: release-bundle
path: app/build/outputs/bundle/release/app-release.aab
- name: Upload Debug APK
uses: actions/upload-artifact@v3
with:
name: debug-apk
path: app/build/outputs/apk/debug/app-debug.apk
deploy-to-play-store:
runs-on: ubuntu-latest
needs: build-and-test
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download release bundle
uses: actions/download-artifact@v3
with:
name: release-bundle
- name: Deploy to Google Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT }}
packageName: com.example.myapp
releaseFiles: 'app-release.aab'
track: internal
status: inProgress
- name: Slack Notification
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'App deployment to Play Store: ${{ job.status }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
6.2 로컬 자동화 배치 스크립트 (Windows)
build-and-deploy.bat:
@echo off
setlocal enabledelayedexpansion
REM ===== Android App Build & Deploy Automation =====
REM Windows 11용 자동화 스크립트
echo [%date% %time%] Starting Android Build & Deploy Pipeline...
REM 1. 시장수요조사
echo.
echo ===== 1. Market Research Phase =====
python scripts/market_research.py > market_research_report.txt
if %errorlevel% neq 0 (
echo Market research failed!
exit /b 1
)
echo Market research completed successfully
REM 2. 사전검사
echo.
echo ===== 2. Pre-Build Checks =====
if not exist "keystore.jks" (
echo Generating keystore...
keytool -genkey -v -keystore keystore.jks ^
-keyalg RSA -keysize 2048 -validity 10000 ^
-alias release-key -dname "CN=MyApp"
)
echo Pre-build checks completed
REM 3. 빌드
echo.
echo ===== 3. Build Phase =====
set JAVA_HOME=C:\Program Files\Java\jdk-17.x.x
set ANDROID_HOME=C:\Users\%USERNAME%\AppData\Local\Android\Sdk
call gradlew.bat clean
if %errorlevel% neq 0 exit /b 1
REM 테스트 실행
echo Running unit tests...
call gradlew.bat test
if %errorlevel% neq 0 (
echo Tests failed!
exit /b 1
)
REM 디버그 빌드
echo Building debug APK...
call gradlew.bat assembleDebug
if %errorlevel% neq 0 exit /b 1
REM 릴리스 빌드
echo Building release AAB...
set KEYSTORE_PASSWORD=%1
set KEY_ALIAS=%2
set KEY_PASSWORD=%3
if [%KEYSTORE_PASSWORD%]==[] (
echo Error: Keystore password required
echo Usage: build-and-deploy.bat keystore_pwd key_alias key_pwd
exit /b 1
)
call gradlew.bat bundleRelease ^
-Pandroid.injected.signing.store.password=%KEYSTORE_PASSWORD% ^
-Pandroid.injected.signing.key.alias=%KEY_ALIAS% ^
-Pandroid.injected.signing.key.password=%KEY_PASSWORD%
if %errorlevel% neq 0 exit /b 1
REM 4. 검수
echo.
echo ===== 4. Quality Assurance Phase =====
echo Analyzing APK/Bundle...
REM APK 분석 도구 실행 (bundletool)
bundletool validate --bundle-path=app\build\outputs\bundle\release\app-release.aab
if %errorlevel% neq 0 (
echo Bundle validation failed!
exit /b 1
)
REM 5. Google Play Store 필수 파일 생성
echo.
echo ===== 5. Generating Required Files for Play Store =====
echo Creating play store listing files...
REM 메타데이터 파일 생성
(
echo {
echo "title": "My Application",
echo "shortDescription": "Short app description",
echo "fullDescription": "Full app description with features and details",
echo "appCategory": "PRODUCTIVITY",
echo "contentRating": "4",
echo "appContentRatingRating": "EVERYONE"
echo }
) > metadata.json
REM 스크린샷 및 그래픽 확인
if not exist "graphics" mkdir graphics
echo Check graphics folder for screenshots, icons, and feature graphics
REM 6. Google Play Store 업로드
echo.
echo ===== 6. Upload to Google Play Store =====
echo Preparing upload...
REM fastlane를 통한 자동 업로드
if exist "Fastfile" (
echo Running fastlane...
call fastlane android upload_to_play_store ^
package: com.example.myapp ^
track: internal
) else (
echo Using manual upload method...
echo Please upload manually:
echo - File: app\build\outputs\bundle\release\app-release.aab
echo - Metadata: metadata.json
echo - Graphics: graphics folder
pause
)
REM 7. 완료 및 보고
echo.
echo ===== 7. Completion Report =====
echo [%date% %time%] Build & Deploy completed successfully!
echo.
echo Generated artifacts:
echo - Debug APK: app\build\outputs\apk\debug\app-debug.apk
echo - Release AAB: app\build\outputs\bundle\release\app-release.aab
echo - Market Report: market_research_report.txt
echo.
pause
7. Google Play Store 배포 자동화
7.1 Fastlane 설치 및 설정
# Fastlane 설치
gem install fastlane
# 프로젝트에서 fastlane 초기화
fastlane init android
7.2 Fastfile 설정
android/fastlane/Fastfile:
default_platform(:android)
platform :android do
desc "Build and upload to Google Play Store"
lane :deploy do |options|
# 1. 빌드
gradle(
task: "clean bundle",
project_dir: "android/",
properties: {
"android.injected.signing.store.file" => "keystore.jks",
"android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
"android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
}
)
# 2. 메타데이터 준비
upload_to_play_store(
package_name: "com.example.myapp",
json_key_data: ENV["PLAY_STORE_CREDENTIALS"],
aab: "android/app/build/outputs/bundle/release/app-release.aab",
track: options[:track] || "internal",
skip_upload_screenshots: false,
skip_upload_images: false,
metadata_path: "android/fastlane/metadata/"
)
# 3. 완료 알림
slack(
message: "✅ App successfully deployed to #{options[:track] || 'internal'} track",
success: true
)
end
desc "Internal testing build"
lane :internal do
deploy(track: "internal")
end
desc "Beta testing build"
lane :beta do
deploy(track: "beta")
end
desc "Production release"
lane :production do
deploy(track: "production")
end
end
7.3 Google Play Service Account 설정
Google Cloud Console에서:
# 1. 서비스 계정 생성
# https://console.cloud.google.com/ → 서비스 계정 → 새 서비스 계정
# 2. JSON 키 다운로드
# 다운로드된 파일을 GitHub Secrets에 추가
# GitHub Secrets 설정:
PLAY_STORE_SERVICE_ACCOUNT = [전체 JSON 내용]
KEYSTORE_PASSWORD = [keystore 비밀번호]
KEY_ALIAS = release-key
KEY_PASSWORD = [키 비밀번호]
7.4 필수 Play Store 메타데이터 준비
android/fastlane/metadata/android/ 디렉토리 구조:
metadata/
├── ko/
│ ├── short_description.txt (80자 이내)
│ ├── full_description.txt (4000자 이내)
│ ├── title.txt (50자 이내)
│ ├── video.txt (유튜브 링크)
│ └── images/
│ ├── icon.png (512x512)
│ ├── feature_graphic.png (1024x500)
│ ├── screenshots/
│ │ ├── phone_1.png (최소 540x720)
│ │ └── phone_2.png
│ └── tv_screenshots/
├── en/
│ └── (영문 메타데이터)
└── changelogs/
├── default.txt
├── ko.txt
└── en.txt
short_description.txt:
효율적인 작업 관리를 위한 간편한 앱입니다.
full_description.txt:
주요 기능:
- 직관적인 UI
- 실시간 동기화
- 오프라인 지원
- 멀티 디바이스 동기화
릴리스 노트:
- 버전 1.0.0: 초기 출시
8. 통합 자동화 스크립트 (Python)
automation_pipeline.py - 전체 파이프라인 통합 스크립트:
#!/usr/bin/env python3
"""
완전 자동화 Android 배포 파이프라인
모든 단계를 자동으로 관리합니다
"""
import os
import sys
import subprocess
import json
import time
from datetime import datetime
from pathlib import Path
import logging
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='[%(asctime)s] %(levelname)s: %(message)s',
handlers=[
logging.FileHandler('build_log.txt'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class AndroidAutomationPipeline:
def __init__(self, config_file='pipeline_config.json'):
self.config = self.load_config(config_file)
self.start_time = time.time()
self.results = {}
def load_config(self, config_file):
"""설정 파일 로드"""
if not os.path.exists(config_file):
logger.warning(f"Config file not found: {config_file}")
return self.get_default_config()
with open(config_file, 'r') as f:
return json.load(f)
def get_default_config(self):
"""기본 설정"""
return {
'package_name': 'com.example.myapp',
'app_name': 'My Application',
'keystore_path': 'keystore.jks',
'gradle_wrapper': './gradlew' if sys.platform != 'win32' else '.\\gradlew.bat',
'play_store_track': 'internal',
'enable_market_research': True,
'enable_tests': True,
'enable_upload': False,
'slack_webhook': os.getenv('SLACK_WEBHOOK_URL', '')
}
def run_step(self, step_name, command, env=None):
"""단계별 명령어 실행"""
logger.info(f"Running: {step_name}")
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
env={**os.environ, **(env or {})}
)
if result.returncode == 0:
logger.info(f"✓ {step_name} completed successfully")
self.results[step_name] = {'status': 'success', 'output': result.stdout}
return True
else:
logger.error(f"✗ {step_name} failed: {result.stderr}")
self.results[step_name] = {'status': 'failed', 'error': result.stderr}
return False
except Exception as e:
logger.error(f"✗ {step_name} exception: {str(e)}")
self.results[step_name] = {'status': 'error', 'error': str(e)}
return False
def phase_1_market_research(self):
"""Phase 1: 시장수요조사"""
logger.info("\n========== PHASE 1: MARKET RESEARCH ==========")
if not self.config['enable_market_research']:
logger.info("Market research disabled")
return True
return self.run_step(
"Market Research",
f"python scripts/market_research.py"
)
def phase_2_prebuild_checks(self):
"""Phase 2: 사전검사"""
logger.info("\n========== PHASE 2: PRE-BUILD CHECKS ==========")
# 필수 파일 확인
required_files = ['build.gradle', 'AndroidManifest.xml']
for file in required_files:
if not os.path.exists(file):
logger.error(f"Missing required file: {file}")
return False
logger.info("✓ All required files present")
# Keystore 확인/생성
if not os.path.exists(self.config['keystore_path']):
logger.warning("Keystore not found, generating new keystore...")
return self.run_step(
"Generate Keystore",
f"keytool -genkey -v -keystore {self.config['keystore_path']} "
f"-keyalg RSA -keysize 2048 -validity 10000 -alias release-key"
)
logger.info(f"✓ Keystore found: {self.config['keystore_path']}")
return True
def phase_3_build(self):
"""Phase 3: 빌드"""
logger.info("\n========== PHASE 3: BUILD PHASE ==========")
gradle = self.config['gradle_wrapper']
# 정리
if not self.run_step("Clean", f"{gradle} clean"):
return False
# 테스트
if self.config['enable_tests']:
if not self.run_step("Unit Tests", f"{gradle} test"):
logger.warning("Tests failed, continuing anyway...")
# 디버그 빌드
if not self.run_step("Debug Build", f"{gradle} assembleDebug"):
return False
# 릴리스 빌드
env = {
'KEYSTORE_PASSWORD': os.getenv('KEYSTORE_PASSWORD', ''),
'KEY_ALIAS': os.getenv('KEY_ALIAS', 'release-key'),
'KEY_PASSWORD': os.getenv('KEY_PASSWORD', '')
}
return self.run_step("Release Build", f"{gradle} bundleRelease", env=env)
def phase_4_qa(self):
"""Phase 4: 검수"""
logger.info("\n========== PHASE 4: QUALITY ASSURANCE ==========")
# Bundle 검증
bundle_path = 'app/build/outputs/bundle/release/app-release.aab'
if not os.path.exists(bundle_path):
logger.error(f"Bundle not found: {bundle_path}")
return False
logger.info(f"✓ Bundle found: {bundle_path}")
# 파일 크기 확인
bundle_size = os.path.getsize(bundle_path) / (1024 * 1024)
logger.info(f"Bundle size: {bundle_size:.2f} MB")
if bundle_size > 150: # Google Play 최대 크기
logger.error("Bundle size exceeds 150MB limit")
return False
# 메타데이터 검증
metadata_required = {
'short_description.txt': 80,
'full_description.txt': 4000,
'title.txt': 50
}
for meta_file, max_size in metadata_required.items():
path = f'metadata/{meta_file}'
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
if len(content) <= max_size:
logger.info(f"✓ {meta_file}: {len(content)}/{max_size} chars")
else:
logger.warning(f"⚠ {meta_file} exceeds recommended size")
logger.info("✓ QA checks completed")
return True
def phase_5_generate_store_files(self):
"""Phase 5: Google Play Store 파일 생성"""
logger.info("\n========== PHASE 5: GENERATE STORE FILES ==========")
os.makedirs('generated_store_files', exist_ok=True)
# Store Listing JSON
store_listing = {
'title': self.config['app_name'],
'shortDescription': 'Short description',
'fullDescription': 'Full description with features',
'category': 'PRODUCTIVITY',
'contentRating': '4',
'targetAudience': 'EVERYONE',
'releaseNotes': f'Release {datetime.now().strftime("%Y.%m.%d")}'
}
store_file = 'generated_store_files/store_listing.json'
with open(store_file, 'w', encoding='utf-8') as f:
json.dump(store_listing, f, ensure_ascii=False, indent=2)
logger.info(f"✓ Generated: {store_file}")
# 메타데이터 목록
files_info = {
'bundle': 'app/build/outputs/bundle/release/app-release.aab',
'store_listing': store_file,
'generated_at': datetime.now().isoformat()
}
info_file = 'generated_store_files/files_info.json'
with open(info_file, 'w', encoding='utf-8') as f:
json.dump(files_info, f, indent=2)
logger.info(f"✓ Generated: {info_file}")
return True
def phase_6_upload(self):
"""Phase 6: Play Store 업로드"""
logger.info("\n========== PHASE 6: UPLOAD TO PLAY STORE ==========")
if not self.config['enable_upload']:
logger.info("Upload disabled in configuration")
return True
# Fastlane을 통한 업로드
track = self.config['play_store_track']
return self.run_step(
f"Upload to {track} track",
f"fastlane android upload_to_play_store "
f"track:{track} "
f"package:{self.config['package_name']}"
)
def send_notification(self, success):
"""Slack 알림 발송"""
if not self.config['slack_webhook']:
return
status = "✅ SUCCESS" if success else "❌ FAILED"
elapsed = time.time() - self.start_time
message = {
'text': f"{status} - Android Deployment Pipeline",
'blocks': [
{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': f"*{status}* - Android App Deployment\n"
f"*App:* {self.config['app_name']}\n"
f"*Time:* {elapsed:.0f}s\n"
f"*Timestamp:* {datetime.now().isoformat()}"
}
}
]
}
try:
import requests
requests.post(self.config['slack_webhook'], json=message)
except:
logger.warning("Failed to send Slack notification")
def generate_report(self):
"""최종 보고서 생성"""
report = {
'timestamp': datetime.now().isoformat(),
'duration_seconds': time.time() - self.start_time,
'app_name': self.config['app_name'],
'package_name': self.config['package_name'],
'results': self.results,
'overall_status': 'SUCCESS' if all(
r['status'] == 'success' for r in self.results.values()
) else 'FAILED'
}
report_file = f"deployment_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
logger.info(f"\n✓ Report saved: {report_file}")
return report
def run_full_pipeline(self):
"""전체 파이프라인 실행"""
logger.info(f"\n{'='*50}")
logger.info("ANDROID AUTOMATION PIPELINE - STARTED")
logger.info(f"{'='*50}\n")
phases = [
self.phase_1_market_research,
self.phase_2_prebuild_checks,
self.phase_3_build,
self.phase_4_qa,
self.phase_5_generate_store_files,
self.phase_6_upload,
]
for phase in phases:
try:
if not phase():
logger.error(f"\n❌ Pipeline stopped at: {phase.__name__}")
self.send_notification(False)
return False
except Exception as e:
logger.error(f"\n❌ Exception in {phase.__name__}: {str(e)}")
self.send_notification(False)
return False
logger.info(f"\n{'='*50}")
logger.info("✅ PIPELINE COMPLETED SUCCESSFULLY")
logger.info(f"{'='*50}\n")
report = self.generate_report()
self.send_notification(True)
return True
if __name__ == "__main__":
pipeline = AndroidAutomationPipeline()
success = pipeline.run_full_pipeline()
sys.exit(0 if success else 1)
9. 설정 파일 샘플
pipeline_config.json:
{
"package_name": "com.example.myapp",
"app_name": "My Application",
"keystore_path": "keystore.jks",
"gradle_wrapper": ".\\gradlew.bat",
"play_store_track": "internal",
"enable_market_research": true,
"enable_tests": true,
"enable_upload": false,
"slack_webhook": "${SLACK_WEBHOOK_URL}",
"market_research": {
"categories": ["PRODUCTIVITY"],
"keywords": ["todo", "task", "productivity"],
"max_competitors": 20
}
}
10. 실행 방법
10.1 첫 번째 실행 (수동)
# 1. 저장소 클론 또는 프로젝트 생성
mkdir my-android-app
cd my-android-app
# 2. 필수 파일 준비
pip install google-play-scraper textblob pandas requests
# 3. Gradle 시스템 준비
chmod +x gradlew # Linux/Mac
# 또는 Windows에서는 gradlew.bat 사용
# 4. 전체 파이프라인 실행
python automation_pipeline.py
10.2 CI/CD를 통한 자동 실행
# GitHub에 푸시하면 자동으로 실행됨
git add .
git commit -m "Add app features"
git push origin main
# 또는 수동 트리거
gh workflow run android-build-deploy.yml
10.3 로컬에서 스케줄링
Windows 작업 스케줄러:
# PowerShell 관리자 권한으로 실행
$action = New-ScheduledTaskAction -Execute "python" -Argument "automation_pipeline.py"
$trigger = New-ScheduledTaskTrigger -Daily -At 02:00AM
Register-ScheduledTask -TaskName "AndroidBuildDeploy" -Action $action -Trigger $trigger
11. 환경변수 설정 (필수)
Windows 환경변수:
KEYSTORE_PASSWORD=your_keystore_password
KEY_ALIAS=release-key
KEY_PASSWORD=your_key_password
JAVA_HOME=C:\Program Files\Java\jdk-17.x.x
ANDROID_HOME=C:\Users\%USERNAME%\AppData\Local\Android\Sdk
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
12. 트러블슈팅
| 문제 | 해결방법 |
|---|---|
| gradlew 명령어 없음 | gradle wrapper 또는 Android Studio에서 생성 |
| Keystore 비밀번호 오류 | 환경변수 정확히 설정, 또는 keytool 재생성 |
| 번들 크기 초과 | 불필요한 의존성 제거, ProGuard 활성화 |
| Play Store 업로드 실패 | 서비스 계정 권한 확인, JSON 키 재발급 |
| 테스트 실패 | 에뮬레이터 실행, connectedAndroidTest 실행 |
이 가이드로 Windows 11에서 완전 자동화된 Android 앱 개발 및 배포 파이프라인을 구성할 수 있습니다. 각 단계별로 질문이 있으시면 알려주세요!