Showing posts with label Composer. Show all posts
Showing posts with label Composer. Show all posts

Monday, 15 January 2018

Documenting Composer scripts

For open source projects I'm involved with, I developed the habit to define, and document the steady growing amount of repository and build utilities via Composer scripts. Having Composer scripts available makes it trivial to define aliases or shortcuts for complex and hard to remember CLI calls. It also lowers the barrier for contributors to start using these tools while helping out with fixing bugs or providing new features. Finally they're also simplifying build scripts by stashing away complexity.

Defining Composer scripts

If you've already defined or worked with Composer scripts or even their npm equivalents you can skip this section, otherwise the next code snippet allows you to study how to define these. The here defined Composer scripts range from simple CLI commands with set options (e.g. the test-with-coverage script) to more complex build utility tools (i.e. the application-version-guard script) which are extracted into specific CLI commands to avoid cluttering up the composer.json or even the .travis.yml.

composer.json
{
  "scripts": {
    "test": "phpunit",
    "test-with-coverage": "phpunit --coverage-html coverage-reports",
    "cs-fix": "php-cs-fixer fix . -vv || true",
    "cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run",
    "configure-commit-template": "git config --add commit.template .gitmessage",
    "application-version-guard": "php bin/application-version --verify-tag-match"
  }
}

Describing Composer scripts

Since Composer 1.6.0 it's possible to set custom script descriptions via the scripts-descriptions element like shown next. It's to point out here that the name of a description has to match the name of a defined custom Composer script to be recognised at runtime. On another note it's to mention that the description should be worded in simple present to align with the other Composer command descriptions.

composer.json
{
  "scripts-descriptions": {
    "test": "Runs all tests.",
    "test-with-coverage": "Runs all tests and measures code coverage.",
    "cs-fix": "Fixes coding standard violations.",
    "cs-lint": "Checks for coding standard violations.",
    "configure-commit-template": "Configures a local commit message template.",
    "application-version-guard": "Checks that the application version matches the given Git tag."
  },
  "scripts": {
    "test": "phpunit",
    "test-with-coverage": "phpunit --coverage-html coverage-reports",
    "cs-fix": "php-cs-fixer fix . -vv || true",
    "cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run",
    "configure-commit-template": "git config --add commit.template .gitmessage",
    "application-version-guard": "php bin/application-version --verify-tag-match"
  }
}
Now when running $ composer via the terminal the descriptions of defined custom scripts will show up sorted in into the list of available commands, which makes it very hard to spot the Composer scripts of the package at hand. Luckily Composer scripts can also be namespaced.

Namespacing Composer scripts

To namespace (i.e. some-namespace) the custom Composer scripts for any given package define the script names with a namespace prefix as shown next. As the chances are very high that you will be using the one or other Composer script several times, while working on the package, it's recommended to use a short namespace like in the range from two to four characters.

composer.json
{
  "scripts": {
    "some-namespace:test": "phpunit",
    "some-namespace:test-with-coverage": "phpunit --coverage-html coverage-reports",
    "some-namespace:cs-fix": "php-cs-fixer fix . -vv || true",
    "some-namespace:cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run",
    "some-namespace:configure-commit-template": "git config --add commit.template .gitmessage",
    "some-namespace:application-version-guard": "php bin/application-version --verify-tag-match"
  }
}
Now this time when running $ composer via the terminal the defined custom scripts will show up in the list of available commands in a namespaced manner giving an immediate overview of the available Composer script of the package at hand.

$ composer
 ... ommitted content
Available commands:
  ... ommitted content
 some-namespace
  some-namespace:application-version-guard  Checks that the application version matches the given Git tag.
  some-namespace:configure-commit-template  Configures a local commit message template.
  some-namespace:cs-fix                     Fixes coding standard violations.
  some-namespace:cs-lint                    Checks for coding standard violations.
  some-namespace:test                       Runs all tests.
  some-namespace:test-with-coverage         Runs all tests and measures code coverage.
To use any namespaced Composer script, e.g. to fix coding standard violations after a substantial refactoring, it has to be called with its namespace e.g.$ composer some-namespace:cs-fix, which is the one disadavantage of Composer script namespacing.

Monday, 10 October 2016

Eight knobs to adjust and improve your Travis CI builds

After having refactored several Travis CI configuration files over the last weeks, this post will provide eight adjustments or patterns immediately applicable for faster, changeable, and economic builds.

1. Reduce git clone depth

The first one is a simple configuration addition with a positive impact on the time and disk space consumption, which should be quite noticeable on larger code bases. Having this configured will enable shallow clones of the Git repository and reduce the clone depth from 50 to 2.

.travis.yml
git:
  depth: 2

2. Enable caching

The second one is also a simple configuration addition for caching the Composer dependencies of the system under build (SUB) or its result of static code analysis. Generally have a look if your used tools allow caching and if so cache away. This one deserves a shout out to @localheinz for teaching me about this one.

