Skip to content

Replace per-extension composer install with unified autoloader#85

Merged
yaronkoren merged 10 commits intomasterfrom
unified-composer-autoloader
Feb 13, 2026
Merged

Replace per-extension composer install with unified autoloader#85
yaronkoren merged 10 commits intomasterfrom
unified-composer-autoloader

Conversation

@cicalese
Copy link
Contributor

@cicalese cicalese commented Feb 12, 2026

Summary

  • Skip per-extension composer install in extensions-skins.php; log a message instead
  • After the main extension/skin loop, create build-time symlinks in extensions//skins/canasta-extensions//canasta-skins/ so composer.json references work at build time
  • Write composer.local.json with specific entries for bundled extensions that need composer (identified by "composer update" in their additional steps)
  • Run a single root-level composer update to produce a unified vendor/autoload.php
  • Save a hash of all contributing composer.json files to .composer-deps-hash
  • Save composer.local.json to $MW_ORIGIN_FILES/config/ so it is synced to the user's config/ volume via rsync --ignore-existing on first container start
  • In run-all.sh, detect changes to composer.local.json at runtime and re-run composer update only when needed; users who add extensions with composer dependencies should manually add entries to config/composer.local.json
  • If composer.local.json is missing or has an empty include array, use the build-time autoloader as-is
  • Set $smwgConfigFileDir in CanastaDefaultSettings.php to store .smw.json on the persistent volume (config/smw/)
  • Auto-run rebuildData.php for newly-setup SMW wikis: snapshot .smw.json wiki IDs before run_autoupdate (which triggers setupStore.php via hooks), then run rebuildData.php for any new entries; supports wiki farms with per-wiki detection

Closes #84

Merge order: This PR is 1 of 4 — merge first, then CanastaWiki/Canasta#578, then CanastaWiki/Canasta-DockerCompose#91, then CanastaWiki/Canasta-CLI#236.

Test plan

  • Build CanastaBase image, then Canasta image on top, and verify no per-extension vendor/ directories exist under canasta-extensions/ (e.g. canasta-extensions/CirrusSearch/vendor/ should not exist)
  • Verify composer.local.json exists in the built image with specific extension entries (no wildcards)
  • Verify .composer-deps-hash exists in the built image
  • Verify SMW classes are in the unified autoloader: php -r "require '/var/www/mediawiki/w/vendor/autoload.php'; echo class_exists('SMW\Setup') ? 'OK' : 'FAIL';"
  • Fresh canasta create: verify config/composer.local.json is synced from the image on first container start
  • Enable SMW via wfLoadExtension('SemanticMediaWiki'); enableSemantics('localhost'); and verify setupStore.php runs automatically via update.php hooks and rebuildData.php runs afterward
  • Verify config/smw/.smw.json is created on the persistent volume with a key for the wiki ID
  • Wiki farm: add a new wiki and verify rebuildData.php runs only for the new wiki
  • Test runtime: manually add a user-extension entry to config/composer.local.json, restart container, verify composer update runs
  • Test runtime: restart without changes, verify composer update is skipped
  • Test runtime: delete config/composer.local.json, restart, verify build-time autoloader is used as-is

Build time (extensions-skins.php):
- Skip per-extension 'composer install' for extensions with
  'additional steps: composer update'
- Create composer.local.json with merge-plugin globs to include
  all canasta-extension and canasta-skin composer.json files
- Run a single 'composer update' at the MediaWiki root to produce
  a unified vendor/autoload.php
- Save a hash of all contributing composer.json files for runtime
  change detection

Runtime (run-all.sh):
- Copy user's config/composer.local.json to MW_HOME if present
- Compute hash of composer.local.json + all extension/skin
  composer.json files (resolved through symlinks)
- Re-run 'composer update' only when the hash differs from the
  build-time or last-run hash
Treat a missing or empty-include composer.local.json the same way:
preserve the build-time autoloader as-is without running composer
update. This handles old Canasta-DockerCompose templates that had
empty arrays and gives admins a way to opt out of runtime updates.

