M3 namespace migration: complete Phase B, partial Phase D, docs and tests

- Native runtime under bearhub/ (api, commons, gems, view/util, view/core, view/qt)
- bauh/ compatibility re-export shims for legacy imports
- Phase D: canonical __version__/__app_name__ in bearhub/__init__.py; pyproject.toml
  and setup.py point at bearhub package
- Native gem loader at bearhub/view/core/gems.py
- Namespace compatibility tests (159 tests passing)
- Documentation: CHANGELOG [Unreleased], NAMESPACE_MIGRATION, ROADMAP, README,
  CONTRIBUTING, docs/qt6-migration.md, packaging/aur/README.md
This commit is contained in:
Sebastian Palencsar
2026-06-27 10:26:41 +02:00
parent da377aecdc
commit e95a23bc20
240 changed files with 1382 additions and 943 deletions

View File

@@ -4,6 +4,52 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Native gem loader at `bearhub/view/core/gems.py` (scans `bearhub/gems/` first, falls back to `bauh/gems/` during transition)
- Namespace compatibility tests for migrated packages:
- `tests/gems/appimage/test_namespace.py`
- `tests/gems/flatpak/test_namespace.py`
- `tests/gems/web/test_namespace.py`
- `tests/gems/arch/test_namespace.py`
- `tests/api/test_namespace.py`
- `tests/view/util/test_namespace.py`
- `tests/view/core/test_namespace.py`
- `tests/view/qt/test_namespace.py`
- `tests/test_namespace_phase_d.py`
### Changed
- **M3 namespace migration (Phase B):** all runtime implementation moved from `bauh/` to `bearhub/`:
- `bearhub/gems/` — AppImage, Flatpak, Web, Arch/AUR (28+ modules per backend)
- `bearhub/api/` and `bearhub/commons/` — full API and shared utility layer
- `bearhub/view/util/` — cache, disk, logs, translation, util (`resource.py` already Bearhub-native)
- `bearhub/view/core/` — controller, downloader, settings, suggestions, timeshift, tray_client, update (`config`, `gems` already native)
- `bearhub/view/qt/` — all 18 Qt UI modules (window, thread, systray, components, …)
- **M3 namespace migration (Phase D, partial):**
- `__version__` and `__app_name__` canonical in `bearhub/__init__.py`
- `bauh/__init__.py` and `bauh/stylesheet.py` reduced to compatibility shims
- `pyproject.toml` reads version from `bearhub.__version__`
- `setup.py` uses `APP_PACKAGE = 'bearhub'` and ships resources only from `bearhub/`
- Unit tests updated to import from `bearhub.*` where modules were migrated (gems, api, commons, arch patches)
- Test suite expanded from 132 to 159 tests (all passing locally)
### Deprecated
- `import bauh` and `import bauh.*` — still supported via re-export shims under `bauh/`, but `bearhub` is the canonical namespace for new code
- `bauh/view/resources/` — kept in `MANIFEST.in` only as legacy fallback for `bearhub/view/util/resource.py`; scheduled for removal after shim deprecation window
### Documentation
- `NAMESPACE_MIGRATION.md` rewritten and kept current through G.1G.4, B.3B.6, and Phase D slices
- Gem migration plan documented (G.1 AppImage → G.4 Arch order)
- Phase D completion criteria and remaining shim-removal steps documented
- `docs/qt6-migration.md`, `ROADMAP.md` M6, and `NAMESPACE_MIGRATION.md` history sections updated to `bearhub/*` paths
- `packaging/aur/README.md` release checklist aligned with next tag after `[Unreleased]`
### Compatibility
- `bauh/api/*`, `bauh/commons/*`, `bauh/gems/*`, `bauh/view/*` remain installable as thin re-export shims (`from bearhub.<module> import *`)
- `bauh/app.py`, `bauh/manage.py`, `bauh/tray.py`, `bauh/context.py`, `bauh/app_args.py` continue to delegate to `bearhub` entry modules
- No intentional user-facing behavior changes; migration is import-path and packaging structure only
## [0.10.7-bearhub.6] 2026-06-26
### Fixed
- Arch metadata URLs no longer point to non-existent `bearhub-files` (restored `bauh-files` data sources)

View File

@@ -23,8 +23,10 @@ The way to go here is to ask yourself if the improvement would be useful for mor
# Adding a new translation
* To add a new translation, you will have to create a file in each directory listed below named as the first two letters of the language in the ISO format (e.g: for 'english' would be 'en'):
- `bearhub/view/resources/locale` (or `bauh/view/resources/locale` during namespace migration)
- `bauh/gems/appimage/resources/locale`
- `bauh/gems/arch/resources/locale`
- `bauh/gems/flatpak/resources/locale`
- `bauh/gems/web/resources/locale`
- `bearhub/view/resources/locale`
- `bearhub/gems/appimage/resources/locale`
- `bearhub/gems/arch/resources/locale`
- `bearhub/gems/flatpak/resources/locale`
- `bearhub/gems/web/resources/locale`
New code should use `bearhub.*` imports. Legacy `bauh.*` imports still work via compatibility shims (see `NAMESPACE_MIGRATION.md`).

View File

@@ -2,64 +2,380 @@
This document defines the technical migration strategy for moving the Python package namespace from `bauh` to `bearhub` safely.
Last updated: 2026-06-27 (Phase D slice: version flip + packaging canonicalization).
## Current State
- Runtime implementation still lives under `bauh/`.
- Project/package identity is already `bearhub` (metadata, binaries, releases).
- Entry points still target `bauh.*` modules.
| Area | Status |
|------|--------|
| Project identity | `bearhub` (PyPI name, binaries, AUR, docs, desktop files) |
| Version source | `bearhub/__init__.py` (`pyproject.toml``bearhub.__version__`) |
| Entry points | **Flipped**`bearhub`, `bearhub-tray`, `bearhub-cli` target `bearhub.*` |
| Runtime implementation | **Native under `bearhub/`**`api/*`, `commons/*`, `gems/*`, `view/*`, app shell, CLI |
| Legacy `bauh/` tree | Compatibility re-export shims only (+ `bauh/view/resources/` fallback data) |
| `gems/` backends | AppImage, Flatpak, Web, Arch/AUR — all under `bearhub/gems/` |
| Gem loader | Native `bearhub/view/core/gems.py` (dual-root scan + import fallback) |
| Tests | 159 unit tests, all passing (CI: Python 3.11, 3.12) |
| Compatibility shims | `bauh/*` re-exports `bearhub.*`; documented in `CHANGELOG.md` `[Unreleased]` |
### Progress Overview (~95 % of M3 — Phase B complete, Phase D in progress)
```
Phase A [████████████████████] 100 % skeleton + wrappers
Phase B [████████████████████] 100 % all runtime modules native under `bearhub/` (shims in `bauh/`)
Phase C [████████████████████] 100 % entry points on bearhub.*
Phase D [██████████░░░░░░░░░░] 50 % version flip done; full shim removal after stable release
```
### Native vs Shim Modules
**Native (implemented in `bearhub/`):**
- App shell: `app_main.py`, `manage.py`, `tray.py`, `context.py`, `stylesheet.py`, `app_args.py`, `app.py`
- CLI: `cli/*`
- `api/*`, `commons/*`, `gems/*`
- `view/util/*`, `view/core/*`, `view/qt/*`
- `view/util/resource.py` prefers `bearhub/view/resources`, falls back to `bauh/view/resources`
**Legacy `bauh/` tree (compatibility shims only):**
- `bauh/api/*`, `bauh/commons/*`, `bauh/gems/*`, `bauh/view/*` — all re-export from `bearhub.*`
- `bauh/app.py`, `bauh/stylesheet.py`, `bauh/context.py` (partial) — thin redirects to `bearhub`
**Still only in `bauh/` (shim-only, pending removal):**
- `bauh/__init__.py`, `bauh/stylesheet.py`, `bauh/app*.py`, `bauh/context.py`, `bauh/manage.py`, `bauh/tray.py`
- `bauh/api/*`, `bauh/commons/*`, `bauh/gems/*`, `bauh/view/*` — re-export shims
- `bauh/view/resources/` — legacy resource fallback (still in `MANIFEST.in`)
**Dead upstream backends (only `__pycache__` left, no `.py` sources):**
- `bauh/gems/debian/`, `bauh/gems/snap/` — skip migration; remove cache dirs during M4 cleanup.
## Migration Goals
- Make `bearhub` the canonical Python namespace.
- Keep temporary compatibility for legacy `bauh` imports.
- Avoid breaking AUR/PyPI/runtime while migration is in progress.
- Complete `gems/` migration before starting M6 (Qt6) to avoid dual refactors.
## Phase Plan
### Phase A: Introduce target namespace skeleton (current)
### Phase A: Introduce target namespace skeleton — DONE
- Add `bearhub/` package wrappers that delegate to `bauh/`.
- Keep behavior unchanged.
- Do not switch entry points yet.
- [x] Add `bearhub/` package wrappers that delegate to `bauh/`.
- [x] Add native resource tree at `bearhub/view/resources/`.
- [x] Add desktop integration under `bearhub/desktop/`.
- [x] Keep behavior unchanged during wrapper phase.
### Phase B: Move core modules
### Phase B: Move core modules — DONE
Order:
1. `api`, `commons`, `context`
2. `view` / `qt`
3. `gems`
4. CLI + app entry modules
Original order (adjusted based on actual progress):
| Step | Module group | Status | Notes |
|------|-------------|--------|-------|
| B.1 | CLI + app entry | **Done** | `bearhub/app_main.py`, `cli/app_main.py`, `manage.py`, `tray.py` |
| B.2 | `context`, `stylesheet`, `view/core/config`, `view/util/resource` | **Done** | Native implementations |
| B.3 | `api`, `commons` | **Done** | Native under `bearhub/`; `bauh/api` + `bauh/commons` are shims |
| B.4 | `view/util` | **Done** | Native under `bearhub/view/util/`; `bauh/view/util` are shims |
| B.5 | `view/core` | **Done** | Native under `bearhub/view/core/`; `bauh/view/core` are shims |
| B.6 | `view/qt` | **Done** | Native under `bearhub/view/qt/`; `bauh/view/qt` are shims |
| B.7 | `gems/` | **Done** | G.1G.4 complete |
Rules (unchanged):
Rules:
- Move one group at a time.
- After each move, keep compatibility imports in `bauh`.
- Run smoke test: start, search, install, uninstall, update.
- After each move, keep compatibility imports in `bauh/`.
- Run smoke test after every slice: start, search, install, uninstall, update.
- One release per backend migration slice is acceptable (`0.10.7-bearhub.N`).
### Phase C: Flip entry points
### Phase C: Flip entry points — DONE
- Change project scripts to:
- `bearhub = bearhub.app:main`
- `bearhub-tray = bearhub.app:tray`
- `bearhub-cli = bearhub.cli.app:main`
- Keep compatibility layer for one deprecation window.
```toml
# pyproject.toml / setup.py
bearhub = "bearhub.app:main"
bearhub-tray = "bearhub.app:tray"
bearhub-cli = "bearhub.cli.app:main"
```
### Phase D: Remove legacy compatibility
Legacy redirect: `bauh/app.py` re-exports from `bearhub.app_main`.
- Remove `bauh` wrappers only after:
- one stable release cycle without namespace-related regressions
- migration completion is documented in changelog
Compatibility window: keep `bauh` import path until **Phase D** (target: one stable release after full Phase B).
### Phase D: Remove legacy compatibility — IN PROGRESS
Prerequisites:
- [x] All Phase B groups moved (no `from bauh` in `bearhub/` runtime paths).
- [ ] One stable release cycle (`bearhub` + `bearhub-git`) without namespace regressions.
- [ ] Changelog documents shim removal.
Completed (2026-06-27):
1. [x] `__version__` / `__app_name__` canonical in `bearhub/__init__.py`
2. [x] `bauh/__init__.py` → shim re-exporting from `bearhub`
3. [x] `bauh/stylesheet.py` → shim re-exporting from `bearhub.stylesheet`
4. [x] `pyproject.toml``version = {attr = "bearhub.__version__"}`
5. [x] `setup.py``APP_PACKAGE = 'bearhub'`, `package_data` only under `bearhub`
6. [x] Tests: `tests/test_namespace_phase_d.py`
Remaining (after one stable release):
1. Delete `bauh/*` compatibility re-export shims (or reduce to minimal stub package).
2. Remove legacy `bauh/view/resources` from `MANIFEST.in` once fallback is no longer needed.
3. Remove `provides=('bauh')` from AUR after deprecation window.
4. Drop `bauh` package from `find_packages` / wheel entirely.
**Target full removal:** earliest `0.10.8` stable (after validated release on current shims).
---
## Gems Migration Plan — DONE (G.1G.4)
All active gem backends now live under `bearhub/gems/`. The native loader at `bearhub/view/core/gems.py`:
1. Scans `{ROOT_DIR}/gems` where `ROOT_DIR` comes from `bearhub`.
2. Imports `bearhub.gems.{name}.controller` (with `bauh.gems.*` fallback during the shim window).
3. Loads per-gem locale files from `{gem}/resources/locale`.
4. Uses `FORBIDDEN_GEMS_FILE = f'/etc/{__app_name__}/gems.forbidden'` with `bearhub.__app_name__`.
`bauh/view/core/gems.py` is a compatibility shim only. See slice notes below for historical move order.
### Recommended migration order
Migrate smallest / best-tested backends first; leave Arch (largest, most critical) for last.
| PR | Backend | Files | Tests | Risk | Rationale |
|----|---------|-------|-------|------|-----------|
| G.1 | **Loader + AppImage** | 10 + `gems.py` | 4 tests | Low | **Done** — smallest backend; validates loader changes |
| G.2 | **Flatpak** | 10 | 11 tests | Lowmedium | **Done** — good test coverage; no pacman coupling |
| G.3 | **Web** | 12 | 14 tests | Medium | **Done** — optional deps (`lxml`, `bs4`); node/nativefier paths |
| G.4 | **Arch/AUR** | 28 | 67 tests | High | **Done** — primary backend; most internal cross-imports |
### Per-slice checklist (repeat for G.1G.4)
**1. Move source tree**
```bash
git mv bauh/gems/<backend> bearhub/gems/<backend>
```
**2. Update imports inside moved files**
Replace namespace references:
```
bauh.gems.<backend> → bearhub.gems.<backend>
bauh.api.* → bearhub.api.* (or keep bauh.* via wrappers during transition)
bauh.commons.* → bearhub.commons.*
bauh.view.util.* → bearhub.view.util.*
from bauh import ... → from bearhub import ...
```
**3. Add compatibility shims in `bauh/gems/<backend>/`**
```python
# bauh/gems/<backend>/__init__.py (and per-module stubs as needed)
from bearhub.gems.<backend> import * # noqa: F401,F403
```
**4. Update gem loader (G.1 only, then verify each slice)**
Native `bearhub/view/core/gems.py` must:
- Import `ROOT_DIR` from `bearhub` (not `bauh`).
- Scan `os.path.join(ROOT_DIR, 'gems')`.
- Import `bearhub.gems.{name}.controller` (with fallback to `bauh.gems.{name}.controller` during transition if needed).
- Keep `FORBIDDEN_GEMS_FILE = f'/etc/{__app_name__}/gems.forbidden'` using `bearhub.__app_name__`.
**5. Update packaging metadata**
In `pyproject.toml` and `setup.py`, add/update:
```python
"bearhub": [
...
"gems/*/resources/img/*",
"gems/*/resources/locale/*",
]
```
**6. Update tests**
Prefer `from bearhub.gems.<backend>...` in test imports. Keep one compatibility test per backend verifying `import bauh.gems.<backend>` still works until Phase D.
**7. Verify**
```bash
python -m unittest discover -s tests -p "test_*.py" -v
bearhub-cli --version
bearhub --help # offscreen if no display
```
### G.1 detailed steps (first slice) — DONE
Completed 2026-06-27.
**Moved:** `bauh/gems/appimage/``bearhub/gems/appimage/` (resources included)
**Rewritten natively:**
- `bearhub/view/core/gems.py` — scans `bearhub/gems/` first, falls back to `bauh/gems/`; imports `bearhub.gems.*.controller` with `bauh.*` fallback
- `bauh/view/core/gems.py` — compatibility shim → `bearhub.view.core.gems`
**Compatibility shims added:**
- `bauh/gems/appimage/*.py` → re-export from `bearhub.gems.appimage.*`
- Extra `bearhub/api/abstract/*` and `bearhub/commons/*` wrappers required by AppImage imports
**Tests:** `tests/gems/appimage/test_util.py` (canonical import), `tests/gems/appimage/test_namespace.py` (compat + loader)
**Result:** AppImage canonical under `bearhub/gems/appimage/`; loader established for subsequent backends.
### G.2 detailed steps (second slice) — DONE
Completed 2026-06-27.
**Moved:** `bauh/gems/flatpak/``bearhub/gems/flatpak/` (resources included)
**Compatibility shims:** `bauh/gems/flatpak/*.py` → re-export from `bearhub.gems.flatpak.*`
**Additional wrappers:** `bearhub/api/exception.py`, `bearhub/api/abstract/cache.py`, `bearhub/commons/suggestions.py`, `bearhub/commons/util.py`
**Tests:** flatpak tests switched to `bearhub.*` imports; `tests/gems/flatpak/test_namespace.py` added
**Result:** Flatpak canonical under `bearhub/gems/flatpak/`.
### G.3 detailed steps (third slice) — DONE
Completed 2026-06-27.
**Moved:** `bauh/gems/web/``bearhub/gems/web/` (resources included)
**Compatibility shims:** `bauh/gems/web/*.py` → re-export from `bearhub.gems.web.*`
**Additional wrappers:** `bearhub/api/abstract/download.py`, `bearhub/commons/view_utils.py`
**Tests:** web tests switched to `bearhub.*` imports/patch targets; `tests/gems/web/test_namespace.py` added
**Result:** Web canonical under `bearhub/gems/web/`.
### G.4 detailed steps (fourth slice) — DONE
Completed 2026-06-27.
**Moved:** `bauh/gems/arch/``bearhub/gems/arch/` (28 Python modules + resources)
**Compatibility shims:** `bauh/gems/arch/*.py` → re-export from `bearhub.gems.arch.*`
**Additional wrappers:** `bearhub/commons/category.py`
**Tests:** all `tests/gems/arch/*` switched to `bearhub.*` imports/patch targets; `tests/gems/arch/test_namespace.py` added
**Result:** All active gem backends canonical under `bearhub/gems/` (Phase B.7 complete).
### B.3/B.4 slice: native `api/` + `commons/` — DONE
Completed 2026-06-27.
**Moved:** `bauh/api/``bearhub/api/` (14 modules), `bauh/commons/``bearhub/commons/` (15 modules)
**Compatibility shims:** `bauh/api/*.py`, `bauh/commons/*.py` → re-export from `bearhub.*`
**Tests:** `tests/common/*`, `tests/api/abstract/*` switched to `bearhub.*`; `tests/api/test_namespace.py` added
**Result:** `bearhub.api` and `bearhub.commons` are canonical.
### B.4 slice: native `view/util/` — DONE
Completed 2026-06-27.
**Moved:** `bauh/view/util/{cache,disk,logs,translation,util}.py``bearhub/view/util/`
**Kept native:** `bearhub/view/util/resource.py` (Bearhub-first resource resolver with `bauh` fallback)
**Compatibility shims:** `bauh/view/util/*.py` → re-export from `bearhub.view.util.*`
**Tests:** `tests/view/util/test_namespace.py` added
**Result:** `bearhub.view.util` is canonical (`resource.py` Bearhub-first with `bauh` fallback).
### B.5 slice: native `view/core/` — DONE
Completed 2026-06-27.
**Moved:** `bauh/view/core/{controller,downloader,settings,suggestions,timeshift,tray_client,update}.py``bearhub/view/core/`
**Already native:** `config.py`, `gems.py`
**Compatibility shims:** `bauh/view/core/*.py` → re-export from `bearhub.view.core.*`
**Tests:** `tests/view/core/test_namespace.py` added
**Result:** `bearhub.view.core` is canonical (except `config`/`gems`, already native).
### B.6 slice: native `view/qt/` — DONE
Completed 2026-06-27.
**Moved:** all 18 modules from `bauh/view/qt/``bearhub/view/qt/`
**Key import updates:** `bearhub.context`, `bearhub.stylesheet`, `bearhub.view.core/*`, `bearhub.view.util/*`
**Compatibility shims:** `bauh/view/qt/*.py` → re-export from `bearhub.view.qt.*`
**Tests:** `tests/view/qt/test_namespace.py` added
**Result:** Phase B complete — all runtime logic is native under `bearhub/`. Remaining M3 work is Phase D (shim removal after one stable release).
### G.4 Arch-specific notes
Arch has the most cross-module imports (~31 files) and the most tests. Additional care:
- `worker.py` has a lazy import of `ArchManager` — preserve pattern.
- `CUSTOM_MAKEPKG_FILE`, `ARCH_CONFIG_DIR`, pacman integration — smoke-test install/uninstall on a real Arch system or VM.
- Locale files under `gems/arch/resources/locale/` must ship in `package_data`.
- Run full arch test suite: `python -m unittest discover -s tests/gems/arch -v`.
### Phase B completion summary
Phase B (B.1B.7) is **complete** as of 2026-06-27. All runtime modules are native under `bearhub/`; `bauh/` holds re-export shims only.
**Next (Phase D):** ship one stable release on current shims, then remove `bauh/*` compatibility tree per checklist above. Do not start M6 (Qt6) until M3 is tagged and stable — see `docs/qt6-migration.md`.
---
## Compatibility Policy
- During migration, both imports should work:
- `import bearhub`
- `import bauh`
- `bearhub` is canonical for new code.
- `bauh` should only be used as temporary compatibility path.
During migration, both imports should work:
```python
import bearhub
import bauh # temporary — deprecated
```
- `bearhub` is canonical for all new code and entry points.
- `bauh` is a compatibility path only; do not add new `bauh` imports.
- `bearhub/__init__.py` currently delegates `__version__` to `bauh` — fix in Phase D.
- User config/cache paths use `__app_name__` (`bearhub`); legacy `bauh` dirs are auto-migrated via `bauh/api/paths.py`.
## Verification Checklist
- `python -m py_compile` passes for changed modules.
- GUI starts and basic actions work.
- AUR `bearhub` and `bearhub-git` build/install successfully.
- No user-facing regression in naming/path behavior.
Run after every migration slice:
- [ ] `python -m unittest discover -s tests -p "test_*.py"` passes
- [ ] `python -m py_compile` on changed modules
- [ ] `bearhub --version` prints expected version
- [ ] `bearhub-cli --help` works
- [ ] GUI smoke test (if display available): start → search → list installed apps
- [ ] Backend-specific smoke test for migrated gem (install/uninstall dry-run or test env)
- [ ] `pip install -e .` succeeds
- [ ] AUR `bearhub` and `bearhub-git` PKGBUILDs build (maintainer check)
- [ ] No new user-facing `bauh` labels in changed UI paths
- [ ] Changelog entry under `[Unreleased]` or next `bearhub.N` tag
## Related Documents
- `ROADMAP.md` — milestone M3 (namespace) and M6 (Qt6, after M3 tag)
- `docs/qt6-migration.md` — Qt6 inventory under `bearhub/view/qt/*` and `bearhub/view/core/*`
- `CHANGELOG.md``[Unreleased]` holds full Phase B + partial Phase D notes; tagged slices from `0.10.7-bearhub.4` through `.6`

