New in 4.6 Dev: Composer and NodeJS

Közzétéve
2015-01-03 06:54
Written by
totten - member of the CiviCRM community and Core Team member - about the Core Team

Summary

Beginning with CiviCRM v4.6, developing code for CiviCRM (civicrm-core.git) will require two additional tools: Composer and NodeJS. If you develop CiviCRM, then you should install them in order to continue development.

Who is affected?

  • NOT AFFECTED: Site administrators who install CiviCRM (from .tar.gz or .zip files) do NOT need to install these tools.
  • NOT AFFECTED: Developers who write modules or extensions do NOT need to install these tools.
  • AFFECTED: Developers and maintainers who work with the CiviCRM git repositories (esp. the "master" branch -- v4.6) DO need to install these tools.

What to do?

If you are a developer who works with the CiviCRM git repositories, then please ensure that these tools are installed:

  • Composer: The "composer" command downloads PHP libraries. You can check if it's already installed by going to the command-line and running "composer --help". For install instructions, see http://getcomposer.org/ .
  • NodeJS: The "node" command executes Javascript code on the command-line. You can check if it's already installed by running "node --help". For install instructions, see http://nodejs.org/ .

Note: civicrm-buildkit is a bundle of tools useful in CiviCRM development. If you use buildkit, then you already have a copy of composer. However, NodeJS must be installed separately. (Buildkit only includes portable programs written in languages like PHP or bash. NodeJS requires native binaries that are tailored to your operating system. Fortunately, nodejs.org distributes pre-compiled binaries for most platforms, and recent Linux distributions -- like Ubuntu 14.04 -- provide a NodeJS package.)

Do I have to use these new tools?

If you've installed the tools, then you shouldn't need to do anything more. Whenever you run "setup.sh", it will automatically run the most important commands.

However, there are two times you might use the new tools:

  • If you want to add, upgrade, or remove dependencies, then you may need to edit a .json file and/or run a command.
  • If you work on the Javascript tests, then you may need to run "npm test" or "karma".

What if there are problems?

Civi has used the civicrm-packages process and phpunit for a long time -- so we've collectively learned a bit about how to support them. As with any newly introduced tool or process, there may be some quirks in adopting Composer or NodeJS. If you encounter problems with using these in Civi, please share your experience on the developer forum so that we can improve the process and make it easier for future developers.

In Depth

If you've installed Composer and NodeJS, then you can stop reading -- we've covered the urgent stuff. In the rest of the blog post, we'll dig a bit more into the why's and how's of this change.

In Depth: Why? Reason 1: To improve testing of Javascript code.

CiviCRM includes automated testing based on PHPUnit and PHPUnit-Selenium. PHPUnit is great for writing tests that cover server-side PHP code and database code. With the addition of Selenium, one can also write PHPUnit tests which perform end-to-end testing.

In recent development cycles, CiviCRM increasingly implements major features using Javascript logic and APIv3. For example, v4.5 introduces a CiviCase configuration GUI based on AngularJS, and v4.6 introduces a revised CiviMail GUI also based on AngularJS. As more code is written in Javascript, Javascript testing becomes more important. Unfortunatey, neither PHPUnit nor PHPUnit-Selenium is well-suited to testing client-side Javascript code.

The Javascript community has produced some great tools for Javascript testing. Generally, these tools require NodeJS. Specifically, in AngularJS the standard practice is to write tests with Karma and Jasmine -- which are better suited for testing Javascript code (more concise, more performant, better documentation). And they require NodeJS.

In Depth: Why? Reason 2: To improve management of libraries.

If a developer wants to write Javascript along with some tests, he must first download the NodeJS runtime -- and then download several other tools and libraries written in Javascript (Karma, Jasmine, AngularJS, etc). For any new developer (and many experienced developers), understanding and downloading all these dependencies can make the head spin. Forutnately, one can download dependencies automatically with a package-manager (such as "npm").

Dependencies are not a new problem for CiviCRM. With CiviCRM v1.0 - v4.5, CiviCRM's approach to dependencies was based entirely on the "packages" folder - which currently works like this: if CiviCRM needs a dependency "Foo", then a Civi developer downloads "Foo", copies it into the "packages" folder, and sends a pull-request to the "civicrm-packages.git" repository. This poses some trade-offs:

  • Pro: It works with almost anything (PHP, JS, CSS, PNG, Java, etc -- basically anything except native binaries).
  • Pro: It doesn't require any extra tools to perform a download. (Which was important in 2005 -- because the tooling sucked!)
  • Pro: If you have the CiviCRM source code, then you have all the dependencies - there's nothing else to download.
  • Pro: If one of the dependencies needs a small patch, you can put the patch in civicrm-packages.git -- without any administrative overhead or negotiation with the original developer.
  • Con: It removes the incentive to collaborate with the original developer.
  • Con: It's difficult to determine the list of dependencies and their versions. (The VERSIONS file isn't always up-to-date, and browsing the file-system doesn't reveal embedded dependencies.)
  • Con: It's difficult to determine which dependencies have been patched - and why.
  • Con: It's difficult to upgrade a dependency (because you need to determine if it's been patched).
  • Con: If a developer wants to use a new library in some code, he must submit two PRs. The first PR (civicrm-packages.git) adds the library, and the second PR (civicrm-core.git) uses the library. Split PRs make it harder to manage development over a long period of time and interfere with automated testing of PRs.
  • Con: PRs for new or upgraded libraries are so big that they can't be meaningfully read.
  • Con: civicrm-packages.git grows large over time -- because it includes a copy of each version of of each dependency ever shipped with Civi.
  • Con: It's not a well-received practice in the broader PHP open-source community. The recent "PHP renaissance" (circa 2011+) has been based on the broad adoption of "composer" as the standard solution for dependencies.

"npm" and "composer" are package-managers which follow a different approach. (There's actually a number of similar tools, each targetted at slightly different programming communities: CPAN for Perl, Gem for Ruby, Maven for Java, Composer for PHP, NPM for backend Javascript, and bower for frontend JS+CSS.) In each case, one creates a document (such as composer.json, package.json, or bower.json) which lists each of the required dependencies. Any developer who works on the application can download the dependencies with a small command (e.g. "composer install" or "npm install").

The trade-offs are basically the reverse:

  • Con: Each package-manager works with different dependencies. ("composer" works with PHP; "npm" works with headless Javascript.)
  • Con: You have to download extra tools ("composer" and "node/npm").
  • Con: Dependencies are downloaded separately. If the distribution channel for any one dependency breaks, then you can't install a new developer site. (This is not as bad as it sounds -- package-managers include caching, and most dependencies rely on the same distribution channel as CiviCRM -- i.e. github.com and sf.net. If those go down, then you'd be SOL in any case.)
  • Con: It's more difficult to use forked/patched code (but it is possible).
  • Pro: It preserves the incentive to collaborage with the original developer.
  • Pro: The list of dependencies (and their versions) is readily browsable and necessarily accurate.
  • Pro: It's readily apparent when the application uses clean/official code from upstream -- and when it uses forked/patched code.
  • Pro: When using forked/patched code, you can maintain the fork with git's branch/merge/rebase features.
  • Pro: If a developer wants to add or upgrade a dependency, the developer can submit a single, concise PR (e.g. which updates the composer.json and uses the new library).
  • Pro: "npm" and "composer" are well-recognized in the broader community of JS and PHP developers (respectively)

In Depth: How to add a new package in v4.6+?

Unfortunately, "composer" and "npm" are separate tools, and there is no grand-unified dependency-manager that will do everything we need for CiviCRM. However, the division of responsibility should be pretty clear among the tools. To add a new package in v4.6+:

  • If the package is a PHP library (or tool), then follow the directions from http://getcomposer.org. In brief, this means running "composer require PKGNAME" (or editing "composer.json").
  • If the package is a backend library (or tool) written in Javascript, then follow the directions from http://npmjs.com. In brief, this means running "npm install PKGNAME --save" (or editing "package.json").
  • If the package is a frontend library (or tool) written in Javascript, then follow the directions from http://bower.io. In brief, this means running "bower install PKGNAME --save" (or editing "bower.json").
  • If the package doesn't meet any of the above criteria, or if the upstream author doesn't support the appropriate package-manager, then add the code to civicrm-packages.git.

