Https

Now that Let's Encrypt is live, the site is now accessible over https. The only thing of note is that you need to reference site local assets using:

<a href='/some/protocol/free/path'>some internal link</a>

Otherwise the site isn't entirely secured by https.

Powershell hr

I saw this on reddit (or possibly hackernews) the other day that produces a simple banner line in bash.

So here's a powershell version:

function hr($txt)
{
  $ui = (Get-Host).UI.RawUI
  $width = $ui.WindowSize.width
  $n = ($width / $txt.Length) -1
  $bar = "$txt" *  $n
  echo $bar
}

Year in Review

An extremely busy and quite interesting year for all the right reasons:

  • Played around with a few new languages: go, ruby, D, Java.
  • Lines of C++ and Python written: lost count!
  • Some Android and OS X development.

Go is an enjoyable little language with lightweight and simple concurrency, great library support and fast compilations times. Coming from a C++ background, building the entire Go compiler suite from source in seconds is excellent.

Pycharm has been a revelation for Python development. Debugging, testing and framework support all built in.

Python virtual envs

A simple function to bootstrap Python virtualenvs in Powershell. Add it to your profile and then call it as:

create_virtualenv c:\temp\myvirtualenv

Script is here:

function create_virtualenv([string] $virtualenv_path)
{
    #  I assume that python and 7z are on your path.
    $python = "python.exe"
    $z = "7z.exe"
    $temp_dir="C:\temp"

    if (-not(Test-Path $temp_dir)) {
        mkdir $temp_dir
    }

    #  For some reason 7zip doesn't like the path that comes from $env:TEMP
    $downloaded_archive = $temp_dir + "\virtualenv.tar.gz"
    $temp_virtualenv = $temp_dir + "\temporary_virtualenv"
    $url = "https://github.com/pypa/virtualenv/tarball/develop"

    #  Get virtual env
    Write-Host -fore green "Downloading virtual env from $url"
    $client = new-object System.Net.WebClient
    $client.DownloadFile($url, $downloaded_archive)

    #  Unzip it
    Write-Host -fore green "Unzipping $downloaded_archive"
    & "$z" x -y -o$temp_dir $downloaded_archive > $null

    #  Extract tar
    $tarfile = $downloaded_archive -replace '.gz', ''
    if (-not(Test-Path $tarfile)) {
        Write-Host -fore red "Failed to extract to $tarfile"
        return 1
    }
    Write-Host -fore green "Extacting from tarball $tarfile into $temp_virtualenv"
    $output = "-o" + $temp_virtualenv
    & "$z" x -y $tarfile $output > $null

    if (-not(Test-Path $temp_virtualenv)) {
        Write-Host -fore red "Failed to extract to $temp_virtualenv"
        return 1
    }

    #  Find the virtual env python script in the temporary virtual env we downloaded
    $search_for = "virtualenv.py"
    $virtualenv = (Get-ChildItem -Path $temp_virtualenv -Filter $search_for -Recurse).FullName

    if (-not(Test-Path $virtualenv)) {
        Write-Host -fore red "Could not find the virtual env script $virtualenv"
        return
    }

    Write-Host -fore green "Creating virtual env $virtualenv_path"
    if (Test-Path $virtualenv_path) {
        Write-Host -fore yellow "The path $virtualenv_path already exists, deleting it"
        Remove-Item -Force -Recurse $virtualenv_path
    }
    & $python $virtualenv $virtualenv_path

    #  Clean up the temporary virtual env
    Write-Host -fore green "Cleaning up"
    Remove-Item -Force -Recurse $temp_virtualenv
    Write-Host -fore green "Finished - now run the activate script in $virtualenv_path\Scripts"
}

A Basic Rakefile

I don't like spending too much time setting up a new automated build, and rakefiles are an effective way to capture a lot of the boring repetition. Also, in the spirit of "don't repeat yourself", I have a rakefile that that I have been gradually building over the past few months. For my purposes, it now just requires a single edit to use in a new project.

It is now at the point where, if you stick with the fairly standard Visual Studio project layout (this is the only implicit hidden logic in the script), all I have to do each time is change the name of the solution I want to build, and drop the solution at the top of my folder tree.

The file can be downloaded from here.

The rakefile provides:

  • nuget package restore via rake setup
  • nuget self updating via rake update_nuget
  • automatic test location, and running via xunit (end your test names as .Tests.dll)
  • debug and release builds.
  • adding the git hash to assemblies.

The task list:

rake all                # Clean, rebuild and test entire build
rake build              # Build everything
rake clean              # Clean entire build
rake debug:build        # Build
rake debug:clean        # Clean
rake debug:tests        # Run tests
rake default            # Default is to build everything
rake release:build      # Build
rake release:clean      # Clean
rake release:tests      # Run tests
rake restore_packages   # Restore nuget packages
rake restore_xunit      # Restore xunit from nuget
rake setup              # Set up everything if checked out for the first time
rake tests              # Run all tests
rake update_nuget       # Update nuget
rake version            # Add git hash to assemblies

Currently, I am not a big fan of dependencies, especially since in this case, rake setup will go to nuget.org repeateedly, I do not need it to run every time I type rake tests.

On the other hand, tests require builds, so that is added as a task dependency.

I have chosen to use namespaces to nest the builds, with the build type as the outer namespace, rather than the reverse, i.e. rake debug:build rather than rake build:debug. This tends to lead to a more discoverable hierarchy of tasks.

In order to use it, change:

SOLUTION=File.join(ROOT, 'MY_SOLUTION_HERE.sln' )

and then:

rake setup
rake all

and you're done. In fact these are the only two steps you need to add into your automated build system, and being able to repeat your automated build on your developer PC is "A Good Thing".

Rake and albacore tasks are fairly boilerplate, so the resulting rakefile is extremely simple. The file in all its glory:

#  Call
#
#    rake setup
#
#  to restore nuget packages, and grab xunit

require 'albacore'
require 'find'

################################
#  Helper methods
################################

#  Find any tests that end with '.tests.dll' for the given build type
#  assuming that is built into bin/<build_type>
def find_tests(root, build_type)
    paths = []
    re = Regexp.new(".*/bin/#{build_type}.*Tests\.dll$", Regexp::IGNORECASE)
    Find.find(root) do |path|
        paths << path if path =~ re
    end
    return paths
end

################################
#  Variables/configuration
################################

#  Define the root relative to this file.
ROOT=File.expand_path('.', File.dirname(__FILE__))

PACKAGES_DIR=File.join(ROOT, 'packages')

#  What we're building
SOLUTION=File.join(ROOT, 'MY_SOLUTION_HERE.sln' )

#  Nuget binary
NUGET_EXE = File.join(ROOT, '.nuget/nuget.exe')

#  Nuget args to get XUnit
NUGET_XUNIT_PARAMS = "install xunit.runners -Version 1.9.1 -OutputDirectory " + PACKAGES_DIR

#  Path to xunit (gets installed by nuget)
XUNIT_EXE = File.join(PACKAGES_DIR, "xunit.runners.1.9.1/tools/xunit.console.clr4.x86.exe")

#  Path to xunit tests - auto discovered by convention
XUNIT_DEBUG_TESTS = find_tests(ROOT, 'Debug')
XUNIT_RELEASE_TESTS = find_tests(ROOT, 'Release')

#  Multi-cpu compile, no logo, low verbosity
COMPILER_SWITCHES = {
    'M' => true,
    'verbosity' => 'quiet',
    'nologo' => true
}

################################
#  Common top-most tasks.
################################


#  Relative to this file
desc 'Restore nuget packages'
task :restore_packages do
    FileList["**/packages.config"].each do |filepath|
        sh "#{NUGET_EXE} install #{filepath} -OutputDirectory #{PACKAGES_DIR}"
    end
end


desc "Restore xunit from nuget"
exec :restore_xunit do |cmd|
    cmd.command = NUGET_EXE
    cmd.parameters = NUGET_XUNIT_PARAMS
end

desc "Update nuget"
exec :update_nuget do |cmd|
    cmd.command = NUGET_EXE
    cmd.parameters = "update -Self"
end


desc 'Set up everything if checked out for the first time'
task :setup => [:restore_xunit , :restore_packages]


desc 'Clean entire build'
task :clean => ["release:clean", "debug:clean"]


desc 'Run all tests'
task :tests => ["debug:tests", "release:tests"]


desc 'Build everything'
task :build => ["debug:build", "release:build"]


desc 'Clean, rebuild and test entire build'
task :all => [:clean, :build, :tests]


desc 'Add git hash to assemblies'
task :version do |t|
    clean_clone = `git diff --shortstat #{ROOT}`.empty?

    if clean_clone
        abbrev_commit = `git log -1 --pretty="%h" #{ROOT}`.delete("\n")
        assemblies = []
        Find.find('.') do |path|
            assemblies << File.expand_path(path) if path =~ /AssemblyInfo\.cs$/
        end

        assemblies.each do |assembly|
            text = File.read(assembly)
            File.open(assembly, 'w') { |f|  f.write(text.gsub(/development\-version/, abbrev_commit)) }
        end
    else
        puts 'You have working copy changes'
    end
end


desc 'Default is to build everything'
task :default => :all


################################
#  Debug tasks
################################


namespace 'debug' do
    desc 'Build'
    msbuild :build do |msb|
        msb.properties = { :configuration => :Debug }
        msb.targets = [ :Build ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Clean'
    msbuild :clean do |msb|
        msb.properties = { :configuration => :Debug }
        msb.targets = [ :Clean ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Run tests'
    xunit :tests => "debug:build" do |xunit|
        xunit.command = XUNIT_EXE
        xunit.assemblies = XUNIT_DEBUG_TESTS
    end
end


################################
#  Release tasks
################################


namespace 'release' do
    desc 'Build'
    msbuild :build do |msb|
        msb.properties = { :configuration => :Release, :platform => 'Any CPU' }
        msb.targets = [ :Build ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Clean'
    msbuild :clean do |msb|
        msb.properties = { :configuration => :Release, :platform => 'Any CPU' }
        msb.targets = [ :Clean ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Run tests'
    xunit :tests => "release:build" do |xunit|
        xunit.command = XUNIT_EXE
        xunit.assemblies = XUNIT_RELEASE_TESTS
    end
end

If you have never used rakefiles before, simply download the latest version of Ruby, followed by gem install albacore in a command prompt, and that is it.