ENHANCEMENT: Add first version of tools for pulling in modules, now that we cant just use svn externals

This commit is contained in:
Hamish Friedlander 2011-02-03 17:36:03 +13:00
parent d8ee7bde57
commit 7b777c70e8
7 changed files with 393 additions and 0 deletions

100
tools/lib/new-project.php Normal file
View File

@ -0,0 +1,100 @@
<?php
/**
Command line binary to pull core SilverStripe modules into a new project
This is an attempt to solve a problem introduced by the move to git, namely that new developers
who checkout the silverstripe-installer repo now don't get a "read to go" set of code, and they
shouldn't need to know how to use git or what modules need pulling in to get a base SilverStripe
install up and running.
Currently it doesn't attempt to solve the more general problem of updating or installing new modules
in an existing project - that's likely to be handled by some of this code + a re-architecture of sake
so that we can let developers add code to handle particulars of the process in an environment-aware manner
*/
$base = dirname(__FILE__);
require dirname($base).'/versions.php';
require 'sources.php';
require 'tools.php';
$opts = getopt('m:t:h',array('mode:', 'template:', 'help'));
$mode = isset($opts['m']) ? $opts['m'] : (isset($opts['mode']) ? $opts['mode'] : 'piston');
$templatefile = isset($opts['t']) ? $opts['t'] : (isset($opts['template']) ? $opts['template'] : 'template.php');
include $templatefile;
if (!isset($template)) {
echo "Template could not be found.\n\n";
$templatefile = null;
}
else if ($mode) {
$errors = array();
foreach ($template as $dest => $source) {
if ($mode == 'flat') $errors = array_merge($errors, (array)$source->canExport());
elseif ($mode == 'piston') $errors = array_merge($errors, (array)$source->canPiston());
elseif ($mode == 'contribute') $errors = array_merge($errors, (array)$source->canCheckout());
}
$errors = array_unique($errors);
if ($errors) {
echo "\nRequirements were not met for mode $mode:\n ";
echo implode("\n ", $errors);
echo "\n\nEither correct the requirements or try a different mode\n\n";
$mode = null;
}
}
if (($mode != 'piston' && $mode != 'flat' && $mode != 'contribute') || !$templatefile || isset($opts['h']) || isset($opts['help'])) {
echo "Usage: new-project [-m | --mode piston | flat | contribute] [-t | --template template.php] [-h | --help]\n";
echo "\n";
echo " piston is the default mode, and uses the piston tool to add the core modules\n";
echo " It allows pulling down core module updates later while maintaining your changes.\n";
echo " It does not provide any tools for contributing your changes back upstream, though a third-party tool for git is available\n";
echo " It requires the external piston tool and all it's dependancies to be installed.\n";
echo " It only works on svn and git managed repositories.\n";
echo "\n";
echo " flat copies the core module code without using any tools or version control\n";
echo " It does not provide any tools for pulling down core modules updates later, or contributing changes back upstream\n";
echo " It requires only php with the zip and curl extensions\n";
echo " It works regardless of version control system\n";
echo "\n";
echo " contribute sets up the core as separate modules, allowing you to contribute any changes back upstream\n";
echo " It allows pulling down core module updates later while maintaining your changes.\n";
echo " It allows contributing your changes back upstream\n";
echo " It requires git and all it's dependancies to be installed.\n";
echo " It only works on git managed repositories.\n";
die;
}
// Check we're not being re-called before we do anything
$alreadyexists = false;
foreach ($template as $dest => $source) {
if (file_exists($dest)) {
echo "ERROR: Module $dest already exists. This script can not be called multiple times, or upgrade existing modules.\n";
$alreadyexists = true;
}
}
if ($alreadyexists) die;
if ($mode == 'piston') {
echo "Now running piston to add modules. Piston is quite noisy, and can sometimes list errors as fatal that can be ignored.\n";
echo "If errors are shown, please check result before assuming failure\n\n";
}
foreach ($template as $dest => $source) {
if ($mode == 'contribute') {
GIT::ignore($dest);
$source->checkout($dest);
}
else if ($mode == 'piston') $source->piston($dest);
else $source->export($dest);
}
if ($mode == 'piston' && GIT::isGITRepo()) {
echo "\n\nNow commit the changes with something like \"git commit -m 'Import core SilverStripe modules'\"\n";
}

119
tools/lib/sources.php Normal file
View File

@ -0,0 +1,119 @@
<?php
/**
Sources define a set of instructions for exporting, pistoning or checking out code from some sort of repository
*/
class GitRepo {
function __construct($data) {
$this->data = $data;
}
function repoURL() {
return $this->data['repo'];
}
function export($out) {
throw new Exception('Dont know how to do this yet');
}
function canExport() {
return array('Cant currently use flat mode with git source repositories');
}
function piston($out) {
$data = $this->data;
Piston::import($this->repoURL(), $data['branch'], $out);
}
function canPiston() {
$errors = array();
if (!GIT::available()) $errors = "Git is not available.";
if (!Piston::available()) $errors[] = "Piston is not available.";
if (!SVN::isSVNRepo() && !GIT::isGITRepo()) $errors[] = "Piston only works on svn working copies and git repositories.";
return $errors;
}
function checkout($out) {
$data = $this->data;
GIT::checkout($this->repoURL(), $data['branch'], $out);
}
function canCheckout() {
$errors = array();
if (!GIT::available()) $errors = "Git is not available.";
return $errors;
}
}
class Github extends GitRepo {
protected $data;
function __construct($data) {
$this->data = $data;
}
function repoURL() {
$data = $this->data;
return "git://github.com/{$data['user']}/{$data['project']}.git";
}
function export($out) {
$data = $this->data;
$tmp = tempnam(sys_get_temp_dir(), 'phpinstaller-') . '.zip';
HTTP::get("https://github.com/{$data['user']}/{$data['project']}/zipball/{$data['branch']}", $tmp);
Zip::import($tmp, $out, 1);
}
function canExport() {
$errors = array();
if (!HTTP::available()) $errors[] = "The curl module is not available";
if (!Zip::available()) $errors[] = "The zip module is not available";
return $errors;
}
}
class SvnRepo {
function __construct($data) {
$this->data = $data;
}
function repoURL() {
$data = $this->data;
return "{$data['repo']}/{$data['branch']}" . (isset($data['subdir']) ? "/{$data['subdir']}" : '');
}
function export($out) {
SVN::export($this->repoURL(), $out);
}
function canExport() {
$errors = array();
if (!SVN::available()) $errors[] = "Subversion is not available.";
return $errors;
}
function piston($out) {
Piston::import($this->repoURL(), null, $out);
}
function canPiston() {
$errors = array();
if (!SVN::available()) $errors[] = "Subversion is not available.";
if (!Piston::available()) $errors[] = "Piston is not available.";
if (!SVN::isSVNRepo() && !GIT::isGITRepo()) $errors[] = "Piston only works on svn working copies and git repositories.";
return $errors;
}
function checkout($out) {
SVN::checkout($this->repoURL(), $out);
}
function canCheckout() {
$errors = array();
if (!SVN::available()) $errors[] = "Subversion is not available.";
return $errors;
}
}

30
tools/lib/template.php Normal file
View File

@ -0,0 +1,30 @@
<?php
/**
Base template used by new-project to know what modules to install where & where to get them from
Structure is likely to change at some point
*/
$template = array(
'sapphire' => new Github(array(
'user' => 'silverstripe',
'project' => 'sapphire',
'branch' => SAPPHIRE_CURRENT_BRANCH
)),
'cms' => new Github(array(
'user' => 'silverstripe',
'project' => 'silverstripe-cms',
'branch' => SAPPHIRE_CURRENT_BRANCH
)),
'themes/blackcandy' => new SvnRepo(array(
'repo' => 'http://svn.silverstripe.com/open/themes/blackcandy',
'branch' => 'trunk',
'subdir' => 'blackcandy'
)),
'themes/blackcandy_blog' => new SvnRepo(array(
'repo' => 'http://svn.silverstripe.com/open/themes/blackcandy',
'branch' => 'trunk',
'subdir' => 'blackcandy_blog'
))
);

