# build_dcss_release.rb
#   Builds DOS and Windows binaries for a Stone Soup release.
# Needs rubyzip to be installed.

require 'fileutils'
require 'zip/zipfilesystem'

SVN_BASE_URL    = 'https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/'
SVN_BRANCH      = 'branches/stone_soup-0.3'
SVN_URL         = SVN_BASE_URL + SVN_BRANCH + '/crawl-ref'

# If empty, nothing is done. Useful to sync svk mirrors.
SVN_PRESYNC     = ''

SVN             = 'svn'

BUILDS          = [ Proc.new { build_win32 }, 
                    Proc.new { build_dos } ].reverse

W32MAKE         = 'mingw32-make'
DOSMAKE         = 'make'

UPX             = 'upx'

SVN_CO_DIR      = 'stone_soup'

DEV_NULL        = 'nul'

CLEANPATH       = ENV['PATH']
# Leave empty if svn is already in the path
SVN_PATH_PREFIX = 'C:\etc\misc\svk\bin;C:\etc\misc\svn\bin;'

W32MAKE_PATH_PREFIX = 'C:\etc\MinGW\bin;C:\etc\misc\yacc;'
DOSMAKE_PATH_PREFIX = 'C:\etc\djgpp\bin;'

BUILD_HOME      = File.join( FileUtils.pwd(), 'release' )

# Relative to source directory
CRAWL_NDB_PATH  = 'rel/crawl.exe'
CRAWL_DBG_PATH  = 'dbg/crawl.exe'
CRAWL_DOS_PATH  = 'crawl.exe'

PACKAGE_PATH    = BUILD_HOME

def build_soup
  checkout
  make
  package
end

def setup_svn_env
  ENV['PATH'] = SVN_PATH_PREFIX + CLEANPATH
end

def setup_build_home
  FileUtils.mkdir_p(BUILD_HOME)
  Dir.chdir(BUILD_HOME)
end

def full_checkout
  puts "#{SVN} co #{SVN_URL} #{SVN_CO_DIR}"
  system "#{SVN} co #{SVN_URL} #{SVN_CO_DIR}" or 
          raise "#{SVN} co failed: #$?"
  Dir.chdir SVN_CO_DIR
end

def checkout
  setup_svn_env

  if SVN_PRESYNC and not SVN_PRESYNC.empty?
    puts "Running presync: '#{SVN_PRESYNC}'"
    system(SVN_PRESYNC) or raise "#{SVN_PRESYNC} failed: #$?!"
  end

  setup_build_home

  clean_directory SVN_CO_DIR if File.directory? SVN_CO_DIR
  full_checkout
end

def clean_directory(*dirs)
  dirs.each do |dir|
    currdir = FileUtils.pwd
    if currdir != BUILD_HOME
      raise "In #{currdir}, need to be in #{BUILD_HOME}!"
    end
    raise "Evil directory name: #{dir}" if dir =~ /\.\./

    puts "Trying to remove #{dir}"
    FileUtils.rm_r dir, :verbose => true
  end
end

def path_prefix(prefix)
  ENV['PATH'] = prefix + CLEANPATH unless ENV['PATH'].index(prefix)
end

def clean_objects
  [ '.', 'rel', 'dbg', 'util', 'contrib/lua/src', 'contrib/sqlite' ].each do |dir|
    if File.directory? dir
      FileUtils.rm( Dir[dir + '/*.o'], :force => true )
      FileUtils.rm( Dir[dir + '/*.a'], :force => true )
    end
  end
end

def clean_w32build_area
  clean_objects
  system "#{W32MAKE} -f makefile.mgw clean"
end

def clean_dosbuild_area
  clean_objects
  system "#{DOSMAKE} -f makefile.dos clean"
end

def make
  BUILDS.each do |builder|
    Dir.chdir( File.join(BUILD_HOME, SVN_CO_DIR) )
    builder.call
  end
end

def setup_w32make_env
  Dir.chdir 'source'
  path_prefix W32MAKE_PATH_PREFIX
  clean_w32build_area
end

def setup_dosmake_env
  Dir.chdir 'source'
  path_prefix DOSMAKE_PATH_PREFIX
  clean_dosbuild_area
end

$release_version = nil
def release_version
  if not $release_version
    raise "Can't find version.h" unless File.file? 'version.h'
    IO.readlines('version.h').each do |line|
      if line =~ /VER_NUM\s+"(\d\.\d(?:\.\d)?)/
        $release_version = $1
      end
      if line =~ /VER_QUAL\s+"(.*)"/
        if $1 =~ /-svn/
          raise "Version number has -svn suffix! Remove it to proceed"
        end
      end
    end
  end
  $release_version or raise "Unable to read version at #{FileUtils.pwd}"
end

