1
0
Fork 0

Add a warning message when Composer is not able to guess the root package version (#11858)

Co-authored-by: Jordi Boggiano <j.boggiano@seld.be>
pull/11866/head
Pol Dellaiera 2024-02-23 10:47:36 +01:00 committed by GitHub
parent 1b7a71f7e7
commit a0d474f75c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 14 deletions

View File

@ -71,12 +71,43 @@ indirectly) back on the root package itself, issues can occur in two cases:
but some CIs do shallow clones so that process can fail when testing pull requests but some CIs do shallow clones so that process can fail when testing pull requests
and feature branches. In these cases the branch alias may then not be recognized. and feature branches. In these cases the branch alias may then not be recognized.
The best solution is to define the version you are on via an environment variable The best solution is to define the version you are on via an environment variable
called COMPOSER_ROOT_VERSION. You set it to `dev-main` for example to define called `COMPOSER_ROOT_VERSION`. You set it to `dev-main` for example to define
the root package's version as `dev-main`. the root package's version as `dev-main`.
Use for example: `COMPOSER_ROOT_VERSION=dev-main composer install` to export Use for example: `COMPOSER_ROOT_VERSION=dev-main composer install` to export
the variable only for the call to composer, or you can define it globally in the the variable only for the call to composer, or you can define it globally in the
CI env vars. CI env vars.
## Root package version detection
Composer relies on knowing the version of the root package to resolve
dependencies effectively. The version of the root package is determined
using a hierarchical approach:
1. **composer.json Version Field**: Firstly, Composer looks for a `version`
field in the project's root `composer.json` file. If present, this field
specifies the version of the root package directly. This is generally not
recommended as it needs to be constantly updated, but it is an option.
2. **Environment Variable**: Composer then checks for the `COMPOSER_ROOT_VERSION`
environment variable. This variable can be explicitly set by the user to
define the version of the root package, providing a straightforward way to
inform Composer of the exact version, especially in CI/CD environments or
when the VCS method is not applicable.
3. **Version Control System (VCS) Inspection**: Composer then attempts to guess
the version by interfacing with the version control system of the project. For
instance, in projects versioned with Git, Composer executes specific Git
commands to deduce the project's current version based on tags, branches, and
commit history. If a `.git` directory is missing or the history is incomplete
because CI is using a shallow clone for example, this detection may fail to find
the correct version.
4. **Fallback**: If all else fails, Composer uses `1.0.0` as default version.
Note that relying on the default/fallback version might potentially lead to dependency
resolution issues, especially when the root package depends on a package which ends up
depending (directly or indirectly)
[back on the root package itself](#dependencies-on-the-root-package).
## Network timeout issues, curl error ## Network timeout issues, curl error

View File

@ -554,6 +554,8 @@ class Config
* This should be used to read COMPOSER_ environment variables * This should be used to read COMPOSER_ environment variables
* that overload config values. * that overload config values.
* *
* @param non-empty-string $var
*
* @return string|false * @return string|false
*/ */
private function getComposerEnv(string $var) private function getComposerEnv(string $var)

View File

@ -49,6 +49,11 @@ class RootPackageLoader extends ArrayLoader
*/ */
private $versionGuesser; private $versionGuesser;
/**
* @var IOInterface|null
*/
private $io;
public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null) public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null)
{ {
parent::__construct($parser); parent::__construct($parser);
@ -56,6 +61,7 @@ class RootPackageLoader extends ArrayLoader
$this->manager = $manager; $this->manager = $manager;
$this->config = $config; $this->config = $config;
$this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser); $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser);
$this->io = $io;
} }
/** /**
@ -93,6 +99,14 @@ class RootPackageLoader extends ArrayLoader
} }
if (!isset($config['version'])) { if (!isset($config['version'])) {
if ($this->io !== null && $config['name'] !== '__root__') {
$this->io->warning(
sprintf(
"Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version",
$config['name']
)
);
}
$config['version'] = '1.0.0'; $config['version'] = '1.0.0';
$autoVersioned = true; $autoVersioned = true;
} }

View File

@ -55,6 +55,8 @@ class Platform
/** /**
* getenv() equivalent but reads from the runtime global variables first * getenv() equivalent but reads from the runtime global variables first
* *
* @param non-empty-string $name
*
* @return string|false * @return string|false
*/ */
public static function getEnv(string $name) public static function getEnv(string $name)
@ -99,6 +101,7 @@ class Platform
return Preg::replaceCallback('#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#', static function ($matches): string { return Preg::replaceCallback('#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#', static function ($matches): string {
assert(is_string($matches['var'])); assert(is_string($matches['var']));
assert('' !== $matches['var']);
// Treat HOME as an alias for USERPROFILE on Windows for legacy reasons // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
if (Platform::isWindows() && $matches['var'] === 'HOME') { if (Platform::isWindows() && $matches['var'] === 'HOME') {

View File

@ -23,6 +23,7 @@ class LicensesCommandTest extends TestCase
$this->initTempComposer([ $this->initTempComposer([
'name' => 'test/pkg', 'name' => 'test/pkg',
'version' => '1.2.3',
'license' => 'MIT', 'license' => 'MIT',
'require' => [ 'require' => [
'first/pkg' => '^2.0', 'first/pkg' => '^2.0',
@ -57,7 +58,7 @@ class LicensesCommandTest extends TestCase
$expected = [ $expected = [
["Name:", "test/pkg"], ["Name:", "test/pkg"],
["Version:", "1.0.0+no-version-set"], ["Version:", "1.2.3"],
["Licenses:", "MIT"], ["Licenses:", "MIT"],
["Dependencies:"], ["Dependencies:"],
[], [],
@ -88,7 +89,7 @@ class LicensesCommandTest extends TestCase
$expected = [ $expected = [
["Name:", "test/pkg"], ["Name:", "test/pkg"],
["Version:", "1.0.0+no-version-set"], ["Version:", "1.2.3"],
["Licenses:", "MIT"], ["Licenses:", "MIT"],
["Dependencies:"], ["Dependencies:"],
[], [],
@ -118,7 +119,7 @@ class LicensesCommandTest extends TestCase
$expected = [ $expected = [
"name" => "test/pkg", "name" => "test/pkg",
"version" => "1.0.0+no-version-set", "version" => "1.2.3",
"license" => ["MIT"], "license" => ["MIT"],
"dependencies" => [ "dependencies" => [
"dev/pkg" => [ "dev/pkg" => [

View File

@ -573,7 +573,7 @@ OUTPUT;
public function testSelfAndNameOnly(): void public function testSelfAndNameOnly(): void
{ {
$this->initTempComposer(['name' => 'vendor/package']); $this->initTempComposer(['name' => 'vendor/package', 'version' => '1.2.3']);
$appTester = $this->getApplicationTester(); $appTester = $this->getApplicationTester();
$appTester->run(['command' => 'show', '--self' => true, '--name-only' => true]); $appTester->run(['command' => 'show', '--self' => true, '--name-only' => true]);
@ -591,7 +591,7 @@ OUTPUT;
public function testSelf(): void public function testSelf(): void
{ {
$this->initTempComposer(['name' => 'vendor/package', 'time' => date('Y-m-d')]); $this->initTempComposer(['name' => 'vendor/package', 'version' => '1.2.3', 'time' => date('Y-m-d')]);
$appTester = $this->getApplicationTester(); $appTester = $this->getApplicationTester();
$appTester->run(['command' => 'show', '--self' => true]); $appTester->run(['command' => 'show', '--self' => true]);
@ -599,7 +599,7 @@ OUTPUT;
'name' => 'vendor/package', 'name' => 'vendor/package',
'descrip.' => '', 'descrip.' => '',
'keywords' => '', 'keywords' => '',
'versions' => '* 1.0.0+no-version-set', 'versions' => '* 1.2.3',
'released' => date('Y-m-d'). ', today', 'released' => date('Y-m-d'). ', today',
'type' => 'library', 'type' => 'library',
'homepage' => '', 'homepage' => '',

View File

@ -53,7 +53,9 @@ class ValidateCommandTest extends TestCase
$appTester = $this->getApplicationTester(); $appTester = $this->getApplicationTester();
$appTester->run(['command' => 'validate']); $appTester->run(['command' => 'validate']);
$expected = <<<OUTPUT $expected = <<<OUTPUT
./composer.json is valid but your composer.lock has some errors <warning>Composer could not detect the root package (test/suite) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version</warning>
<warning>Composer could not detect the root package (test/suite) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version</warning>
./composer.json is valid but your composer.lock has some errors
# Lock file errors # Lock file errors
- Required package "root/req" is not present in the lock file. - Required package "root/req" is not present in the lock file.
This usually happens when composer files are incorrectly merged or the composer.json file is manually edited. This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.
@ -109,7 +111,11 @@ OUTPUT;
yield 'validation passing' => [ yield 'validation passing' => [
self::MINIMAL_VALID_CONFIGURATION, self::MINIMAL_VALID_CONFIGURATION,
[], [],
'./composer.json is valid', <<<OUTPUT
<warning>Composer could not detect the root package (test/suite) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version</warning>
<warning>Composer could not detect the root package (test/suite) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version</warning>
./composer.json is valid
OUTPUT
]; ];
$publishDataStripped= array_diff_key( $publishDataStripped= array_diff_key(