Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/cn/php/phpy.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ composer require swoole/phpy

`update`命令会根据`phpy.json`配置信息进行更新,更多查看`--help`

- 如果已经构建好Python环境,建议使用`./vendor/bin/phpy update --skip-build-tools --skip-env --skip-ext`跳过环境构建,避免重复执行

### 4. 环境检查

#### 命令:
Expand Down Expand Up @@ -135,6 +137,10 @@ composer require swoole/phpy
`scan`命令会根据`phpy.json`的`config.scan-dirs`扫描所有php文件并检查依赖的`Python-module`,
引入并安装,更多查看`--help`

- `scan`命令维护了一个`Python module`的`top_level`与`module_name`的映射表,在映射表中不存在映射关系的时候需要手动确认
- `scan`命令负责将扫描结果保存至`phpy.json`,由`ModuleInstall->upgrade()`负责构建`requirements.txt`及安装
- 如安装过程失败,请根据错误信息进行环境补足,一般情况是缺少依赖,待依赖安装完成后重复执行`scan`即可安装

### 6. 缓存清除

#### 命令:
Expand All @@ -143,3 +149,21 @@ composer require swoole/phpy
```

`clear-cache`命令会根据`phpy.json`的`config.cache-dir`清除相关缓存,更多查看`--help`

## 共建维护

### 公共映射库

包 = 模块,包名 = 模块名,但在模块之中以import引入的名称是模块的`top_level`,`requirements.txt`
安装的依据是`module_name`,但在`Python`世界中,`top_level`并不一定与`module_name`相同;

因此`PHPy`通过`supabase`公共库储存维护了一张`top_level`与`module_name`的映射表,这张映射表需要
开发者们一起积极维护;
- `PHPy`提供了一个`metadata:push`的命令,开发者可以手动提交映射关系至公共库;
- `PHPy`提供了一个`metadata:query`的命令,开发者可以查看映射关系公共库;
- `PHPy`的`scan`的命令也会在未索引到映射关系时提示开发者手动输入,输入数据在随后会自动同步到公共库;

**!这里我们倡导所有使用者及开发者,请爱护好该映射库,请勿破坏!**

**!公共库目前以免费版储存数据提供至开源社区,请勿过量占用资源!**

10 changes: 10 additions & 0 deletions tests/mock/import.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

require_once __DIR__ . '/../../vendor/autoload.php';

PyCore::import('gi');
PyCore::import('wx');
PyCore::import('pyqt5');
PyCore::import('llama_index');
PyCore::import('webview');
PyCore::import('TermTk');
4 changes: 4 additions & 0 deletions tools/src/Phpy/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
use PhpyTool\Phpy\Commands\ClearCacheCommand;
use PhpyTool\Phpy\Commands\InitConfigCommand;
use PhpyTool\Phpy\Commands\InstallCommand;
use PhpyTool\Phpy\Commands\MetadataQueryCommand;
use PhpyTool\Phpy\Commands\PhpyInstall;
use PhpyTool\Phpy\Commands\PipMirrorConfig;
use PhpyTool\Phpy\Commands\PipModuleInstall;
use PhpyTool\Phpy\Commands\MetadataPushCommand;
use PhpyTool\Phpy\Commands\PythonInstall;
use PhpyTool\Phpy\Commands\ScanCommand;
use PhpyTool\Phpy\Commands\ScanImport;
Expand All @@ -37,6 +39,8 @@ public function __construct()
System::setcwd(getcwd());
parent::__construct('PHPy', static::VERSION);
$this->addCommands([
new MetadataQueryCommand(),
new MetadataPushCommand(),
new ScanCommand(),
new InitConfigCommand(),
new InstallCommand(),
Expand Down
4 changes: 3 additions & 1 deletion tools/src/Phpy/Commands/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ protected function handler(): int
}
$config = new Config($lockFile ?: $jsonFile);
// build tools
(new BuildToolsInstaller($config, $this->consoleIO))->install();
if (!$this->consoleIO?->getInput()->getOption('skip-build-tools')) {
(new BuildToolsInstaller($config, $this->consoleIO))->install();
}
// install python env
if (!$this->consoleIO?->getInput()->getOption('skip-env')) {
(new PythonInstaller($config, $this->consoleIO))->install();
Expand Down
91 changes: 91 additions & 0 deletions tools/src/Phpy/Commands/MetadataPushCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace PhpyTool\Phpy\Commands;

use PhpyTool\Phpy\Application;
use PhpyTool\Phpy\Config;
use PhpyTool\Phpy\Exceptions\CommandFailedException;
use PhpyTool\Phpy\Exceptions\CommandStopException;
use PhpyTool\Phpy\Exceptions\CommandSuccessedException;
use PhpyTool\Phpy\Helpers\PythonMetadata;
use PhpyTool\Phpy\Helpers\System;
use PhpyTool\Phpy\Installers\ModuleInstaller;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class MetadataPushCommand extends AbstractCommand
{

/** @inheritdoc */
protected function configure(): void
{
parent::configure();
$this
->setName('metadata:push')
->setDescription('Submit metadata to make scan smarter.')
->setHelp(<<<EOT
The <info>metadata:push</info> command collects the mapping between Python modules and
top levels to a public database; more accurate data submissions can help
make the scan command smarter.
EOT
);
}

/** @inheritdoc */
protected function handler(): int
{
$pushed = [];
while (1) {
$metadata = $this->consoleIO?->ask(
"Please enter the metadata information in the format: <info>[top_level]:[module_name]</info>"
);
$metadataArray = explode(':', $metadata);
if (count($metadataArray) !== 2) {
$this->consoleIO?->error("Invalid metadata information. $metadata");
continue;
}
list($topLevel, $moduleName) = $metadataArray;
$pushed[$topLevel] = $moduleName;
$continue = $this->consoleIO?->ask(
"Is there anything else? [<comment>Y,n</comment>]",
true,
questionClass: ConfirmationQuestion::class
);
if (!$continue) {
break;
}
}
if (!$pushed) {
return $this->consoleIO?->success('No metadata information entered.');
}
$count = count($pushed);
// 输出 $pushed 的内容供用户确认
$this->consoleIO?->output("Metadata to be pushing ($count):");
foreach ($pushed as $topLevel => $moduleName) {
$this->consoleIO?->subOutput(
"<info>top_level</info>: <comment>$topLevel</comment> <info>module_name</info>: <comment>$moduleName</comment>"
);
}
// 确认是否继续
$confirm = $this->consoleIO?->ask(
'Do you want to proceed with pushing the metadata? [<comment>Y,n</comment>]',
true,
questionClass: ConfirmationQuestion::class
);
if (!$confirm) {
return $this->consoleIO?->comment('Cancelled.');
}
$bar = new ProgressBar($this->consoleIO?->getOutput(), count($pushed));
$this->consoleIO?->output('Start pushing metadata.');
foreach ($pushed as $topLevel => $moduleName) {
PythonMetadata::pushMetadata($topLevel, $moduleName);
$bar->advance();
}
$bar->finish();
$bar->clear();
return $this->consoleIO?->success('completed.');
}
}
95 changes: 95 additions & 0 deletions tools/src/Phpy/Commands/MetadataQueryCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace PhpyTool\Phpy\Commands;

use PhpyTool\Phpy\Helpers\PythonMetadata;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputOption;

class MetadataQueryCommand extends AbstractCommand
{

/** @inheritdoc */
protected function configure(): void
{
parent::configure();
$this
->setName('metadata:query')
->setDescription('Query metadata')
->addOption('top_level', 't',InputOption::VALUE_OPTIONAL, 'top_level')
->addOption('module_name', 'm',InputOption::VALUE_OPTIONAL, 'module_name');
}

/** @inheritdoc */
protected function handler(): int
{
$topLevel = $this->consoleIO->getInput()->getOption('top_level');
$moduleName = $this->consoleIO->getInput()->getOption('module_name');

// 分页配置
$pageSize = 5;
$currentPage = 1;
$get = true;
$list = [];
$schema = ['ID', 'Module Name', 'Top Level', 'Version', 'Created At'];
do {
$list = $get ? PythonMetadata::queryMetadata($topLevel, $moduleName, $pageSize, ($currentPage - 1) * $pageSize) : $list;
// 检查是否有数据
if ($list) {
$table = new Table($this->consoleIO->getOutput());
$table->setHeaders($schema);
foreach ($list as $item) {
$table->addRow([
$item['id'],
$item['module_name'],
$item['top_level'],
$item['version'],
$item['created_at']
]);
}
$ask = ($currentPage > 1) ?
'Press <comment>n</comment> for next page, <comment>p</comment> for previous page, or <comment>Ctrl + C</comment> to quit.' :
'Press <comment>n</comment> for next page, or <comment>Ctrl + C</comment> to quit.';
} else {
$table = new Table($this->consoleIO->getOutput());
$table->setHeaders($schema);
$table->addRow([
'--',
'--',
'--',
'--',
'--'
]);
$ask = 'Press <comment>p</comment> for previous page, or <comment>Ctrl + C</comment> to quit.';
}
$table->render();
// 显示导航信息
$this->consoleIO->output("Page <comment>$currentPage</comment>");
$input = $this->consoleIO?->ask($ask);
switch ($input) {
case 'n':
if (!$list) {
$get = false;
break;
}
$get = true;
$currentPage ++;
break;
case 'p':
$currentPage --;
$get = true;
if ($currentPage < 1) {
$get = false;
$currentPage = 1;
break;
}
break;
default:
$get = false;
break;
}
} while (true);
}
}
2 changes: 1 addition & 1 deletion tools/src/Phpy/Commands/ScanCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected function handler(): int
// 解析 import依赖
$moduleInstaller->scan();
// 安装
$moduleInstaller->install();
$moduleInstaller->upgrade();
} catch (CommandStopException $exception) {
return $this->consoleIO?->success($exception->getMessage() ?: 'Scan stop.');
} catch (CommandSuccessedException $exception) {
Expand Down
11 changes: 9 additions & 2 deletions tools/src/Phpy/Commands/UpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ protected function configure(): void
->setName('update')
->setDescription('Updates your dependencies to the latest version according to phpy.json, and updates the phpy.lock file')
->addArgument('module', InputArgument::OPTIONAL, 'Python module name to update')
->addOption('skip-build-tools', null, null, 'Skip the build tools installation.')
->addOption('skip-env', null, null, 'Skip the environment upgrade.')
->addOption('skip-ext', null, null, 'Skip the phpy extension upgrade.')
->addOption('skip-module', null, null, 'Skip the module upgrade.')
->setHelp(
<<<EOT
The <info>update</info> command reads the phpy.json file from the
Expand Down Expand Up @@ -62,9 +66,12 @@ protected function handler(): int
}
// 尝试读取lock
$lockFile = Application::getLockFile(System::getcwd());
$config = new Config($lockFile ?: $jsonFile);
$config = new Config($jsonFile);
$config->merge(new Config($lockFile));
// build tools
(new BuildToolsInstaller($config, $this->consoleIO))->install();
if (!$this->consoleIO?->getInput()->getOption('skip-build-tools')) {
(new BuildToolsInstaller($config, $this->consoleIO))->install();
}
// update python env
if (!$this->consoleIO?->getInput()->getOption('skip-env')) {
(new PythonInstaller($config, $this->consoleIO))->upgrade();
Expand Down
24 changes: 21 additions & 3 deletions tools/src/Phpy/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,27 @@ public function load(string $file): void
$this->config = array_replace_recursive($this->config, $config ?? []);
}

/**
* @param string $file
* @return void
*/
public function save(string $file): void
{
$content = $this->__toString();
System::putFileContent($file, $content);
}

/**
* @param Config ...$configs
* @return void
*/
public function merge(Config ...$configs): void
{
foreach ($configs as $config) {
$this->config = array_merge_recursive($this->config, $config->all(false));
}
}

/**
* 获取配置
*
Expand All @@ -99,7 +114,7 @@ public function get(?string $key, mixed $default = null): mixed
$config = $config[$index];
}

return $found ? $config : $default;
return $found ? (($config instanceof \stdClass) ? [] : $config) : $default;
}

return $config;
Expand All @@ -126,11 +141,14 @@ public function set(string $key, mixed $value): void
}

/**
* @param bool $transform
* @return array
*/
public function all(): array
public function all(bool $transform = true): array
{
$this->config['modules'] = $this->config['modules'] ?: new \stdClass();
if ($transform) {
$this->config['modules'] = $this->config['modules'] ?: new \stdClass();
}
return $this->config;
}
}
9 changes: 1 addition & 8 deletions tools/src/Phpy/ConsoleIO.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public function getExtra(?string $key = null, mixed $default = null): mixed
* @param string $message
* @param mixed|null $default
* @param string $tag
* @param class-string $questionClass
* @param class-string $questionClass when $questionClass is ConfirmationQuestion::class, it will be 'confirm'
* @return mixed
*/
public function ask(string $message, mixed $default = null, string $tag = '[?]', string $questionClass = Question::class): mixed
Expand All @@ -138,13 +138,6 @@ public function ask(string $message, mixed $default = null, string $tag = '[?]',
return $questionHelper->ask($this->getInput(), $this->getOutput(), $question);
}

public function confirm(string $msg, $default = false)
{
$helper = new QuestionHelper();
$question = new ConfirmationQuestion($msg, $default);
return $helper->ask($this->getInput(), $this->getOutput(), $question);
}

/**
* sub输出
*
Expand Down
Loading