The next shown configuration excerpt assumes that you lint coding standard compliance with the PHP Coding Standards Fixer in version 2.0.0-alpha and have enable caching in its .php_cs configuration.

.travis.yml
cache:
  directories:
    - $HOME/.composer/cache
    - $HOME/.php-cs-fixer

3. Enforce contribution standards

This one might be a tad controversial, but after having had the joys of merging GitHub pull requests from a master branch I started to fail builds not coming from feature or topic branch with the next shown bash script. It's residing in an external bash script to avoid the risk of terminating the build process.

./bin/travis/fail-non-feature-topic-branch-pull-request
#!/bin/bash
set -e
if [[ $TRAVIS_PULL_REQUEST_BRANCH = master ]]; then
  echo "Please open pull request from a feature / topic branch.";
  exit 1;
fi

.travis.yml
script:
  - ./bin/travis/fail-non-feature-topic-branch-pull-request
The bash script could be extended to fail pull requests not following a branch naming scheme, e.g. feature- for feature additions or fix- for bug fixes, by evaluating the branch name. If this is a requirement for your builds you should also look into the blocklisting branches feature of Travis CI.

4. Configure PHP versions in an include

With configuring the PHP versions to build against in a matrix include it's much easier to inject enviroment variables and therewith configure the version specific build steps. You can even set multiple enviroment variables like done on the 7.0 version.

.travis.yml
env:
  global:
    - OPCODE_CACHE=apc

matrix:
  include:
    - php: hhvm
    - php: nightly
    - php: 7.1
    - php: 7.0
      env: DISABLE_XDEBUG=true LINT=true
    - php: 5.6
      env: 
      - DISABLE_XDEBUG=true

before_script:
  - if [[ $DISABLE_XDEBUG = true ]]; then
      phpenv config-rm xdebug.ini;
    fi
I don't know if enviroment variable injection is also possible with the minimalistic way to define the PHP versions list, so you should take that adjustment with a grain of salt.

It also seems like I stumbled upon a Travis CI bug where the global enviroment variable OPCODE_CACHE is lost, so add another grain of salt. To work around that possible bug the relevant configuration has to look like this, which sadly adds some duplication and might be unsuitable when dealing with a large amount of environment variables.

.travis.yml
matrix:
  include:
    - php: hhvm
      env: 
      - OPCODE_CACHE=apc
    - php: nightly
      env: 
      - OPCODE_CACHE=apc
    - php: 7.1
      env: 
      - OPCODE_CACHE=apc
    - php: 7.0
      env: OPCODE_CACHE=apc DISABLE_XDEBUG=true LINT=true
    - php: 5.6
      env: OPCODE_CACHE=apc DISABLE_XDEBUG=true

before_script:
  - if [[ $DISABLE_XDEBUG = true ]]; then
      phpenv config-rm xdebug.ini;
    fi

5. Only do static code analysis or code coverage measurement once

This one is for reducing the build duration and load by avoiding unnecessary build step repetition. It's achived by linting against coding standard violations or generating the code coverage for just a single PHP version per build, in most cases it will be the same for 5.6 or 7.0.

.travis.yml
matrix:
  include:
    - php: hhvm
    - php: nightly
    - php: 7.1
    - php: 7.0
      env: DISABLE_XDEBUG=true LINT=true
    - php: 5.6
      env: 
      - DISABLE_XDEBUG=true

script:
  - if [[ $LINT=true ]]; then
      composer cs-lint;
      composer test-test-with-coverage;
    fi

6. Only do release releated analysis and checks on tagged builds

This one is also for reducing the build duration and load by targeting the analysis and checks on tagged release builds. For example if you want to ensure that your CLI binary version, the one produced via the --version option, matches the Git repository version tag run this check only on tagged builds.

.travis.yml
script:
  - if [[ ! -z "$TRAVIS_TAG" ]]; then
      composer application-version-guard;
    fi

7. Run integration tests on very xth build

For catching breaking changes in interfaces or API's beyond your control it makes sense do run integration tests against them once in a while, but not on every single build, like shown in the next Travis CI configuration excerpt which runs the integration tests on every 50th build.

.travis.yml
script:
  - if [[ $(( $TRAVIS_BUILD_NUMBER % 50 )) = 0 ]]; then
      composer test-all;
    else
      composer test;
    fi

8. Utilise Composer scripts

The last one is all about improving the readability of the Travis CI configuration by extracting command configurations i.e. options into dedicated Composer scripts. This way the commands are also available during your development activitives and not hidden away in the .travis.yml file.

composer.json
{
    "__comment": "omitted other configuration",
    "scripts": {
        "test": "phpunit",
        "test-with-coverage": "phpunit --coverage-html coverage-reports",
        "cs-fix": "php-cs-fixer fix . -vv || true",
        "cs-lint": "php-cs-fixer fix --diff --verbose --dry-run"
    }
}
To ensure you don't end up with an invalid Travis CI configuration, which might be accidently committed, you can use composer-travis-lint a simple Composer script linting the .travis.yml with the help of the Travis CI API.

Happy refactoring.