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
3 changes: 2 additions & 1 deletion .github/scripts/check-binary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ fi
env -i "$binary" list --raw

"$binary" new "$work/app"
"$binary" package -d "$work/app" --output="$work/app-binary"
printf '{"package": {"output": "%s"}}\n' "$work/app-binary" >"$work/app/skernel.json"
"$binary" package -d "$work/app"

mkdir "$work/elsewhere"
mv "$work/app-binary" "$work/elsewhere/app"
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ jobs:
- name: Install the dependencies
run: composer install --no-dev --no-interaction --no-progress

# The package path is a setting of the project, so it goes into SKERNEL's own configuration file.
- name: Package SKERNEL
run: php bin/bootstrap.php package --output="$BINARY"
run: |
printf '{"package": {"output": "%s"}}\n' "$BINARY" > skernel.json
php bin/bootstrap.php package

- name: Check the binary
run: .github/scripts/check-binary.sh "$PWD/$BINARY"
Expand Down
50 changes: 32 additions & 18 deletions README.md

Large diffs are not rendered by default.

50 changes: 32 additions & 18 deletions README.zh-CN.md

Large diffs are not rendered by default.

50 changes: 40 additions & 10 deletions skernel.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/super-kernel/skernel/main/skernel.schema.json",
"title": "SKERNEL configuration",
"description": "Settings for the options left off SKERNEL's command line, read from skernel.json or skernel.json.dist in the project root.",
"description": "The settings of a project for SKERNEL's commands, read from skernel.json or skernel.json.dist in the project root. A setting left out keeps its default.",
"type": "object",
"additionalProperties": false,
"properties": {
Expand All @@ -12,14 +12,47 @@
"minLength": 1
},
"php": {
"description": "The managed PHP version: major.minor for that branch's latest release, or an exact major.minor.patch.",
"description": "The managed PHP version: major.minor for that branch's latest release, or an exact major.minor.patch; by default the latest release that the root package's require.php allows.",
"type": "string",
"pattern": "^[1-9][0-9]*\\.[0-9]+(\\.[0-9]+)?$",
"examples": [
"8.4",
"8.4.27"
]
},
"composer": {
"description": "The Composer that build, run, and package use: a path, relative to the project root when it contains a /, or a name looked up in PATH; by default the first working Composer 2 or else SKERNEL's own.",
"type": "string",
"minLength": 1
},
"run": {
"description": "Settings of the run command.",
"type": "object",
"additionalProperties": false,
"properties": {
"watch": {
"description": "Whether run rebuilds and restarts the application when a watched file changes.",
"type": "boolean",
"default": false
},
"watch-files": {
"description": "The name patterns of the watched files, with *, ?, and [...]; the vendor directory, target/, and directories whose name starts with a dot are never watched.",
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
},
"default": [
"*.php",
".env",
".env.*",
"composer.json",
"composer.lock"
]
}
}
},
"package": {
"description": "Settings of the package command.",
"type": "object",
Expand All @@ -31,22 +64,19 @@
"minLength": 1
},
"output": {
"description": "The package path, relative to the project root; by default target/<name>.",
"type": "string",
"minLength": 1
},
"composer": {
"description": "The Composer binary that installs the packaged copy, a path or a name looked up in PATH, by default a working Composer 2 or else SKERNEL's own.",
"description": "The package path, relative to the project root; by default target/<name>, or target/<name>.phar for a PHAR.",
"type": "string",
"minLength": 1
},
"dev": {
"description": "Whether the development dependencies are installed into the package.",
"type": "boolean"
"type": "boolean",
"default": false
},
"phar": {
"description": "Whether packaging stops at the PHAR instead of a standalone binary.",
"type": "boolean"
"type": "boolean",
"default": false
}
}
}
Expand Down
15 changes: 3 additions & 12 deletions src/Command/BuildCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

Expand All @@ -18,6 +17,8 @@
/**
* Regenerates the project's Composer autoloader and generates its build artifacts with its managed PHP; the
* application loads them at start.
*
* The PHP version and the Composer come from the project's configuration file.
*/
#[AttributeCommand(name: 'build', description: 'Generate the build artifacts that the project loads at start')]
final class BuildCommand extends Command
Expand All @@ -30,24 +31,14 @@ public function __construct(
parent::__construct();
}

protected function configure(): void
{
$this->addOption(
'php',
null,
InputOption::VALUE_REQUIRED,
'The PHP version that runs the project, such as 8.4 or 8.4.27',
);
}

/**
* @throws RuntimeException When the project or its configuration is invalid, its managed PHP or Composer cannot be
* provided, the project's build protocol differs, or the build cannot start or fails
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$errorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
[$project, $runtime, $composer] = $this->projectRuntime->resolve($input, $errorOutput);
[$project, , $runtime, $composer] = $this->projectRuntime->resolve($input, $errorOutput);
$this->projectBuilder->build(
$project,
$runtime->php,
Expand Down
62 changes: 10 additions & 52 deletions src/Command/PackageCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@
use Src\Package\PackageFile;
use Src\Package\PharBuilder;
use Src\Package\ProjectCopier;
use Src\Project\AbsolutePath;
use Src\Project\ProjectLocator;
use Src\Toolchain\ManagedPhp;
use Src\Toolchain\PhpVersion;
use SuperKernel\Command\Attribute\Command as AttributeCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function basename;
use function getcwd;
use function is_file;
use function sprintf;
use function strrchr;
Expand All @@ -37,7 +33,7 @@
/**
* Builds a staged copy of the project with its managed PHP and packages it as a standalone binary or a PHAR.
*
* Options left off the command line come from the project's SKERNEL configuration file, then from the defaults.
* The PHP version, the Composer, and what is packaged where come from the project's configuration file.
*/
#[AttributeCommand(name: 'package', description: 'Package the project as a standalone binary or a PHAR')]
final class PackageCommand extends Command
Expand All @@ -55,31 +51,6 @@ public function __construct(
parent::__construct();
}

protected function configure(): void
{
$this->addOption('phar', null, InputOption::VALUE_NONE, 'Stop at the PHAR instead of a standalone binary');
$this->addOption('dev', null, InputOption::VALUE_NONE, 'Install the development dependencies into the package');
$this->addOption('output', null, InputOption::VALUE_REQUIRED, 'The package path, by default target/<name>');
$this->addOption(
'php',
null,
InputOption::VALUE_REQUIRED,
'The PHP version that runs the package, such as 8.4 or 8.4.27',
);
$this->addOption(
'composer',
null,
InputOption::VALUE_REQUIRED,
"The Composer binary that installs the packaged copy, by default a working Composer 2 or else SKERNEL's own",
);
$this->addOption(
'runtime',
null,
InputOption::VALUE_REQUIRED,
'A micro.sfx that replaces the managed micro runtime of the standalone binary',
);
}

/**
* @throws RuntimeException When the project or its configuration is invalid, or staging, installing, building,
* or writing the package fails
Expand All @@ -89,12 +60,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$project = $this->projectLocator->find($input->getOption('working-dir'));
$settings = $this->configurationLoader->load($project->directory);
$configuration = $settings->package;
$workingDirectory = (string) getcwd();
$php = $input->getOption('php');

if (null !== $php && !PhpVersion::isValid($php)) {
throw new RuntimeException('The --php option must be a PHP version such as 8.4 or 8.4.27.');
}

if (!is_file($project->directory . '/composer.lock')) {
throw new RuntimeException(sprintf(
Expand All @@ -103,9 +68,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
));
}

$phar = $input->getOption('phar') || true === $configuration->phar;
$runtime = $input->getOption('runtime');
$runtime = null === $runtime ? $configuration->runtime : AbsolutePath::resolve($runtime, $workingDirectory);
$phar = $configuration->phar;
$runtime = $configuration->runtime;

if (!$phar && null !== $runtime && !is_file($runtime)) {
throw new RuntimeException(sprintf('The micro runtime %s was not found.', $runtime));
Expand All @@ -114,9 +78,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$name = null === $project->name
? basename($project->directory)
: substr((string) strrchr('/' . $project->name, '/'), 1);
$target = $input->getOption('output');
$target = null === $target ? $configuration->output : AbsolutePath::resolve($target, $workingDirectory);
$target ??= $project->directory . '/target/' . $name . ($phar ? '.phar' : '');
$target = $configuration->output ?? $project->directory . '/target/' . $name . ($phar ? '.phar' : '');
$errorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
$staging = tempnam(sys_get_temp_dir(), 'skernel-package-');

Expand All @@ -125,19 +87,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

try {
// The configuration file holds SKERNEL's settings for this machine's builds, which the package never reads.
ProjectCopier::copy($project->directory, $staging . '/project', [
$project->vendorDirectory,
$project->directory . '/target',
$project->directory . '/skernel.json',
$project->directory . '/skernel.json.dist',
$target,
]);
$composer = $this->composerLocator->command(
$input->getOption('composer') ?? $configuration->composer,
$errorOutput,
);
$composer = $this->composerLocator->command($settings->composer, $errorOutput);
$status = $this->dependencyInstaller->install(
$staging . '/project',
$composer,
$input->getOption('dev') || true === $configuration->dev,
$configuration->dev,
$errorOutput,
);

Expand All @@ -149,11 +111,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$staged = $this->projectLocator->locate($staging . '/project');
$managed = $this->managedPhp->runtime(
$staged,
$php ?? $settings->php,
$errorOutput,
);
$managed = $this->managedPhp->runtime($staged, $settings->php, $errorOutput);
$this->projectBuilder->build(
$staged,
$managed->php,
Expand Down
25 changes: 10 additions & 15 deletions src/Command/ProjectRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@

use Src\Composer\ComposerLocator;
use Src\Configuration\ConfigurationLoader;
use Src\Configuration\ProjectConfiguration;
use Src\Project\Project;
use Src\Project\ProjectLocator;
use Src\Toolchain\ManagedPhp;
use Src\Toolchain\PhpVersion;
use Src\Toolchain\Runtime;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Provides what a command that builds an installed project needs: the project from `--working-dir`, its managed PHP
* from `--php` or the project's configuration file, and the Composer that regenerates its autoloader.
* Provides what a command that builds an installed project needs: the project from `--working-dir`, its configuration
* file, its managed PHP, and the Composer that regenerates its autoloader.
*/
final readonly class ProjectRuntime
{
Expand All @@ -31,28 +31,23 @@ public function __construct(
}

/**
* @return array{Project, Runtime, non-empty-list<string>} The project, its managed PHP, and the Composer command
* Returns the project, its settings, its managed PHP, and the command that runs its Composer.
*
* @return array{Project, ProjectConfiguration, Runtime, non-empty-list<string>}
*
* @throws RuntimeException When the project or its configuration is invalid, or its managed PHP or Composer cannot
* be provided
*/
public function resolve(InputInterface $input, OutputInterface $errorOutput): array
{
$project = $this->projectLocator->locate($input->getOption('working-dir'));
$php = $input->getOption('php');

if (null !== $php && !PhpVersion::isValid($php)) {
throw new RuntimeException('The --php option must be a PHP version such as 8.4 or 8.4.27.');
}
$configuration = $this->configurationLoader->load($project->directory);

return [
$project,
$this->managedPhp->runtime(
$project,
$php ?? $this->configurationLoader->load($project->directory)->php,
$errorOutput,
),
$this->composerLocator->command(null, $errorOutput),
$configuration,
$this->managedPhp->runtime($project, $configuration->php, $errorOutput),
$this->composerLocator->command($configuration->composer, $errorOutput),
];
}
}
18 changes: 5 additions & 13 deletions src/Command/RunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Builds and runs the project from source with its managed PHP, and with --watch rebuilds and restarts it whenever its
* files change.
* Builds and runs the project from source with its managed PHP, and when the project's configuration file enables
* watching, rebuilds and restarts it whenever a watched file changes.
*/
#[AttributeCommand(name: 'run', description: 'Build and run the project, and restart it on changes with --watch')]
#[AttributeCommand(name: 'run', description: 'Build and run the project, and restart it on changes when it watches')]
final class RunCommand extends Command
{
public function __construct(
Expand All @@ -36,13 +35,6 @@ protected function configure(): void
InputArgument::IS_ARRAY,
'The arguments of the application, such as swoole:start; put options after --',
);
$this->addOption('watch', null, InputOption::VALUE_NONE, 'Restart the application when project files change');
$this->addOption(
'php',
null,
InputOption::VALUE_REQUIRED,
'The PHP version that runs the project, such as 8.4 or 8.4.27',
);
}

/**
Expand All @@ -54,14 +46,14 @@ protected function configure(): void
protected function execute(InputInterface $input, OutputInterface $output): int
{
$errorOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
[$project, $runtime, $composer] = $this->projectRuntime->resolve($input, $errorOutput);
[$project, $configuration, $runtime, $composer] = $this->projectRuntime->resolve($input, $errorOutput);

return $this->projectRunner->run(
$project,
$runtime->php,
$composer,
$input->getArgument('arguments'),
$input->getOption('watch'),
$configuration->run,
(string) $this->getApplication()?->getVersion(),
$output,
$errorOutput,
Expand Down
Loading
Loading