In Depth: How to upgrade a package in v4.6+?

If Civi includes the package by way of composer, npm, or bower, then simply follow the documentation for that tool. In brief, this generally means running a CLI command and/or editing the .json file.

If Civi includes the package by way of civicrm-packages.git, then consider moving it to composer, npm, or bower. This will involve a few steps:

  • Determine current version of the package and determine if the package has been patched.
  • If it's been patched, then identify the patch and start a discussion (e.g. on IRC or the developer forum) so that we can figure out if the patch is still needed, how we want to maintain it, etc.
  • Otherwise...
    • Prepare a pull-request for civicrm-packages.git which removes the old package.
    • Prepare a pull-request for civicrm-core.git; specifically:
      • Add the package using composer, npm, or bower
      • Search the civicrm-core.git source tree for any references to the package.
      • Update any references which would break.

In Depth: See also...

Filed under

Comments

Excellent upgrade to our dev infrastructure. Thanks for the detailed and well structured blog post.

Great work! This is a huge improvement over the existing packages git repository.

One question: is there a way to distinguish tools only needed for development vs tools needed to civicrm in a production environment? And, how will the generation of the civicrm downloadable tar ball be handled?

> Unfortunately, "composer" and "npm" are separate tools, and there is no grand-unified dependency-manager that will do everything we need for CiviCRM

There is apt-get (and yum, etc).

One of our trade-offs is building development tools that work across platforms and can use the most recent tooks vs. using package management systems that provide authentication of the packages.

This problem is surely beyond CiviCRM, however, we should all be conscious of our risks and discuss steps we can take to reduce them.

Using composer means we don't verify the software we are downloading to our development machines (see https://stackoverflow.com/questions/26246143/security-concerns-with-using-composer-install-in-production-environment and https://github.com/composer/composer/issues/38).

A couple suggestions, maybe others have more?

  1. Although it seems discouraged by the composer devs (https://getcomposer.org/doc/04-schema.md#package-links), can we use git commits to specify versions whenever possible, since they are crypographically authenticated?
  2. Sandbox your dev workstation: Many of us use one computer, which stores the credentials to our customers database. This is not a good computer on which to be downloading untrusted software. Instead, you may want to create a virtual guest for development that is isolated from your main computer. If your virtual guest is compromised - it cannot access any data on your regular workstation.

FWIW, I think that limitation is shared by several of these language-specific package-managers (e.g. composer, bower, npm).

The discussion thread raises some interesting points vis-a-vis identity and replay attacks. I'm not sure how those could be solved in the near-term.

How would you feel about augmenting the checksum capabilities of each package-manager with something like https://gist.github.com/totten/52bf03597e092944bff6 . From a workflow perspective, it sucks a little bit that one would need to update two files when adding a new dependency (e.g. update composer.json + dirsum.json; or update bower.json + dirsum.json), but it's probably more feasible than either (a) getting all three package-management communities to agree on and implement a solution to the identity and replay issues or (b) distributing current versions of all dependencies through apt/yum.

I haven't noticed this actually happen yet, but in theory... If you made a branch on Sunday (Jan 4), twiddled your thumbs on Monday (Jan 5), and then tested it on Tuesday (Jan 6), the tests *might* fail due to problems with missing dependencies. If you get unexpected failures in PR tests (esp. on older PRs/older branches), you should rebase to ensure that your PR is based on a recent version of civicrm-core.git#master. (On Monday, Jan 5, we merged PRs to migrate composer.json from civicrm-packages.git to civicrm-core.git.)

The root problem which causes this situation is that changes to civicrm-packages.git and civicrm-core.git cannot be made atomically. In the future, as we get dependencies out of civicrm-packages.git and into composer.json/bower.json, this class of problem should go away.