mozart icon indicating copy to clipboard operation
mozart copied to clipboard

Conflict of installation as "require-dev" and "composer i --no-dev"

Open ChristophWurst opened this issue 3 years ago • 15 comments

Hi there :v:

In the readme you suggest installing the package with composer require coenjacobs/mozart --dev, which adds the package to composer's require-dev section.

That makes sense, because this package is a development tool. However, a composer i --no-dev (for deployment) will fail at

> "vendor/bin/mozart" compose
sh: vendor/bin/mozart: No such file or directory
Script "vendor/bin/mozart" compose handling the post-install-cmd event returned with error code 127

because Mozart isn't installed.

This is kind of a :chicken:-:egg: problem. If you install Mozart as dev dependency, you can't build. If you install as production build, things like symfony/console will end in the deployment because Mozart depends on them.

Is there a workaround?

ChristophWurst avatar Nov 13 '20 12:11 ChristophWurst

As you know, Mozart is primarily aimed at the WordPress ecosystem. I seem to remember that you're using Mozart outside of WordPress, which should work fine of course, but makes this more complicated for you. Within the WordPress ecosystem, it is common to build packages, including vendor directories and upload the entire package as a zip to the plugins directory. Pretty much the same applies to premium marketplaces and other places where plugins are distributed.

This is all because Composer does not have a place in the WordPress ecosystem. Mozart and other tools are trying to make it easier for developers to use best practices and modern approaches to dependency management, like Composer. But Mozart is a Monkey patch, at best.

This issue you're experiencing isn't much of an issue within the WordPress ecosystem (although I do know of a bunch of reasons where this would be a problem there as well). We're basically stuck with having to build a package (manually, or use a script) that can then give a fully installable zip as output.

I would like to address this within Mozart, but I'm currently not quite sure how I'd actually do that.

coenjacobs avatar Nov 16 '20 21:11 coenjacobs

You're absolutely right, we use it outside of WP.

Within the WordPress ecosystem, it is common to build packages, including vendor directories and upload the entire package as a zip to the plugins directory.

Same applies to Nextcloud apps. Apps are essentially plugins.

But the problem is the same in both environments: one package's dependencies can conflict with the main component (WP/NC) or other packages.

I've never worked with WP so I don't know if they use any 3rd party libs for the application core. But in Nextcloud we use the Symfony Console 4.x and therefore having an app/package with Mozart causes a conflict if that package pulls in 5.x because of https://github.com/coenjacobs/mozart/blob/b063c0b3c9923fc763e89376e3d671ce450a839a/composer.json#L16.

I would like to address this within Mozart, but I'm currently not quite sure how I'd actually do that.

No worries. I didn't mean to demand a fix for our problem, just wanted to open to start the discussion and see if anyone else faces the same problem.

ChristophWurst avatar Nov 17 '20 11:11 ChristophWurst

But coming back to the ecosystem independent part of the ticket: I highly double that a --dev installation works for you use cases either. Could you try composer i --no-dev in one of your (wordpress) packages that come with Mozart?

In my understanding Mozart should always be installed as production dependency. The composer lifecycle hooks rely on it.

ChristophWurst avatar Nov 19 '20 10:11 ChristophWurst

@coenjacobs what if Mozart would include a build step that:

  1. moves the dependencies of a copy of itself into it's own namespace
  2. only the outcome is then published at packagist

blizzz avatar Nov 19 '20 10:11 blizzz

@ChristophWurst Thanks for clarifying the details of your problem. I'm going to have to let this sink in for a couple days, to make sure that I don't miss out on anything important, but there is one thing that I'd like to address now:

In my understanding Mozart should always be installed as production dependency. The composer lifecycle hooks rely on it.

This is where, in my humble opinion, you misunderstand how little WordPress has to do with Composer. What Mozart is designed to do, is to prefix classes and namespaces, locally. It is meant to just function on a developers local computer, changing certain files and then do nothing when those files are committed to the version control system. Perhaps this is not stated explicitly enough in the README, but the files that Mozart changes are meant to go in your version control system, not to be ignored (as you otherwise would reasonably do with your /vendor directory).

What this does, yes, is create a bunch of extra files in your version control system that are not maintained by you. This is not ideal, but unfortunately is the only way that I can see this working within the WordPress ecosystem. The entire package that is then checked in (probably including /vendor/autoload... files) to version control, is the package that the end user receives. Yes, this is extremely odd for a tool that somehow is designed to make this easier for every single WordPress developer out there, but bear in mind: Mozart is a monkey patch at the very best. WordPress is not designed to work with package managers, or even packages at all. Composer is about as alien to WordPress (core) as it can be.

What I strongly recommend everyone using Mozart to do (and should probably write about more, I know) is to have Mozart handle all packages that are eventually going to make it into the release of your own project. So if your project requires symfony/console, then Mozart should process that package. Every package that Mozart processes, should then be checked in into version control or whatever process your are using to ship the eventual product to your users. Everything in the /vendor directory that is not touched by Mozart - except for the autoloader files - should then be ignored in your actual package. So if your package doesn't require symfony/console, it isn't processed by Mozart and stays in /vendor/symfony/console and remains part of your development tools. It is never checked into version control with your project and does never make it to your actual project and can therefore never be a production requirement (nor can Mozart).

