ソースを参照

Merge pull request #3325 from rdohms/implementing-abandoned-packages

Abandoned Package Warnings
Nils Adermann 10 年 前
コミット
6f4be698a5

+ 10 - 0
src/Composer/Command/ShowCommand.php

@@ -294,6 +294,16 @@ EOT
         $output->writeln('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
         $output->writeln('<info>names</info>    : ' . implode(', ', $package->getNames()));
 
+        if ($package->isAbandoned()) {
+            $replacement = ($package->getReplacementPackage() !== null)
+                ? ' The author suggests using the ' . $package->getReplacementPackage(). ' package instead.'
+                : null;
+
+            $output->writeln(
+                sprintf('<error>Attention: This package is abandoned and no longer maintained.%s</error>', $replacement)
+            );
+        }
+
         if ($package->getSupport()) {
             $output->writeln("\n<info>support</info>");
             foreach ($package->getSupport() as $type => $value) {

+ 20 - 0
src/Composer/Installer.php

@@ -31,6 +31,7 @@ use Composer\Installer\NoopInstaller;
 use Composer\IO\IOInterface;
 use Composer\Json\JsonFile;
 use Composer\Package\AliasPackage;
+use Composer\Package\CompletePackage;
 use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\Locker;
@@ -240,6 +241,25 @@ class Installer
             }
         }
 
+        # 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->write(
+                sprintf(
+                    "<error>Package %s is abandoned, you should avoid using it. %s.</error>",
+                    $package->getPrettyName(),
+                    $replacement
+                )
+            );
+        }
+
         if (!$this->dryRun) {
             // write lock
             if ($this->update || !$this->locker->isLocked()) {

+ 8 - 0
src/Composer/Package/AliasPackage.php

@@ -333,6 +333,14 @@ class AliasPackage extends BasePackage implements CompletePackageInterface
     {
         return $this->aliasOf->getArchiveExcludes();
     }
+    public function isAbandoned()
+    {
+        return $this->aliasOf->isAbandoned();
+    }
+    public function getReplacementPackage()
+    {
+        return $this->aliasOf->getReplacementPackage();
+    }
     public function __toString()
     {
         return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')';

+ 27 - 0
src/Composer/Package/CompletePackage.php

@@ -27,6 +27,7 @@ class CompletePackage extends Package implements CompletePackageInterface
     protected $homepage;
     protected $scripts = array();
     protected $support = array();
+    protected $abandoned = false;
 
     /**
      * @param array $scripts
@@ -169,4 +170,30 @@ class CompletePackage extends Package implements CompletePackageInterface
     {
         return $this->support;
     }
+
+    /**
+     * @return boolean
+     */
+    public function isAbandoned()
+    {
+        return (boolean) $this->abandoned;
+    }
+
+    /**
+     * @param boolean|string $abandoned
+     */
+    public function setAbandoned($abandoned)
+    {
+        $this->abandoned = $abandoned;
+    }
+
+    /**
+     * If the package is abandoned and has a suggested replacement, this method returns it
+     *
+     * @return string|null
+     */
+    public function getReplacementPackage()
+    {
+        return is_string($this->abandoned)? $this->abandoned : null;
+    }
 }

+ 14 - 0
src/Composer/Package/CompletePackageInterface.php

@@ -78,4 +78,18 @@ interface CompletePackageInterface extends PackageInterface
      * @return array
      */
     public function getSupport();
+
+    /**
+     * Returns if the package is abandoned or not
+     *
+     * @return boolean
+     */
+    public function isAbandoned();
+
+    /**
+     * If the package is abandoned and has a suggested replacement, this method returns it
+     *
+     * @return string
+     */
+    public function getReplacementPackage();
 }

+ 4 - 0
src/Composer/Package/Dumper/ArrayDumper.php

@@ -105,6 +105,10 @@ class ArrayDumper
             if (isset($data['keywords']) && is_array($data['keywords'])) {
                 sort($data['keywords']);
             }
+
+            if ($package->isAbandoned()) {
+                $data['abandoned'] = $package->getReplacementPackage() ?: true;
+            }
         }
 
         if ($package instanceof RootPackageInterface) {

+ 4 - 0
src/Composer/Package/Loader/ArrayLoader.php

@@ -195,6 +195,10 @@ class ArrayLoader implements LoaderInterface
             if (isset($config['support'])) {
                 $package->setSupport($config['support']);
             }
+
+            if (isset($config['abandoned'])) {
+                $package->setAbandoned($config['abandoned']);
+            }
         }
 
         if ($aliasNormalized = $this->getBranchAlias($config)) {

+ 36 - 0
tests/Composer/Test/Fixtures/installer/abandoned-listed.test

@@ -0,0 +1,36 @@
+--TEST--
+Abandoned packages are flagged
+--COMPOSER--
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": [
+                { "name": "a/a", "version": "1.0.0", "abandoned": true }
+            ]
+        },
+        {
+            "type": "package",
+            "package": [
+                { "name": "c/c", "version": "1.0.0", "abandoned": "b/b" }
+            ]
+        }
+    ],
+    "require": {
+        "a/a": "1.0.0",
+        "c/c": "1.0.0"
+    }
+}
+--RUN--
+install
+--EXPECT-OUTPUT--
+<info>Loading composer repositories with package information</info>
+<info>Installing dependencies (including require-dev)</info>
+<error>Package a/a is abandoned, you should avoid using it. No replacement was suggested.</error>
+<error>Package c/c is abandoned, you should avoid using it. Use b/b instead.</error>
+<info>Writing lock file</info>
+<info>Generating autoload files</info>
+
+--EXPECT--
+Installing a/a (1.0.0)
+Installing c/c (1.0.0)

+ 1 - 1
tests/Composer/Test/Fixtures/installer/update-alias-lock.test

@@ -70,4 +70,4 @@ update
     "platform-dev": []
 }
 --EXPECT--
-Updating a/a (dev-master 1234) to a/a (dev-master master)
+Updating a/a (dev-master 1234) to a/a (dev-master master)

+ 1 - 1
tests/Composer/Test/InstallerTest.php

@@ -306,7 +306,7 @@ class InstallerTest extends TestCase
                 die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file)));
             }
 
-            $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode);
+            $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode);
         }
 
         return $tests;

+ 21 - 0
tests/Composer/Test/Package/Dumper/ArrayDumperTest.php

@@ -62,12 +62,33 @@ class ArrayDumperTest extends \PHPUnit_Framework_TestCase
         $this->assertSame('dev', $config['minimum-stability']);
     }
 
+    public function testDumpAbandoned()
+    {
+        $this->packageExpects('isAbandoned', true);
+        $this->packageExpects('getReplacementPackage', true);
+
+        $config = $this->dumper->dump($this->package);
+
+        $this->assertSame(true, $config['abandoned']);
+    }
+
+    public function testDumpAbandonedReplacement()
+    {
+        $this->packageExpects('isAbandoned', true);
+        $this->packageExpects('getReplacementPackage', 'foo/bar');
+
+        $config = $this->dumper->dump($this->package);
+
+        $this->assertSame('foo/bar', $config['abandoned']);
+    }
+
     /**
      * @dataProvider getKeys
      */
     public function testKeys($key, $value, $method = null, $expectedValue = null)
     {
         $this->packageExpects('get'.ucfirst($method ?: $key), $value);
+        $this->packageExpects('isAbandoned', $value);
 
         $config = $this->dumper->dump($this->package);
 

+ 26 - 1
tests/Composer/Test/Package/Loader/ArrayLoaderTest.php

@@ -117,7 +117,8 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
             'archive' => array(
                 'exclude' => array('/foo/bar', 'baz', '!/foo/bar/baz'),
             ),
-            'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem'))
+            'transport-options' => array('ssl' => array('local_cert' => '/opt/certs/test.pem')),
+            'abandoned' => 'foo/bar'
         );
 
         $package = $this->loader->load($config);
@@ -138,4 +139,28 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
         $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
         $this->assertEquals('1.0.x-dev', $package->getPrettyVersion());
     }
+
+    public function testAbandoned()
+    {
+        $config = array(
+            'name' => 'A',
+            'version' => '1.2.3.4',
+            'abandoned' => 'foo/bar'
+        );
+
+        $package = $this->loader->load($config);
+        $this->assertTrue($package->isAbandoned());
+        $this->assertEquals('foo/bar', $package->getReplacementPackage());
+    }
+
+    public function testNotAbandoned()
+    {
+        $config = array(
+            'name' => 'A',
+            'version' => '1.2.3.4'
+        );
+
+        $package = $this->loader->load($config);
+        $this->assertFalse($package->isAbandoned());
+    }
 }