Add detailed comments documenting the behavior for each state of
config/composer.local.json (missing, empty include, non-empty).
Instead of wildcarding extensions/*/composer.json (which picks up all
extensions, including ones with broken composer.json like SubPageList),
generate composer.local.json with specific entries only for extensions
and skins that have "composer update" in their additional steps.

User-extension and user-skin wildcards are still included so runtime
additions are picked up automatically.

The hash computation is also updated to only hash the files referenced
in composer.local.json rather than all extension composer.json files.
Set $smwgConfigFileDir to the persistent volume (config/smw/) so
smw.json survives container recreates.

Automatically run setupStore.php on startup if SMW is installed but
smw.json doesn't exist yet, so SMW works without manual intervention.
Use only specific entries for bundled extensions that need composer.
Users who add extensions with composer dependencies should manually
add entries to config/composer.local.json.
The generated composer.local.json no longer includes wildcards for
user-extensions. Users should manually add entries for extensions
with composer dependencies.
The composer.local.json now contains specific file paths rather than
glob patterns. Rename the variable and log message to reflect this.
@cicalese cicalese requested a review from yaronkoren February 13, 2026 04:36
update.php triggers setupStore.php via SMW hooks, creating database
tables and .smw.json entries. But rebuildData.php (needed for
Special:Browse to work with existing content) does not run automatically.

Snapshot .smw.json wiki IDs before run_autoupdate, then after it
completes, run rebuildData.php for any wikis not in the snapshot
(i.e. newly set up). In wiki farm mode, each wiki is checked
individually so adding a new wiki triggers setup for just that wiki.

SMW stores setup state in .smw.json (dot-prefixed) keyed by wiki ID
in the persistent volume at $MW_VOLUME/config/smw/.
@yaronkoren
Copy link
Member

This looks amazing. I have one question: does CanastaBase specifically need to set $smwgConfigFileDir? Ideally, all the extension-specific stuff would go in Canasta.

@cicalese
Copy link
Contributor Author

cicalese commented Feb 13, 2026

Good point, but CanastaDefaultSettings.php exists in this repo. If we put the change in Canasta, we'd have to do something fancy like sed the file, which is always dangerous, or create another file that CanastaDefaultSettings.php provisionally includes. Both approaches seem like overkill for this one setting, although I do agree it feels wrong to have CanastaBase include something outside its scope. That being said, Semantic MediaWiki has a lot of special handling.

@yaronkoren
Copy link
Member

Okay, that makes sense. Perhaps ideally there should be a second, Canasta-specific default settings file, but it seems like overkill just for a single line. And obviously SMW is a special case.

@github-actions
Copy link

🐳 The image based on a54f561a commit has been built with 1.43.6-20260213-85 tag as ghcr.io/canastawiki/canasta-base:1.43.6-20260213-85

@yaronkoren
Copy link
Member

Okay, this looks great. Just one other question (I think): does the addition of symlinks here mean that the create-symlinks.sh script can/should be simplified?

@cicalese
Copy link
Contributor Author

No. It just needs the canasta-* symlinks early for the bundled extensions. The full create-symlinks.sh is necessary later.

@yaronkoren
Copy link
Member

Does that include create-symlinks.sh's handling of "bundled" extensions? (I assume "bundled" here, in both your and create-symlinks' parlance, means the 150+ extensions of Canasta, not just the 20 or so extensions bundled with MediaWiki.)

@cicalese
Copy link
Contributor Author

Yes, bundled extensions includes both.
It needs to generate the symlinks for the bundled extensions and skins when it builds the image so it can create the default composer.local.json file. But, later, at runtime, it needs to do so again and then add the user extensions and skins, potentially overriding some bundled extensions. It needs to repeat that process at every restart, and it needs to repeat the part for bundled extensions and skins in case somebody removed a user extension or skin that had been overriding a bundled extension or skin. (I asked Claude the same question, and it got it wrong. Score 1 for the human.)

@yaronkoren
Copy link
Member

Oh yeah, that's true, I forgot about the case where someone overrides a bundled extension, then removes their override... just like Claude did. :(

@yaronkoren yaronkoren merged commit b48bb0e into master Feb 13, 2026
2 checks passed
@yaronkoren
Copy link
Member

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace per-extension composer install with unified autoloader

2 participants