1*aec1a091SAndreas Gohr<?php 2*aec1a091SAndreas Gohr 3*aec1a091SAndreas Gohr/* 4*aec1a091SAndreas Gohr * This file is part of Composer. 5*aec1a091SAndreas Gohr * 6*aec1a091SAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 7*aec1a091SAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 8*aec1a091SAndreas Gohr * 9*aec1a091SAndreas Gohr * For the full copyright and license information, please view the LICENSE 10*aec1a091SAndreas Gohr * file that was distributed with this source code. 11*aec1a091SAndreas Gohr */ 12*aec1a091SAndreas Gohr 13*aec1a091SAndreas Gohrnamespace Composer; 14*aec1a091SAndreas Gohr 15*aec1a091SAndreas Gohruse Composer\Autoload\ClassLoader; 16*aec1a091SAndreas Gohruse Composer\Semver\VersionParser; 17*aec1a091SAndreas Gohr 18*aec1a091SAndreas Gohr/** 19*aec1a091SAndreas Gohr * This class is copied in every Composer installed project and available to all 20*aec1a091SAndreas Gohr * 21*aec1a091SAndreas Gohr * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22*aec1a091SAndreas Gohr * 23*aec1a091SAndreas Gohr * To require its presence, you can require `composer-runtime-api ^2.0` 24*aec1a091SAndreas Gohr */ 25*aec1a091SAndreas Gohrclass InstalledVersions 26*aec1a091SAndreas Gohr{ 27*aec1a091SAndreas Gohr /** 28*aec1a091SAndreas Gohr * @var mixed[]|null 29*aec1a091SAndreas Gohr * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null 30*aec1a091SAndreas Gohr */ 31*aec1a091SAndreas Gohr private static $installed; 32*aec1a091SAndreas Gohr 33*aec1a091SAndreas Gohr /** 34*aec1a091SAndreas Gohr * @var bool|null 35*aec1a091SAndreas Gohr */ 36*aec1a091SAndreas Gohr private static $canGetVendors; 37*aec1a091SAndreas Gohr 38*aec1a091SAndreas Gohr /** 39*aec1a091SAndreas Gohr * @var array[] 40*aec1a091SAndreas Gohr * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> 41*aec1a091SAndreas Gohr */ 42*aec1a091SAndreas Gohr private static $installedByVendor = array(); 43*aec1a091SAndreas Gohr 44*aec1a091SAndreas Gohr /** 45*aec1a091SAndreas Gohr * Returns a list of all package names which are present, either by being installed, replaced or provided 46*aec1a091SAndreas Gohr * 47*aec1a091SAndreas Gohr * @return string[] 48*aec1a091SAndreas Gohr * @psalm-return list<string> 49*aec1a091SAndreas Gohr */ 50*aec1a091SAndreas Gohr public static function getInstalledPackages() 51*aec1a091SAndreas Gohr { 52*aec1a091SAndreas Gohr $packages = array(); 53*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 54*aec1a091SAndreas Gohr $packages[] = array_keys($installed['versions']); 55*aec1a091SAndreas Gohr } 56*aec1a091SAndreas Gohr 57*aec1a091SAndreas Gohr if (1 === \count($packages)) { 58*aec1a091SAndreas Gohr return $packages[0]; 59*aec1a091SAndreas Gohr } 60*aec1a091SAndreas Gohr 61*aec1a091SAndreas Gohr return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 62*aec1a091SAndreas Gohr } 63*aec1a091SAndreas Gohr 64*aec1a091SAndreas Gohr /** 65*aec1a091SAndreas Gohr * Returns a list of all package names with a specific type e.g. 'library' 66*aec1a091SAndreas Gohr * 67*aec1a091SAndreas Gohr * @param string $type 68*aec1a091SAndreas Gohr * @return string[] 69*aec1a091SAndreas Gohr * @psalm-return list<string> 70*aec1a091SAndreas Gohr */ 71*aec1a091SAndreas Gohr public static function getInstalledPackagesByType($type) 72*aec1a091SAndreas Gohr { 73*aec1a091SAndreas Gohr $packagesByType = array(); 74*aec1a091SAndreas Gohr 75*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 76*aec1a091SAndreas Gohr foreach ($installed['versions'] as $name => $package) { 77*aec1a091SAndreas Gohr if (isset($package['type']) && $package['type'] === $type) { 78*aec1a091SAndreas Gohr $packagesByType[] = $name; 79*aec1a091SAndreas Gohr } 80*aec1a091SAndreas Gohr } 81*aec1a091SAndreas Gohr } 82*aec1a091SAndreas Gohr 83*aec1a091SAndreas Gohr return $packagesByType; 84*aec1a091SAndreas Gohr } 85*aec1a091SAndreas Gohr 86*aec1a091SAndreas Gohr /** 87*aec1a091SAndreas Gohr * Checks whether the given package is installed 88*aec1a091SAndreas Gohr * 89*aec1a091SAndreas Gohr * This also returns true if the package name is provided or replaced by another package 90*aec1a091SAndreas Gohr * 91*aec1a091SAndreas Gohr * @param string $packageName 92*aec1a091SAndreas Gohr * @param bool $includeDevRequirements 93*aec1a091SAndreas Gohr * @return bool 94*aec1a091SAndreas Gohr */ 95*aec1a091SAndreas Gohr public static function isInstalled($packageName, $includeDevRequirements = true) 96*aec1a091SAndreas Gohr { 97*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 98*aec1a091SAndreas Gohr if (isset($installed['versions'][$packageName])) { 99*aec1a091SAndreas Gohr return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); 100*aec1a091SAndreas Gohr } 101*aec1a091SAndreas Gohr } 102*aec1a091SAndreas Gohr 103*aec1a091SAndreas Gohr return false; 104*aec1a091SAndreas Gohr } 105*aec1a091SAndreas Gohr 106*aec1a091SAndreas Gohr /** 107*aec1a091SAndreas Gohr * Checks whether the given package satisfies a version constraint 108*aec1a091SAndreas Gohr * 109*aec1a091SAndreas Gohr * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 110*aec1a091SAndreas Gohr * 111*aec1a091SAndreas Gohr * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 112*aec1a091SAndreas Gohr * 113*aec1a091SAndreas Gohr * @param VersionParser $parser Install composer/semver to have access to this class and functionality 114*aec1a091SAndreas Gohr * @param string $packageName 115*aec1a091SAndreas Gohr * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 116*aec1a091SAndreas Gohr * @return bool 117*aec1a091SAndreas Gohr */ 118*aec1a091SAndreas Gohr public static function satisfies(VersionParser $parser, $packageName, $constraint) 119*aec1a091SAndreas Gohr { 120*aec1a091SAndreas Gohr $constraint = $parser->parseConstraints($constraint); 121*aec1a091SAndreas Gohr $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 122*aec1a091SAndreas Gohr 123*aec1a091SAndreas Gohr return $provided->matches($constraint); 124*aec1a091SAndreas Gohr } 125*aec1a091SAndreas Gohr 126*aec1a091SAndreas Gohr /** 127*aec1a091SAndreas Gohr * Returns a version constraint representing all the range(s) which are installed for a given package 128*aec1a091SAndreas Gohr * 129*aec1a091SAndreas Gohr * It is easier to use this via isInstalled() with the $constraint argument if you need to check 130*aec1a091SAndreas Gohr * whether a given version of a package is installed, and not just whether it exists 131*aec1a091SAndreas Gohr * 132*aec1a091SAndreas Gohr * @param string $packageName 133*aec1a091SAndreas Gohr * @return string Version constraint usable with composer/semver 134*aec1a091SAndreas Gohr */ 135*aec1a091SAndreas Gohr public static function getVersionRanges($packageName) 136*aec1a091SAndreas Gohr { 137*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 138*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 139*aec1a091SAndreas Gohr continue; 140*aec1a091SAndreas Gohr } 141*aec1a091SAndreas Gohr 142*aec1a091SAndreas Gohr $ranges = array(); 143*aec1a091SAndreas Gohr if (isset($installed['versions'][$packageName]['pretty_version'])) { 144*aec1a091SAndreas Gohr $ranges[] = $installed['versions'][$packageName]['pretty_version']; 145*aec1a091SAndreas Gohr } 146*aec1a091SAndreas Gohr if (array_key_exists('aliases', $installed['versions'][$packageName])) { 147*aec1a091SAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 148*aec1a091SAndreas Gohr } 149*aec1a091SAndreas Gohr if (array_key_exists('replaced', $installed['versions'][$packageName])) { 150*aec1a091SAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 151*aec1a091SAndreas Gohr } 152*aec1a091SAndreas Gohr if (array_key_exists('provided', $installed['versions'][$packageName])) { 153*aec1a091SAndreas Gohr $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 154*aec1a091SAndreas Gohr } 155*aec1a091SAndreas Gohr 156*aec1a091SAndreas Gohr return implode(' || ', $ranges); 157*aec1a091SAndreas Gohr } 158*aec1a091SAndreas Gohr 159*aec1a091SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 160*aec1a091SAndreas Gohr } 161*aec1a091SAndreas Gohr 162*aec1a091SAndreas Gohr /** 163*aec1a091SAndreas Gohr * @param string $packageName 164*aec1a091SAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 165*aec1a091SAndreas Gohr */ 166*aec1a091SAndreas Gohr public static function getVersion($packageName) 167*aec1a091SAndreas Gohr { 168*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 169*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 170*aec1a091SAndreas Gohr continue; 171*aec1a091SAndreas Gohr } 172*aec1a091SAndreas Gohr 173*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName]['version'])) { 174*aec1a091SAndreas Gohr return null; 175*aec1a091SAndreas Gohr } 176*aec1a091SAndreas Gohr 177*aec1a091SAndreas Gohr return $installed['versions'][$packageName]['version']; 178*aec1a091SAndreas Gohr } 179*aec1a091SAndreas Gohr 180*aec1a091SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 181*aec1a091SAndreas Gohr } 182*aec1a091SAndreas Gohr 183*aec1a091SAndreas Gohr /** 184*aec1a091SAndreas Gohr * @param string $packageName 185*aec1a091SAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 186*aec1a091SAndreas Gohr */ 187*aec1a091SAndreas Gohr public static function getPrettyVersion($packageName) 188*aec1a091SAndreas Gohr { 189*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 190*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 191*aec1a091SAndreas Gohr continue; 192*aec1a091SAndreas Gohr } 193*aec1a091SAndreas Gohr 194*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName]['pretty_version'])) { 195*aec1a091SAndreas Gohr return null; 196*aec1a091SAndreas Gohr } 197*aec1a091SAndreas Gohr 198*aec1a091SAndreas Gohr return $installed['versions'][$packageName]['pretty_version']; 199*aec1a091SAndreas Gohr } 200*aec1a091SAndreas Gohr 201*aec1a091SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 202*aec1a091SAndreas Gohr } 203*aec1a091SAndreas Gohr 204*aec1a091SAndreas Gohr /** 205*aec1a091SAndreas Gohr * @param string $packageName 206*aec1a091SAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 207*aec1a091SAndreas Gohr */ 208*aec1a091SAndreas Gohr public static function getReference($packageName) 209*aec1a091SAndreas Gohr { 210*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 211*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 212*aec1a091SAndreas Gohr continue; 213*aec1a091SAndreas Gohr } 214*aec1a091SAndreas Gohr 215*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName]['reference'])) { 216*aec1a091SAndreas Gohr return null; 217*aec1a091SAndreas Gohr } 218*aec1a091SAndreas Gohr 219*aec1a091SAndreas Gohr return $installed['versions'][$packageName]['reference']; 220*aec1a091SAndreas Gohr } 221*aec1a091SAndreas Gohr 222*aec1a091SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 223*aec1a091SAndreas Gohr } 224*aec1a091SAndreas Gohr 225*aec1a091SAndreas Gohr /** 226*aec1a091SAndreas Gohr * @param string $packageName 227*aec1a091SAndreas Gohr * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 228*aec1a091SAndreas Gohr */ 229*aec1a091SAndreas Gohr public static function getInstallPath($packageName) 230*aec1a091SAndreas Gohr { 231*aec1a091SAndreas Gohr foreach (self::getInstalled() as $installed) { 232*aec1a091SAndreas Gohr if (!isset($installed['versions'][$packageName])) { 233*aec1a091SAndreas Gohr continue; 234*aec1a091SAndreas Gohr } 235*aec1a091SAndreas Gohr 236*aec1a091SAndreas Gohr return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 237*aec1a091SAndreas Gohr } 238*aec1a091SAndreas Gohr 239*aec1a091SAndreas Gohr throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 240*aec1a091SAndreas Gohr } 241*aec1a091SAndreas Gohr 242*aec1a091SAndreas Gohr /** 243*aec1a091SAndreas Gohr * @return array 244*aec1a091SAndreas Gohr * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} 245*aec1a091SAndreas Gohr */ 246*aec1a091SAndreas Gohr public static function getRootPackage() 247*aec1a091SAndreas Gohr { 248*aec1a091SAndreas Gohr $installed = self::getInstalled(); 249*aec1a091SAndreas Gohr 250*aec1a091SAndreas Gohr return $installed[0]['root']; 251*aec1a091SAndreas Gohr } 252*aec1a091SAndreas Gohr 253*aec1a091SAndreas Gohr /** 254*aec1a091SAndreas Gohr * Returns the raw installed.php data for custom implementations 255*aec1a091SAndreas Gohr * 256*aec1a091SAndreas Gohr * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 257*aec1a091SAndreas Gohr * @return array[] 258*aec1a091SAndreas Gohr * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} 259*aec1a091SAndreas Gohr */ 260*aec1a091SAndreas Gohr public static function getRawData() 261*aec1a091SAndreas Gohr { 262*aec1a091SAndreas Gohr @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 263*aec1a091SAndreas Gohr 264*aec1a091SAndreas Gohr if (null === self::$installed) { 265*aec1a091SAndreas Gohr // only require the installed.php file if this file is loaded from its dumped location, 266*aec1a091SAndreas Gohr // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 267*aec1a091SAndreas Gohr if (substr(__DIR__, -8, 1) !== 'C') { 268*aec1a091SAndreas Gohr self::$installed = include __DIR__ . '/installed.php'; 269*aec1a091SAndreas Gohr } else { 270*aec1a091SAndreas Gohr self::$installed = array(); 271*aec1a091SAndreas Gohr } 272*aec1a091SAndreas Gohr } 273*aec1a091SAndreas Gohr 274*aec1a091SAndreas Gohr return self::$installed; 275*aec1a091SAndreas Gohr } 276*aec1a091SAndreas Gohr 277*aec1a091SAndreas Gohr /** 278*aec1a091SAndreas Gohr * Returns the raw data of all installed.php which are currently loaded for custom implementations 279*aec1a091SAndreas Gohr * 280*aec1a091SAndreas Gohr * @return array[] 281*aec1a091SAndreas Gohr * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> 282*aec1a091SAndreas Gohr */ 283*aec1a091SAndreas Gohr public static function getAllRawData() 284*aec1a091SAndreas Gohr { 285*aec1a091SAndreas Gohr return self::getInstalled(); 286*aec1a091SAndreas Gohr } 287*aec1a091SAndreas Gohr 288*aec1a091SAndreas Gohr /** 289*aec1a091SAndreas Gohr * Lets you reload the static array from another file 290*aec1a091SAndreas Gohr * 291*aec1a091SAndreas Gohr * This is only useful for complex integrations in which a project needs to use 292*aec1a091SAndreas Gohr * this class but then also needs to execute another project's autoloader in process, 293*aec1a091SAndreas Gohr * and wants to ensure both projects have access to their version of installed.php. 294*aec1a091SAndreas Gohr * 295*aec1a091SAndreas Gohr * A typical case would be PHPUnit, where it would need to make sure it reads all 296*aec1a091SAndreas Gohr * the data it needs from this class, then call reload() with 297*aec1a091SAndreas Gohr * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 298*aec1a091SAndreas Gohr * the project in which it runs can then also use this class safely, without 299*aec1a091SAndreas Gohr * interference between PHPUnit's dependencies and the project's dependencies. 300*aec1a091SAndreas Gohr * 301*aec1a091SAndreas Gohr * @param array[] $data A vendor/composer/installed.php data set 302*aec1a091SAndreas Gohr * @return void 303*aec1a091SAndreas Gohr * 304*aec1a091SAndreas Gohr * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data 305*aec1a091SAndreas Gohr */ 306*aec1a091SAndreas Gohr public static function reload($data) 307*aec1a091SAndreas Gohr { 308*aec1a091SAndreas Gohr self::$installed = $data; 309*aec1a091SAndreas Gohr self::$installedByVendor = array(); 310*aec1a091SAndreas Gohr } 311*aec1a091SAndreas Gohr 312*aec1a091SAndreas Gohr /** 313*aec1a091SAndreas Gohr * @return array[] 314*aec1a091SAndreas Gohr * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> 315*aec1a091SAndreas Gohr */ 316*aec1a091SAndreas Gohr private static function getInstalled() 317*aec1a091SAndreas Gohr { 318*aec1a091SAndreas Gohr if (null === self::$canGetVendors) { 319*aec1a091SAndreas Gohr self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 320*aec1a091SAndreas Gohr } 321*aec1a091SAndreas Gohr 322*aec1a091SAndreas Gohr $installed = array(); 323*aec1a091SAndreas Gohr 324*aec1a091SAndreas Gohr if (self::$canGetVendors) { 325*aec1a091SAndreas Gohr foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 326*aec1a091SAndreas Gohr if (isset(self::$installedByVendor[$vendorDir])) { 327*aec1a091SAndreas Gohr $installed[] = self::$installedByVendor[$vendorDir]; 328*aec1a091SAndreas Gohr } elseif (is_file($vendorDir.'/composer/installed.php')) { 329*aec1a091SAndreas Gohr $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 330*aec1a091SAndreas Gohr if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 331*aec1a091SAndreas Gohr self::$installed = $installed[count($installed) - 1]; 332*aec1a091SAndreas Gohr } 333*aec1a091SAndreas Gohr } 334*aec1a091SAndreas Gohr } 335*aec1a091SAndreas Gohr } 336*aec1a091SAndreas Gohr 337*aec1a091SAndreas Gohr if (null === self::$installed) { 338*aec1a091SAndreas Gohr // only require the installed.php file if this file is loaded from its dumped location, 339*aec1a091SAndreas Gohr // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 340*aec1a091SAndreas Gohr if (substr(__DIR__, -8, 1) !== 'C') { 341*aec1a091SAndreas Gohr self::$installed = require __DIR__ . '/installed.php'; 342*aec1a091SAndreas Gohr } else { 343*aec1a091SAndreas Gohr self::$installed = array(); 344*aec1a091SAndreas Gohr } 345*aec1a091SAndreas Gohr } 346*aec1a091SAndreas Gohr $installed[] = self::$installed; 347*aec1a091SAndreas Gohr 348*aec1a091SAndreas Gohr return $installed; 349*aec1a091SAndreas Gohr } 350*aec1a091SAndreas Gohr} 351