Показаны сообщения с ярлыком git. Показать все сообщения
Показаны сообщения с ярлыком git. Показать все сообщения

суббота, 20 июля 2013 г.

How to package and use Yii framework in PHAR archive

Okay, today's the task: pushing all of 1892 files of Yii framework to the Git repository is a burden, and upgrading it to new version pollutes the git log with changes to files you don't care about. Let's package it into a PHAR!

Packaging

Rasmus Schultz made a special script to package the Yii framework into a PHAR archive. I forked it to save for a future (at least my GitHub account will live as long as this blog).

You need just to put this script to the root of Yii codebase (cloned github repo, for example), and run it as usual:

php yii-phar.php

This will create the PHAR archive in the same directory.

Hovewer, beware the catch 1: PHP can refuse to create packed archive, emitting the following error:

PHP Fatal error:  Uncaught exception 'BadMethodCallException' 
with message 'unable to create temporary file' 
in /path/to/your/yii/root/yii-phar.php:142
Stack trace:
#0 /path/to/your/yii/root/yii-phar.php(142): Phar->compressFiles(4096)
#1 {main}
  thrown in /parh/to/your/yii/root/yii-phar.php on line 142

I decided to just remove lines 140 and 142 from the script:

echo "Compressing files ...\n\n";

$phar->compressFiles($mode);

And that's all. I can bear with 20 MB file in repo, and don't really care about compression.

Using

To connect the resulting PHAR to your Yii application, replace your usual:

require_once('/path/to/your/yii/framework/yii.php');

With the following:

new Phar('/path/to/yii.phar');
require_once('phar://yii/yii.php');

Note that in new Phar() invocation you should use real path to your phar archive file, but second line should be written verbatim, as the PHAR which becomes created is being made with alias 'yii', using feature described in the documentation for Phar::__construct.

However, of course, there's a catch 2: Yii built-in asset manager (CAssetManager) has too specific `publish` method, unable to cope with custom PHP streams. So, we need the fixed version.

I decided to create a descendant of `CAssetManager` descriptively called `PharCompatibleAssetManager` with the following definition exactly:

/**
 * Class PharCompatibleAssetManager
 *
 * As we use Yii packaged into .phar archive, we need to make changes into the Asset Manager,
 * according to the https://code.google.com/p/yii/issues/detail?id=3104
 *
 * Details about packaging Yii into .phar archive can be found at
 * https://gist.github.com/mindplay-dk/1607318
 */
class PharCompatibleAssetManager extends CAssetManager
{
  protected $_published = array();

  public function publish($path,$hashByName=false,$level=-1,$forceCopy=null)
  {
    if($forceCopy===null)
      $forceCopy=$this->forceCopy;
    if($forceCopy && $this->linkAssets)
      throw new CException(Yii::t('yii','The "forceCopy" and "linkAssets" cannot be both true.'));
    if(isset($this->_published[$path]))
      return $this->_published[$path];

    $isPhar = strncmp('phar://', $path, 7) === 0;
    $src = $isPhar ? $path : realpath($path);

    if ($isPhar && $this->linkAssets)
    {
      throw new CException(
        Yii::t(
          'yii',
          'The asset "{asset}" cannot be published using symlink, because the file resides in a phar.',
          array('{asset}' => $path)
        )
      );
    }

    if ($src !== false || $isPhar)
    {
      $dir=$this->generatePath($src,$hashByName);
      $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir;
      if(is_file($src))
      {
        $fileName=basename($src);
        $dstFile=$dstDir.DIRECTORY_SEPARATOR.$fileName;

        if(!is_dir($dstDir))
        {
          mkdir($dstDir,$this->newDirMode,true);
          @chmod($dstDir,$this->newDirMode);
        }

        if($this->linkAssets && !is_file($dstFile)) symlink($src,$dstFile);
        elseif(@filemtime($dstFile)<@filemtime($src))
        {
          copy($src,$dstFile);
          @chmod($dstFile,$this->newFileMode);
        }

        return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName";
      }
      elseif(is_dir($src))
      {
        if($this->linkAssets && !is_dir($dstDir))
        {
          symlink($src,$dstDir);
        }
        elseif(!is_dir($dstDir) || $forceCopy)
        {
          CFileHelper::copyDirectory($src,$dstDir,array(
            'exclude'=>$this->excludeFiles,
            'level'=>$level,
            'newDirMode'=>$this->newDirMode,
            'newFileMode'=>$this->newFileMode,
          ));
        }

        return $this->_published[$path]=$this->getBaseUrl().'/'.$dir;
      }
    }
    throw new CException(Yii::t('yii','The asset "{asset}" to be published does not exist.',
      array('{asset}'=>$path)));
  }
}

I'm really, really sorry that you had to read this traditionally horrible Yii code, but that was inevitable... :(

Main change was starting from the $isPhar = strncmp('phar://', $path, 7) === 0; part.

Now just link this asset manager instead of built-in one:

config/main.php:
        'components' => array(
            'assetManager' => array(
                'class' => 'your.alias.path.to.PharCompatibleAssetManager',
            ),
        )

Congratulations!

Now your Yii web application uses phar archive instead of huge pile of separate files. They say that this increases performance, but my personal reasons was just to reduce the number of files inside the repository.

суббота, 15 сентября 2012 г.

Building changelog from Git log output

That's how you get the name of last tag applied to current branch in Git repo:

git describe | grep -Eo '^[^-]+'

We need to meddle with grep because git describe gives us additional info in the form of

LAST_TAG-COMMITS_SINCE_LAST_TAG-gLAST_COMMIT_HASH

Note the funny literal 'g' before the LAST_COMMIT_HASH.

And that's how you get the list of changes since some COMMIT till the current state of the working copy, in really pretty format "ISO Date (Author) Commit text":

git log --no-merges --pretty=format:'%ai: (%an) %s' COMMIT..HEAD

HEAD is literal "HEAD" there. You can substitute COMMIT token with either commit hash or tag.

Now you write the following script and place it in the root of the codebase of your project:

#!/bin/sh

# Get the list of changes made since the last tag

LAST_TAG=`git describe | grep -Eo '^[^-]+'`
git log --no-merges --pretty=format:'%ai: (%an) %s' $LAST_TAG..HEAD

Name it as changelog and then you can do just:

./changelog

And get something like this:

2012-09-14 23:50:43 +0400: (E. T.) Some stuff for ticket 1584. re #1584
2012-09-14 23:45:05 +0400: (A. Y.) Some stuff for ticket 1584. test #1584
2012-09-14 15:44:49 -0400: (A. Y.) Refactored some old stuff
2012-09-14 22:24:04 +0300: (D. M.) Improved tests

And this will be changes only since last tag applied. Excellent for quick reports about current upstream.