feat(P1): sihirbaza 3 eksik adımı bağla, StepRole ekle, çift crate root'u kaldır

## 1. Sihirbaza hiç bağlanmamış 3 adım
  - LicenseStep: steps/mod.rs'te HİÇ tanımlı değildi, yani dosya
    hiç derlenmiyordu. GlobalState'te license_accepted alanı da yoktu,
    bu yüzden tanımlansa bile derlenemezdi. İkisi de eklendi.
    Orijinal Python Yali'de bu ekran VAR (yali/gui/ScrLicense.py) ve
    sırada info → license → mediaCheck → keyboardSetup; Rust
    yeniden yazımında düşmüş. WelcomeStep'ten hemen sonra bağlandı.
    Demo modunda kabul zorunlu değil (--demo arayüz testi amacıyla
    tıklamadan ilerleyebilmeli).
  - DisplayManagerStep: steps/mod.rs:10'da yorum satırıydı. Bu
    yüzden state.display_manager kalıcı olarak "sddm" idi ve
    kullanıcı SEÇEMİYORDU; masaüstü ortamı da canlı sistemden
    gelen değere kilitliydi. Users → Bootloader arasına bağlandı.
  - NetinstallStep: yazılmış ama wizard vektöründe yoktu. on_enter
    çalışmadığı için selected_package_groups hep boş kalıyor ve
    InstallExtraPackagesJob GUI'den HİÇ çalışmıyordu. Bootloader →
    Summary arasına bağlandı; #[allow(dead_code)] kaldırıldı.
Sihirbaz 10 → 13 adım.

## 2. Konumsal rol tespiti yerine StepRole (regresyon sınıfının kökü)
main.rs rolü `cur == n_steps - 2` / `cur == n_steps - 1` ile tahmin
ediyordu. Yeni bir adım eklemek bu tespiti sessizce kaydırıyor ve
"Geri" butonu ile bitiş ekranını yanlış adıma bağlıyordu — üstelik
test de yoktu. InstallerStep'a role() eklendi (StepRole: Normal /
Execution / Finish, varsayılan Normal). ExecutionStep ve FinishStep
kendilerini bildiriyor, main.rs tahmin etmiyor.

main.rs'e 4 test: tek bir Execution/Finish olduğu, son ikide
oldukları, Execution öncesi tüm adımların Normal olduğu ve —
en önemlisi — Execution ÖNÜNE adım eklenince rol algısının bozulmadığı.

## 3. Çift crate root (src/lib.rs)
lib.rs main.rs ile aynı 7 modülü tekrar tanımlıyordu. Sonuç: her
modül iki kez tip denetiminden geçiyor, rust_i18n::i18n!("locales")
iki kez çalışıp locales dizinini iki kez gömmeye çalışıyor ve
her test iki hedefte koşuyordu. Kimse yali_rs kütüphanesini
kullanmıyordu. README'deki gerekçe "yedek DLL yapısı" idi — Linux
için anlamsız. Silindi; main.rs tek crate root.

## README
Her iki dilde adım sayısı 10 → 13, akış listeleri güncellendi,
lib.rs satırı çıkarıldı.

