diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:40:47 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-22 23:40:47 +0900 |
| commit | 99ef82a9807578c1e5749156a027949efaba75c4 (patch) | |
| tree | e5f6bfb4c74c1ced20a9bc1c884f811f582e38eb | |
| parent | ffd3e239a56172d2a336fb4d6253c01f6b1a0100 (diff) | |
| download | php-shirabe-99ef82a9807578c1e5749156a027949efaba75c4.tar.gz php-shirabe-99ef82a9807578c1e5749156a027949efaba75c4.tar.zst php-shirabe-99ef82a9807578c1e5749156a027949efaba75c4.zip | |
feat(json): resolve bundled Composer schema files via build.rs
JsonFile's schema paths previously relied on php_dir() (__DIR__), which has
no runtime equivalent. Add a build.rs that copies composer-schema.json and
composer-lock-schema.json next to the built executable, and resolve them with
std::env::current_exe(). FilesystemRepository now embeds InstalledVersions.php
directly via include_str! instead of reading it through php_dir().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| -rw-r--r-- | crates/shirabe-php-shim/src/fs.rs | 4 | ||||
| -rw-r--r-- | crates/shirabe/build.rs | 37 | ||||
| -rw-r--r-- | crates/shirabe/src/json/json_file.rs | 31 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/filesystem_repository.rs | 32 |
4 files changed, 73 insertions, 31 deletions
diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs index a97433a..e55b689 100644 --- a/crates/shirabe-php-shim/src/fs.rs +++ b/crates/shirabe-php-shim/src/fs.rs @@ -512,8 +512,8 @@ pub fn file_put_contents3(_filename: &str, _data: &str, _flags: i64) -> Option<i Some(_data.len() as i64) } -pub fn file_get_contents(_path: &str) -> Option<String> { - std::fs::read(_path) +pub fn file_get_contents(path: impl AsRef<std::path::Path>) -> Option<String> { + std::fs::read(path) .ok() .map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) } diff --git a/crates/shirabe/build.rs b/crates/shirabe/build.rs new file mode 100644 index 0000000..d98b07e --- /dev/null +++ b/crates/shirabe/build.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +// Composer ships its JSON schemas in res/ and JsonFile resolves them via __DIR__. +// We copy those schema files next to the built executable so they can be located +// at runtime through std::env::current_exe(). +fn main() { + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + + // OUT_DIR is target/<profile>/build/<pkg>-<hash>/out; its 3rd ancestor is target/<profile>. + let target_profile_dir = out_dir + .ancestors() + .nth(3) + .expect("OUT_DIR has an unexpected layout"); + + let composer_res = manifest_dir.join("../../composer/res"); + let files = ["composer-schema.json", "composer-lock-schema.json"]; + + // Binaries live in target/<profile>, test/example binaries in target/<profile>/deps; + // populate a res/ directory next to both so current_exe()/../res resolves either way. + for dest_dir in [ + target_profile_dir.join("res"), + target_profile_dir.join("deps").join("res"), + ] { + std::fs::create_dir_all(&dest_dir).unwrap(); + for file in files { + std::fs::copy(composer_res.join(file), dest_dir.join(file)).unwrap(); + } + } + + for file in files { + println!( + "cargo:rerun-if-changed={}", + composer_res.join(file).display() + ); + } +} diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index 4145385..0aada36 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -11,8 +11,8 @@ use shirabe_external_packages::seld::json_lint::ParsingException; use shirabe_php_shim::{ InvalidArgumentException, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, PhpMixed, RuntimeException, UnexpectedValueException, dirname, file_exists, file_get_contents, - file_put_contents, is_dir, is_file, json_decode, json_encode_ex, mkdir, php_dir, realpath, - str_contains, str_ends_with, str_repeat, strlen, strpos, usleep, + file_put_contents, is_dir, is_file, json_decode, json_encode_ex, mkdir, realpath, str_contains, + str_ends_with, str_repeat, strlen, strpos, usleep, }; use crate::downloader::TransportException; @@ -87,14 +87,24 @@ impl JsonFile { pub const INDENT_DEFAULT: &'static str = " "; - /// PHP: __DIR__ . '/../../../res/composer-schema.json' - pub fn composer_schema_path() -> String { - format!("{}/../../../res/composer-schema.json", php_dir()) + /// build.rs copies the Composer schema files into a res/ directory next to the + /// executable; this resolves that path via the running executable's location. + /// + /// TODO(phase-f): this on-disk layout is hard to distribute. Embed the schema with + /// include_str! and extract it to a temporary file at runtime instead. + pub fn composer_schema_path() -> std::path::PathBuf { + Self::schema_res_path("composer-schema.json") + } + + /// See composer_schema_path. + pub fn lock_schema_path() -> std::path::PathBuf { + Self::schema_res_path("composer-lock-schema.json") } - /// PHP: __DIR__ . '/../../../res/composer-lock-schema.json' - pub fn lock_schema_path() -> String { - format!("{}/../../../res/composer-lock-schema.json", php_dir()) + fn schema_res_path(filename: &str) -> std::path::PathBuf { + let exe = std::env::current_exe().expect("failed to resolve current executable path"); + let dir = exe.parent().expect("executable has no parent directory"); + dir.join("res").join(filename) } /// Initializes json file reader/parser. @@ -329,8 +339,8 @@ impl JsonFile { schema_file: Option<&str>, ) -> Result<bool> { let mut is_composer_schema_file = false; - let mut schema_file: String = match schema_file { - Some(f) => f.to_string(), + let mut schema_file = match schema_file { + Some(f) => f.into(), None => { if schema == Self::LOCK_SCHEMA { Self::lock_schema_path() @@ -340,6 +350,7 @@ impl JsonFile { } } }; + let mut schema_file = schema_file.to_string_lossy().into_owned(); // Prepend with file:// only when not using a special schema already (e.g. in the phar) if strpos(&schema_file, "://").is_none() { diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 86887d1..2f74eaf 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -9,8 +9,8 @@ use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{ Exception, InvalidArgumentException, LogicException, PhpMixed, SORT_NATURAL, UnexpectedValueException, array_flip, dirname, r#eval, file_get_contents, get_class, - get_class_err, get_debug_type, in_array, is_array, is_null, is_string, ksort, php_dir, - realpath, sort, sort_with_flags, str_repeat, strtr, trim, usort, var_export, + get_class_err, get_debug_type, in_array, is_array, is_null, is_string, ksort, realpath, sort, + sort_with_flags, str_repeat, strtr, trim, usort, var_export, }; use crate::config::is_php_integer_key; @@ -329,25 +329,19 @@ impl FilesystemRepository { &format!("{}/installed.php", repo_dir), &format!("<?php return {};\n", self.dump_to_php_code(&versions, 0),), ); - let installed_versions_class = - file_get_contents(&format!("{}/../InstalledVersions.php", php_dir(),)); - - // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility - if let Some(class_content) = installed_versions_class { - self.filesystem.borrow_mut().file_put_contents_if_modified( - &format!("{}/InstalledVersions.php", repo_dir), - &class_content, - ); + self.filesystem.borrow_mut().file_put_contents_if_modified( + &format!("{}/InstalledVersions.php", repo_dir), + include_str!("../../../../composer/src/Composer/InstalledVersions.php"), + ); - // make sure the in memory state is up to date with on disk - InstalledVersions::reload(versions); + // make sure the in memory state is up to date with on disk + InstalledVersions::reload(versions); - // make sure the selfDir matches the expected data at runtime if the class was loaded from the vendor dir, as it may have been - // loaded from the Composer sources, causing packages to appear twice in that case if the installed.php is loaded in addition to the - // in memory loaded data from above - InstalledVersions::set_self_dir(repo_dir.replace('\\', "/")); - InstalledVersions::set_installed_is_local_dir(true); - } + // make sure the selfDir matches the expected data at runtime if the class was loaded from the vendor dir, as it may have been + // loaded from the Composer sources, causing packages to appear twice in that case if the installed.php is loaded in addition to the + // in memory loaded data from above + InstalledVersions::set_self_dir(repo_dir.replace('\\', "/")); + InstalledVersions::set_installed_is_local_dir(true); } Ok(()) |
