From 3ae9a3bc67d6a940a2ba2fe2cd30e6c389a9599b Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 21 Feb 2025 17:15:39 +0100 Subject: [PATCH 1/2] Add `FFI::addLibraryPath` utility In favor of the `VIPSHOME` env. Resolves: #232. --- README.md | 3 +++ src/FFI.php | 73 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7d3dbc9..e435a8e 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,9 @@ To your `php.ini`. require __DIR__ . '/vendor/autoload.php'; use Jcupitt\Vips; +// handy for Windows +Vips\FFI::addLibraryPath("C:/vips-dev-8.16/bin"); + // check libvips version echo 'libvips version: ' . Vips\Config::version() . PHP_EOL; diff --git a/src/FFI.php b/src/FFI.php index 6af6dce..f513bb5 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -79,6 +79,15 @@ class FFI */ private static bool $ffi_inited = false; + /** + * A list of paths where libvips might reside. + * + * @internal + */ + private static array $libraryPaths = [ + "" // system library + ]; + /** * Look up these once. * @@ -169,6 +178,30 @@ public static function atLeast(int $x, int $y, int $z = 0): bool self::$library_micro >= $z); } + /** + * Adds a directory to the search path for shared libraries. + * + * This method has no effect if FFI handles are already initialized or + * if the specified path is already included. + * + * @param string $path The path of the library. + * @return bool `true` if the path was added; otherwise, `false`. + */ + public static function addLibraryPath(string $path): bool + { + // Already initialized. + if (self::$ffi_inited) { + return false; + } + + if (!in_array($path, self::$libraryPaths)) { + self::$libraryPaths[] = $path; + return true; + } + + return false; + } + /** * Shut down libvips. Call this just before process exit. * @@ -208,14 +241,16 @@ private static function libraryName(string $name, int $abi): string } private static function libraryLoad( - array $libraryPaths, string $libraryName, string $interface ): ?\FFI { Utils::debugLog("trying to open", ["libraryName" => $libraryName]); - foreach ($libraryPaths as $path) { + foreach (self::$libraryPaths as $path) { Utils::debugLog("trying path", ["path" => $path]); try { + if ($path !== '') { + $path .= '/'; + } $library = \FFI::cdef($interface, $path . $libraryName); Utils::debugLog("success", []); return $library; @@ -252,26 +287,14 @@ private static function init(): void $is_64bits = PHP_INT_SIZE === 8; - $libraryPaths = [ - "" // system library - ]; - - $vipshome = getenv("VIPSHOME"); - if ($vipshome) { - // lib/ predicates lib/ - $libraryPaths[] = $vipshome . ($is_64bits ? "/lib64/" : "/lib32/"); - // lib/ is always searched - $libraryPaths[] = $vipshome . "/lib/"; - } - if (PHP_OS_FAMILY === "OSX" || PHP_OS_FAMILY === "Darwin") { // Homebrew on Apple Silicon - $libraryPaths[] = "/opt/homebrew/lib/"; + self::$libraryPaths[] = "/opt/homebrew/lib"; // See https://github.com/Homebrew/brew/issues/13481#issuecomment-1207203483 - $libraryPaths[] = "/usr/local/lib/"; + self::$libraryPaths[] = "/usr/local/lib"; } - $vips = self::libraryLoad($libraryPaths, $vips_libname, <<<'CPP' + $vips = self::libraryLoad($vips_libname, <<<'CPP' int vips_init (const char *argv0); const char *vips_error_buffer (void); int vips_version(int flag); @@ -279,10 +302,10 @@ private static function init(): void if ($vips === null) { // drop the "" (system path) member - array_shift($libraryPaths); + array_shift(self::$libraryPaths); $msg = "Unable to open library '$vips_libname'"; - if (!empty($libraryPaths)) { - $msg .= " in any of ['" . implode("', '", $libraryPaths) . "']"; + if (!empty(self::$libraryPaths)) { + $msg .= " in any of ['" . implode("', '", self::$libraryPaths) . "']"; } $msg .= ". Make sure that you've installed libvips and that '$vips_libname'"; $msg .= " is on your system's library search path."; @@ -777,13 +800,13 @@ private static function init(): void * one that libvips itself is using, and they will share runtime types. */ self::$glib = - self::libraryLoad($libraryPaths, $vips_libname, $glib_decls) ?? - self::libraryLoad($libraryPaths, $glib_libname, $glib_decls); + self::libraryLoad($vips_libname, $glib_decls) ?? + self::libraryLoad($glib_libname, $glib_decls); self::$gobject = - self::libraryLoad($libraryPaths, $vips_libname, $gobject_decls) ?? - self::libraryLoad($libraryPaths, $gobject_libname, $gobject_decls); + self::libraryLoad($vips_libname, $gobject_decls) ?? + self::libraryLoad($gobject_libname, $gobject_decls); - self::$vips = self::libraryLoad($libraryPaths, $vips_libname, $vips_decls); + self::$vips = self::libraryLoad($vips_libname, $vips_decls); # Useful for debugging # self::$vips->vips_leak_set(1); From deaf4e15d030a8ca923b1e89681d74861a1257a8 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 4 Apr 2025 14:13:59 +0200 Subject: [PATCH 2/2] Incorporate review comment --- src/FFI.php | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index f513bb5..f259f7b 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -181,8 +181,9 @@ public static function atLeast(int $x, int $y, int $z = 0): bool /** * Adds a directory to the search path for shared libraries. * - * This method has no effect if FFI handles are already initialized or - * if the specified path is already included. + * This method has no effect if FFI handles are already initialized, + * if the specified path is non-existent, or if the path is already + * included. * * @param string $path The path of the library. * @return bool `true` if the path was added; otherwise, `false`. @@ -194,12 +195,20 @@ public static function addLibraryPath(string $path): bool return false; } - if (!in_array($path, self::$libraryPaths)) { - self::$libraryPaths[] = $path; - return true; + $path = realpath($path); + if ($path === false) { + return false; + } + + $path .= DIRECTORY_SEPARATOR; + + if (in_array($path, self::$libraryPaths)) { + return false; } - return false; + self::$libraryPaths[] = $path; + + return true; } /** @@ -248,9 +257,6 @@ private static function libraryLoad( foreach (self::$libraryPaths as $path) { Utils::debugLog("trying path", ["path" => $path]); try { - if ($path !== '') { - $path .= '/'; - } $library = \FFI::cdef($interface, $path . $libraryName); Utils::debugLog("success", []); return $library; @@ -289,9 +295,9 @@ private static function init(): void if (PHP_OS_FAMILY === "OSX" || PHP_OS_FAMILY === "Darwin") { // Homebrew on Apple Silicon - self::$libraryPaths[] = "/opt/homebrew/lib"; + self::addLibraryPath("/opt/homebrew/lib"); // See https://github.com/Homebrew/brew/issues/13481#issuecomment-1207203483 - self::$libraryPaths[] = "/usr/local/lib"; + self::addLibraryPath("/usr/local/lib"); } $vips = self::libraryLoad($vips_libname, <<<'CPP'