cargo check --all-targets : 0 hata, 0 uyarı
cargo test              : 47/47 (tek hedefte; iki kez koşmuyor)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-25 22:42:14 +03:00
parent c02d732bef
commit bf6cf727fe
10 changed files with 171 additions and 39 deletions
+14 -12
View File
@@ -7,7 +7,7 @@ Rust ve egui ile yeniden yazılmış **Pisi GNU/Linux** kurulum aracı. YALI, ca
## Özellikler ## Özellikler
- **10 adımlı kurulum sihirbazı**: Hoş Geldiniz → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Bootloader → Özet → Kurulum → Bitir - **13 adımlı kurulum sihirbazı**: Hoş Geldiniz → Lisans → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Ekran Yöneticisi → Bootloader → Ek Paketler → Özet → Kurulum → Bitir
- **İki mod**: Grafik arayüz (varsayılan) ve cevap dosyasıyla otomatik kurulum (`--auto-install answer.toml`) - **İki mod**: Grafik arayüz (varsayılan) ve cevap dosyasıyla otomatik kurulum (`--auto-install answer.toml`)
- **Demo modu**: Sistem kontrollerini atlar, kurulumu simüle eder (`--demo`) - **Demo modu**: Sistem kontrollerini atlar, kurulumu simüle eder (`--demo`)
- **Disk bölümleme**: Otomatik (GPT/MBR) veya manuel, LVM ve LUKS şifreleme desteği - **Disk bölümleme**: Otomatik (GPT/MBR) veya manuel, LVM ve LUKS şifreleme desteği
@@ -44,8 +44,7 @@ cargo run --bin slides_test [desktop] [lang]
``` ```
├── src/ ├── src/
│ ├── main.rs # Giriş noktası, eframe App, GUI düzeni │ ├── main.rs # Giriş noktası, eframe App, GUI düzeni (tek crate root)
│ ├── lib.rs # Kütüphane kökü (yedek DLL yapısı için ayrıldı)
│ ├── installer.rs # InstallerStep trait'i, GlobalState, WelcomeStep │ ├── installer.rs # InstallerStep trait'i, GlobalState, WelcomeStep
│ ├── funct.rs # Paylaşılan UI yardımcıları (form alanları, dil seçici) │ ├── funct.rs # Paylaşılan UI yardımcıları (form alanları, dil seçici)
│ ├── branding.rs # Branding.toml yükleyici (logo, slayt, tema, OEM) │ ├── branding.rs # Branding.toml yükleyici (logo, slayt, tema, OEM)
@@ -95,15 +94,18 @@ cargo run --bin slides_test [desktop] [lang]
## Kurulum İş Akışı ## Kurulum İş Akışı
1. **Hoş Geldiniz** — Dil seçimi, sistem kontrolleri, kurtarma modu 1. **Hoş Geldiniz** — Dil seçimi, sistem kontrolleri, kurtarma modu
2. **Konum / Zaman** — Saat dilimi seçimi (bölge/alan çift ComboBox), GeoIP, NTP veya manuel tarih/saat 2. **Lisans** — GPL metni ve kabul onayı
3. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap) 3. **Konum / Zaman** — Saat dilimi seçimi (bölge/alan çift ComboBox), GeoIP, NTP veya manuel tarih/saat
4. **Ağ** — WiFi tarama, bağlantı, proxy 4. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap)
5. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS 5. **Ağ** — WiFi tarama, bağlantı, proxy
6. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası, otomatik oturum açma 6. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS
7. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri 7. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası, otomatik oturum açma
8. **Özet** — Tüm seçimlerin gözden geçirilmesi 8. **Ekran Yöneticisi** — DM ve masaüstü ortamı seçimi
9. **Kurulum** — Slayt + ilerleme çubuğu + canlı log 9. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri
10. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön 10. **Ek Paketler** — Paket grubu seçimi (ofis, multimedya, geliştirme, …)
11. **Özet** — Tüm seçimlerin gözden geçirilmesi
12. **Kurulum** — Slayt + ilerleme çubuğu + canlı log
13. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön
## Bağımlılıklar ## Bağımlılıklar
+13 -10
View File
@@ -4,7 +4,7 @@ A rewritten **Pisi GNU/Linux** installation tool in Rust and egui. YALI installs
## Features ## Features
- **10-step installation wizard**: Welcome → Location → Keyboard → Network → Disk Partitioning → Users → Bootloader → Summary → Installation → Finish - **13-step installation wizard**: Welcome → License → Location → Keyboard → Network → Disk Partitioning → Users → Display Manager → Bootloader → Extra Packages → Summary → Installation → Finish
- **Two modes**: Graphical interface (default) and automatic installation with answer file (`--auto-install answer.toml`) - **Two modes**: Graphical interface (default) and automatic installation with answer file (`--auto-install answer.toml`)
- **Demo mode**: Skips system checks, simulates installation (`--demo`) - **Demo mode**: Skips system checks, simulates installation (`--demo`)
- **Disk partitioning**: Automatic (GPT/MBR) or manual, LVM and LUKS encryption support - **Disk partitioning**: Automatic (GPT/MBR) or manual, LVM and LUKS encryption support
@@ -92,15 +92,18 @@ cargo run --bin slides_test [desktop] [lang]
## Installation Workflow ## Installation Workflow
1. **Welcome** — Language selection, system checks, rescue mode 1. **Welcome** — Language selection, system checks, rescue mode
2. **Location / Time** — Timezone selection (region/area dual ComboBox), GeoIP, NTP or manual date/time 2. **License** — GPL text and acceptance
3. **Keyboard** — Layout/variant selection, search, live preview (setxkbmap) 3. **Location / Time** — Timezone selection (region/area dual ComboBox), GeoIP, NTP or manual date/time
4. **Network** — WiFi scanning, connection, proxy 4. **Keyboard** — Layout/variant selection, search, live preview (setxkbmap)
5. **Disk Partitioning** — Automatic (guided) or manual, LVM, LUKS 5. **Network** — WiFi scanning, connection, proxy
6. **Users** — Account creation, password strength indicator, root password, autologin 6. **Disk Partitioning** — Automatic (guided) or manual, LVM, LUKS
7. **Bootloader** — GRUB device, timeout, password, kernel parameters 7. **Users** — Account creation, password strength indicator, root password, autologin
8. **Summary** — Review all selections 8. **Display Manager** — DM and desktop environment selection
9. **Installation** — Slideshow + progress bar + live log 9. **Bootloader** — GRUB device, timeout, password, kernel parameters
10. **Finish** — Successful installation, reboot or return to live desktop 10. **Extra Packages** — Package group selection (office, multimedia, development, …)
11. **Summary** — Review all selections
12. **Installation** — Slideshow + progress bar + live log
13. **Finish** — Successful installation, reboot or return to live desktop
## Dependencies ## Dependencies
+26
View File
@@ -59,12 +59,34 @@ pub fn part_path(disk: &str, num: u32) -> String {
} }
/// Bir adımın sihirbaz içindeki rolü.
///
/// Rol, adımın LİSTEDEKİ KONUMUNDAN değil kendisinden bildirilir. Daha
/// önce `main.rs` rolü `cur == n_steps - 2` gibi konumsal olarak
/// tahmin ediyordu; sihirbaza yeni bir adım eklemek (ör. LicenseStep)
/// bu tespiti sessizce kaydırıp "Geri" butonunu veya bitiş ekranını
/// yanlış adıma bağlıyordu.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum StepRole {
/// Sıradan bir yapılandırma adımı.
Normal,
/// Kurulumun çalıştığı ekran. "İleri" gizlenir.
Execution,
/// Kurulum sonrası ekran. Yalnızca yeniden başlatma/çıkış sunar.
Finish,
}
/// Calamares'in "ViewStep" yapısına benzer bir Trait. /// Calamares'in "ViewStep" yapısına benzer bir Trait.
pub trait InstallerStep { pub trait InstallerStep {
fn name(&self) -> String; fn name(&self) -> String;
fn on_enter(&mut self, _state: &mut GlobalState) {} fn on_enter(&mut self, _state: &mut GlobalState) {}
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState); fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState);
fn is_complete(&self, state: &GlobalState) -> bool; fn is_complete(&self, state: &GlobalState) -> bool;
/// Adımın rolü. Varsayılan `Normal`.
fn role(&self) -> StepRole {
StepRole::Normal
}
} }
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@@ -294,6 +316,9 @@ pub struct GlobalState {
pub demo_mode: bool, pub demo_mode: bool,
pub is_dark: bool, pub is_dark: bool,
pub rescue_mode: bool, pub rescue_mode: bool,
/// GPL lisans metnini kullanıcı okudu ve kabul etti.
/// Demo modunda zorunlu değildir.
pub license_accepted: bool,
pub bootloader_device: String, pub bootloader_device: String,
pub bootloader_timeout: u32, pub bootloader_timeout: u32,
pub bootloader_password: String, pub bootloader_password: String,
@@ -357,6 +382,7 @@ impl Default for GlobalState {
demo_mode: false, demo_mode: false,
is_dark: true, is_dark: true,
rescue_mode: false, rescue_mode: false,
license_accepted: false,
bootloader_device: String::new(), bootloader_device: String::new(),
bootloader_timeout: 5, bootloader_timeout: 5,
bootloader_password: String::new(), bootloader_password: String::new(),
-9
View File
@@ -1,9 +0,0 @@
pub mod installer;
pub mod steps;
pub mod jobs;
pub mod ui;
pub mod autoinstall;
pub mod branding;
pub mod funct;
rust_i18n::i18n!("locales");
+101 -5
View File
@@ -7,7 +7,7 @@ mod branding;
mod funct; mod funct;
use installer::{GlobalState, InstallerStep, WelcomeStep}; use installer::{GlobalState, InstallerStep, WelcomeStep};
use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep}; use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep, NetinstallStep, LicenseStep};
use eframe::egui; use eframe::egui;
use rust_i18n::t; use rust_i18n::t;
@@ -247,12 +247,18 @@ impl Default for YaliApp {
state: GlobalState::default(), state: GlobalState::default(),
steps: vec![ steps: vec![
Box::new(WelcomeStep::default()), Box::new(WelcomeStep::default()),
// Orijinal Python Yali'de sıra: info → license → mediaCheck
// → keyboardSetup. Rust yeniden yazımında LicenseStep yazıldı
// ama hiç bağlanmadı (mod.rs'te tanımsızdı).
Box::new(LicenseStep),
Box::new(LocationStep::default()), Box::new(LocationStep::default()),
Box::new(KeyboardStep::default()), Box::new(KeyboardStep::default()),
Box::new(NetworkStep::default()), Box::new(NetworkStep::default()),
Box::new(PartitionStep::default()), Box::new(PartitionStep::default()),
Box::new(UsersStep::default()), Box::new(UsersStep::default()),
Box::new(DisplayManagerStep),
Box::new(BootloaderStep::default()), Box::new(BootloaderStep::default()),
Box::new(NetinstallStep),
Box::new(SummaryStep), Box::new(SummaryStep),
Box::new(ExecutionStep::default()), Box::new(ExecutionStep::default()),
Box::new(FinishStep), Box::new(FinishStep),
@@ -328,9 +334,13 @@ impl eframe::App for YaliApp {
} }
let cur = self.state.current_step; let cur = self.state.current_step;
let n_steps = self.steps.len(); // Rol artık konumsal tahminle değil, adımın kendi bildirdiği
let is_exec = cur == n_steps - 2; // `role()` ile belirleniyor. Sihirbaza yeni adım eklemek rol
let is_finish = cur == n_steps - 1; // algısını bozmaz (bkz. StepRole).
let role = self.steps.get(cur).map(|s| s.role())
.unwrap_or(installer::StepRole::Normal);
let is_exec = role == installer::StepRole::Execution;
let is_finish = role == installer::StepRole::Finish;
// Adım değişmişse on_enter çağır // Adım değişmişse on_enter çağır
if cur != self.last_step { if cur != self.last_step {
@@ -518,4 +528,90 @@ impl eframe::App for YaliApp {
}); });
}); });
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use installer::StepRole;
/// Sihirbazın adım dizisi. main.rs::YaliApp::default() ile AYNI
/// olmalı — test bu yüzden diziyi yeniden kuruyor.
fn wizard() -> Vec<Box<dyn InstallerStep>> {
vec![
Box::new(WelcomeStep::default()),
Box::new(LicenseStep),
Box::new(LocationStep::default()),
Box::new(KeyboardStep::default()),
Box::new(NetworkStep::default()),
Box::new(PartitionStep::default()),
Box::new(UsersStep::default()),
Box::new(DisplayManagerStep),
Box::new(BootloaderStep::default()),
Box::new(NetinstallStep),
Box::new(SummaryStep),
Box::new(ExecutionStep::default()),
Box::new(FinishStep),
]
}
#[test]
fn wizard_has_one_execution_and_one_finish_step() {
let steps = wizard();
assert_eq!(
steps.iter().filter(|s| s.role() == StepRole::Execution).count(),
1,
"tam olarak bir ExecutionStep olmalı"
);
assert_eq!(
steps.iter().filter(|s| s.role() == StepRole::Finish).count(),
1,
"tam olarak bir FinishStep olmalı"
);
}
/// Konumsal tespit yerine role() kullanıldığı için, Execution ve
/// Finish listenin SONU olmak zorunda değil. Bu test yine de
/// beklenen sırayı sabitler; bir adım yanlış yere eklendiğinde
/// (ör. Finish'ten sonra yeni bir adım) kırılır.
#[test]
fn execution_and_finish_are_the_last_two_steps() {
let steps = wizard();
let n = steps.len();
assert_eq!(steps[n - 2].role(), StepRole::Execution);
assert_eq!(steps[n - 1].role(), StepRole::Finish);
}
#[test]
fn all_ordinary_steps_precede_the_execution_step() {
let steps = wizard();
let first_exec = steps.iter().position(|s| s.role() == StepRole::Execution)
.expect("ExecutionStep bulunmalı");
assert!(
steps[..first_exec].iter().all(|s| s.role() == StepRole::Normal),
"ExecutionStep'tan önceki tüm adımlar Normal olmalı"
);
}
/// Yeni bir adım eklemek rol algısını bozmamalı. Bu, 22de7d5
/// sonrası yaşanan regresyon sınıfının tersidir: orada liste
/// değişti ve konumsal `cur == n_steps - 2` tespiti sessizce
/// kaydı.
#[test]
fn roles_survive_inserting_a_step_before_execution() {
let mut steps = wizard();
let exec_at = steps.iter().position(|s| s.role() == StepRole::Execution).unwrap();
// Yeni bir Normal adım ekle
steps.insert(exec_at, Box::new(NetinstallStep));
let n = steps.len();
// Hâlâ doğru adımlar son ikide mi?
assert_eq!(steps[n - 2].role(), StepRole::Execution);
assert_eq!(steps[n - 1].role(), StepRole::Finish);
// Role() sayesinde Execution'ın KONUMU değil KENDİSİ bulunuyor:
// konum bir kayar, ama Execution yine n-2'de.
let exec_at2 = steps.iter().position(|s| s.role() == StepRole::Execution).unwrap();
assert_eq!(exec_at2, exec_at + 1, "eklenen adım Execution'ı bir kaydırmalı");
}
}
+4
View File
@@ -368,6 +368,10 @@ impl InstallerStep for ExecutionStep {
} }
} }
fn role(&self) -> crate::installer::StepRole {
crate::installer::StepRole::Execution
}
fn is_complete(&self, _state: &GlobalState) -> bool { fn is_complete(&self, _state: &GlobalState) -> bool {
self.finished && self.error_screen.is_none() self.finished && self.error_screen.is_none()
} }
+4
View File
@@ -53,5 +53,9 @@ impl InstallerStep for FinishStep {
}); });
} }
fn role(&self) -> crate::installer::StepRole {
crate::installer::StepRole::Finish
}
fn is_complete(&self, _state: &GlobalState) -> bool { true } fn is_complete(&self, _state: &GlobalState) -> bool { true }
} }
+4 -1
View File
@@ -47,6 +47,9 @@ impl InstallerStep for LicenseStep {
} }
fn is_complete(&self, state: &GlobalState) -> bool { fn is_complete(&self, state: &GlobalState) -> bool {
state.license_accepted // Demo modu gerçek kurulum yapmadığı için kabul zorunlu değil;
// demo gezintisinin tıklanmadan ilerleyebilmesi gerekir
// (README'deki "--demo ... arayüz testi" amacı).
state.license_accepted || state.demo_mode
} }
} }
+5 -1
View File
@@ -7,8 +7,9 @@ pub mod partition;
pub mod bootloader; pub mod bootloader;
pub mod network; pub mod network;
pub mod rescue; pub mod rescue;
//pub mod display_manager; pub mod display_manager;
pub mod netinstall; pub mod netinstall;
pub mod license;
pub mod location; pub mod location;
pub use keyboard::KeyboardStep; pub use keyboard::KeyboardStep;
@@ -20,4 +21,7 @@ pub use partition::PartitionStep;
pub use bootloader::BootloaderStep; pub use bootloader::BootloaderStep;
pub use network::NetworkStep; pub use network::NetworkStep;
pub use rescue::RescueStep; pub use rescue::RescueStep;
pub use display_manager::DisplayManagerStep;
pub use netinstall::NetinstallStep;
pub use license::LicenseStep;
pub use location::LocationStep; pub use location::LocationStep;
-1
View File
@@ -14,7 +14,6 @@ pub const PACKAGE_GROUPS: &[(&str, &str, &[&str])] = &[
]; ];
#[derive(Default)] #[derive(Default)]
#[allow(dead_code)]
pub struct NetinstallStep; pub struct NetinstallStep;
impl InstallerStep for NetinstallStep { impl InstallerStep for NetinstallStep {