/** * Update github release notes via github API * * @param OutputInterface $output * @param LibraryRelease $release */ protected function updateGithubChangelog(OutputInterface $output, LibraryRelease $release) { $library = $release->getLibrary(); if (!$library->hasGithubChangelog()) { return; } // Check github slug is available $slug = $release->getLibrary()->getGithubSlug(); if (empty($slug)) { throw new InvalidArgumentException("Could not find github slug for " . $library->getName()); } list($org, $repo) = explode('/', $slug); // Log release $version = $release->getVersion(); $tag = $version->getValue(); $this->log($output, "Creating github release <info>{$org}/{$repo} v{$tag}</info>"); /** @var Repo $reposAPI */ $client = $this->getGithubClient($output); $reposAPI = $client->api('repo'); $releasesAPI = $reposAPI->releases(); // Build release payload // Note target_commitish is omitted since the tag already exists $releaseData = ['tag_name' => $tag, 'name' => $tag, 'prerelease' => !$version->isStable(), 'draft' => false]; $changelog = $release->getChangelog(); if ($changelog) { $releaseData['body'] = $changelog; } // Determine if editing or creating a release $existing = null; try { sleep(1); // Ensure non-zero period between tagging and searching for this tag $existing = $releasesAPI->tag($org, $repo, $tag); } catch (RuntimeException $ex) { if ($ex->getCode() !== 404) { throw $ex; } } // Create or update if ($existing && !empty($existing['id'])) { $result = $releasesAPI->edit($org, $repo, $existing['id'], $releaseData); if (isset($result['created_at'])) { $this->log($output, "Successfully updated github release"); return; } } else { $result = $releasesAPI->create($org, $repo, $releaseData); if (isset($result['created_at'])) { $this->log($output, "Successfully created at <info>" . $result['created_at'] . "</info>"); return; } } $this->log($output, "Github API update failed for <info>" . $library->getName() . "</info>", "error"); }
/** * 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; }
/** * 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")); } } }
/** * Add or replace release for child object * * @param LibraryRelease $release * @return $this */ public function addItem(LibraryRelease $release) { $name = $release->getLibrary()->getName(); $this->items[$name] = $release; return $this; }
/** * 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; }
/** * Update selected version of a given library * * @param LibraryRelease $selectedVersion * @param Version $newVersion New version */ protected function modifyLibraryReleaseVersion(LibraryRelease $selectedVersion, $newVersion) { $wasNewRelease = $selectedVersion->getIsNewRelease(); // Replace tag $selectedVersion->setVersion($newVersion); // If the "create new release" tag changes, we need to re-generate all child dependencies $isNewRelease = $selectedVersion->getIsNewRelease(); if ($wasNewRelease !== $isNewRelease) { // Need to either clear, or regenerate all children $selectedVersion->clearItems(); // Changing to require a new tag will populate children again from scratch if ($isNewRelease) { $this->generateChildReleases($selectedVersion); } } }