TL;DR: Should I use @dev
or dev-main
in my composer.json
for local packages?
In our project we have a central composer.json
which includes all the dependencies needed including local ones - code included in the git repo but isolated out as a separate composer packages.
We have a local folder set up as a repository:
{
"repositories": [
{
"type": "path",
"url": "./app/*/*"
}
]
}
I read somewhere that we should include the dependencies with the @dev
syntax - e.g.
{
"require": {
"app/local": "@dev"
}
}
However, this stores the current branch in the composer.lock
, so when a feature branch gets merged, the lock file references a non-existent branch until the next composer update
is run. Is this ok?
I like the idea of @dev
as it signifies which packages are local, but I don't like that a non-existent branch could be referenced.
TL;DR: Neither! Show it's born here, the star: *
You ask:
Should I use
@dev
ordev-main
in my composer.json for local packages?
And @dev
or dev-main
are meant as (part?) of a version constraint as in a requirement like the following:
{ "require" :
{ "app/local" : "@dev"
}
}
And while the package app/local is from a Composer path repository:
{ "repositories" :
[
{ "type" : "path"
, "url" : "./app/*/*"
}
]
}
The answer is: Neither, there is no need for both, and in case it is easily misunderstood what they mean ("I like the idea of @dev as it signifies which packages are local"), it is often better to not practice adding to the confusion, but reducing the complexity and learn about the things their original meaning (enjoy the design, don't step over it).
IMHO the much shorter and better speaking version constraint is the star/sun (asterisk) symbol "*
" to denote its birthplace is local,
and you're in or close to the centre of it.
This should go without saying that dev-main
denotes a non-version-able branch referencing (never stable) version constraint while @dev
is a stability flag in a package link, so is different to the asterisk (in this isolated form currently undocumented for the meaning within version constraints), and therefore you can still use them, .e.g. @dev
for non-local packages (perhaps an option you're missing to see so far, but a documented case).
So not using dev-main
or @dev
allows to use them additionally to *
when they're (more) applicable. Perhaps the freedom you're looking for dev@local.git.
Consider using Composer 2 as the repositories are canonical then; with Composer 1 they aren't.
For all practicalities in this answer, Packagist and Composer networking are disabled. [Composer & PHP configuration below.]
For a Composer path repository,
If the package is a local VCS repository, the version may be inferred by the branch or tag that is currently checked out. Otherwise, the version should be explicitly defined in the package's
composer.json
file. If the version can't be resolved by these means, it is assumed to bedev-master
. ¹
As you have stated the precondition that the repository itself is within the projects own Git repository ("[...] in the git repo [...] as [...] separate composer packages" ²), all packages from that path repository are part of the local VCS repository which roots in the root package //project/composer.json
side-by-side to //project/app/*/*/...
and //project/.git
; for Composer this results in the meaning of the block-quote above.
The version constraint that reflects the package being local then certainly is: ¹⸴³
composer.json#/require/app~1local "*"
As you're using Git for version control, and those packages are local (only), I see absolutely no requirement to:
There is also no confusion about the lock file then. It will always represent the state of the local system, and if there is an update, you will have an update. (Invalidations are flagged by dangling symbolic links.)
Within the vendor folder, you should find those symbolic links, therefore, there is not even a requirement to run composer-update(1) when the repositories-json rooted ./app path repositories packages change as they're in the projects git work tree, so the tree is always leading.
Composer supports such a scheme by the reference path repository option: ³
composer.json#/respositories/0/options/reference "none"
And (the more explicit as true is the preferred auto-default) symlink path repository option: ³
composer.json#/respositories/0/options/symlink "true"
I recommend making both explicit in that file.
To export your main project (the root package), use composer-archive(1) or do any other form of appropriate packaging, as the with local repositories Composer will not be able to acquire those packages on a system without those repositories configured (which could be any other system than this local one).
It's a bit superfluous to state this explicitly, as this is true with any Composer project that installs/updates in an unpredictable WAN infrastructure.
(and I need a lame excuse as I have not tested composer-archive(1) with this)
To be able to use *
instead of @dev
⁴ the packages' version has to be hinted – due to Composers default (and adequate) default stability – via the composer.json#/repositories/0/options/versions
³ JSON Object that has property names as package names and the version as its properties' value:
{ "repositories" :
{ "type" : "path"
, "url" : "./app/*/*"
, "options" :
{ "versions" :
{ "app/local" : "0.0.0" }
}
}
}
For example, here the version "0.0.0
".
This now allows to use *
from the get-go and versioning on the packages themselves apart from the main repositories tagging.
Note: A
/version
³ within any actualapp/*/*/composer.json
package does override, but likely there is some use in indexing the versions centrally within the root package. (For the answers' example it helped me.)
$ rm -rf vendor composer.lock
$ cat composer.json
{
"name": "app/root",
"description": "app/root",
"license": "AGPL-3.0-or-later",
"repositories": [
{
"type": "path",
"url": "./app/*/*",
"options": {
"versions": {
"app/local": "0.0.0"
}
}
}
]
}
$ composer require app/local '*'
./composer.json has been updated
Running composer update app/local
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking app/local (0.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing app/local (0.0.0): Symlinking from ./app/app/local
Generating autoload files
Naturally, the version "0.0.0
" is exemplary, but remains compatible with Semantic Versioning; remind, it is that Composer considers 0.x versions stable.
Therefore, there is no requirement any longer to (ab)use the "@dev" stability flag in Composer Schema Package Links only to express that there is a package and that it's local. These will always lead as from the same repository, first file-system, then Git and finally Composer.
The alternative, perhaps less expressive but more tracking, is to have Composer choose the requirement:
$ composer require app/local
...
Using version ^0.0.0 for app/local
You get the Semantic Versioning leaning caret version constraint by its version, not the star – by the choice of Composer at the state of the .git repository.
Note: Caret (^) Notation:
^0.0.0
– potential version range 0.0.0... excluding 0.0.0.999..., same as excluding 0.0.1.This is per Composers' acting with pre-1.0 versions safety-in-mind. To give the different example for comparison:
^1.0.0
– potential version range 1.0.0... excluding 1.999... .999... .999... .999..., same as excluding 2.0.0.Now imagine and understand how you can make use of this in such a setup to incubate local only packages by controlling the version in root, in package, ... . @dev stands in your way then right from day one for local packages.
$ rm -rf vendor composer.lock
$ git checkout -- composer.json
$ composer require app/local
./composer.json has been updated
Running composer update app/local
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking app/local (0.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing app/local (0.0.0): Symlinking from ./app/app/local
Generating autoload files
Using version ^0.0.0 for app/local
Both rundowns with
Composer version 2.6-dev+f605389dc3101c903081ed0e95f9a872a8bfc930 (2.6-dev) 2023-08-16 12:05:14
PHP 8.2.9 (cli) (built: Aug 16 2023 19:49:37) (NTS)
References/Footnotes
¹ Composer path repository; cf. https://getcomposer.org/doc/05-repositories.md#path
² I received your wording with less clarity and therefore took the opportunity to add some to the extent declaring it such a local VCS repository, let me know if that was a misinterpretation; Cf. https://stackoverflow.com/revisions/76904227/1 . clarified per comment:
To clarify, the packages are local, but are all part of the same git repository as the main application, the local packages themselves do not have separate repositories
– it didn't change anything how it works, Composer supports the single repository form. (-- hk)
³ JSON Pointer notation for brevity; JavaScript Object Notation (JSON) Pointer; RFC 6901; https://www.rfc-editor.org/rfc/rfc6901
⁴ I don't recommend – for a single git repository – to use the @dev stability flag to denote the package is for development – as I'd like to stabilize during development phases – and prefer branch aliasing instead. It allows me a better symmetry between the repository package version options and signals the main git repositories main branch (could also be made use of in the root package).