diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index 29c734f006..2e07a778da 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -240,7 +240,7 @@ add_executable(kphp2cpp ${KPHP_COMPILER_DIR}/kphp2cpp.cpp) target_include_directories(kphp2cpp PUBLIC ${KPHP_COMPILER_DIR}) prepare_cross_platform_libs(COMPILER_LIBS yaml-cpp re2) -set(COMPILER_LIBS vk::kphp2cpp_src vk::tlo_parsing_src vk::popular_common ${COMPILER_LIBS} fmt::fmt OpenSSL::Crypto pthread) +set(COMPILER_LIBS -lstdc++fs vk::kphp2cpp_src vk::tlo_parsing_src vk::popular_common ${COMPILER_LIBS} fmt::fmt OpenSSL::Crypto pthread) target_link_libraries(kphp2cpp PRIVATE ${COMPILER_LIBS}) target_link_options(kphp2cpp PRIVATE ${NO_PIE}) diff --git a/compiler/composer.cpp b/compiler/composer.cpp index 0f3187b546..afdf840af4 100644 --- a/compiler/composer.cpp +++ b/compiler/composer.cpp @@ -6,6 +6,8 @@ #include // using YAML parser to handle JSON files +#include + #include "common/algorithms/contains.h" #include "common/wrappers/fmt_format.h" #include "compiler/kphp_assert.h" @@ -123,6 +125,42 @@ class Psr0Loader : public PsrLoader { }; } // namespace +bool ComposerAutoloader::is_classmap_file(const std::string &filename) const noexcept { + return vk::contains(classmap_files_, filename); +} + +void ComposerAutoloader::scan_classmap(const std::string &filename) { + // supporting the real composer classmap is cumbersome: it requires full PHP parsing to + // fetch all classes from files (the filename doesn't have to follow any conventions); + // we could also invoke php interpreter over vendor/composer/autoload_classmap.php to + // print a JSON dump of the generated classmap and then decode that, but then + // it will be impossible to compile a kphp program that uses a classmap without php interpreter; + // as an alternative, we add all classmap files to auto-required lists that will be + // included along "autoload.files" files, if some classes are not needed, they will be + // discarded after we compute actually used symbols + // + // this approach works well as long as there is no significant side effects related to + // the files being autoloaded (otherwise those side effects will trigger at different point in time) + + const auto add_classmap_file = [&](const std::string &filename) { + classmap_files_.insert(filename); + files_to_require_.emplace_back(filename); + }; + + auto file_info = std::filesystem::status(filename); + kphp_error(file_info.type() != std::filesystem::file_type::not_found, + fmt_format("can't find {} classmap file", filename)); + if (file_info.type() == std::filesystem::file_type::directory) { + for (const auto &entry : std::filesystem::directory_iterator(filename)) { + scan_classmap(entry.path().string()); + } + } else if (file_info.type() == std::filesystem::file_type::regular) { + if (vk::string_view(filename).ends_with(".php") || vk::string_view(filename).ends_with(".inc")) { + add_classmap_file(filename); + } + } +} + std::string ComposerAutoloader::psr_lookup_nocache(const PsrMap &psr, const std::string &class_name, bool transform_underscore) { std::string prefix = class_name; // we start from a longest prefix and then try to match it @@ -253,6 +291,11 @@ void ComposerAutoloader::load_file(const std::string &pkg_root, bool is_root_fil // "": "fallback-dir/", // <...> // }, + // "classmap": [ + // "src/", + // "lib/file.php", + // <...> + // ], // "files": [ // "file.php", // <...> @@ -315,6 +358,12 @@ void ComposerAutoloader::add_autoload_section(const YAML::Node &autoload, const Psr0Loader psr0_loader{autoload, autoload_psr0_classmap_, autoload_psr0_, pkg_root}; psr0_loader.load(); + // https://getcomposer.org/doc/04-schema.md#classmap + const auto &classmap_src = autoload["classmap"]; + for (const auto &elem : classmap_src) { + scan_classmap(pkg_root + elem.as()); + } + if (require_files) { // files that are required by the composer-generated autoload.php // https://getcomposer.org/doc/04-schema.md#files diff --git a/compiler/composer.h b/compiler/composer.h index d337e00ca0..dadcd9e0fa 100644 --- a/compiler/composer.h +++ b/compiler/composer.h @@ -48,6 +48,8 @@ class ComposerAutoloader : private vk::not_copyable { return filename == autoload_filename_; } + bool is_classmap_file(const std::string &filename) const noexcept; + const std::vector &get_files_to_require() const noexcept { return files_to_require_; } @@ -60,11 +62,14 @@ class ComposerAutoloader : private vk::not_copyable { static std::string psr_lookup_nocache(const PsrMap &psr, const std::string &class_name, bool transform_underscore = false); + void scan_classmap(const std::string &filename); + bool use_dev_; PsrMap autoload_psr4_; PsrMap autoload_psr0_; std::map autoload_psr0_classmap_; std::unordered_set deps_; + std::unordered_set classmap_files_; std::string autoload_filename_; std::vector files_to_require_; diff --git a/compiler/data/class-data.cpp b/compiler/data/class-data.cpp index b3a462e317..7673744892 100644 --- a/compiler/data/class-data.cpp +++ b/compiler/data/class-data.cpp @@ -42,7 +42,8 @@ void ClassData::set_name_and_src_name(const std::string &full_name) { std::string namespace_name = pos == std::string::npos ? "" : full_name.substr(0, pos); std::string class_name = pos == std::string::npos ? full_name : full_name.substr(pos + 1); - this->can_be_php_autoloaded = file_id && namespace_name == file_id->namespace_name && class_name == file_id->short_file_name; + this->can_be_php_autoloaded = file_id && ((namespace_name == file_id->namespace_name && class_name == file_id->short_file_name) || + (G->get_composer_autoloader().is_classmap_file(file_id->file_name))); this->can_be_php_autoloaded |= this->is_builtin(); this->is_lambda = vk::string_view{full_name}.starts_with("Lambda$") || vk::string_view{full_name}.starts_with("ITyped$"); diff --git a/tests/python/tests/composer/php/classmap/classmap_lib.php b/tests/python/tests/composer/php/classmap/classmap_lib.php new file mode 100644 index 0000000000..553bbaaf28 --- /dev/null +++ b/tests/python/tests/composer/php/classmap/classmap_lib.php @@ -0,0 +1,11 @@ +value); +$classmap_c2 = new \ClassmapLib\Classes\ClassmapClass2(); +var_dump($classmap_c2->value); +$classmap_c3 = new ClassmapNoNamespace(); +$classmap_c3->f(); +$classmap_c4 = new \OtherClassmap\OtherClassmapClass(); +$classmap_c4->g(); + $controller = new Controller(); $t = new FeedTester(); diff --git a/tests/python/tests/composer/php/vendor/composer/autoload_classmap.php b/tests/python/tests/composer/php/vendor/composer/autoload_classmap.php index 7a91153b0d..4a112bfc89 100644 --- a/tests/python/tests/composer/php/vendor/composer/autoload_classmap.php +++ b/tests/python/tests/composer/php/vendor/composer/autoload_classmap.php @@ -6,4 +6,9 @@ $baseDir = dirname($vendorDir); return array( + 'ClassmapLib\\Classes\\ClassmapClass1' => $baseDir . '/classmap/classmap_lib.php', + 'ClassmapLib\\Classes\\ClassmapClass2' => $baseDir . '/classmap/classmap_lib.php', + 'ClassmapNoNamespace' => $baseDir . '/classmap/classmap_no_namespace.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'OtherClassmap\\OtherClassmapClass' => $baseDir . '/classmap/dir/classmap.php', ); diff --git a/tests/python/tests/composer/php/vendor/composer/autoload_static.php b/tests/python/tests/composer/php/vendor/composer/autoload_static.php index cd31e9808b..fbf8708fe1 100644 --- a/tests/python/tests/composer/php/vendor/composer/autoload_static.php +++ b/tests/python/tests/composer/php/vendor/composer/autoload_static.php @@ -50,12 +50,21 @@ class ComposerStaticInit9539f736c30192217f7a8384c08769a6 1 => __DIR__ . '/..' . '/vk/utils/utils-fallback/src', ); + public static $classMap = array ( + 'ClassmapLib\\Classes\\ClassmapClass1' => __DIR__ . '/../..' . '/classmap/classmap_lib.php', + 'ClassmapLib\\Classes\\ClassmapClass2' => __DIR__ . '/../..' . '/classmap/classmap_lib.php', + 'ClassmapNoNamespace' => __DIR__ . '/../..' . '/classmap/classmap_no_namespace.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'OtherClassmap\\OtherClassmapClass' => __DIR__ . '/../..' . '/classmap/dir/classmap.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit9539f736c30192217f7a8384c08769a6::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit9539f736c30192217f7a8384c08769a6::$prefixDirsPsr4; $loader->fallbackDirsPsr4 = ComposerStaticInit9539f736c30192217f7a8384c08769a6::$fallbackDirsPsr4; + $loader->classMap = ComposerStaticInit9539f736c30192217f7a8384c08769a6::$classMap; }, null, ClassLoader::class); }