2

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.

yivi
  • 42,438
  • 18
  • 116
  • 138
mikestreety
  • 833
  • 6
  • 28
  • _"I read somewhere that we should include the dependencies with the @dev syntax "_: **Where?** And **Why?** did you draw these conclusions? What was *your* understanding? What made you looking this up? – hakre Aug 22 '23 at 09:54
  • It was nothing concrete, but answers like this made sense - I never questioned the why: https://stackoverflow.com/a/39802804/1324321 – mikestreety Aug 23 '23 at 22:07
  • *Sense* in which sense? Where did you recieve the sense from? The _"Why"_ not questioning them, but your retrieval, why did you consider it to be with "sense" from your perspective? Not questioning your question, just asking so that I can better understand your question. Also preparing an update for the answer, have to double check some of your constraints that were not clear to me (layout of the git repository/ies). – hakre Aug 24 '23 at 00:00
  • Thanks @hakre, I appreciate your due diligence and not taking this personally! To me it made "sense" as the packages are in the same repository and, in my mind, under development. They have no specific branch or tag, so having a generic `@dev` looked like the clear answer. I appreciate reading the docs it is not how it is intended (and seeing how @dev is an alias for the current branch) now makes me see how I misunderstood it's use. – mikestreety Aug 24 '23 at 06:26
  • @mikestreety Just for clarification:`@dev` is **not** an alias for the "current branch". Are you still getting this from somewhere? – yivi Aug 24 '23 at 10:34
  • In my composer.lock - if i have `@dev` for a package, the current branch gets stored in the `composer.lock` file as the version for that package. E.g `"require" { "app/hdc": "@dev"}` in `composer.json` equates to `"packages": [{"name": "app/hdc", "version": "dev-main",...}]` in the composer.lock. Changing the branch and running a `composer update` changes that version stored in the file – mikestreety Aug 24 '23 at 12:54

3 Answers3

3

You are mixing different concepts, that do have some overlap, but that ultimately mean different things.

I like the idea of @dev as it signifies which packages are local

This is wrong. The meaning of @dev is a "stability flag"

The complete package link form is "[constraint][@stability flag]".

With the syntax

"require": {
    "foo/package": "@dev"
}

You are simply saying:

"I don't care for the specific version for this package, install whatever available or dictated by other constraints; but the minimum stability for this package is "dev", instead of whatever minimum stability I had rest for the rest of the project". This is understood by composer as a link with the form foo/package:*@dev.

If you use:

"require": {
    "foo/package": "dev-main"
}

... then you are specifying an actual version constraint. (Additionally, since the version constraint starts with dev-, composer will automatically infer that the minimum stability for this package is @dev, so it will understand this constraint as: foo/package:dev-main@dev).

If you don't want to use a specific constraint, you could use an asterisk (*) to represent "anything".

"require": {
    "foo/package": "*"
}

... but since your package is a "dev" package (by not having a release tag, and coming from a dev branch it will be interpreted as having "dev" stability), this will likely fail, since unless your project has "minimum-stability" of dev, it won't match anything there. (Which is why @dev would work, since it would be equivalent to *@dev, as "any version, with minimum stability as "dev").


TLDR: Between @dev and dev-main... you should probably use actual version constraints.

Stability flags do not do what you think they do. There are cases for simply specifying a stability flag without a version constraint in your package, but that's generally just to override the project's minimum stability for a single required package.

But the important thing to note, in regard to the question as posted, is that your understanding of @dev was flawed and that it actually means something different.

yivi
  • 42,438
  • 18
  • 116
  • 138
  • Thanks very much for taking the time to reply. It all makes sense and I completely understand how all this would be used when dealing with external packages. However, my local packages are in the same git repository - how would this work with versioning? Do I just use a generic `1.0.0` in both composer files and leave it be? – mikestreety Aug 22 '23 at 08:07
  • That the packages are "local" or that in the "same git repository" is not really relevant. You should very likely use the appropriate version constraint (e.g. point to the correct branch). If you do not care about which version branch you'll get, you could use a generic `*` constraint (anything goes). Or keep using `@dev` to override the root package "minimum stability". What I'm trying to explain is that the question ("use 'dev-branch' vs @dev") does not make, IMO, a lot of sense. These two mean different things. Pick the one that works for you. – yivi Aug 22 '23 at 09:13
  • Using a "generic 1.0.0" wouldn't likely work, unless you have a tag/branch with that name. – yivi Aug 22 '23 at 09:14
0

TL;DR: Neither! Show it's born here, the star: *


You ask:

Should I use @dev or dev-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.

Caution

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.]

Rationale

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 be dev-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:

  • Either mark those packages as local – that should be obvious by their package name (e.g. "app/local", the example given by yourself) or at least by their repository (it is a path repository)
  • Nor mark them having development stability – the checked-out version will denote that (tag or branch name) or the dev-master fallback by Composer itself.

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.

Notes on Exporting the Project

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)


Sunrise Example

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 actual app/*/*/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.)

Rundown:

$ 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.

Alternative Requirement Version Constraint

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.

Alternative Rundown:

$ 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).

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Thanks so much - I think I need to read this a few times to properly understand it! 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 – mikestreety Aug 22 '23 at 14:09
  • Interestingly, I tried updating one of our packages to use the `*` and got the following error: `- Root composer.json requires app/ara *, found app/ara[dev-main] but it does not match your minimum-stability.` - something I didn't get with the `@dev`. I also added a `"version": "1.0.0"` to my `app/ara` composer.json and it let me install with `^1` in my root composer – mikestreety Aug 24 '23 at 07:13
  • Yes, if you don't communicate on the options of the path repository per each package your intended version (e.g. 1.0.0 in your comment, see the path repository _options_), there is the one from VCS and henceforth you either want to have minimum stability changed in your project, the @dev (you can suffix it as usual, e.g. *@dev). The app/app/local/composer.json#version is of little relevance in that setup IIRC. I'd suggest to look for a branch-alias there instead for better documentation of your project. And with * for requires henceforth _path repository version options_. – hakre Aug 24 '23 at 12:24
  • @mikestreety: It's perhaps better to see an example, too, find **Sunrise Example** in the answer now. – hakre Aug 24 '23 at 12:44
-3

According to The composer.json schema:

require and require-dev additionally support stability flags that take the form "constraint@stability flag". They allow you to further restrict or expand the stability of a package beyond the scope of the minimum-stability setting. You can apply them to a constraint, or apply them to an empty constraint if you want to allow unstable packages of a dependency for example.

If you are working with production build I would lock the package to certain branch and hash. In this case consider using Semantic Versioning.

{
    "require": {
        "my/package": "1.0.0#abc123"
    }
}

Hope this helps!

judx
  • 462
  • 2
  • 6