Skip to content

Conversation

@simensen
Copy link
Contributor

@simensen simensen commented May 3, 2013

Use case is embedding composer. It is extremely useful to know the package information for the package that has has embedded Composer.

The root package will be added to the local repository (installed.json) as the final step of the installation process.

Special handling was required to ensure that the root package did not ever end up in the lock. Packages matching the root package name will be filtered from both the required and dev required package lists.

The installer fixtures were updated to account for --EXPECT-INSTALLED-- similar to how the lock file handling is currently handled.

The mock installed filesystem repository was updated to allow the write method to pass thru.

Two of the simpler tests, one for install, one for update, were updated to account for the new installed behavior.

All tests pass.


For some additional context, I'm working with this for my Embedded Composer project and currently have the following hacky workaround to make things kinda work:

<?php
$classLoader = require(__DIR__.'./vendor/autoload.php');

use Composer\Package\Package;
use Dflydev\EmbeddedComposer\Core\EmbeddedComposer;
use Symfony\Component\Console\Input\ArgvInput;

$input = new ArgvInput;

$projectDir = $input->getParameterOption('--project-dir') ?: '.';

// This package should accurately represent the package that contains
// the application being run.
$package = new Package('my/app', '2.0.5', '2.0.5');

$embeddedComposer = new EmbeddedComposer(
    $classLoader,
    $projectDir,
    $package
);

$embeddedComposer->processExternalAutoloads();

// Composer is now ready to load local packages as well as the packages
// that make up the calling application.

I'd like to be able to change this to something more like:

<?php
$embeddedComposer = new EmbeddedComposer(
    $classLoader,
    $projectDir,
    'my/package'
);

This would break the version tracking stuff out to Composer and would allow me to just specify the root package's name and the version can be captured from the version of the package with that name embedded in installed.json.

@simensen
Copy link
Contributor Author

simensen commented May 4, 2013

I'm adding some additional functionality and testing to handle when the root version "changes." It looks like that isn't working very well right now. I'm going to try and bypass update/uninstall/install operations for the root package.

@simensen
Copy link
Contributor Author

simensen commented May 4, 2013

So, I think things were working as expected, but now with the test I can prove that they were. :) The only oddity was some unexpected output. I added a test and another special case for operations involving the root package to account for this.

Without the most recent commit one would see the following if the version of the root package changed:

Uninstalling __root__ (1.0.0)

I did not think this was desired or expected behavior. If this is not a problem and is actually preferred, I'm happy to tweak the last commit to remove the special handling for uninstall operations involving the root package.

@simensen
Copy link
Contributor Author

simensen commented May 6, 2013

For what it is worth, I was able to update EmbeddedComposer to work against these changes and the constructor is now able to accept a package name only:

$embeddedComposer = new EmbeddedComposer(
    $classLoader,
    $projectDir,
    "sculpin/sculpin"
);

It works as expected, both internally and compiled as a phar.

@simensen
Copy link
Contributor Author

I've continued to work on EmbeddedComposer. The constructor no longer needs to know the root package in any way so the above examples are not really all that accurate, though I doubt that matters. ;) I only mention it because although it is different than listed above I still need to be able to access the root package.

I've added a findPackage that can be used to access any installed dependency. It still currently relies on this patch, though, to ensure that the root package is added to the installed repository.

To see this patch in action, this is the current state of one of the projects I'm experimenting on:

<?php