136
tools/lib/tools.php Normal file
View File

@ -0,0 +1,136 @@
<?php
/**
Interfaces to various external modules or binaries
*/
class HTTP {
static function available() {
return function_exists('curl_init');
}
static function get($url, $dst = null) {
$hndl = curl_init($url); $fhndl = null;
curl_setopt($hndl, CURLOPT_FOLLOWLOCATION, true);
// Unfortunately, ssl isn't set up right by default in php for windows
if (strpos(PHP_OS, "WIN") !== false) curl_setopt($hndl, CURLOPT_SSL_VERIFYPEER, false);
if ($dst) {
$fhndl = fopen($dst, 'wb');
curl_setopt($hndl, CURLOPT_FILE, $fhndl);
}
else {
curl_setopt($hndl, CURLOPT_RETURNTRANSFER, true);
}
$res = curl_exec($hndl);
if (!$res) {
throw new Exception("Downloading ".$url." failed - curl says: ".curl_error($hndl));
}
curl_close($hndl);
if ($fhndl) fclose($fhndl);
return $res;
}
}
class SVN {
static function available() {
exec('svn --version', $out, $rv);
return $rv === 0;
}
static function isSVNRepo() {
return is_dir('.svn');
}
static function export($repo, $out) {
`svn export $repo $out`;
}
static function checkout($repo, $out) {
`svn checkout $repo $out`;
}
}
class GIT {
static function available() {
exec('git --version', $out, $rv);
return $rv === 0;
}
static function isGITRepo() {
return is_dir('.git');
}
static function ignore($dir) {
$hndl = fopen('.gitignore', 'a');
fwrite($hfnl, $dir."\n");
fclose($hndl);
}
static function checkout($repo, $branch, $out) {
if ($branch) `git clone -b $branch $repo $out`;
else `git clone -b $branch $repo $out`;
}
}
class Piston {
static function available() {
exec('piston --version', $out, $rv);
return $rv === 0;
}
static function import($src, $branch, $dest) {
if ($branch) `piston import --commit $branch $src $dest`;
else `piston import $src $dest`;
}
}
class Zip {
static function available() {
return class_exists('ZipArchive');
}
static function import($src, $dest, $skipdirs = 0) {
$zip = new ZipArchive;
$res = $zip->open($src);
if ($res === TRUE) {
if ($skipdirs) {
$tmpdir = tempnam(sys_get_temp_dir(), 'phpinstaller-') . '.ext';
mkdir($tmpdir, 0700);
mkdir($dest);
$zip->extractTo($tmpdir);
for($i = 0; $i < $zip->numFiles; $i++){
$name = $srcname = $zip->getNameIndex($i);
$parts = array();
while ($name && $name != '.' && $name != '/') {
array_unshift($parts, basename($name));
$name = dirname($name);
}
// We only need to move the very next level after the skipdirs level
if (count($parts) != $skipdirs+1) continue;
$dstname = $parts[$skipdirs];
rename($tmpdir.'/'.$srcname, $dest.'/'.$dstname);
}
}
else {
$zip->extractTo($dest);
}
$zip->close();
} else {
throw new Exception('Could not extract zip at '.$src.' to '.$dest);
}
}
}

3
tools/new-project Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
base=`dirname $0`
php $base/lib/new-project.php "$@"

2
tools/new-project.bat Normal file
View File

@ -0,0 +1,2 @@
@echo off
php %~dp0\lib\new-project.php %*

3
tools/versions.php Normal file
View File

@ -0,0 +1,3 @@
<?php
define('SAPPHIRE_CURRENT_BRANCH', 'master');