Skip to content

Instantly share code, notes, and snippets.

@shoover
Last active August 29, 2015 14:19
Show Gist options
  • Save shoover/f2dc72737d043be56861 to your computer and use it in GitHub Desktop.
Save shoover/f2dc72737d043be56861 to your computer and use it in GitHub Desktop.
A hacky C# script compiler runner
# C# script compiler and runner
#
# Hash headers at the top can be used to override default csc options. This is
# mainly for specifying references, but you can pass anything csc.exe takes.
# Extra plain .cs files should work if you put them last.
#
# References are tricky because practically they have to be in the application
# directory, which we stash away in HOME/.csx/something. Any reference csx can
# find (relative to the working directory) it will copy, which should work.
#
# Usage:
# > ruby csx.rb filechecker.csx test.txt # test.txt false
#
# Example:
# # /r:liby.dll
# # /r:redist/libx.dll
# using System.IO;
# public static class FileChecker
# {
# public static void Main(string[] argv)
# {
# Console.WriteLine("{0}: {1}", argv[0], File.Exists(argv[0]));
# }
# }
#
# For additional CLI calling convenience, edit csx_auto_file.reg with your paths.
# Then you can invoke scripts on the command line: `filechecker.csx test.txt`.
#
# Inspired by Lawrence's java_launcher:
# https://github.com/lkesteloot/java_launcher/blob/master/java_launcher
require 'fileutils'
include FileUtils
require 'digest'
cache = File.join(Dir.home, '.csx')
mkdir_p cache
script = ARGV.shift
script_base = File.basename(script, '.*')
script_dir = File.dirname(script)
# Use the hash of the .csx script and this script to determine if we
# need to recompile.
sum = Digest::MD5.hexdigest(File.read(script) + File.read(__FILE__))[0...8]
bin = File.join(cache, script_base)
src_base = script_base + '.cs'
src = File.join(bin, src_base)
exe = File.join(bin, script_base + '.exe')
cached_sum = bin + '/sum'
def dirty_refs?(refs, bin)
refs.any? do |ref|
target = File.join(bin, File.basename(ref))
File.exists?(ref) && !File.exists?(target) || File.mtime(ref) != File.mtime(target)
end
end
# Options for locating referenced assemblies:
# - copy references to the cache directory and do dirty checking
# - strong-named references only
# - find in the GAC
# - specify in codeBase config
# - store the exe in the script dir and require the user to reference DLLs that
# live there
#
# For now, we copy DLLs and dirty check.
refs = []
File.open script, 'r' do |fin|
# Process references so we can check if any DLLs changed.
while (line = fin.gets.chomp) =~ /^# /
refs << $1 if line =~ /^# \/r(?:eference)?:(.*)/ and File.exists?($1)
end
end
if !File.exists?(exe) or sum != File.read(cached_sum) or dirty_refs?(refs, bin)
mkdir_p bin
cp script, bin
File.write(cached_sum, sum)
args = []
File.open script, 'r' do |fin|
File.open src, 'w' do |fout|
# Process headers by appending them as csc command line parameters.
# References are processed separately so we can locate files with
# relative paths but copy to the bin directory with a flat structure.
while (line = fin.gets.chomp) =~ /^# /
arg = $'
args << arg unless line =~ /^# \/r(?:eference)?:(.*)/ and File.exists?($1)
end
# Copy the code to a .cs file.
fout.puts line
while (line = fin.gets) != nil
fout.puts line
end
end
end
# Copy any DLLs referenced
rm Dir["#{bin}/*.dll"]
refs.each do |ref|
cp ref, bin, :preserve => true
end
# Compile
Dir.chdir bin do
# /lib:#{script_dir}
ref_bases = refs.collect{|r|'/r:'+File.basename(r)}.join(' ')
cmd = "csc #{ref_bases} /nologo /t:exe #{args.join(' ')} #{src_base}"
puts cmd
system cmd or fail "csx compilation failed"
end
puts
$stdout.flush
end
# Run it
system "#{exe} #{ARGV.join(' ')}"
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\csx_auto_file]
@=""
[HKEY_CLASSES_ROOT\csx_auto_file\shell]
[HKEY_CLASSES_ROOT\csx_auto_file\shell\open]
[HKEY_CLASSES_ROOT\csx_auto_file\shell\open\command]
@="c:\\ruby193\\bin\\ruby.exe c:\\usr\\local\\script\\csx.rb \"%1\" %*"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment