Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dev): add command to update composer deps #6773

Merged
merged 10 commits into from
Nov 21, 2023
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
7 changes: 5 additions & 2 deletions .github/run-package-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ for DIR in ${DIRS}; do {
fi
done

echo "Running $DIR Unit Tests"
composer -q --no-interaction --no-ansi --no-progress update -d ${DIR};
echo "Installing composer in $DIR"
COMPOSER_ROOT_VERSION=$(cat $DIR/VERSION) composer -q --no-interaction --no-ansi --no-progress update -d ${DIR};
if [ $? != 0 ]; then
echo "$DIR: composer install failed" >> "${FAILED_FILE}"
# run again but without "-q" so we can see the error
COMPOSER_ROOT_VERSION=$(cat $DIR/VERSION) composer --no-interaction --no-ansi --no-progress update -d ${DIR};
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this PR, but should this not be exit instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, because we want to run all the tests even if one fails, so we can see if the others succeed or not. By sending the error in FAILED_FILE, we ensure that we will exit below.

fi
echo "Running $DIR Unit Tests"
${DIR}/vendor/bin/phpunit -c ${DIR}/phpunit.xml.dist;
if [ $? != 0 ]; then
echo "$DIR: failed" >> "${FAILED_FILE}"
Expand Down
2 changes: 1 addition & 1 deletion ContainerAnalysis/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"require": {
"php": ">=7.4",
"google/gax": "^1.24.0",
"google/grafeas": "^0.8.0"
"google/grafeas": "^0.8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
Expand Down
4 changes: 3 additions & 1 deletion Debugger/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
}
},
"archive": {
"exclude": ["/ext"]
"exclude": [
"/ext"
]
},
"bin": [
"bin/google-cloud-debugger"
Expand Down
2 changes: 1 addition & 1 deletion ShoppingMerchantReports/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"require": {
"php": ">=7.4",
"google/gax": "^1.24.0",
"google/shopping-common-protos": "^0.1.0"
"google/shopping-common-protos": "^0.1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
Expand Down
2 changes: 1 addition & 1 deletion Trace/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"ramsey/uuid": "^3.0|^4.0",
"google/gax": "^1.24.0"
},
"require-dev": {
"require-dev": {
"phpunit/phpunit": "^9.0",
"phpspec/prophecy-phpunit": "^2.0",
"squizlabs/php_codesniffer": "2.*",
Expand Down
2 changes: 1 addition & 1 deletion Translate/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"google/cloud-core": "^1.52.7",
"google/gax": "^1.24.0"
},
"require-dev": {
"require-dev": {
"phpunit/phpunit": "^9.0",
"phpspec/prophecy-phpunit": "^2.0",
"squizlabs/php_codesniffer": "2.*",
Expand Down
2 changes: 2 additions & 0 deletions dev/google-cloud
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use Google\Cloud\Dev\Command\DocFxCommand;
use Google\Cloud\Dev\Command\RepoInfoCommand;
use Google\Cloud\Dev\Command\ReleaseInfoCommand;
use Google\Cloud\Dev\Command\SplitCommand;
use Google\Cloud\Dev\Command\UpdateDepsCommand;
use Symfony\Component\Console\Application;

if (!class_exists(Application::class)) {
Expand All @@ -47,4 +48,5 @@ $app->add(new DocFxCommand());
$app->add(new RepoInfoCommand());
$app->add(new ReleaseInfoCommand());
$app->add(new SplitCommand($rootDirectory));
$app->add(new UpdateDepsCommand());
$app->run();
16 changes: 0 additions & 16 deletions dev/sh/bump-dep.sh

This file was deleted.

2 changes: 1 addition & 1 deletion dev/src/Command/ComponentInfoCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected function configure()
{
$this->setName('component-info')
->setDescription('list info of a component or the whole library')
->addOption('component', 'c', InputOption::VALUE_REQUIRED, 'Generate docs only for a single component.', '')
->addOption('component', 'c', InputOption::VALUE_REQUIRED, 'get info for a single component', '')
->addOption('csv', '', InputOption::VALUE_REQUIRED, 'export findings to csv.')
->addOption('fields', 'f', InputOption::VALUE_REQUIRED, sprintf(
"Comma-separated list of fields. The following fields are available: \n - %s\n" .
Expand Down
115 changes: 115 additions & 0 deletions dev/src/Command/UpdateDepsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php
/**
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Dev\Command;

use Google\Cloud\Dev\Composer;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;

/**
* Update package dependencies
*
* @internal
*/
class UpdateDepsCommand extends Command
{
protected function configure()
{
$this->setName('update-deps')
->setDescription('update a dependency across all components')
->addArgument('package', InputArgument::REQUIRED, 'Package name to update, e.g. "google/gax"')
->addArgument('version', InputArgument::OPTIONAL, 'Package version to update to, e.g. "1.4.0"', '')
->addOption('component', 'c', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, 'bumps deps for the specified component/file')
->addOption('bump', '', InputOption::VALUE_NONE, 'Bump to latest version of the package')
->addOption('add', '', InputOption::VALUE_OPTIONAL, 'Adds the dep if it doesn\'t exist (--add=dev for require-dev)', false)
->addOption('local', '', InputOption::VALUE_NONE, 'Add a link to the local component')
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$package = $input->getArgument('package');
if (!$version = $input->getArgument('version')) {
if (!$input->getOption('bump')) {
throw new \InvalidArgumentException('You must either supply a package version or the --bump flag');
}
if (!$version = Composer::getLatestVersion($package)) {
throw new \InvalidArgumentException('Could not find a version for ' . $package);
}
} elseif ($input->getOption('bump')) {
throw new \InvalidArgumentException('You cannot supply both a package version and the --bump flag');
}

$projectRoot = realpath(__DIR__ . '/../../../');
$result = (new Finder())->files()->in($projectRoot)->depth('<= 1')->name('composer.json');
$paths = array_map(fn ($file) => $file->getPathname(), iterator_to_array($result));
sort($paths);

$componentPath = $input->getOption('local') ? $this->getComponentName($paths, $package) : null;
$updateCount = 0;
foreach ($input->getOption('component') ?: $paths as $jsonFile) {
if (is_dir($jsonFile) && file_exists($jsonFile . '/composer.json')) {
$jsonFile .= '/composer.json';
}
$composerJson = json_decode(file_get_contents($jsonFile), true);
$require = 'require';
if (!isset($composerJson['require'][$package])) {
if (isset($composerJson['require-dev'][$package])) {
$require = 'require-dev';
} elseif (false === $input->getOption('add')) {
continue;
} elseif ('dev' === $input->getOption('add')) {
$require = 'require-dev';
}
}
$composerJson[$require][$package] = $version;
if ($input->getOption('local')) {
$composerJson['repositories'] ??= [];
$composerJson['repositories'][] = [
'type' => 'path',
'url' => '../' . $componentPath,
'options' => [
'versions' => [$package => $version],
]
];
}
$output->writeln(sprintf('Updated <info>%s</>', basename(dirname($jsonFile))));
file_put_contents($jsonFile, json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
$updateCount++;
}
$output->writeln("Updated <fg=white>$updateCount packages</> to use <comment>$package</>: <info>$version</>");

return 0;
}

private function getComponentName(array $paths, string $package): string
{
foreach ($paths as $path) {
$composerJson = json_decode(file_get_contents($path), true);
if (isset($composerJson['name']) && $composerJson['name'] === $package) {
return basename(dirname($path));
}
}

throw new \InvalidArgumentException('Component not found for package ' . $package);
}
}
138 changes: 138 additions & 0 deletions dev/tests/Unit/Command/UpdateDepsCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Dev\Tests\Unit\Command;

use Google\Cloud\Dev\Command\UpdateDepsCommand;
use Google\Cloud\Dev\Composer;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;

/**
* @group dev
*/
class UpdateDepsCommandTest extends TestCase
{
/**
* @dataProvider provideUpdateDeps
*/
public function testUpdateDeps(array $cmdOptions, array $json, array $expectedJson)
{
$tmpFile = sys_get_temp_dir() . '/composer.json';
file_put_contents($tmpFile, json_encode($json));
$cmdOptions['--component'] = [$tmpFile];
$commandTester = new CommandTester(new UpdateDepsCommand());
$commandTester->execute($cmdOptions);

$this->assertEquals($expectedJson, json_decode(file_get_contents($tmpFile), true));
}

public function testBumpWithVersionThrowsException()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('You cannot supply both a package version and the --bump flag');

$commandTester = new CommandTester(new UpdateDepsCommand());
$commandTester->execute([
'package' => 'google/gax',
'version' => '1.2.3',
'--bump' => true,
]);
}

public function testNoBumpOrVersionThrowsException()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('You must either supply a package version or the --bump flag');

$commandTester = new CommandTester(new UpdateDepsCommand());
$commandTester->execute([
'package' => 'google/gax',
]);
}

public function testInvalidComponentWithLocalThrowsException()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Component not found for package google/foo');

$commandTester = new CommandTester(new UpdateDepsCommand());
$commandTester->execute([
'package' => 'google/foo',
'version' => '1.2.3',
'--local' => true,
]);
}

public function provideUpdateDeps()
{
return [
// Update package (require)
[
['package' => 'google/gax', 'version' => '4.5.6'],
['require' => ['google/gax' => '1.2.3']],
['require' => ['google/gax' => '4.5.6']],
],
// Update package (require-dev)
[
['package' => 'google/gax', 'version' => '4.5.6'],
['require-dev' => ['google/gax' => '1.2.3']],
['require-dev' => ['google/gax' => '4.5.6']],
],
// Update package (doesn't exist)
[
['package' => 'google/gax', 'version' => '4.5.6'],
[],
[],
],
// Update package with add (require)
[
['package' => 'google/gax', 'version' => '4.5.6', '--add' => true],
[],
['require' => ['google/gax' => '4.5.6']],
],
// Update package with add (require-dev)
[
['package' => 'google/gax', 'version' => '4.5.6', '--add' => 'dev'],
[],
['require-dev' => ['google/gax' => '4.5.6']],
],
// Update package with bump (require-dev)
[
['package' => 'google/gax', '--bump' => true],
['require' => ['google/gax' => '1.2.3']],
['require' => ['google/gax' => Composer::getLatestVersion('google/gax')]],
],
// Update package with local
[
['package' => 'google/cloud-core', 'version' => '1.100', '--local' => true],
['require' => ['google/cloud-core' => '1.2.3']],
[
'require' => ['google/cloud-core' => '1.100'],
'repositories' => [
[
'type' => 'path',
'url' => '../Core',
'options' => ['versions' => ['google/cloud-core' => '1.100']],
],
],
],
],
];
}
}