From 2760e7e466fcf581d67cc2574241a824fbe158ff Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 25 Jul 2026 22:33:35 +0900 Subject: refactor(json): embed Composer schemas instead of copying them to target build.rs guessed target/ from OUT_DIR to place res/*.json next to the executable (twice, since test binaries live in deps/), and JsonFile resolved them through current_exe(). That made the binary undistributable on its own. The schemas are now include_str!'d and referenced through a shirabe:///res/ URI that SchemaRetriever resolves, keeping the $ref indirection PHP uses for the phar case. The res/ path segment is required so composer-lock-schema.json's relative "./composer-schema.json" reference still resolves. --- crates/shirabe/build.rs | 37 ------------------- crates/shirabe/src/command/config_command.rs | 11 ++---- crates/shirabe/src/json/json_file.rs | 55 ++++++++++++++-------------- 3 files changed, 31 insertions(+), 72 deletions(-) delete mode 100644 crates/shirabe/build.rs diff --git a/crates/shirabe/build.rs b/crates/shirabe/build.rs deleted file mode 100644 index d98b07e7..00000000 --- a/crates/shirabe/build.rs +++ /dev/null @@ -1,37 +0,0 @@ -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//build/-/out; its 3rd ancestor is target/. - 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/, test/example binaries in target//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/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index a92c2f59..9e2f4e66 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -23,9 +23,9 @@ use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, RuntimeException, array_is_list, array_merge, - escapeshellcmd, exec, explode, file_exists, file_get_contents, implode, in_array, is_array, - is_bool, is_dir, is_numeric, is_object, is_string, json_encode, php_regex, str_replace, strpos, - strtolower, system, touch, var_export, + escapeshellcmd, exec, explode, file_exists, implode, in_array, is_array, is_bool, is_dir, + is_numeric, is_object, is_string, json_encode, php_regex, str_replace, strpos, strtolower, + system, touch, var_export, }; use shirabe_semver::VersionParser; @@ -431,10 +431,7 @@ impl Command for ConfigCommand { // ensure we get {} output for properties which are objects if value.as_array().map(|a| a.is_empty()).unwrap_or(false) { let schema = JsonFile::parse_json( - Some( - &file_get_contents(JsonFile::composer_schema_path()) - .unwrap_or_default(), - ), + Some(JsonFile::COMPOSER_SCHEMA_JSON), Some("composer.schema.json"), )?; let type_value = schema diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index 868745ef..dbcd7a19 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -83,25 +83,17 @@ impl JsonFile { pub const INDENT_DEFAULT: &'static str = " "; - /// 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") - } - - 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) - } + /// PHP points these at Composer's res/ directory; here the schemas are embedded in the + /// binary and SchemaRetriever resolves these URIs back to the constants below. The res/ + /// path segment matters: composer-lock-schema.json reaches the composer schema through the + /// relative reference "./composer-schema.json", which must resolve to COMPOSER_SCHEMA_PATH. + pub const COMPOSER_SCHEMA_PATH: &'static str = "shirabe:///res/composer-schema.json"; + pub const LOCK_SCHEMA_PATH: &'static str = "shirabe:///res/composer-lock-schema.json"; + + pub const COMPOSER_SCHEMA_JSON: &'static str = + include_str!("../../../../composer/res/composer-schema.json"); + const LOCK_SCHEMA_JSON: &'static str = + include_str!("../../../../composer/res/composer-lock-schema.json"); /// Initializes json file reader/parser. /// @@ -348,18 +340,17 @@ impl JsonFile { schema_file: Option<&str>, ) -> anyhow::Result { let mut is_composer_schema_file = false; - let schema_file = match schema_file { - Some(f) => f.into(), + let mut schema_file = match schema_file { + Some(f) => f.to_string(), None => { if schema == Self::LOCK_SCHEMA { - Self::lock_schema_path() + Self::LOCK_SCHEMA_PATH.to_string() } else { is_composer_schema_file = true; - Self::composer_schema_path() + Self::COMPOSER_SCHEMA_PATH.to_string() } } }; - 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() { @@ -379,7 +370,7 @@ impl JsonFile { }; if schema == Self::STRICT_SCHEMA && is_composer_schema_file { - schema_data = json_decode(&file_get_contents(&schema_file).unwrap_or_default(), false)?; + schema_data = json_decode(Self::COMPOSER_SCHEMA_JSON, false)?; if let PhpMixed::Object(map) = &mut schema_data { map.insert("additionalProperties".to_string(), PhpMixed::Bool(false)); map.insert( @@ -407,7 +398,7 @@ impl JsonFile { let schema_value = serde_json::to_value(&schema_data)?; let data_value = serde_json::to_value(data)?; let validator = jsonschema::options() - .with_retriever(FileRetriever) + .with_retriever(SchemaRetriever) .build(&schema_value) .map_err(|e| anyhow::anyhow!("{e}"))?; @@ -594,14 +585,22 @@ impl JsonFile { } #[derive(Debug)] -struct FileRetriever; +struct SchemaRetriever; -impl jsonschema::Retrieve for FileRetriever { +impl jsonschema::Retrieve for SchemaRetriever { fn retrieve( &self, uri: &jsonschema::Uri, ) -> anyhow::Result> { match uri.scheme().as_str() { + "shirabe" => { + let contents = match uri.path().as_str() { + "/res/composer-schema.json" => JsonFile::COMPOSER_SCHEMA_JSON, + "/res/composer-lock-schema.json" => JsonFile::LOCK_SCHEMA_JSON, + path => return Err(format!("Unknown embedded resource {path}").into()), + }; + Ok(serde_json::from_str(contents)?) + } "file" => { let file = std::fs::File::open(uri.path().as_str())?; Ok(serde_json::from_reader(std::io::BufReader::new(file))?) -- cgit v1.3.1