Skip to content

Instantly share code, notes, and snippets.

@thomascube
Last active December 17, 2015 23:59
Show Gist options
  • Save thomascube/5693475 to your computer and use it in GitHub Desktop.
Save thomascube/5693475 to your computer and use it in GitHub Desktop.
dcomposer - Install Composer dependencies into a global location

dcomposer

This is simple approach to work-around the missing --global option in Composer. See composer/composer#55

It reads the dependencies from a local composer.json file and installs them into a global location (e.g. /usr/local/lib/composer) and creates a light-weight local Composer installation providing the necessary autoload magic.

The script includes a special handling for PEAR modules defined in composer.json. These are not installed via Composer but using a locally installed PEAR repository.

Installation

  1. Install Composer globally as described in http://getcomposer.org/doc/00-intro.md#globally
  2. Create a directory for your global Composer vendor library (e.g. /usr/local/lib/composer)
  3. Copy the composer.json template file from this gist to that directory
  4. Enter the PEAR include dir path to the "classmap" property of the global composer.json file
  5. Install the dcomposer binary to a global location in your PATH (e.g. /usr/local/bin) and make it executable

Usage

Use the dcomposer binary to install and update Composer dependencies in your project direcotry.

$ cd <your-project-dir>
$ dcomposer

The dependencies from your project's composer.json file will now be added to the global Composer vendor dir or installed with PEAR respectively. In the local project dir, a minimal Composer setup is installed which mainly includes the autoloading facilities of the global Composer directory.

License

This code is licensed under the GNU Affero General Public License Version 3 or higher. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY.

For suggestions or contributions, please add comments to this gist.

{
"minimum-stability": "dev",
"config": {
"vendor-dir": "vendor",
"bin-dir": "bin"
},
"autoload": {
"classmap": [ "<pear-php_dir>" ]
},
"require": {
"php": ">=5.3.3"
},
"repositories": {
}
}
#!/usr/bin/env php
<?php
/**
* dcomposer - Install Composer dependencies into a global location
*
* This is a work-around for the missing --global option in Composer.
* see https://github.com/composer/composer/issues/55
*
* It reads the dependencies from the composer.json file and installs them
* into a global location (/usr/local/lib/composer) and creates a light-weight
* local Composer installation providing the necessary autoload magic.
*
* @version 0.1.0
* @author Thomas Bruederli <bruederli(at)kolabsys.com>
*
* Copyright (C) 2013, Kolab Systems AG <contact(at)kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// change these if necessary
$COMPOSER_DIR = '/usr/local/lib/composer';
$COMPOSER_CMD = 'composer';
$verbose = true;
// suppress error notices
ini_set('error_reporting', E_ALL &~ E_NOTICE &~ E_STRICT);
// read Composer input files
$json = @json_decode(($json_src = @file_get_contents('./composer.json')), true);
$global = @json_decode(@file_get_contents($COMPOSER_DIR . '/composer.json'), true);
if (empty($json)) {
die("Cannot read composer.json");
}
else if (empty($global)) {
die("Cannot read $COMPOSER_DIR/composer.json");
}
$installed_comp = array();
$installed_pear = array();
// get installed Composer modules
if (exec("$COMPOSER_CMD show --installed --working-dir=$COMPOSER_DIR", $installed_p)) {
foreach ($installed_p as $i => $line) {
list($package, $version) = preg_split('/\s+/', $line);
$installed_comp[$package] = $version;
}
}
// get installed PEAR modules
if (exec("pear list", $installed_p)) {
foreach ($installed_p as $i => $line) {
if ($i < 2) continue;
list($package, $version, $stable) = preg_split('/\s+/', $line);
$installed_pear[strtolower($package)] = $version;
}
}
$updates = 0;
foreach ($json['require'] as $package => $version) {
if ($package == 'php')
continue;
list($vendor, $pack) = explode("/", $package);
if ($vendor == 'pear-pear') {
require_pear($pack, $version);
}
else {
$updates += require_composer($package, $version);
}
}
// register custom repositories
foreach ((array)$json['repositories'] as $k => $rep) {
if ($rep['type'] == 'pear')
continue;
if (is_numeric($k)) {
$k = substr(md5($rep['type'] . $rep['url']), 0, 8);
}
if (empty($global['repositories'][$k])) {
_exec(sprintf("$COMPOSER_CMD --working-dir=$COMPOSER_DIR config repositories.%s %s %s",
$k,
escapeshellarg($rep['type']),
escapeshellarg($rep['url'])
));
$updates++;
}
}
// update global composer repository
if ($updates) {
_exec("$COMPOSER_CMD --working-dir=$COMPOSER_DIR update");
}
// create local instance with autoloader
if ($updates || !is_file('./composer-local.json')) {
unset($json['require-dev'], $json['repositories']);
$json['require'] = array('php' => '>=5.3.2');
file_put_contents('./composer-local.json', str_replace('\\/', '/', json_encode($json)) . "\n");
_exec("COMPOSER=composer-local.json $COMPOSER_CMD -v update");
// append global composer autoloader
if (is_writeable('./vendor/autoload.php')) {
$autoload = file_get_contents('./vendor/autoload.php');
$autoload = preg_replace('/return\s+ComposerAutoloader/', 'ComposerAutoloader', $autoload);
$autoload .= "\n// add global Composer autoloader\nrequire_once('$COMPOSER_DIR/vendor/autoload.php');\n";
file_put_contents('./vendor/autoload.php', $autoload);
}
else {
echo "ERROR: Failed to patch vendor/autoload.php\n";
}
}
/**
*
*/
function require_composer($package, $version, $dev = false)
{
global $COMPOSER_DIR, $COMPOSER_CMD, $installed_comp;
if (!$installed_comp[$package] || _version_compare($version, $installed_comp[$package]) < 0) {
_exec("$COMPOSER_CMD --working-dir=$COMPOSER_DIR --no-update require " . escapeshellarg($package.":".$version));
return 1;
}
else if (_version_compare($version, $installed_comp[$package]) > 0){
echo "A newer version of " . $package . " is installed. installed: " . $installed_comp[$package] . "; required: " . $version . "\n";
}
return 0;
}
/**
*
*/
function require_pear($package, $version)
{
global $installed_pear;
$package_lc = strtolower($package);
if (!$installed_pear[$package_lc]) {
_exec("sudo pear install $package");
}
else if (_version_compare($version, $installed_pear[$package_lc]) < 0) {
echo "A newer version of PEAR::" . $package . " is required. installed: " . $installed_pear[$package_lc] . "; required: " . $version . "\n";
}
}
function _version_compare($reqversion, $compversion)
{
$op1 = $op2 = '>=';
$regex = '/^([><=]*)([\d.*a-z-]+)/';
if (preg_match($regex, $reqversion, $m)) {
$op1 = $m[1] ?: '>=';
$reqversion = strtr($m[2], array('*'=>'99'));
}
if (preg_match($regex, $compversion, $m)) {
$op2 = $m[1] ?: '>=';
$compversion = rtrim($m[2], '.*');
}
return version_compare($compversion, $reqversion);
}
function _exec($cmd)
{
if ($GLOBALS['verbose']) echo "> $cmd\n";
return system($cmd, $lines);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment