#!/bin/bash # build_java_apk.sh # Java kodunu Android APK'ya derler set -e WORK_DIR="$1" APP_NAME="$2" PKG_NAME="$3" KEYSTORE="$4" ANDROID_SDK=/opt/android-sdk BUILD_TOOLS=$ANDROID_SDK/build-tools/33.0.0 PLATFORM=$ANDROID_SDK/platforms/android-33 echo "[HtStudio Java APK] Başlıyor: $APP_NAME ($PKG_NAME)" # Klasör yapısı mkdir -p "$WORK_DIR/src/$( echo $PKG_NAME | tr '.' '/' )" mkdir -p "$WORK_DIR/res/layout" mkdir -p "$WORK_DIR/res/values" mkdir -p "$WORK_DIR/res/mipmap-mdpi" "$WORK_DIR/res/mipmap-hdpi" "$WORK_DIR/res/mipmap-xhdpi" "$WORK_DIR/res/mipmap-xxhdpi" "$WORK_DIR/res/mipmap-xxxhdpi" mkdir -p "$WORK_DIR/bin" mkdir -p "$WORK_DIR/obj" mkdir -p "$WORK_DIR/gen" mkdir -p "$WORK_DIR/assets" # İkon: kullanıcının logosu varsa onu kullan (process_logo tarafından # $WORK_DIR/icons/mipmap-*/ic_launcher.png içine kaydedilmiş olur), yoksa # varsayılan ikonu kullan. Bu adım eskiden hiç yapılmıyordu, o yüzden Java # APK'lar her zaman sistem varsayılan (Android robotu) ikonuyla çıkıyordu. if [ -f "$WORK_DIR/has_logo" ]; then echo "[HtStudio Java APK] Kullanıcı logosu kullanılıyor" cp "$WORK_DIR/icons/mipmap-mdpi/ic_launcher.png" "$WORK_DIR/res/mipmap-mdpi/ic_launcher.png" cp "$WORK_DIR/icons/mipmap-hdpi/ic_launcher.png" "$WORK_DIR/res/mipmap-hdpi/ic_launcher.png" cp "$WORK_DIR/icons/mipmap-xhdpi/ic_launcher.png" "$WORK_DIR/res/mipmap-xhdpi/ic_launcher.png" cp "$WORK_DIR/icons/mipmap-xxhdpi/ic_launcher.png" "$WORK_DIR/res/mipmap-xxhdpi/ic_launcher.png" cp "$WORK_DIR/icons/mipmap-xxxhdpi/ic_launcher.png" "$WORK_DIR/res/mipmap-xxxhdpi/ic_launcher.png" else echo "[HtStudio Java APK] Varsayılan ikon kullanılıyor" cp /opt/default_icons/mdpi/ic_launcher.png "$WORK_DIR/res/mipmap-mdpi/ic_launcher.png" cp /opt/default_icons/hdpi/ic_launcher.png "$WORK_DIR/res/mipmap-hdpi/ic_launcher.png" cp /opt/default_icons/xhdpi/ic_launcher.png "$WORK_DIR/res/mipmap-xhdpi/ic_launcher.png" cp /opt/default_icons/xxhdpi/ic_launcher.png "$WORK_DIR/res/mipmap-xxhdpi/ic_launcher.png" cp /opt/default_icons/xxxhdpi/ic_launcher.png "$WORK_DIR/res/mipmap-xxxhdpi/ic_launcher.png" fi # Kullanıcının Java kodunu yerleştir. # İki mod var: # 1) "Tam Activity modu" - kullanıcı kendi MainActivity/View/onDraw vs. kodunu # yazmışsa (gerçek Android UI), kodunu direkt MainActivity.java olarak # kullanıyoruz, hiçbir wrapper üretmiyoruz (aksi halde "duplicate class" # hatası alınır çünkü iki MainActivity çakışır). # 2) "Konsol modu" - kullanıcı basit `class Main { main(...) }` yazmışsa, # eskisi gibi çıktısını TextView'da gösteren bir wrapper MainActivity # otomatik üretilir. SRC_DIR="$WORK_DIR/src/$( echo $PKG_NAME | tr '.' '/' )" # Kullanıcının kodundaki `package ...;` satırı proje ayarındaki gerçek paket # adından farklıysa derleme patlar (paket/klasör uyuşmazlığı). Otomatik # eşitliyoruz ki kullanıcı hangi paket adını yazdıysa yazsın çalışsın. if grep -q "^package " "$WORK_DIR/Main.java"; then sed -i "s/^package .*/package ${PKG_NAME};/" "$WORK_DIR/Main.java" else sed -i "1i package ${PKG_NAME};" "$WORK_DIR/Main.java" fi if grep -qE "class[[:space:]]+MainActivity\b" "$WORK_DIR/Main.java"; then echo "[HtStudio Java APK] Tam Activity modu (kullanıcı kendi MainActivity'sini yazmış)" cp "$WORK_DIR/Main.java" "$SRC_DIR/MainActivity.java" else echo "[HtStudio Java APK] Konsol modu (Main.main() çıktısı sarmalanıyor)" cp "$WORK_DIR/Main.java" "$SRC_DIR/Main.java" cat > "$SRC_DIR/MainActivity.java" << JAVA package $PKG_NAME; import android.app.Activity; import android.os.Bundle; import android.widget.ScrollView; import android.widget.TextView; import java.io.ByteArrayOutputStream; import java.io.PrintStream; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ScrollView sv = new ScrollView(this); TextView tv = new TextView(this); tv.setPadding(32, 32, 32, 32); tv.setTextSize(14f); tv.setTextColor(0xFFCDD6F4); tv.setBackgroundColor(0xFF1E1E2E); tv.setTypeface(android.graphics.Typeface.MONOSPACE); sv.addView(tv); setContentView(sv); sv.setBackgroundColor(0xFF1E1E2E); // Kullanıcının main metodunu çalıştır, stdout'u yakala ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); PrintStream old = System.out; System.setOut(ps); try { Main.main(new String[]{}); } catch (Exception e) { System.out.println("Hata: " + e.getMessage()); } finally { System.out.flush(); System.setOut(old); } tv.setText(baos.toString()); } } JAVA fi # AndroidManifest.xml cat > "$WORK_DIR/AndroidManifest.xml" << XML XML # R.java oluştur (aapt2) $BUILD_TOOLS/aapt2 compile --dir "$WORK_DIR/res" -o "$WORK_DIR/res_compiled.zip" 2>&1 $BUILD_TOOLS/aapt2 link \ --manifest "$WORK_DIR/AndroidManifest.xml" \ -I "$PLATFORM/android.jar" \ --java "$WORK_DIR/gen" \ -A "$WORK_DIR/assets" \ -o "$WORK_DIR/resources.apk" \ "$WORK_DIR/res_compiled.zip" \ --auto-add-overlay 2>&1 echo "[HtStudio Java APK] javac ile derleniyor..." # Java kaynaklarını derle find "$WORK_DIR/src" -name "*.java" > "$WORK_DIR/sources.txt" find "$WORK_DIR/gen" -name "*.java" >> "$WORK_DIR/sources.txt" 2>/dev/null || true # Projeye eklenen kütüphaneler (process_libraries() tarafından $WORK_DIR/libs/ # altına konuluyor). İki formatta gelebilir: # - .jar -> direkt kullanılır # - .aar -> Android Archive, içinde classes.jar + kaynaklar var. Burada # sadece classes.jar'ı çıkarıp kullanıyoruz (kaynak/manifest # birleştirme yapmıyoruz - çoğu kod kütüphanesi için yeterli, # ama kütüphane kendi layout/drawable gibi Android # kaynaklarına ihtiyaç duyuyorsa bu basit çıkarma yetmez). mkdir -p "$WORK_DIR/libs_extracted" for AAR in "$WORK_DIR"/libs/*.aar; do [ -e "$AAR" ] || continue AAR_NAME=$(basename "$AAR" .aar) echo "[HtStudio Java APK] AAR açılıyor: $AAR_NAME" TMP_EXTRACT="$WORK_DIR/libs_extracted/${AAR_NAME}_tmp" mkdir -p "$TMP_EXTRACT" unzip -o -q "$AAR" classes.jar -d "$TMP_EXTRACT" 2>&1 || true if [ -f "$TMP_EXTRACT/classes.jar" ]; then mv "$TMP_EXTRACT/classes.jar" "$WORK_DIR/libs_extracted/${AAR_NAME}.jar" else echo "[UYARI] $AAR_NAME.aar içinde classes.jar bulunamadı, atlanıyor" fi rm -rf "$TMP_EXTRACT" done LIB_JARS=$(find "$WORK_DIR/libs" "$WORK_DIR/libs_extracted" -name "*.jar" 2>/dev/null | tr '\n' ':') if [ -n "$LIB_JARS" ]; then echo "[HtStudio Java APK] Kütüphaneler classpath'e ekleniyor: $LIB_JARS" CLASSPATH="$PLATFORM/android.jar:${LIB_JARS}" else CLASSPATH="$PLATFORM/android.jar" fi javac -source 8 -target 8 \ -cp "$CLASSPATH" \ -d "$WORK_DIR/obj" \ @"$WORK_DIR/sources.txt" 2>&1 echo "[HtStudio Java APK] d8 ile dex oluşturuluyor..." # NOT: Kütüphane jar'ları (aar'dan çıkarılanlar dahil) burada da d8'e # veriliyor - sadece derleme classpath'inde olmaları yeterli değil, # kütüphanenin kendi bytecode'unun da dex'lenip APK'ya gömülmesi lazım, # yoksa runtime'da ClassNotFoundException alınır. $BUILD_TOOLS/d8 \ --lib "$PLATFORM/android.jar" \ --output "$WORK_DIR/bin" \ $(find "$WORK_DIR/obj" -name "*.class") \ $(find "$WORK_DIR/libs" "$WORK_DIR/libs_extracted" -name "*.jar" 2>/dev/null) 2>&1 echo "[HtStudio Java APK] APK paketleniyor..." cd "$WORK_DIR" cp resources.apk unsigned.apk 2>/dev/null || $BUILD_TOOLS/aapt package -f \ -M AndroidManifest.xml \ -I "$PLATFORM/android.jar" \ -F unsigned.apk 2>&1 # classes.dex ekle zip -j unsigned.apk bin/classes.dex 2>&1 echo "[HtStudio Java APK] Hizalanıyor (zipalign)..." $BUILD_TOOLS/zipalign -f -p 4 unsigned.apk aligned.apk 2>&1 mv aligned.apk unsigned.apk echo "[HtStudio Java APK] İmzalanıyor..." $BUILD_TOOLS/apksigner sign \ --ks "$KEYSTORE" \ --ks-pass pass:android \ --key-pass pass:android \ --ks-key-alias androiddebugkey \ --out output.apk \ unsigned.apk 2>&1 echo "[HtStudio Java APK] Başarılı: $WORK_DIR/output.apk"