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
parent
1b7a71f7e7
commit
a0d474f75c
|
@ -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
|
||||
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
|
||||
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`.
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
@ -554,6 +554,8 @@ class Config
|
|||
* This should be used to read COMPOSER_ environment variables
|
||||
* that overload config values.
|
||||
*
|
||||
* @param non-empty-string $var
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private function getComposerEnv(string $var)
|
||||
|
|
|
@ -49,6 +49,11 @@ class RootPackageLoader extends ArrayLoader
|
|||
*/
|
||||
private $versionGuesser;
|
||||
|
||||
/**
|
||||
* @var IOInterface|null
|
||||
*/
|
||||
private $io;
|
||||
|
||||
public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null)
|
||||
{
|
||||
parent::__construct($parser);
|
||||
|
@ -56,6 +61,7 @@ class RootPackageLoader extends ArrayLoader
|
|||
$this->manager = $manager;
|
||||
$this->config = $config;
|
||||
$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 ($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';
|
||||
$autoVersioned = true;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ class Platform
|
|||
/**
|
||||
* getenv() equivalent but reads from the runtime global variables first
|
||||
*
|
||||
* @param non-empty-string $name
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
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 {
|
||||
assert(is_string($matches['var']));
|
||||
assert('' !== $matches['var']);
|
||||
|
||||
// Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
|
||||
if (Platform::isWindows() && $matches['var'] === 'HOME') {
|
||||
|
|
|
@ -23,6 +23,7 @@ class LicensesCommandTest extends TestCase
|
|||
|
||||
$this->initTempComposer([
|
||||
'name' => 'test/pkg',
|
||||
'version' => '1.2.3',
|
||||
'license' => 'MIT',
|
||||
'require' => [
|
||||
'first/pkg' => '^2.0',
|
||||
|
@ -57,7 +58,7 @@ class LicensesCommandTest extends TestCase
|
|||
|
||||
$expected = [
|
||||
["Name:", "test/pkg"],
|
||||
["Version:", "1.0.0+no-version-set"],
|
||||
["Version:", "1.2.3"],
|
||||
["Licenses:", "MIT"],
|
||||
["Dependencies:"],
|
||||
[],
|
||||
|
@ -88,7 +89,7 @@ class LicensesCommandTest extends TestCase
|
|||
|
||||
$expected = [
|
||||
["Name:", "test/pkg"],
|
||||
["Version:", "1.0.0+no-version-set"],
|
||||
["Version:", "1.2.3"],
|
||||
["Licenses:", "MIT"],
|
||||
["Dependencies:"],
|
||||
[],
|
||||
|
@ -118,7 +119,7 @@ class LicensesCommandTest extends TestCase
|
|||
|
||||
$expected = [
|
||||
"name" => "test/pkg",
|
||||
"version" => "1.0.0+no-version-set",
|
||||
"version" => "1.2.3",
|
||||
"license" => ["MIT"],
|
||||
"dependencies" => [
|
||||
"dev/pkg" => [
|
||||
|
|
|
@ -573,7 +573,7 @@ OUTPUT;
|
|||
|
||||
public function testSelfAndNameOnly(): void
|
||||
{
|
||||
$this->initTempComposer(['name' => 'vendor/package']);
|
||||
$this->initTempComposer(['name' => 'vendor/package', 'version' => '1.2.3']);
|
||||
|
||||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'show', '--self' => true, '--name-only' => true]);
|
||||
|
@ -591,7 +591,7 @@ OUTPUT;
|
|||
|
||||
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->run(['command' => 'show', '--self' => true]);
|
||||
|
@ -599,7 +599,7 @@ OUTPUT;
|
|||
'name' => 'vendor/package',
|
||||
'descrip.' => '',
|
||||
'keywords' => '',
|
||||
'versions' => '* 1.0.0+no-version-set',
|
||||
'versions' => '* 1.2.3',
|
||||
'released' => date('Y-m-d'). ', today',
|
||||
'type' => 'library',
|
||||
'homepage' => '',
|
||||
|
|
|
@ -33,7 +33,7 @@ class ValidateCommandTest extends TestCase
|
|||
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
||||
}
|
||||
|
||||
public function testValidateOnFileIssues(): void
|
||||
public function testValidateOnFileIssues(): void
|
||||
{
|
||||
$directory = $this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
||||
unlink($directory.'/composer.json');
|
||||
|
@ -45,7 +45,7 @@ class ValidateCommandTest extends TestCase
|
|||
$this->assertSame($expected, trim($appTester->getDisplay(true)));
|
||||
}
|
||||
|
||||
public function testWithComposerLock(): void
|
||||
public function testWithComposerLock(): void
|
||||
{
|
||||
$this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
||||
$this->createComposerLock();
|
||||
|
@ -53,7 +53,9 @@ class ValidateCommandTest extends TestCase
|
|||
$appTester = $this->getApplicationTester();
|
||||
$appTester->run(['command' => 'validate']);
|
||||
$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
|
||||
- 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.
|
||||
|
@ -64,12 +66,12 @@ OUTPUT;
|
|||
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
||||
}
|
||||
|
||||
public function testUnaccessibleFile(): void
|
||||
public function testUnaccessibleFile(): void
|
||||
{
|
||||
if (Platform::isWindows()) {
|
||||
$this->markTestSkipped('Does not run on windows');
|
||||
}
|
||||
|
||||
|
||||
$directory = $this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
||||
chmod($directory.'/composer.json', 0200);
|
||||
|
||||
|
@ -105,11 +107,15 @@ OUTPUT;
|
|||
|
||||
public static function provideValidateTests(): \Generator
|
||||
{
|
||||
|
||||
|
||||
yield 'validation passing' => [
|
||||
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(
|
||||
|
|
Loading…
Reference in New Issue