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
|
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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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" => [
|
||||||
|
|
|
@ -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' => '',
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ValidateCommandTest extends TestCase
|
||||||
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testValidateOnFileIssues(): void
|
public function testValidateOnFileIssues(): void
|
||||||
{
|
{
|
||||||
$directory = $this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
$directory = $this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
||||||
unlink($directory.'/composer.json');
|
unlink($directory.'/composer.json');
|
||||||
|
@ -45,7 +45,7 @@ class ValidateCommandTest extends TestCase
|
||||||
$this->assertSame($expected, trim($appTester->getDisplay(true)));
|
$this->assertSame($expected, trim($appTester->getDisplay(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testWithComposerLock(): void
|
public function testWithComposerLock(): void
|
||||||
{
|
{
|
||||||
$this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
$this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
||||||
$this->createComposerLock();
|
$this->createComposerLock();
|
||||||
|
@ -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.
|
||||||
|
@ -64,12 +66,12 @@ OUTPUT;
|
||||||
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
$this->assertSame(trim($expected), trim($appTester->getDisplay(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnaccessibleFile(): void
|
public function testUnaccessibleFile(): void
|
||||||
{
|
{
|
||||||
if (Platform::isWindows()) {
|
if (Platform::isWindows()) {
|
||||||
$this->markTestSkipped('Does not run on windows');
|
$this->markTestSkipped('Does not run on windows');
|
||||||
}
|
}
|
||||||
|
|
||||||
$directory = $this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
$directory = $this->initTempComposer(self::MINIMAL_VALID_CONFIGURATION);
|
||||||
chmod($directory.'/composer.json', 0200);
|
chmod($directory.'/composer.json', 0200);
|
||||||
|
|
||||||
|
@ -105,11 +107,15 @@ OUTPUT;
|
||||||
|
|
||||||
public static function provideValidateTests(): \Generator
|
public static function provideValidateTests(): \Generator
|
||||||
{
|
{
|
||||||
|
|
||||||
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(
|
||||||
|
|
Loading…
Reference in New Issue