def build_dos
  setup_dosmake_env

  puts "\nBuilding stone_soup (ndebug) for #{release_version} DOS"
  ENV['LIB'] = "-static -Lcontrib\\lua\\src -llua -lpcre -Lcontrib\\sqlite -lsql3"
  ENV['EXTRA_FLAGS'] = "-O2 -DCLUA_BINDINGS -DREGEX_PCRE -DDEBUG -DWIZARD"

  puts %{ #{DOSMAKE} -e -f makefile.dos DOYACC=y }
  system( %{ #{DOSMAKE} -e -f makefile.dos DOYACC=y } ) or
            raise "#{DOSMAKE} failed: #$?"

  upx CRAWL_DOS_PATH
end

def build_win32
  setup_w32make_env
  
  puts "\nBuilding stone_soup (non-debug) for #{release_version} release!"
  system( %{#{W32MAKE} -f makefile.mgw DOYACC=y "EXTRA_FLAGS=-O2 } +
         %{-DCLUA_BINDINGS -DREGEX_PCRE -DDEBUG -DWIZARD" } +
         %{"LIB=-lwinmm -static -Lcontrib/lua/src -llua -lpcre -Lcontrib/sqlite -lsqlite3"} ) or
            raise "#{W32MAKE} failed: #$?"

  clean_w32build_area

  #puts "\nBuilding stone_soup (debug) for #{release_version}!"
  #system( %{#{W32MAKE} -f makefile.mgw debug DEBUG_CRAWL=y "EXTRA_FLAGS=-O2 } +
  #       %{-DCLUA_BINDINGS -DREGEX_PCRE -DFULLDEBUG -DWIZARD" } +
  #       %{"LIB=-lwinmm -static -llua -lpcre" } +
  #       %{DOYACC=y} ) or
  #          raise "#{W32MAKE} failed: #$?"

  #upx CRAWL_NDB_PATH, CRAWL_DBG_PATH
  upx CRAWL_NDB_PATH
end

def upx(*files)
  return unless UPX and not UPX.empty?
  files.each do |file|
    system "#{UPX} #{file}" or raise "#{UPX} failed: #$?"
  end
end

def makezip(path, name, exe)
  zipname = File.join(path, name) + '.zip'

  puts "Creating zip archive at #{zipname}"
  if File.exists? zipname
    FileUtils.rm(zipname)
  end
  Zip::ZipFile.open( zipname, Zip::ZipFile::CREATE ) do |zip|
    zip.dir.mkdir(name)
    
    zip.dir.chdir name

    # The exe itself
    zip.add 'source/' + exe

    # Add base documentation
    zip.add [ 'CREDITS', 'licence.txt', 'readme.txt' ] + Dir['README*']
    
    # Add base config
    zip.add [ 'init.txt', 'macro.txt' ]

    zip.add( Dir['docs/*'].find_all { |f| not File.directory?(f) },
            :keep_paths => true )

    [ 'lua', 'dat', 'dat/clua', 'dat/descript' ].each do |dir|
      zip.add( Dir['source/' + dir + '/*'], :prefix => dir )
    end
  end
end

def package
  Dir.chdir( File.join(BUILD_HOME, SVN_CO_DIR) )

  # Wipe all existing zips!
  FileUtils.rm( Dir[ File.join(PACKAGE_PATH, '*.zip') ] )

  [ [ "stone_soup-#{release_version}-win32", CRAWL_NDB_PATH ],
    # [ "stone_soup-#{release_version}-win32-debug", CRAWL_DBG_PATH ],
    [ "ss#{release_version.tr '.', ''}dos", CRAWL_DOS_PATH ]
  ].
    each do |pkg, exe|

    makezip(PACKAGE_PATH, pkg, exe)
  end
end

##########################################################################
# Zip extensions

class Zip::ZipFile
  def mkdir_p(dirpath)
    dirpath.tr! '\\', '/'
    segments = File.split(dirpath)
    fullpath = nil
    segments.each do |dir|
      next if dir == '.'
      fullpath = fullpath ? File.join(fullpath, dir) : dir
      self.dir.mkdir fullpath unless self.file.directory? fullpath
    end
  end

  def add(files, options = {})
    files = [ files ] unless files.respond_to? :to_ary
    files.each do |f|
      entryname = options[:keep_paths]? f : File.basename(f)
      entryname = File.join(options[:prefix], entryname) if options[:prefix]

      if File.directory? f
        # We DON'T add the contents of the directory automagically
        self.mkdir_p entryname
      else
        dirname = File.dirname(entryname)
        self.mkdir_p(dirname) unless dirname == '.'

        self.file.open(entryname, 'w') do |outf|
          File.open(f, 'rb') do |inf|
            outf.write( inf.read )
          end
        end
      end
    end
  end
end

build_soup