/** * Rewrite all composer dependencies for this tag * * @param OutputInterface $output * @param LibraryRelease $releasePlan */ protected function stabiliseRequirements(OutputInterface $output, LibraryRelease $releasePlan) { $parentLibrary = $releasePlan->getLibrary(); $originalData = $composerData = $parentLibrary->getComposerData(); $constraintType = $parentLibrary->getDependencyConstraint(); // Inspect all dependencies foreach ($releasePlan->getItems() as $item) { $childName = $item->getLibrary()->getName(); $stabiliseDependencyRequirement = $this->stabiliseDependencyRequirement($output, $item, $constraintType); $composerData['require'][$childName] = $stabiliseDependencyRequirement; } // Save modifications to the composer.json for this module if ($composerData !== $originalData) { $this->updateComposerData($output, $parentLibrary, $composerData); } }
/** * Determine historic release plan from a past composer constraint * * @param LibraryRelease $newRelease * @param Version $historicVersion * @return ChangelogLibrary Changelog information for a library */ protected function getChangelogLibrary(LibraryRelease $newRelease, Version $historicVersion) { // Build root release node $historicRelease = new ChangelogLibrary($newRelease, $historicVersion); // Check all dependencies from this past commit $pastComposer = null; foreach ($newRelease->getItems() as $childNewRelease) { // Lazy-load historic composer content as needed if (!isset($pastComposer)) { $pastComposer = $newRelease->getLibrary()->getHistoryComposerData($historicVersion); } // Check if this release has a historic tag. $childReleaseName = $childNewRelease->getLibrary()->getName(); if (empty($pastComposer['require'][$childReleaseName])) { continue; } $historicConstraintName = $pastComposer['require'][$childReleaseName]; // Get oldest existing tag that matches the given constraint as the "from" for changelog purposes. $historicConstraint = new ComposerConstraint($historicConstraintName, $historicVersion, $childReleaseName); $childVersions = $historicConstraint->filterVersions($childNewRelease->getLibrary()->getTags()); // If "to" is stable, then filter out unstable "from" // E.g. prefer "3.4.0..3.4.1" over "3.4.0-rc1..3.4.1" if ($childNewRelease->getVersion()->isStable()) { $childVersions = Version::filter($childVersions, function (Version $nextTag) { return $nextTag->isStable(); }); } // Get smallest matching version $childVersions = Version::sort($childVersions, Version::ASC); if (empty($childVersions)) { throw new \LogicException("No historic version for library {$childReleaseName} matches constraint {$historicConstraintName}"); } // Check if to == from version $childHistoricVersion = reset($childVersions); if ($childHistoricVersion->getValue() === $childNewRelease->getVersion()->getValue()) { continue; } // Recursively generate historic tree $childChangelog = $this->getChangelogLibrary($childNewRelease, $childHistoricVersion); $historicRelease->addItem($childChangelog); } return $historicRelease; }
/** * Serialise a plan to json * * @param LibraryRelease $plan * @return array Encoded json data */ public function serialisePlan(LibraryRelease $plan) { $content = []; $name = $plan->getLibrary()->getName(); $content[$name] = ['Version' => $plan->getVersion()->getValue(), 'Changelog' => $plan->getChangelog(), 'Items' => []]; foreach ($plan->getItems() as $item) { $content[$name]['Items'] = array_merge($content[$name]['Items'], $item->getLibrary()->serialisePlan($item)); } return $content; }
/** * Increment any dependencies on x-dev versions that need updating * * @param OutputInterface $output * @param LibraryRelease $releasePlan */ protected function incrementDevDependencies(OutputInterface $output, LibraryRelease $releasePlan) { $parentLibrary = $releasePlan->getLibrary(); $parentName = $parentLibrary->getName(); $originalData = $composerData = $parentLibrary->getComposerData(); // Inspect all dependencies foreach ($releasePlan->getItems() as $item) { $childName = $item->getLibrary()->getName(); $childVersion = $item->getVersion(); $childConstraint = $parentLibrary->getChildConstraint($childName, $releasePlan->getVersion()); // Compare installed dependency vs version we plan to tag $comparison = $childConstraint->compareTo($childVersion); if (!$comparison) { // Dev dependency is acceptable, no rewrite needed. continue; } // Warn if downgrading constraint, but rewrite anyway. // E.g. installed with 3.1.x-dev, but tagging as 3.0.0 if ($comparison > 0) { $this->log($output, "Warning: Dependency <info>{$childName}</info> of parent <info>{$parentName}</info> " . "was installed with constraint <info>" . $childConstraint->getValue() . "</info> but is " . "being released at a lower version <info>" . $childVersion->getValue() . "</info>", "error"); } // Check if this constraint supports rewriting $newConstraint = $childConstraint->rewriteToSupport($childVersion); if (!$newConstraint || $newConstraint->getValue() === $childConstraint->getValue()) { continue; } $this->log($output, "Rewriting installation constraint of <info>{$childName}</info> from <info>" . $childConstraint->getValue() . "</info> to <info>" . $newConstraint->getValue() . "</info>"); $composerData['require'][$childName] = $newConstraint->getValue(); } // Save modifications to the composer.json for this module if ($composerData !== $originalData) { $this->log($output, "Rewriting composer.json for <info>{$parentName}</info>"); $parentLibrary->setComposerData($composerData); // Commit to git $path = $parentLibrary->getComposerPath(); $repo = $parentLibrary->getRepository(); $repo->run("add", array($path)); $status = $repo->run("status"); if (stripos($status, 'Changes to be committed:')) { $repo->run("commit", array("-m", "Update development dependencies")); } } }
/** * Build user-visible option selection list based on a prepared plan * * @param LibraryRelease $node * @param int $depth * @return array List of options */ protected function getReleaseOptions(LibraryRelease $node, $depth = 0) { $options = []; // Format / indent this line $formatting = str_repeat(' ', $depth) . ($depth ? html_entity_decode('└', ENT_NOQUOTES, 'UTF-8') . ' ' : ''); // Get version release information if ($node->getIsNewRelease()) { $version = ' (<info>' . $node->getVersion()->getValue() . '</info>) new tag'; // If releasing a new tag, show previous version $tags = $node->getLibrary()->getTags(); $previous = $node->getVersion()->getPriorVersionFromTags($tags, $node->getLibrary()->getName()); if ($previous) { $version .= ', prior version <comment>' . $previous->getValue() . '</comment>'; } } else { $version = ' (<comment>' . $node->getVersion()->getValue() . '</comment> existing tag)'; } // Build string $options[$node->getLibrary()->getName()] = $formatting . $node->getLibrary()->getName() . $version; // Build child version options foreach ($node->getItems() as $child) { $options = array_merge($options, $this->getReleaseOptions($child, $depth ? $depth + 3 : 1)); } return $options; }