View File

@@ -201,13 +201,14 @@ Bearhub is currently distributed through source releases and AUR:
#### <a name="maintainer_docs">Maintainer docs</a>
- Namespace migration notes: `NAMESPACE_MIGRATION.md`
- Namespace migration notes: `NAMESPACE_MIGRATION.md` (runtime is native under `bearhub/`; `bauh/` is shim-only until Phase D completion)
- Unreleased migration changelog: `CHANGELOG.md``[Unreleased]`
- Qt6 migration plan: `docs/qt6-migration.md`
- Brand assets: `gfx/bearhub.svg`, `gfx/bearhub.png` (synced into `bearhub/view/resources/img/logo.svg` for runtime/packaging)
- AppImage build (from `linux_dist/appimage/`):
```bash
export BEARHUB_VERSION=0.10.7-bearhub.6 # release tag to build
export BEARHUB_VERSION=0.10.7-bearhub.7 # next tag after CHANGELOG [Unreleased] is released
./build.sh
```
@@ -530,11 +531,13 @@ It is a separate repository with some files downloaded during runtime.
#### <a name="code">Code structure</a>
- `view`: code associated with the graphical interface
- `gems`: code responsible to work with the different packaging technologies (every submodule deals with one or more types)
- `api`: code abstractions representing the main actions that a user can do with Linux packages (search, install, ...). These abstractions are implemented by the `gems`, and
the `view` code is only attached to them (it does not know how the `gems` handle these actions)
- `commons`: common code used by `gems` and `view`
Canonical Python package: **`bearhub/`** (import paths like `bearhub.gems.arch`, `bearhub.view.qt`).
- `bearhub/view`: graphical interface (`qt`, `core`, `util`)
- `bearhub/gems`: packaging backends (Arch/AUR, Flatpak, AppImage, Web)
- `bearhub/api`: abstractions for package actions (search, install, …); implemented by `gems`, consumed by `view`
- `bearhub/commons`: shared utilities for `gems` and `view`
- `bauh/`: legacy compatibility shims only (`import bauh` still works during migration)
#### <a name="roadmap">Roadmap</a>
- Support for other packaging technologies

View File

@@ -37,6 +37,8 @@ Definition of Done:
### M3: Namespace Migration (`bauh` -> `bearhub`) (3-5 weeks)
Status (2026-06-27): **~95 % complete** — runtime code native under `bearhub/`; `bauh/` is shim-only; version flip done; full shim removal pending one stable release. See `NAMESPACE_MIGRATION.md` and `CHANGELOG.md` `[Unreleased]`.
Scope:
- Migrate Python package namespace from `bauh` to `bearhub`.
- Provide temporary import compatibility layer for transition period.
@@ -47,6 +49,11 @@ Definition of Done:
- Compatibility shim exists and is marked for removal date.
- Packaging and tests run successfully with new namespace.
Remaining for M3 closure:
- Ship `[Unreleased]` migration changes as a tagged release (`0.10.7-bearhub.7` or `0.10.8`)
- Validate AUR `bearhub` / `bearhub-git` on real Arch systems
- Remove `bauh/*` shims after one stable cycle (Phase D completion)
### M4: Backend Governance (2-3 weeks)
Scope:
@@ -86,7 +93,7 @@ Prerequisites:
Scope:
- Choose Qt6 binding target: **PySide6** (preferred default: LGPL) or **PyQt6** (if GPL-only strategy is confirmed).
- Migrate `bauh/view/qt/*` and app entry modules to Qt6 APIs.
- Migrate `bearhub/view/qt/*` and app entry modules to Qt6 APIs.
- Update themes/QSS validation and tray behavior on Wayland.
- Update packaging (AUR deps, AppImage Qt6 bundle, CI GUI smoke checks).
@@ -146,7 +153,8 @@ Rollback strategy:
## Immediate Next Actions
1. Close remaining M1 gaps (smoke-test checklist, `NAMESPACE_MIGRATION.md` refresh).
2. Build M2 issue list from current crash reports and prioritize top 3.
3. Continue M3 namespace migration (`gems/` move order + shim removal date).
4. Schedule M6 spike after M3 core move (do not start full Qt6 migration in parallel).
1. Tag release with `CHANGELOG.md` `[Unreleased]` namespace migration entries.
2. Close remaining M1 gaps (smoke-test checklist on real Arch desktop).
3. Build M2 issue list from current crash reports and prioritize top 3.
4. Complete M3 Phase D after one stable release (remove `bauh/*` shims, drop `provides=('bauh')` in AUR).
5. Schedule M6 Qt6 PR-6.2 after M3 tag (namespace stable; do not parallelize full Qt6 migration).

View File

@@ -1,5 +1,8 @@
__version__ = '0.10.7'
__app_name__ = 'bearhub'
"""
Legacy namespace compatibility shim.
import os
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
Deprecated: import from `bearhub` instead of `bauh`.
"""
from bearhub import ROOT_DIR, __app_name__, __version__
__all__ = ["__app_name__", "__version__", "ROOT_DIR"]

1
bauh/api/__init__.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.api import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.cache import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.context import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.controller import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.disk import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.download import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.handler import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.model import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.api.abstract.view import * # noqa: F401,F403

1
bauh/api/exception.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.api.exception import * # noqa: F401,F403

1
bauh/api/http.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.api.http import * # noqa: F401,F403

1
bauh/api/paths.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.api.paths import * # noqa: F401,F403

1
bauh/api/user.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.api.user import * # noqa: F401,F403

1
bauh/commons/__init__.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons import * # noqa: F401,F403

1
bauh/commons/boot.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.boot import * # noqa: F401,F403

1
bauh/commons/category.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.category import * # noqa: F401,F403

1
bauh/commons/config.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.config import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.commons.custom_types import * # noqa: F401,F403

1
bauh/commons/html.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.html import * # noqa: F401,F403

1
bauh/commons/internet.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.internet import * # noqa: F401,F403

