]> source.dussan.org Git - nextcloud-server.git/commitdiff
adding supported libraries - including min and max version
authorThomas Müller <thomas.mueller@tmit.eu>
Fri, 5 Dec 2014 12:52:51 +0000 (13:52 +0100)
committerThomas Müller <thomas.mueller@tmit.eu>
Thu, 11 Dec 2014 11:02:11 +0000 (12:02 +0100)
lib/private/app/dependencyanalyzer.php
lib/private/app/platform.php
lib/private/app/platformrepository.php [new file with mode: 0644]
tests/data/app/expected-info.json
tests/data/app/valid-info.xml
tests/lib/app/dependencyanalyzer.php

index 172b8e88c06f209cbbae9db90004c3cc3f0ecb19..af36637b675899a04717df886b77ee4a90232013 100644 (file)
@@ -44,13 +44,14 @@ class DependencyAnalyzer {
         * @returns array of missing dependencies
         */
        public function analyze() {
-               $this->analysePhpVersion();
-               $this->analyseSupportedDatabases();
-               $this->analyseCommands();
+               $this->analyzePhpVersion();
+               $this->analyzeDatabases();
+               $this->analyzeCommands();
+               $this->analyzeLibraries();
                return $this->missing;
        }
 
-       private function analysePhpVersion() {
+       private function analyzePhpVersion() {
                if (isset($this->dependencies['php']['@attributes']['min-version'])) {
                        $minVersion = $this->dependencies['php']['@attributes']['min-version'];
                        if (version_compare($this->platform->getPhpVersion(), $minVersion, '<')) {
@@ -60,12 +61,12 @@ class DependencyAnalyzer {
                if (isset($this->dependencies['php']['@attributes']['max-version'])) {
                        $maxVersion = $this->dependencies['php']['@attributes']['max-version'];
                        if (version_compare($this->platform->getPhpVersion(), $maxVersion, '>')) {
-                               $this->addMissing((string)$this->l->t('PHP with a version less then %s is required.', $maxVersion));
+                               $this->addMissing((string)$this->l->t('PHP with a version lower than %s is required.', $maxVersion));
                        }
                }
        }
 
-       private function analyseSupportedDatabases() {
+       private function analyzeDatabases() {
                if (!isset($this->dependencies['database'])) {
                        return;
                }
@@ -83,7 +84,7 @@ class DependencyAnalyzer {
                }
        }
 
-       private function analyseCommands() {
+       private function analyzeCommands() {
                if (!isset($this->dependencies['command'])) {
                        return;
                }
@@ -101,6 +102,39 @@ class DependencyAnalyzer {
                }
        }
 
+       private function analyzeLibraries() {
+               if (!isset($this->dependencies['lib'])) {
+                       return;
+               }
+
+               $libs = $this->dependencies['lib'];
+               foreach($libs as $lib) {
+                       $libName = $this->getValue($lib);
+                       $libVersion = $this->platform->getLibraryVersion($libName);
+                       if (is_null($libVersion)) {
+                               $this->addMissing((string)$this->l->t('The library %s is not available.', $libName));
+                               continue;
+                       }
+
+                       if (is_array($lib)) {
+                               if (isset($lib['@attributes']['min-version'])) {
+                                       $minVersion = $lib['@attributes']['min-version'];
+                                       if (version_compare($libVersion, $minVersion, '<')) {
+                                               $this->addMissing((string)$this->l->t('Library %s with a version higher than %s is required - available version %s.',
+                                                       array($libName, $minVersion, $libVersion)));
+                                       }
+                               }
+                               if (isset($lib['@attributes']['max-version'])) {
+                                       $maxVersion = $lib['@attributes']['max-version'];
+                                       if (version_compare($libVersion, $maxVersion, '>')) {
+                                               $this->addMissing((string)$this->l->t('Library %s with a version lower than %s is required - available version %s.',
+                                                       array($libName, $maxVersion, $libVersion)));
+                                       }
+                               }
+                       }
+               }
+       }
+
        /**
         * @param $element
         * @return mixed
index da515a235a2fdc505cb734677e1f102cc9b57f0a..6279bb5f20c4fb37c6e44b9d950e02dc9568d937 100644 (file)
@@ -62,4 +62,10 @@ class Platform {
                $path = \OC_Helper::findBinaryPath($command);
                return ($path !== null);
        }
+
+       public function getLibraryVersion($name) {
+               $repo = new PlatformRepository();
+               $lib = $repo->findLibrary($name);
+               return $lib;
+       }
 }
diff --git a/lib/private/app/platformrepository.php b/lib/private/app/platformrepository.php
new file mode 100644 (file)
index 0000000..96d04ec
--- /dev/null
@@ -0,0 +1,210 @@
+<?php
+
+namespace OC\App;
+
+/**
+ * Class PlatformRepository
+ *
+ * Inspired by the composer project - licensed under MIT
+ * https://github.com/composer/composer/blob/master/src/Composer/Repository/PlatformRepository.php#L82
+ *
+ * @package OC\App
+ */
+class PlatformRepository {
+       public function __construct() {
+               $this->packages = $this->initialize();
+       }
+
+       protected function initialize() {
+               $loadedExtensions = get_loaded_extensions();
+               $packages = array();
+
+               // Extensions scanning
+               foreach ($loadedExtensions as $name) {
+                       if (in_array($name, array('standard', 'Core'))) {
+                               continue;
+                       }
+
+                       $ext = new \ReflectionExtension($name);
+                       try {
+                               $prettyVersion = $ext->getVersion();
+                       } catch (\UnexpectedValueException $e) {
+                               $prettyVersion = '0';
+                       }
+                       try {
+                               $prettyVersion = $this->normalizeVersion($prettyVersion);
+                       } catch (\UnexpectedValueException $e) {
+                               continue;
+                       }
+
+                       $packages[$this->buildPackageName($name)] = $prettyVersion;
+               }
+
+               foreach ($loadedExtensions as $name) {
+                       $prettyVersion = null;
+                       switch ($name) {
+                               case 'curl':
+                                       $curlVersion = curl_version();
+                                       $prettyVersion = $curlVersion['version'];
+                                       break;
+
+                               case 'iconv':
+                                       $prettyVersion = ICONV_VERSION;
+                                       break;
+
+                               case 'intl':
+                                       $name = 'ICU';
+                                       if (defined('INTL_ICU_VERSION')) {
+                                               $prettyVersion = INTL_ICU_VERSION;
+                                       } else {
+                                               $reflector = new \ReflectionExtension('intl');
+
+                                               ob_start();
+                                               $reflector->info();
+                                               $output = ob_get_clean();
+
+                                               preg_match('/^ICU version => (.*)$/m', $output, $matches);
+                                               $prettyVersion = $matches[1];
+                                       }
+
+                                       break;
+
+                               case 'libxml':
+                                       $prettyVersion = LIBXML_DOTTED_VERSION;
+                                       break;
+
+                               case 'openssl':
+                                       $prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]?).*}', function ($match) {
+                                               return $match[1] . (empty($match[2]) ? '' : '.' . (ord($match[2]) - 96));
+                                       }, OPENSSL_VERSION_TEXT);
+                                       break;
+
+                               case 'pcre':
+                                       $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION);
+                                       break;
+
+                               case 'uuid':
+                                       $prettyVersion = phpversion('uuid');
+                                       break;
+
+                               case 'xsl':
+                                       $prettyVersion = LIBXSLT_DOTTED_VERSION;
+                                       break;
+
+                               default:
+                                       // None handled extensions have no special cases, skip
+                                       continue 2;
+                       }
+
+                       try {
+                               $prettyVersion = $this->normalizeVersion($prettyVersion);
+                       } catch (\UnexpectedValueException $e) {
+                               continue;
+                       }
+
+                       $packages[$this->buildPackageName($name)] = $prettyVersion;
+               }
+
+               return $packages;
+       }
+
+       private function buildPackageName($name) {
+               return str_replace(' ', '-', $name);
+       }
+
+       /**
+        * @param $name
+        * @return string
+        */
+       public function findLibrary($name) {
+               $extName = $this->buildPackageName($name);
+               if (isset($this->packages[$extName])) {
+                       return $this->packages[$extName];
+               }
+               return null;
+       }
+
+       private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
+
+       /**
+        * Normalizes a version string to be able to perform comparisons on it
+        *
+        * https://github.com/composer/composer/blob/master/src/Composer/Package/Version/VersionParser.php#L94
+        *
+        * @param string $version
+        * @param string $fullVersion optional complete version string to give more context
+        * @throws \UnexpectedValueException
+        * @return string
+        */
+       public function normalizeVersion($version, $fullVersion = null) {
+               $version = trim($version);
+               if (null === $fullVersion) {
+                       $fullVersion = $version;
+               }
+               // ignore aliases and just assume the alias is required instead of the source
+               if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $version, $match)) {
+                       $version = $match[1];
+               }
+               // match master-like branches
+               if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
+                       return '9999999-dev';
+               }
+               if ('dev-' === strtolower(substr($version, 0, 4))) {
+                       return 'dev-' . substr($version, 4);
+               }
+               // match classical versioning
+               if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) {
+                       $version = $matches[1]
+                               . (!empty($matches[2]) ? $matches[2] : '.0')
+                               . (!empty($matches[3]) ? $matches[3] : '.0')
+                               . (!empty($matches[4]) ? $matches[4] : '.0');
+                       $index = 5;
+               } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { // match date-based versioning
+                       $version = preg_replace('{\D}', '-', $matches[1]);
+                       $index = 2;
+               } elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) {
+                       $version = $matches[1]
+                               . (!empty($matches[2]) ? $matches[2] : '.0')
+                               . (!empty($matches[3]) ? $matches[3] : '.0')
+                               . (!empty($matches[4]) ? $matches[4] : '.0');
+                       $index = 5;
+               }
+               // add version modifiers if a version was matched
+               if (isset($index)) {
+                       if (!empty($matches[$index])) {
+                               if ('stable' === $matches[$index]) {
+                                       return $version;
+                               }
+                               $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? $matches[$index + 1] : '');
+                       }
+                       if (!empty($matches[$index + 2])) {
+                               $version .= '-dev';
+                       }
+                       return $version;
+               }
+               $extraMessage = '';
+               if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
+                       $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
+               } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
+                       $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
+               }
+               throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
+       }
+
+       private function expandStability($stability) {
+               $stability = strtolower($stability);
+               switch ($stability) {
+                       case 'a':
+                               return 'alpha';
+                       case 'b':
+                               return 'beta';
+                       case 'p':
+                       case 'pl':
+                               return 'patch';
+                       case 'rc':
+                               return 'RC';
+                       default:
+                               return $stability;
+               }
+       }
+}
index fc0ab22497705b7d132af766f1b6ab112e2b14da..a425622998bdff5c8757b4bbcec987ca80a4c251 100644 (file)
                                },
                                "@value": "notepad.exe"
                        }
+               ],
+               "lib": [
+                       {
+                               "@attributes" : {
+                                       "min-version": "1.2"
+                               },
+                               "@value": "xml"
+                       },
+                       {
+                               "@attributes" : {
+                                       "max-version": "2.0"
+                               },
+                               "@value": "intl"
+                       },
+                       "curl"
                ]
        }
 }
index f01f5fd55eaad585b60b8930b64e6bbaf5b1292f..0ea15b63a4b9c5794ff8cfb4c6689f8332d31913 100644 (file)
@@ -25,5 +25,8 @@
                <database>mysql</database>
                <command os="linux">grep</command>
                <command os="windows">notepad.exe</command>
+               <lib min-version="1.2">xml</lib>
+               <lib max-version="2.0">intl</lib>
+               <lib>curl</lib>
        </dependencies>
 </info>
index a21b53264bc30d77c5284841790ae59e1a831e5c..872d5cfb2c55283c54c679d1bc5a9ced803c82bd 100644 (file)
@@ -43,6 +43,14 @@ class DependencyAnalyzer extends \PHPUnit_Framework_TestCase {
                        ->will( $this->returnCallback(function($command) {
                                return ($command === 'grep');
                        }));
+               $this->platformMock->expects($this->any())
+                       ->method('getLibraryVersion')
+                       ->will( $this->returnCallback(function($lib) {
+                               if ($lib === 'curl') {
+                                       return "2.3.4";
+                               }
+                               return null;
+                       }));
 
                $this->l10nMock = $this->getMockBuilder('\OCP\IL10N')
                        ->disableOriginalConstructor()
@@ -112,6 +120,42 @@ class DependencyAnalyzer extends \PHPUnit_Framework_TestCase {
                $this->assertEquals($expectedMissing, $missing);
        }
 
+       /**
+        * @dataProvider providesLibs
+        * @param $expectedMissing
+        * @param $libs
+        */
+       function testLibs($expectedMissing, $libs) {
+               $app = array(
+                       'dependencies' => array(
+                       )
+               );
+               if (!is_null($libs)) {
+                       $app['dependencies']['lib'] = $libs;
+               }
+
+               $analyser = new \OC\App\DependencyAnalyzer($app, $this->platformMock, $this->l10nMock);
+               $missing = $analyser->analyze();
+
+               $this->assertTrue(is_array($missing));
+               $this->assertEquals($expectedMissing, $missing);
+       }
+
+       function providesLibs() {
+               return array(
+                       // we expect curl to exist
+                       array(array(), array('curl')),
+                       // we expect abcde to exist
+                       array(array('The library abcde is not available.'), array('abcde')),
+                       // curl in version 100.0 does not exist
+                       array(array('Library curl with a version higher than 100.0 is required - available version 2.3.4.'),
+                               array(array('@attributes' => array('min-version' => '100.0'), '@value' => 'curl'))),
+                       // curl in version 100.0 does not exist
+                       array(array('Library curl with a version lower than 1.0.0 is required - available version 2.3.4.'),
+                               array(array('@attributes' => array('max-version' => '1.0.0'), '@value' => 'curl')))
+               );
+       }
+
        function providesCommands() {
                return array(
                        array(array(), null),
@@ -142,7 +186,7 @@ class DependencyAnalyzer extends \PHPUnit_Framework_TestCase {
                        array(array(), null, '5.5'),
                        array(array(), '5.4', '5.5'),
                        array(array('PHP 5.4.4 or higher is required.'), '5.4.4', null),
-                       array(array('PHP with a version less then 5.4.2 is required.'), null, '5.4.2'),
+                       array(array('PHP with a version lower than 5.4.2 is required.'), null, '5.4.2'),
                );
        }
 }