I hope this makes sense to you now. If anything, this emphasises how important solid documentation and perhaps even straight tutorials for implementing Mozart are (no offence to you obviously, I see how weak Mozart is still on that front).

@blizzz I have a feeling that the above comment answers both your questions. If not, please let me know and I'll be happy to further clarify any remaining questions you might still have. :v:

coenjacobs avatar Nov 23 '20 21:11 coenjacobs

Hm. Maybe I'm really missing something but I still don't see the big difference between our two ecosystems. For us composer also plays a secondary role. It just happens to be the dependency manager for php these days. And we also transform our code with Mozart locally, though we don't check it in because we deploy from tarballs and not from git clones. But the deployment doesn't really matter in the discussion about conflicting classes.

The common problem we're trying to solve is two or more composer packages loaded in the same php process. For you it's wordpress packages, for us it's Nextcloud apps. It could also imagine that this happens for any other project where you don't have a global composer project where packages are added through the means of composer.

ChristophWurst avatar Nov 24 '20 07:11 ChristophWurst

@ChristophWurst I've started writing a response to this, a couple times, over the past few days. It never felt right, as I truly feel that Mozart is at a crossroad right now. There is no easy, or simple way to solve this issue so that it works out of the box for all implementations. So, after pondering on it some more, I've decided to write out the big decision that I'm about to make when it comes to the future of Mozart in #96. Your feedback is very much welcome there and thanks again for giving me the push in this direction, to force me to think this through properly. 🥇

coenjacobs avatar Nov 30 '20 21:11 coenjacobs

@ChristophWurst

I've created a mozart.phar to use without requiring Mozart in your composer.json, although it does still read its config from your project's composer.json.

Please try:

curl -OL https://github.com/BrianHenryIE/mozart/releases/download/v0.6.0-bh/mozart.phar
composer install --no-dev
php mozart.phar compose

BrianHenryIE avatar Dec 16 '20 21:12 BrianHenryIE

Very nice @BrianHenryIE. I've also seen this with other tools like Psalm: https://psalm.dev/docs/running_psalm/installation/. They offer a "real" installation and once with the isolated phar.

I tried and it seems to work. But how would a phar work with the post-install-cmd and post-update-cmd? I guess those scripts only work reliably if the phar is installed via composer like the Psalm phar, right? That means the phar would be a production dependency of composer, though the problem with the conflicting Mozart dependencies in ones project would be solved.

ChristophWurst avatar Dec 21 '20 13:12 ChristophWurst

I was picturing adding something like this to the script:

curl -OL https://github.com/BrianHenryIE/mozart/releases/download/v0.6.0-bh/mozart.phar
php mozart.phar compose
rm mozart.phar

The .phar doesn't depend on Composer except to read the Mozart config. The Psalm phar install is just a convenient way to download it (nice standard composer require and automatically link from your bin folder).

Alternatively, I don't see any reason this wouldn't work globally once copied somewhere on $PATH.

BrianHenryIE avatar Dec 24 '20 21:12 BrianHenryIE

I'd argue that even the phar should be a dependency of specified in composer.json even if the tool is merely standalone. The reason is that then builds of the package are consistent and reproducible. Like I know my colleagues and my CI system will build with the same version of Mozart. And when Mozart changes (and thus config has to be adjusted) I can pin the version of Mozart to the commit where I adjust the config -> I can still build older releases (for bugfixes) as that would then install the previous version of Mozart.

ChristophWurst avatar Dec 28 '20 07:12 ChristophWurst

I agree it's useful to specify the expected version for the build. I was more describing the mechanics' independence from the project's composer.

Did you try the script approach / did it work? I'm pretty sure this is good to go. Or do you have a project/branch I can checkout and I'll run this on it with --no-dev.

BrianHenryIE avatar Dec 29 '20 07:12 BrianHenryIE

@ChristophWurst Okay, this has been in the making for far too long, but we're finally there! You basically have three options since release 0.7.0, to install Mozart without affecting the dependencies of the project itself:

  1. Use the Docker container as part of your build process
  2. Use the PHAR file added to each release from now on
  3. Install Mozart as a global dependency on your machine (if that's where you're building your projects)

All three work the exact same, as in that you still configure them in your projects composer.json file. It's just different in how you run them. Please let me know how this works out for you!

coenjacobs avatar Feb 02 '21 21:02 coenjacobs

:raised_hands: thanks a lot, I've been looking forward to this.

What I'm missing personally is a versioned phar available via composer, just like my example of Psalm earlier. The motivation is still the same: I want people to be able to check out my application with composer i and things should just work. I know we have different workflows but for us Mozart should work kind of transparent for the devs. They shouldn't have to know that this tool is used.

Would it be possible to automatically publish the phar on packagist as well?

ChristophWurst avatar Feb 10 '21 09:02 ChristophWurst

Small update: we now use https://github.com/bamarni/composer-bin-plugin in combination with Mozart. That makes sure that the Mozart dependencies do not "pollute" the application with Symfony and other things. This seems to work just fine at the moment. With a phar we could avoid this extra step.

ChristophWurst avatar Mar 03 '21 09:03 ChristophWurst