aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-core/src/factory.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mozart-core/src/factory.rs')
-rw-r--r--crates/mozart-core/src/factory.rs346
1 files changed, 3 insertions, 343 deletions
diff --git a/crates/mozart-core/src/factory.rs b/crates/mozart-core/src/factory.rs
index a7a690c..39024c8 100644
--- a/crates/mozart-core/src/factory.rs
+++ b/crates/mozart-core/src/factory.rs
@@ -9,15 +9,10 @@
//! wiring are intentionally omitted as they are out of scope for the
//! current port.
+use crate::composer::composer_home;
+use crate::config::Config;
use std::collections::BTreeMap;
-use std::path::{Path, PathBuf};
-
-use crate::composer::{
- AutoloadGenerator, Composer, InstallationManager, LocalPackage, LocalRepository, Locker,
- RepositoryManager, composer_home,
-};
-use crate::config::{Config, resolve_references};
-use crate::package::read_from_file;
+use std::path::PathBuf;
/// Rust port of `Factory::getCacheDir()`.
///
@@ -156,194 +151,6 @@ pub fn create_config() -> anyhow::Result<Config> {
Ok(config)
}
-/// Rust port of `Factory::createComposer()`.
-///
-/// Builds the project-level [`Composer`]:
-/// 1. Read `composer.json` from `composer_json` and load it into both
-/// the merged [`Config`] (overlaying [`create_config`]) and the
-/// untyped [`crate::package::RawPackageData`].
-/// 2. Resolve all `{$home}` / `{$vendor-dir}` placeholders via
-/// [`resolve_references`].
-/// 3. Resolve `vendor-dir` against `project_dir` if it is relative, so
-/// the installation manager hands back absolute paths
-/// (`Factory::createComposer` does the same via
-/// `Filesystem::isAbsolutePath`).
-/// 4. Wire up the [`InstallationManager`] and a [`RepositoryManager`]
-/// whose local repository is populated from
-/// `vendor/composer/installed.json` — the same role
-/// `Factory::addLocalRepository` plays in PHP.
-/// 5. Construct a fresh [`AutoloadGenerator`] with PHP defaults
-/// (`new AutoloadGenerator($eventDispatcher, $io)` in PHP, minus the
-/// not-yet-ported event dispatcher and IO dependencies).
-/// 6. Construct a [`Locker`] pointed at `composer.lock` next to the
-/// composer.json — same as `Factory::createComposer`'s
-/// `new Locker($io, new JsonFile($lockFile, …), $im, $contents)`,
-/// minus the IO/installation-manager/contents dependencies that
-/// only matter once we port `setLockData`.
-///
-/// The plugin manager, download manager, and event dispatcher that
-/// `Factory::createComposer` also wires up are not yet ported.
-pub fn create_composer(project_dir: PathBuf, composer_json: &Path) -> anyhow::Result<Composer> {
- let content = std::fs::read_to_string(composer_json)?;
- let value: serde_json::Value = serde_json::from_str(&content)?;
- let mut config = create_config()?;
- if let Some(cfg_obj) = value.get("config").and_then(|v| v.as_object()) {
- let overrides: BTreeMap<String, serde_json::Value> = cfg_obj
- .iter()
- .map(|(k, v)| (k.clone(), v.clone()))
- .collect();
- config.merge(&overrides)?;
- }
- resolve_references(&mut config);
-
- let package = read_from_file(composer_json)?;
-
- // Mirrors `Factory::createComposer`'s `vendorDir` handling. The
- // value out of `Config::get('vendor-dir')` already had `{$...}`
- // placeholders substituted, but it may still be relative — resolve
- // it against the project root so install paths are absolute.
- let vendor_dir = if Path::new(&config.vendor_dir).is_absolute() {
- PathBuf::from(&config.vendor_dir)
- } else {
- project_dir.join(&config.vendor_dir)
- };
-
- let (local_packages, dev_mode) = read_local_packages(&vendor_dir)?;
- let repository_manager =
- RepositoryManager::new(LocalRepository::with_dev_mode(local_packages, dev_mode));
- let installation_manager = InstallationManager::new(vendor_dir);
- let autoload_generator = AutoloadGenerator::new();
-
- // Mirrors `Factory::createComposer`'s lock-file path: the lockfile
- // sits next to composer.json, with `.json` swapped for `.lock`.
- let lock_file_path = composer_json
- .parent()
- .map(|p| p.to_path_buf())
- .unwrap_or_else(|| project_dir.clone())
- .join(
- composer_json
- .file_name()
- .and_then(|n| n.to_str())
- .map(|n| n.strip_suffix(".json").unwrap_or(n))
- .map(|stem| format!("{stem}.lock"))
- .unwrap_or_else(|| "composer.lock".to_string()),
- );
- let locker = Locker::new(lock_file_path);
-
- Ok(Composer::new(
- project_dir,
- config,
- package,
- repository_manager,
- installation_manager,
- autoload_generator,
- locker,
- ))
-}
-
-/// Read `vendor/composer/installed.json` into the minimal shape the
-/// installation manager needs. Mirrors the relevant slice of
-/// `Composer\Repository\FilesystemRepository::initialize`: accept both
-/// the v2 object form (`{packages: [...]}`) and the legacy v1 array
-/// form. Returns an empty list when the file is missing — the same
-/// semantics as `FilesystemRepository::isFresh`.
-///
-/// We deliberately avoid pulling the full `InstalledPackages` reader from
-/// `mozart-registry` here to keep `mozart-core` at the bottom of the
-/// dependency graph; the parsing that's actually load-bearing for the
-/// install-path computation is just the package name + optional
-/// `target-dir`.
-fn read_local_packages(vendor_dir: &Path) -> anyhow::Result<(Vec<LocalPackage>, Option<bool>)> {
- let path = vendor_dir.join("composer/installed.json");
- if !path.exists() {
- return Ok((Vec::new(), None));
- }
- let content = std::fs::read_to_string(&path)?;
- let value: serde_json::Value = serde_json::from_str(&content)?;
-
- let (entries, dev_mode): (&[serde_json::Value], Option<bool>) = match &value {
- serde_json::Value::Object(obj) => {
- let entries = match obj.get("packages") {
- Some(serde_json::Value::Array(arr)) => arr.as_slice(),
- _ => return Ok((Vec::new(), obj.get("dev").and_then(|v| v.as_bool()))),
- };
- (entries, obj.get("dev").and_then(|v| v.as_bool()))
- }
- serde_json::Value::Array(arr) => (arr.as_slice(), None),
- _ => return Ok((Vec::new(), None)),
- };
-
- let mut out = Vec::with_capacity(entries.len());
- for entry in entries {
- let pretty_name = entry
- .get("name")
- .and_then(|v| v.as_str())
- .unwrap_or("")
- .to_string();
- let pretty_version = entry
- .get("version")
- .and_then(|v| v.as_str())
- .unwrap_or("")
- .to_string();
- let target_dir = entry
- .get("target-dir")
- .and_then(|v| v.as_str())
- .map(|s| s.to_string());
- let package_type = entry
- .get("type")
- .and_then(|v| v.as_str())
- .map(|s| s.to_string());
- let installation_source = entry
- .get("installation-source")
- .and_then(|v| v.as_str())
- .and_then(crate::composer::InstallationSource::parse);
- let source = read_package_reference(entry.get("source"));
- let dist = read_package_reference(entry.get("dist"));
- let extra = entry
- .get("extra")
- .cloned()
- .unwrap_or(serde_json::Value::Null);
- out.push(LocalPackage::new(
- pretty_name,
- pretty_version,
- target_dir,
- package_type,
- installation_source,
- source,
- dist,
- extra,
- ));
- }
- Ok((out, dev_mode))
-}
-
-fn read_package_reference(
- value: Option<&serde_json::Value>,
-) -> Option<crate::composer::PackageReference> {
- let v = value?;
- let kind = v.get("type").and_then(|x| x.as_str())?.to_string();
- let url = v
- .get("url")
- .and_then(|x| x.as_str())
- .unwrap_or("")
- .to_string();
- let reference = v
- .get("reference")
- .and_then(|x| x.as_str())
- .map(|s| s.to_string());
- let shasum = v
- .get("shasum")
- .and_then(|x| x.as_str())
- .filter(|s| !s.is_empty())
- .map(|s| s.to_string());
- Some(crate::composer::PackageReference {
- kind,
- url,
- reference,
- shasum,
- })
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -405,151 +212,4 @@ mod tests {
result.display()
);
}
-
- mod create_composer {
- use super::*;
- use std::fs;
- use tempfile::tempdir;
-
- fn write(path: &Path, content: &str) {
- fs::create_dir_all(path.parent().unwrap()).unwrap();
- fs::write(path, content).unwrap();
- }
-
- #[test]
- fn install_path_is_vendor_dir_plus_pretty_name() {
- let dir = tempdir().unwrap();
- write(&dir.path().join("composer.json"), r#"{"name": "acme/app"}"#);
- write(
- &dir.path().join("vendor/composer/installed.json"),
- r#"{"packages": [{"name": "Vendor/Pkg", "version": "1.0.0"}]}"#,
- );
-
- let composer = Composer::require(dir.path()).unwrap();
- let pkg = composer
- .repository_manager()
- .local_repository()
- .get_canonical_packages()
- .next()
- .unwrap();
-
- let install_path = composer
- .installation_manager()
- .get_install_path(pkg)
- .unwrap();
-
- // Mirrors `LibraryInstaller::getInstallPath`:
- // `vendorDir + '/' + prettyName`. `pretty-name` is preserved
- // case (Composer/Repository/FilesystemRepository keeps the original).
- assert_eq!(install_path, dir.path().join("vendor").join("Vendor/Pkg"));
- }
-
- #[test]
- fn install_path_appends_target_dir() {
- let dir = tempdir().unwrap();
- write(&dir.path().join("composer.json"), r#"{"name": "acme/app"}"#);
- write(
- &dir.path().join("vendor/composer/installed.json"),
- r#"{"packages": [{"name": "vendor/pkg", "target-dir": "src/lib"}]}"#,
- );
-
- let composer = Composer::require(dir.path()).unwrap();
- let pkg = composer
- .repository_manager()
- .local_repository()
- .get_canonical_packages()
- .next()
- .unwrap();
-
- let install_path = composer
- .installation_manager()
- .get_install_path(pkg)
- .unwrap();
-
- assert_eq!(install_path, dir.path().join("vendor/vendor/pkg/src/lib"));
- }
-
- #[test]
- fn local_repository_is_empty_when_installed_json_missing() {
- let dir = tempdir().unwrap();
- write(&dir.path().join("composer.json"), r#"{"name": "acme/app"}"#);
-
- let composer = Composer::require(dir.path()).unwrap();
- let count = composer
- .repository_manager()
- .local_repository()
- .get_canonical_packages()
- .count();
- assert_eq!(count, 0);
- }
-
- #[test]
- fn local_repository_accepts_v1_array_form() {
- // Older Composer 1.x / fixture format: bare array of packages.
- // FilesystemRepository::initialize accepts this; our minimal
- // reader must too.
- let dir = tempdir().unwrap();
- write(&dir.path().join("composer.json"), r#"{"name": "acme/app"}"#);
- write(
- &dir.path().join("vendor/composer/installed.json"),
- r#"[{"name": "a/a"}, {"name": "b/b"}]"#,
- );
-
- let composer = Composer::require(dir.path()).unwrap();
- let names: Vec<&str> = composer
- .repository_manager()
- .local_repository()
- .get_canonical_packages()
- .map(|p| p.pretty_name())
- .collect();
- assert_eq!(names, vec!["a/a", "b/b"]);
- }
-
- #[test]
- fn package_returns_root_composer_json() {
- let dir = tempdir().unwrap();
- write(
- &dir.path().join("composer.json"),
- r#"{"name": "acme/app", "require": {"vendor/pkg": "^1.0"}}"#,
- );
-
- let composer = Composer::require(dir.path()).unwrap();
- assert_eq!(composer.package().name, "acme/app");
- assert_eq!(
- composer
- .package()
- .require
- .get("vendor/pkg")
- .map(String::as_str),
- Some("^1.0"),
- );
- }
-
- #[test]
- fn install_path_uses_configured_vendor_dir() {
- let dir = tempdir().unwrap();
- write(
- &dir.path().join("composer.json"),
- r#"{"name": "acme/app", "config": {"vendor-dir": "deps"}}"#,
- );
- write(
- &dir.path().join("deps/composer/installed.json"),
- r#"{"packages": [{"name": "vendor/pkg"}]}"#,
- );
-
- let composer = Composer::require(dir.path()).unwrap();
- let pkg = composer
- .repository_manager()
- .local_repository()
- .get_canonical_packages()
- .next()
- .unwrap();
-
- let install_path = composer
- .installation_manager()
- .get_install_path(pkg)
- .unwrap();
- assert_eq!(install_path, dir.path().join("deps/vendor/pkg"));
- }
- }
}