1
bauh/commons/regex.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.regex import * # noqa: F401,F403

1
bauh/commons/resource.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.resource import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.commons.singleton import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.commons.suggestions import * # noqa: F401,F403

1
bauh/commons/system.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.system import * # noqa: F401,F403

1
bauh/commons/util.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.commons.util import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.commons.version_util import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.commons.view_utils import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage.config import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage.controller import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage.model import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage.query import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage.util import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.appimage.worker import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.__init__ import * # noqa: F401,F403

1
bauh/gems/arch/aur.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.aur import * # noqa: F401,F403

1
bauh/gems/arch/config.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.config import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.confirmation import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.controller import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.cpu_manager import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.database import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.dependencies import * # noqa: F401,F403

1
bauh/gems/arch/disk.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.disk import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.download import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.exceptions import * # noqa: F401,F403

1
bauh/gems/arch/git.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.git import * # noqa: F401,F403

1
bauh/gems/arch/gpg.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.gpg import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.makepkg import * # noqa: F401,F403

1
bauh/gems/arch/mapper.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.mapper import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.message import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.mirrors import * # noqa: F401,F403

1
bauh/gems/arch/model.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.model import * # noqa: F401,F403

1
bauh/gems/arch/output.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.output import * # noqa: F401,F403

1
bauh/gems/arch/pacman.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.pacman import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.pkgbuild import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.proc_util import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.rebuild_detector import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.sorting import * # noqa: F401,F403

1
bauh/gems/arch/sshell.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.sshell import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.suggestions import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.arch.updates import * # noqa: F401,F403

1
bauh/gems/arch/worker.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.arch.worker import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak.config import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak.constants import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak.controller import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak.flatpak import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak.model import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.flatpak.worker import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.web import * # noqa: F401,F403

1
bauh/gems/web/config.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.web.config import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.web.controller import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.web.environment import * # noqa: F401,F403

1
bauh/gems/web/model.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.web.model import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.web.nativefier import * # noqa: F401,F403

1
bauh/gems/web/search.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.web.search import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.gems.web.suggestions import * # noqa: F401,F403

1
bauh/gems/web/worker.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.gems.web.worker import * # noqa: F401,F403