// assume installation via phar:// this is the correct location of the
// embedded autoload.php.
if (!$classLoader = @include __DIR__.'/../vendor/autoload.php') {
    die ('There is something terribly wrong with your archive.
Try downloading again?');
}

use Dflydev\EmbeddedComposer\Core\EmbeddedComposer;
use Sculpin\Bundle\SculpinBundle\Console\Application;
use Sculpin\Bundle\SculpinBundle\HttpKernel\KernelFactory;
use Symfony\Component\Console\Input\ArgvInput;

$input = new ArgvInput;

$projectDir = $input->getParameterOption('--project-dir') ?: null;

$embeddedComposer = new EmbeddedComposer($classLoader, $projectDir);

// one can optionally configure embedded composer to read from
// a composer.json named something different and default to
// another vendor directory.
$embeddedComposer
    ->setComposerFilename('sculpin.json')
    ->setVendorDirectory('.sculpin');

// if actually embedded in a phar or installed globally, this will
// process the local project's autoloader. otherwise, this is a
// noop. since the same script may operate under either mode
// it should always be called.
$embeddedComposer->processAdditionalAutoloads();

$kernel = KernelFactory::create($input);
$application = new Application($kernel, $embeddedComposer);
$application->run($input);

The Application can use $embeddedComposer to find information about the embedded sculpin/sculpin package for various purposes, including to get the version information.

<?php

namespace Sculpin\Bundle\SculpinBundle\Console;

use Dflydev\EmbeddedComposer\Core\EmbeddedComposerInterface;
use Dflydev\EmbeddedComposer\Core\EmbeddedComposerAwareInterface;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\HttpKernel\KernelInterface;

class Application extends BaseApplication implements EmbeddedComposerAwareInterface
{
    /**
     * Constructor.
     *
     * @param KernelInterface           $kernel           A KernelInterface instance
     * @param EmbeddedComposerInterface $embeddedComposer Composer Class Loader
     */
    public function __construct(KernelInterface $kernel, EmbeddedComposerInterface $embeddedComposer)
    {
        $this->kernel = $kernel;
        $this->embeddedComposer = $embeddedComposer;

        // leverages this patch, sculpin/sculpin is the root package that creates the phar
        // and needs to be in the installed.json in order to work.
        $version = $embeddedComposer->findPackage('sculpin/sculpin')->getPrettyVersion();
        if ($version !== Sculpin::GIT_VERSION && Sculpin::GIT_VERSION !== '@'.'git_version'.'@') {
            $version .= ' ('.Sculpin::GIT_VERSION.')';
        }

        parent::__construct('Sculpin', $version.' - '.$kernel->getName().'/'.$kernel->getEnvironment().($kernel->isDebug() ? '/debug' : ''));

        // ...
    }

    /** ... */
}

A custom InstallCommand provided by Embedded Composer leverages $embeddedComposer to create a Composer instance for the install/update process. This ensures that the right files and vendor directory is used and also ensures that any previously included dependencies are taken into account when solving a set of project-specific dependencies.

Current state:

<?php
class InstallCommand extends Command
{
    protected function configure()
    {
        $this
            ->setName('composer:install')
            ->setDescription('Parses the composer.json file and downloads the needed dependencies.')
            ->setDefinition(array(
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of dev-require packages.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
            ))
            ->setHelp(<<<EOT
The <info>install</info> command reads the composer.json file from the
current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file.

<info>php composer.phar install</info>

EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (!$this->getApplication() instanceof EmbeddedComposerAwareInterface) {
            throw new \RuntimeException('Application must be instance of EmbeddedComposerAwareInterface');
        }

        $embeddedComposer = $this->getApplication()->getEmbeddedComposer();

        $io = new ConsoleIO($input, $output, $this->getApplication()->getHelperSet());
        // Create a Composer instance that is aware of its embedded environment
        $composer = $embeddedComposer->createComposer($io);
        $installer = Installer::create($io, $composer);

        $installer
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($input->getOption('prefer-source'))
            ->setDevMode($input->getOption('dev'))
            ->setRunScripts(!$input->getOption('no-scripts'));

        // perform any additional configuration on the installer
        // instance. for example, set an additional installed
        // repository in the case that the script is running
        // in an embedded environment.
        $embeddedComposer->configureInstaller($installer);

        return $installer->run() ? 0 : 1;
    }
}
?>

Use case is embedding composer. It is extremely useful to know the package
information for the package that has has embedded Composer.

The root package will be added to the local repository (installed.json) as the
final step of the installation process.

Special handling was required to ensure that the root package did not ever end
up in the lock. Packages matching the root package name will be filtered from
both the required and dev required package lists.

The installer fixtures were updated to account for `--EXPECT-INSTALLED--`
similar to how the lock file handling is currently handled.

The mock installed filesystem repository was updated to allow the write method
to pass thru.

Two of the simpler tests, one for install, one for update, were updated to
account for the new installed behavior.

Handle case where root package changes in some way. This will happen any time
versions change for example.

All tests pass.
@simensen
Copy link
Contributor Author

Rebased and squashed.

simensen added a commit to dflydev/dflydev-embedded-composer that referenced this pull request May 13, 2013
simensen added a commit to dflydev/dflydev-embedded-composer-core that referenced this pull request May 21, 2013
@Seldaek Seldaek closed this Feb 29, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants