Composer系列—install命令

来源:互联网 发布:淘宝客服中心750模板 编辑:程序博客网 时间:2024/06/06 01:32

InstallCommand.php

从composer命令的执行过程我们知道最终程序会走到InstallCommand的execute方法:

在方法的执行过程中,首先根据默认配置和输入参数设置了命令,获取到composer实例,通过工厂方法创建了一个Installer实例,这个实例的初始化过程把很多组件关联在了一起。

    protected function execute(InputInterface $input, OutputInterface $output)    {        $io = $this->getIO();        if ($args = $input->getArgument('packages')) {            $io->writeError('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>');            return 1;        }        // 检查配置是否禁用plugins        if ($input->getOption('no-custom-installers')) {            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');            $input->setOption('no-plugins', true);        }        // 检查配置是否安装dev包        if ($input->getOption('dev')) {            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');        }        // 获取composer实例        $composer = $this->getComposer(true, $input->getOption('no-plugins'));        //获取composer的下载管理器 包括git hg svn file zip等        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));        // 触发事件        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);        // 初始化安装器实例 安装器把io 和 Repository仓库 Downloader Packages等组件关联在一起        $install = Installer::create($io, $composer);        $preferSource = false;        $preferDist = false;        // 获取默认的配置信息        $config = $composer->getConfig();        switch ($config->get('preferred-install')) {            case 'source':                $preferSource = true;                break;            case 'dist':                $preferDist = true;                break;            case 'auto':            default:                // noop                break;        }        if ($input->getOption('prefer-source') || $input->getOption('prefer-dist')) {            $preferSource = $input->getOption('prefer-source');            $preferDist = $input->getOption('prefer-dist');        }        //        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');        // 安装器设置执行的各种参数 和require update 参数不同        $install            ->setDryRun($input->getOption('dry-run'))            ->setVerbose($input->getOption('verbose'))            ->setPreferSource($preferSource)            ->setPreferDist($preferDist)            ->setDevMode(!$input->getOption('no-dev'))            ->setDumpAutoloader(!$input->getOption('no-autoloader'))            ->setRunScripts(!$input->getOption('no-scripts'))            ->setSkipSuggest($input->getOption('no-suggest'))            ->setOptimizeAutoloader($optimize)            ->setClassMapAuthoritative($authoritative)            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))        ;        if ($input->getOption('no-plugins')) {            $install->disablePlugins();        }        // 最终执行到安装器的入口 执行安装过程        return $install->run();    }

Installer.php

文件有关键的一步,禁用垃圾回收器 gc_disable()

  • 由于PHP的GC是基于引用计数的,为了能够回收循环引用的对象,会在refcount减少但不到0的时候,试图检测并回收循环引用的孤岛对象,但当有效对象的数量及互相引用较大(比如composer中代表包、版本和互相的依赖关系)的时候,这种搜索的开销就会变得非常巨大,造成大量的CPU计算。

  • gc_disable 不是完全关闭 gc ,而是关闭检查循环引用计数。

  • composer 的这个地方是在进行依赖包检查,对于安装了大量包的项目来说,这是比较耗时、耗内存的操作,并且这部分代码(依据设计/依据提交者的看法)无需考虑循环引用计数问题。

  • 因为占用内存太大,并且频繁触发 gc ,导致效率降低。

整个的运行流程:

  • 1.判断是不是更新操作,不是的话读取lock文件。
  • 2.最终会获取到各个仓库Repository(Repository中可能包括PlatformRepository InstalledArray InstalledFilesystem - ComposerRepository NpmRepository BowerRepository)
  • 3.把Repository都加入到一个Pool池子中,$this->package从rootPackage(要安装依赖的项目)开始找白名单中的Requires和DevRequires
  • 4.然后初始化请求队列
  • 5.如果是update调到6步否则跳7步
  • 6.从远程获取Requires和DevRequires并加入请求队列。
  • 7.从本地locked文件获取加入请求队列。
  • 8.解决依赖(返回operations) //过程较复杂
  • 9.处理devPackages
  • 10.遍历operations获取推荐的包和请求的包信息,获取安装管理器执行安装操作
  • 11.处理packageUrls,为后续写入lock文件
  • 12.安装管理器通知安装器,处理信息后写入lock文件。
  • 13.最后生成autoload.php psr4 installed.json等文件,执行脚本,生成binaries文件,重新启用gc。
    /**     * Run installation (or update)     *     * @throws \Exception     * @return int        0 on success or a positive error code on failure     */    public function run()    {        // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands        // of PHP objects, the GC can spend quite some time walking the tree of references looking        // for stuff to collect while there is nothing to collect. This slows things down dramatically        // and turning it off results in much better performance. Do not try this at home however.        // 禁用GC 大约效率提升50% 原因下面会详细介绍        gc_collect_cycles();        gc_disable();        // Force update if there is no lock file present        // 这里判断是否更新        if (!$this->update && !$this->locker->isLocked()) {            $this->update = true;        }        // 模拟运行        if ($this->dryRun) {            $this->verbose = true;            $this->runScripts = false;            // NoopInstaller 不会安装任何package            $this->installationManager->addInstaller(new NoopInstaller);            // 模拟运行的话 会伪造本地仓库            $this->mockLocalRepositories($this->repositoryManager);        }        // 检查配置的脚本 在这时触发 composer.json文件中配置的多个脚本        if ($this->runScripts) {            // dispatch pre event            $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);        }        $this->downloadManager->setPreferSource($this->preferSource);        $this->downloadManager->setPreferDist($this->preferDist);        // create installed repo, this contains all local packages + platform packages (php & extensions)        // 这时候获取已安装的仓库        $localRepo = $this->repositoryManager->getLocalRepository();        if ($this->update) {            $platformOverrides = $this->config->get('platform') ?: array();        } else {            $platformOverrides = $this->locker->getPlatformOverrides();        }        $platformRepo = new PlatformRepository(array(), $platformOverrides);        $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);        $aliases = $this->getRootAliases();        $this->aliasPlatformPackages($platformRepo, $aliases);        if (!$this->suggestedPackagesReporter) {            $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);        }        try {            // 调用doInstall 将结果 $res = $result[0];$devPackages = $result[1];            list($res, $devPackages) = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases);            if ($res !== 0) {                return $res;            }        } catch (\Exception $e) {            if (!$this->dryRun) {                $this->installationManager->notifyInstalls($this->io);            }            throw $e;        }        if (!$this->dryRun) {            $this->installationManager->notifyInstalls($this->io);        }        // output suggestions if we're in dev mode        if ($this->devMode && !$this->skipSuggest) {            $this->suggestedPackagesReporter->output($installedRepo);        }        # Find abandoned packages and warn user        foreach ($localRepo->getPackages() as $package) {            if (!$package instanceof CompletePackage || !$package->isAbandoned()) {                continue;            }            $replacement = (is_string($package->getReplacementPackage()))                ? 'Use ' . $package->getReplacementPackage() . ' instead'                : 'No replacement was suggested';            $this->io->writeError(                sprintf(                    "<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",                    $package->getPrettyName(),                    $replacement                )            );        }        // 不是模拟运行时 写入lock        if (!$this->dryRun) {            // write lock            if ($this->update) {                $localRepo->reload();                $platformReqs = $this->extractPlatformRequirements($this->package->getRequires());                $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());                // 把包信息更新到lock文件                $updatedLock = $this->locker->setLockData(                    array_diff($localRepo->getCanonicalPackages(), $devPackages),                    $devPackages,                    $platformReqs,                    $platformDevReqs,                    $aliases,                    $this->package->getMinimumStability(),                    $this->package->getStabilityFlags(),                    $this->preferStable || $this->package->getPreferStable(),                    $this->preferLowest,                    $this->config->get('platform') ?: array()                );                if ($updatedLock) {                    $this->io->writeError('<info>Writing lock file</info>');                }            }            if ($this->dumpAutoloader) {                // write autoloader                if ($this->optimizeAutoloader) {                    $this->io->writeError('<info>Generating optimized autoload files</info>');                } else {                    $this->io->writeError('<info>Generating autoload files</info>');                }                // 写入autoload                $this->autoloadGenerator->setDevMode($this->devMode);                $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);                // 执行script                $this->autoloadGenerator->setRunScripts($this->runScripts);                // 写入psr4 autoload 等文件                $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);            }            if ($this->runScripts) {                // dispatch post event                // 监听者执行事件                $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;                $this->eventDispatcher->dispatchScript($eventName, $this->devMode);            }            // force binaries re-generation in case they are missing            // 重新生成bin文件            foreach ($localRepo->getPackages() as $package) {                $this->installationManager->ensureBinariesPresence($package);            }            $vendorDir = $this->config->get('vendor-dir');            if (is_dir($vendorDir)) {                // suppress errors as this fails sometimes on OSX for no apparent reason                // see https://github.com/composer/composer/issues/4070#issuecomment-129792748                @touch($vendorDir);            }        }        // re-enable GC except on HHVM which triggers a warning here        if (!defined('HHVM_VERSION')) {            // 安装完成重新启用gc            gc_enable();        }        return 0;    }    /**     * @param  RepositoryInterface $localRepo     * @param  RepositoryInterface $installedRepo     * @param  PlatformRepository  $platformRepo     * @param  array               $aliases     * @return array [int, PackageInterfaces[]|null] with the exit code and an array of dev packages on update, or null on install     */    protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases)    {        // init vars        $lockedRepository = null;        $repositories = null;        // initialize locked repo if we are installing from lock or in a partial update        // and a lock file is present as we need to force install non-whitelisted lock file        // packages in that case        // 如果不更新 就从本地的composer.lock文件获取包信息        if (!$this->update || (!empty($this->updateWhitelist) && $this->locker->isLocked())) {            try {                // 从lockfile中获取                $lockedRepository = $this->locker->getLockedRepository($this->devMode);            } catch (\RuntimeException $e) {                // if there are dev requires, then we really can not install                if ($this->package->getDevRequires()) {                    throw $e;                }                // no require-dev in composer.json and the lock file was created with no dev info, so skip them                $lockedRepository = $this->locker->getLockedRepository();            }        }        // 白名单更新列表 中的依赖 composer update symfony/console        $this->whitelistUpdateDependencies(            $localRepo,            $this->package->getRequires(),            $this->package->getDevRequires()        );        $this->io->writeError('<info>Loading composer repositories with package information</info>');        // 创建默认的协议 prefer        $policy = $this->createPolicy();        // creating repository pool        // 创建一个大池子        $pool = $this->createPool($this->update ? null : $lockedRepository);        $pool->addRepository($installedRepo, $aliases);        // Repository中可能包括PlatformRepository InstalledArray InstalledFilesystem        // ComposerRepository NpmRepository BowerRepository        if ($this->update) {            $repositories = $this->repositoryManager->getRepositories();            foreach ($repositories as $repository) {                $pool->addRepository($repository, $aliases);            }        }        // Add the locked repository after the others in case we are doing a        // partial update so missing packages can be found there still.        // For installs from lock it's the only one added so it is first        // 添加locked的仓库 原因如上 在其他都添加完成之后再添加locked仓库是因为在我们只更新部分包时 未命中的包依然能被找到        if ($lockedRepository) {            $pool->addRepository($lockedRepository, $aliases);        }        // creating requirements request        $request = $this->createRequest($this->package, $platformRepo);        if ($this->update) {            // remove unstable packages from the localRepo if they don't match the current stability settings            $removedUnstablePackages = array();            foreach ($localRepo->getPackages() as $package) {                if (                    !$pool->isPackageAcceptable($package->getNames(), $package->getStability())                    && $this->installationManager->isPackageInstalled($localRepo, $package)                ) {                    $removedUnstablePackages[$package->getName()] = true;                    $request->remove($package->getName(), new Constraint('=', $package->getVersion()));                }            }            $this->io->writeError('<info>Updating dependencies'.($this->devMode ? ' (including require-dev)' : '').'</info>');            // 添加update-all命令            $request->updateAll();            $links = array_merge($this->package->getRequires(), $this->package->getDevRequires());            // 将request加入job队列            foreach ($links as $link) {                $request->install($link->getTarget(), $link->getConstraint());            }            // if the updateWhitelist is enabled, packages not in it are also fixed            // to the version specified in the lock, or their currently installed version            if ($this->updateWhitelist) {                $currentPackages = $this->getCurrentPackages($installedRepo);                // collect packages to fixate from root requirements as well as installed packages                $candidates = array();                foreach ($links as $link) {                    $candidates[$link->getTarget()] = true;                    $rootRequires[$link->getTarget()] = $link;                }                foreach ($currentPackages as $package) {                    $candidates[$package->getName()] = true;                }                // fix them to the version in lock (or currently installed) if they are not updateable                foreach ($candidates as $candidate => $dummy) {                    foreach ($currentPackages as $curPackage) {                        if ($curPackage->getName() === $candidate) {                            if (!$this->isUpdateable($curPackage) && !isset($removedUnstablePackages[$curPackage->getName()])) {                                $constraint = new Constraint('=', $curPackage->getVersion());                                $description = $this->locker->isLocked() ? '(locked at' : '(installed at';                                $requiredAt = isset($rootRequires[$candidate]) ? ', required as ' . $rootRequires[$candidate]->getPrettyConstraint() : '';                                $constraint->setPrettyString($description . ' ' . $curPackage->getPrettyVersion() . $requiredAt . ')');                                $request->install($curPackage->getName(), $constraint);                            }                            break;                        }                    }                }            }        } else {            // install命令的的执行过程            $this->io->writeError('<info>Installing dependencies'.($this->devMode ? ' (including require-dev)' : '').' from lock file</info>');            if (!$this->locker->isFresh()) {                $this->io->writeError('<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. Run update to update them.</warning>', true, IOInterface::QUIET);            }            foreach ($lockedRepository->getPackages() as $package) {                $version = $package->getVersion();                if (isset($aliases[$package->getName()][$version])) {                    $version = $aliases[$package->getName()][$version]['alias_normalized'];                }                $constraint = new Constraint('=', $version);                $constraint->setPrettyString($package->getPrettyVersion());                // 往请求中添加job                $request->install($package->getName(), $constraint);            }            foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) {                $request->install($link->getTarget(), $link->getConstraint());            }        }        // force dev packages to have the latest links if we update or install from a (potentially new) lock        $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links');        // solve dependencies        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request);        $solver = new Solver($policy, $pool, $installedRepo, $this->io);        try {            // 很重要的一步 把request->jobs队列 解决依赖            $operations = $solver->solve($request, $this->ignorePlatformReqs);            $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations);        } catch (SolverProblemsException $e) {            $this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);            $this->io->writeError($e->getMessage());            return array(max(1, $e->getCode()), array());        }        $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);        $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE);        // force dev packages to be updated if we update or install from a (potentially new) lock        $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations);        // execute operations        if (!$operations) {            $this->io->writeError('Nothing to install or update');        }        /**         * Workaround: if your packages depend on plugins, we must be sure         * that those are installed / updated first; else it would lead to packages         * being installed multiple times in different folders, when running Composer         * twice.         */        $operations = $this->movePluginsToFront($operations);        /**         * Removals of packages should be executed before installations in         * case two packages resolve to the same path (due to custom installers)         */        $operations = $this->moveUninstallsToFront($operations);        // extract dev packages and mark them to be skipped if it's a --no-dev install or update        // we also force them to be uninstalled if they are present in the local repo        if ($this->update) {            $devPackages = $this->extractDevPackages($operations, $localRepo, $platformRepo, $aliases);            if (!$this->devMode) {                $operations = $this->filterDevPackageOperations($devPackages, $operations, $localRepo);            }        } else {            $devPackages = null;        }        foreach ($operations as $operation) {            // collect suggestions            if ('install' === $operation->getJobType()) {                // 展示推荐的package                $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage());            }            // updating, force dev packages' references if they're in root package refs            if ($this->update) {                $package = null;                if ('update' === $operation->getJobType()) {                    $package = $operation->getTargetPackage();                } elseif ('install' === $operation->getJobType()) {                    $package = $operation->getPackage();                }                if ($package && $package->isDev()) {                    $references = $this->package->getReferences();                    if (isset($references[$package->getName()])) {                        $this->updateInstallReferences($package, $references[$package->getName()]);                    }                }                if ('update' === $operation->getJobType()                    && $operation->getTargetPackage()->isDev()                    && $operation->getTargetPackage()->getVersion() === $operation->getInitialPackage()->getVersion()                    && (!$operation->getTargetPackage()->getSourceReference() || $operation->getTargetPackage()->getSourceReference() === $operation->getInitialPackage()->getSourceReference())                    && (!$operation->getTargetPackage()->getDistReference() || $operation->getTargetPackage()->getDistReference() === $operation->getInitialPackage()->getDistReference())                ) {                    $this->io->writeError('  - Skipping update of '. $operation->getTargetPackage()->getPrettyName().' to the same reference-locked version', true, IOInterface::DEBUG);                    $this->io->writeError('', true, IOInterface::DEBUG);                    continue;                }            }            $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType());            if (defined($event) && $this->runScripts) {                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation);            }            // output non-alias ops in dry run, output alias ops in debug verbosity            if ($this->dryRun && false === strpos($operation->getJobType(), 'Alias')) {                $this->io->writeError('  - ' . $operation);                $this->io->writeError('');            } elseif ($this->io->isDebug() && false !== strpos($operation->getJobType(), 'Alias')) {                $this->io->writeError('  - ' . $operation);                $this->io->writeError('');            }            $this->installationManager->execute($localRepo, $operation);            // output reasons why the operation was ran, only for install/update operations            if ($this->verbose && $this->io->isVeryVerbose() && in_array($operation->getJobType(), array('install', 'update'))) {                $reason = $operation->getReason();                if ($reason instanceof Rule) {                    switch ($reason->getReason()) {                        case Rule::RULE_JOB_INSTALL:                            $this->io->writeError('    REASON: Required by the root package: '.$reason->getPrettyString($pool));                            $this->io->writeError('');                            break;                        case Rule::RULE_PACKAGE_REQUIRES:                            $this->io->writeError('    REASON: '.$reason->getPrettyString($pool));                            $this->io->writeError('');                            break;                    }                }            }            $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($operation->getJobType());            if (defined($event) && $this->runScripts) {                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation);            }            if (!$this->dryRun) {                $localRepo->write();            }        }        if (!$this->dryRun) {            // force source/dist urls to be updated for all packages            // 处理package url等信息 为后续写入lock做准备            $this->processPackageUrls($pool, $policy, $localRepo, $repositories);            $localRepo->write();        }        return array(0, $devPackages);    }

依赖的解决 RuleSetGenerator

 /**     * Creates a new rule for the requirements of a package     *     * This rule is of the form (-A|B|C), where B and C are the providers of     * one requirement of the package A.     *     * @param  PackageInterface $package    The package with a requirement     * @param  array            $providers  The providers of the requirement     * @param  int              $reason     A RULE_* constant describing the     *                                      reason for generating this rule     * @param  mixed            $reasonData Any data, e.g. the requirement name,     *                                      that goes with the reason     * @return Rule             The generated rule or null if tautological     */    protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)    {        $literals = array(-$package->id);        foreach ($providers as $provider) {            // self fulfilling rule?            if ($provider === $package) {                return null;            }            $literals[] = $provider->id;        }        return new Rule($literals, $reason, $reasonData);    }    /**     * Creates a rule to install at least one of a set of packages     *     * The rule is (A|B|C) with A, B and C different packages. If the given     * set of packages is empty an impossible rule is generated.     *     * @param  array $packages The set of packages to choose from     * @param  int   $reason   A RULE_* constant describing the reason for     *                         generating this rule     * @param  array $job      The job this rule was created from     * @return Rule  The generated rule     */    protected function createInstallOneOfRule(array $packages, $reason, $job)    {        $literals = array();        foreach ($packages as $package) {            $literals[] = $package->id;        }        return new Rule($literals, $reason, $job['packageName'], $job);    }    /**     * Creates a rule to remove a package     *     * The rule for a package A is (-A).     *     * @param  PackageInterface $package The package to be removed     * @param  int              $reason  A RULE_* constant describing the     *                                   reason for generating this rule     * @param  array            $job     The job this rule was created from     * @return Rule             The generated rule     */    protected function createRemoveRule(PackageInterface $package, $reason, $job)    {        return new Rule(array(-$package->id), $reason, $job['packageName'], $job);    }    /**     * Creates a rule for two conflicting packages     *     * The rule for conflicting packages A and B is (-A|-B). A is called the issuer     * and B the provider.     *     * @param  PackageInterface $issuer     The package declaring the conflict     * @param  PackageInterface $provider   The package causing the conflict     * @param  int              $reason     A RULE_* constant describing the     *                                      reason for generating this rule     * @param  mixed            $reasonData Any data, e.g. the package name, that     *                                      goes with the reason     * @return Rule             The generated rule     */    protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)    {        // ignore self conflict        if ($issuer === $provider) {            return null;        }        return new Rule(array(-$issuer->id, -$provider->id), $reason, $reasonData);    }

A依赖的包是B,B就是provider。A B C 表示包的id

- //createRequireRule-A | B | CA ->issue  B C ->providers(提供者)A依赖于B C- //createConflictRule-A | -BA ->issue  B ->providers(提供者)A和B冲突- //createInstallOneOfRuleA | B | CA B C需要安装- //createRemoveRule-AA需要删除

如果出现-A | B -B | A 环形依赖,composer会报环形依赖错误。
在做决定时,-A | B | C,如果A要安装,那么B,C需要已经确定安装。意思就是需要从一个无依赖的成品单元来开始做决定动作。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 注册淘宝企业店铺需要审核怎么办 淘宝店铺被投诉知识产权怎么办 一般违规扣48分怎么办 金税盘处于报税期不能开票怎么办 小规模税率开错了怎么办 我是代购卖家被买家投诉偷税怎么办 天猫盒子内存不够怎么办 天猫品牌申请不通过怎么办 天猫商家发货发个空包裹怎么办 无限流量怎么办没有4g 海外直邮身份证过期了怎么办 买车的人不过户怎么办 天猫精灵球泡离线怎么办 花呗被骗了2万怎么办 天猫公司变更地址发票怎么办 支付宝自助解限怎么办 支付宝16岁限额怎么办 支付宝提不了现怎么办 支付宝余额受限需要身份证怎么办 微信被骗了6000怎么办 被代运营骗了该怎么办 淘宝店铺过节放假无人打理怎么办 淘宝店太久没打理出现未开店怎么办 淘宝店关了售后怎么办 发货运单号发错了怎么办 天猫积分为零怎么办 山东聊城小型车脱审一年怎么办? 廉租房如果夫妻离婚怎么办 淘宝客服不给退货怎么办 天猫客服打字慢怎么办 京东买的kindle坏了怎么办 欧巴怎么办韩语怎么写 聚划算淘宝口令打不开怎么办 道聚城白银礼包下架怎么办 聚星输了很多钱怎么办 弹力运动裤被烟烧了个洞怎么办 生完宝宝胯宽怎么办 黑色纯棉裤子洗的发白怎么办 金盾保险柜密码忘了怎么办 装修好的房子漏水怎么办 刚装修的房子墙面开裂怎么办