View File

@@ -1,258 +1 @@
import glob
import os
import re
from typing import Optional, Dict, Tuple, Set
from bauh.api.paths import USER_THEMES_DIR
from bauh.view.util import resource
from bauh.view.util.translation import I18n
# RE_WIDTH_PERCENT = re.compile(r'[\d\\.]+%w') TODO percentage measures disabled for the moment (requires more testing)
# RE_HEIGHT_PERCENT = re.compile(r'[\d\\.]+%h') TODO percentage measures disabled for the moment (requires more testing)
RE_META_I18N_FIELDS = re.compile(r'((name|description)(\[\w+])?)')
RE_VAR_PATTERN = re.compile(r'^@[\w.\-_]+')
RE_QSS_EXT = re.compile(r'\.qss$')
class ThemeMetadata:
def __init__(self, file_path: str, default: bool, default_name: Optional[str] = None,
default_description: Optional[str] = None, version: Optional[str] = None,
root_theme: Optional[str] = None, abstract: bool = False):
self.names = {}
self.default_name = default_name
self.descriptions = {}
self.default_description = default_description
self.root_theme = root_theme
self.version = version
self.file_path = file_path
self.file_dir = '/'.join(file_path.split('/')[0:-1])
self.default = default
self.key = self.file_path.split('/')[-1].split('.')[0] if self.default else self.file_path
self.abstract = abstract
def __eq__(self, other) -> bool:
if isinstance(other, ThemeMetadata):
return self.file_path == other.file_path
return False
def __hash__(self):
return self.file_path.__hash__()
def __repr__(self):
return self.file_path if self.file_path else ''
def get_i18n_name(self, i18n: I18n) -> str:
if self.names:
name = self.names.get(i18n.current_key, self.names.get(i18n.default_key))
if name:
return name
if self.default_name:
return self.default_name
else:
return self.file_path.split('/')[-1]
def get_i18n_description(self, i18n: I18n) -> Optional[str]:
if self.descriptions:
des = self.descriptions.get(i18n.current_key, self.descriptions.get(i18n.default_key))
if des:
return des
return self.default_description
def read_theme_metada(key: str, file_path: str) -> ThemeMetadata:
meta_file = RE_QSS_EXT.sub('.meta', file_path)
meta_obj = ThemeMetadata(file_path=file_path, default_name=key, default=not key.startswith('/'))
if os.path.exists(meta_file):
meta_dict = {}
with open(meta_file) as f:
for line in f.readlines():
if line:
field_split = line.split('=')
if len(field_split) > 1:
meta_dict[field_split[0].strip()] = field_split[1].strip()
if meta_dict:
for field, val in meta_dict.items():
if field == 'version':
meta_obj.version = val
elif field == 'root_theme':
meta_obj.root_theme = val
elif field == 'name':
meta_obj.default_name = val
elif field == 'description':
meta_obj.default_description = val
elif field == 'abstract':
boolean = val.lower()
if boolean == 'true':
meta_obj.abstract = True
elif boolean == 'false':
meta_obj.abstract = False
else:
i18n_field = RE_META_I18N_FIELDS.findall(field)
if i18n_field:
if i18n_field[0][1] == 'name':
meta_obj.names[i18n_field[0][2][1:-1]] = val
else:
meta_obj.descriptions[i18n_field[0][2][1:-1]] = val
return meta_obj
def read_default_themes() -> Dict[str, str]:
return {f.split('/')[-1].split('.')[0].lower(): f for f in glob.glob(resource.get_path('style/**/*.qss'))}
def read_user_themes() -> Dict[str, str]:
return {f: f for f in glob.glob('{}/**/*.qss'.format(USER_THEMES_DIR), recursive=True)}
def read_all_themes_metadata() -> Set[ThemeMetadata]:
themes = set()
for key, file_path in read_default_themes().items():
themes.add(read_theme_metada(key=key, file_path=file_path))
for key, file_path in read_user_themes().items():
themes.add(read_theme_metada(key=key, file_path=file_path))
return themes
def process_theme(file_path: str, theme_str: str, metadata: ThemeMetadata,
available_themes: Optional[Dict[str, str]]) -> Optional[Tuple[str, ThemeMetadata]]:
if theme_str and metadata:
root_theme = None
if metadata.root_theme and metadata.root_theme in available_themes:
root_file = available_themes[metadata.root_theme]
if os.path.isfile(root_file):
with open(root_file) as f:
root_theme_str = f.read()
if root_theme_str:
root_metadata = read_theme_metada(key=metadata.root_theme, file_path=root_file)
root_theme = process_theme(file_path=root_file,
theme_str=root_theme_str,
metadata=root_metadata,
available_themes=available_themes)
var_map = _read_var_file(file_path)
var_map['images'] = resource.get_path('img')
var_map['style_dir'] = metadata.file_dir
if var_map:
var_list = [*var_map.keys()]
var_list.sort(key=_by_str_len, reverse=True)
for var in var_list:
theme_str = theme_str.replace('@' + var, var_map[var])
# TODO percentage measures disabled for the moment (requires more testing)
# screen_size = QApplication.primaryScreen().size()
# theme_str = process_width_percent_measures(theme_str, screen_size.width())
# theme_str = process_height_percent_measures(theme_str, screen_size.height())
return theme_str if not root_theme else '{}\n{}'.format(root_theme[0], theme_str), metadata
def _by_str_len(string: str) -> int:
return len(string)
def _read_var_file(theme_file: str) -> dict:
vars_file = theme_file.replace('.qss', '.vars')
var_map = {}
if os.path.isfile(vars_file):
with open(vars_file) as f:
for line in f.readlines():
if line:
line_strip = line.strip()
if line_strip:
var_value = line_strip.split('=')
if var_value and len(var_value) == 2:
var, value = var_value[0].strip(), var_value[1].strip()
if var and value:
var_map[var] = value
if var_map:
process_var_of_vars(var_map) # mapping keys that point to others
return var_map
def process_var_of_vars(var_map: dict):
while True:
pending_vars, invalid = {}, set()
for k, v in var_map.items():
var_match = RE_VAR_PATTERN.match(v)
if var_match:
var_name = var_match.group()[1:]
if var_name not in var_map or var_name == k:
invalid.add(k)
else:
pending_vars[k] = var_name
for key in invalid:
del var_map[key]
if not pending_vars:
break
resolved = 0
for key, val in pending_vars.items():
real_val = var_map[val]
if not RE_VAR_PATTERN.match(real_val):
var_map[key] = real_val
resolved += 1
if resolved == len(pending_vars):
break
# TODO percentage measures disabled for the moment (requires more testing)
# def process_width_percent_measures(theme: str, screen_width: int) -> str:
# width_measures = RE_WIDTH_PERCENT.findall(theme)
#
# final_theme = theme
# if width_measures:
# for m in width_measures:
# try:
# percent = float(m.split('%')[0])
# final_theme = final_theme.replace(m, '{}px'.format(round(screen_width * percent)))
# except ValueError:
# traceback.print_exc()
#
# return final_theme
# def process_height_percent_measures(theme: str, screen_height: int) -> str:
# width_measures = RE_HEIGHT_PERCENT.findall(theme)
#
# final_sheet = theme
# if width_measures:
# for m in width_measures:
# try:
# percent = float(m.split('%')[0])
# final_sheet = final_sheet.replace(m, '{}px'.format(round(screen_height * percent)))
# except ValueError:
# traceback.print_exc()
#
# return final_sheet
from bearhub.stylesheet import * # noqa: F401,F403

1
bauh/view/core/__init__.py Executable file → Normal file
View File

@@ -0,0 +1 @@
from bearhub.view.core import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.core.controller import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.core.downloader import * # noqa: F401,F403

View File

@@ -1,81 +1 @@
import inspect
import importlib
import os
from logging import Logger
from typing import List, Generator
from bauh import __app_name__, ROOT_DIR
from bauh.api.abstract.controller import SoftwareManager, ApplicationContext
from bauh.view.util import translation
FORBIDDEN_GEMS_FILE = f'/etc/{__app_name__}/gems.forbidden'
def find_manager(member):
if not isinstance(member, str):
if inspect.isclass(member) and inspect.getmro(member)[1].__name__ == 'SoftwareManager':
return member
elif inspect.ismodule(member):
for name, mod in inspect.getmembers(member):
manager_found = find_manager(mod)
if manager_found:
return manager_found
def read_forbidden_gems() -> Generator[str, None, None]:
try:
with open(FORBIDDEN_GEMS_FILE) as f:
forbidden_lines = f.readlines()
for line in forbidden_lines:
clean_line = line.strip()
if clean_line and not clean_line.startswith('#'):
yield clean_line
except FileNotFoundError:
pass
def load_managers(locale: str, context: ApplicationContext, config: dict, default_locale: str, logger: Logger) -> List[SoftwareManager]:
managers = []
forbidden_gems = {gem for gem in read_forbidden_gems()}
for f in os.scandir(f'{ROOT_DIR}/gems'):
if f.is_dir() and f.name != '__pycache__':
if f.name in forbidden_gems:
logger.warning(f"gem '{f.name}' could not be loaded because it was marked as forbidden in '{FORBIDDEN_GEMS_FILE}'")
continue
module_name = f'bauh.gems.{f.name}.controller'
try:
module = importlib.import_module(module_name)
except ModuleNotFoundError:
module = None
if module:
manager_class = find_manager(module)
if manager_class:
if locale:
locale_path = f'{f.path}/resources/locale'
if os.path.exists(locale_path):
context.i18n.current.update(translation.get_locale_keys(locale, locale_path)[1])
if default_locale and context.i18n.default:
context.i18n.default.update(translation.get_locale_keys(default_locale, locale_path)[1])
man = manager_class(context=context)
if config['gems'] is None:
man.set_enabled(man.is_default_enabled())
else:
man.set_enabled(f.name in config['gems'])
managers.append(man)
return managers
from bearhub.view.core.gems import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.core.settings import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.core.suggestions import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.core.timeshift import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.core.tray_client import * # noqa: F401,F403

1
bauh/view/core/update.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.view.core.update import * # noqa: F401,F403

1
bauh/view/qt/__init__.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.view.qt import * # noqa: F401,F403

1
bauh/view/qt/about.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.view.qt.about import * # noqa: F401,F403

View File

@@ -0,0 +1 @@
from bearhub.view.qt.apps_table import * # noqa: F401,F403

1
bauh/view/qt/commons.py Normal file
View File

@@ -0,0 +1 @@
from bearhub.view.qt.commons import * # noqa: F401,F403

Some files were not shown because too many files have changed in this diff Show More