#! /usr/bin/env tclsh
# -*- tcl -*- 
# <20240322.1925.11>


# This file first checks that the change log is up to date
# if so, it builds the release change log
# and builds a version file for the other build tools to 
# use in naming the components.
#
proc Help {} {
  foreach ln [split {
    # This is the filerunner 'makerelease' control
    # file.  Here is a summery of what it does.
    #
    # 0.) Scans all the files to find the latest update
    #     to build the frVersion file. While doing this
    #     build a list of files to exclude from non-windows
    #     versions. Checks availability of programs
    #     required to make the release.
    #
    # 1.) Verifies that the 'HISTORY' file rev code
    #     matches the mains (fr) rev code.
    #     This first updates the version file by
    #     scanning all product files for the latest
    #     updates.
    # 2.) calls './tip_index.tcl to index the
    #     'Tips.txt' file.
    # 3.) calls './buildtclfilelist' start building 
    #     the 'warpfilelist.txt' which controls the
    #     the building of the *.exe files.
    # 4.) calls './makepackagelinks.tcl' to finish
    #     building 'wrapfilelist.txt' by fixing the
    #     package references for the wrap code.
    # 5.) calls './wrapwindows' to build the two
    #     '*.exe' wrap files (32 & 64 bit versions).
    # 6.) calls './pack' to build the tar release file.
    # 7.) only on linux, calls './buildRPM.tcl' to build
    #     the RPM and DEB files.
    # 8.) Moves the RPM and DEB files to the release directory.
    #
    # 9.) Builds the 'msi' version for Windows.
    #
    # If command line contains ver* stop after step 1.
    # If command line contains win* stop after step 5.
    # If command line contains *help* print this massage and stop.
    # If command line contains *v * set verbose mode (mostly for RPM build)
  } \n] {
    puts "[string trim $ln]"
  }
}

if {[string match -nocase *help* $argv]} {Help;exit}

########################################################
#     This is the boiler plate code                    #
########################################################
# This bit of code figures out where the rest of the   #  
# routines are on the assumption that they are in the  #
# same directory as the initial code file.             #
# If 'setIt' is 1 or not coded auto_path is set in any #
# case the resulting dir is returned to the caller     #
# A second dir is also returned, which is the same     #
# with a leading C:/ removed as required by Wrap code  # 
# for windows. If not windows the two will be the same.#
#                                                      #
# To function correctly this code MUST be called prior #
# to completion of the 'source' command that brings it #
# in. Also, since it is used to set up auto_path it    #
# can not be auto loaded.  It may be sourced, but      #
# again the from where issue is there.                 #
#                                                      #
# It is best if this is just merger with the using     #
# code in a location prior to its call.                #
#                                                      #
proc setAutoPath {{setIt 1}} {
  set it [info script]
  set it [expr {$it == "" ? "[pwd]/*" : $it}]
  set it [file dir [file dir [file norm $it/*]]]
  # Wrap code requires we not have the drive letter...
  if {$setIt} {
    lappend ::auto_path $it [regsub {^[a-zA-Z]:/} $it {/}]
  }
  return [list $it [regsub {^[a-zA-Z]:/} $it {/}]]
}
##########################################################
#            End of boiler plate code                    #
##########################################################

# set up our auto_path stuff
setAutoPath
lappend ::auto_path [file norm ../packages] [file norm ../packagesStd]

# we assume fr (the main) is passed and therefor open.
# This code is to build the filerunner release.  We assume
# we are running in the .../filerunner/Makefiles dir.
# We use the following files explicitly to verify that the 
# version of the History matches the filerunner version
# and to build the change log:
#

set history "doc/HISTORY"
set mkDir [pwd]

proc fileError {file err} {
  puts "Error accessing file \"$file\": $err"
  exit 1
}

# this is basically an "in" operater that takes patterns in
# the list.

proc In {this that} {
  foreach pat $that {
    if {[string match $pat $this]} {return 1}
  }
  return 0
}

proc upDateVersion {files exclued} {
  global notInRpm
  # This function takes a list of files and figures the latest
  # version. This is found by looking at the version of each file.
  # This will be either an emacs time string within
  # the first 900 characters of the file or the mtime of the file.
  #
  # We exclude files in the exclude list (mainly those we build here)
  # and also dirs found in the list.
  set complained 0
  set latest 0
  set lastFile {}
  set exc {}
  foreach file $files {
    if {$file == {}} {continue}
    if {![file exists $file]} {
      if {!$complained} {
	puts "The following file(s) do not exist"
	incr complained
      }
      puts "$file"
    }
    
      
    if {[file isdir $file] || [In $file  $exclued]} {
      lappend exc [list [expr {$file in $exclued ? "Exc" : "Dir"}] $file]
      continue
    }
    set ffid [open $file r]
    set lines [split [read $ffid] \n]
    close $ffid
    set tm {}
    # puts "$file"
    set lc 0
    foreach fdata $lines {
      if {[regexp {.*<([0-9]{8}\.[0-9]{4}\.[0-9]{2})>.*} $fdata dum tm] ||\
	      [incr lc] > 25} {break}
    }
    if {$tm == {}} {
      # no time string...
      set tmm [file mtime $file]
      # puts "clock  $tmm $file"
    } else {
      # puts "convert $tm $file"
      set tmm [clock scan $tm -format "%Y%m%d.%H%M.%S"]
      # puts "tstamp $tmm $file $tm $dum $lc"
    }
    # puts "$tmm $file"
    if {$tmm > $latest} {
      # puts "$tmm $file replaces $latest $lastFile $tm" 
      set latest $tmm
      set lastFile $file
    }
  }
  if {0} {
    puts "Excluded from time check"
    foreach en $exc {
      puts "$en"
    }
  }

  return [list [clock format $latest -format "%Y%m%d.%H%M.%S"] $lastFile $complained] 
}

# setPermissions makes sure each component has the right permissions for
# For most files the permession will be rw-rw-rw
# For executables we add 'x'. Since we know where we are, *.tcl & *.bit are NOT
# exectuable (except for fr.tcl) unless in the 'Makefiles' directory.
# So here is the rule: Only *.tcl and files with no extension might be
# executable and *.tcl only in Makefiles. Other file will be set
# executable if they start with '#!/'
#

proc setPermissions {fileList {reportErrors 0}} {
  # For chmod and sleep...
  if {$::tcl_platform(platform) eq "windows"} {return 0}
  # the currend pwd is .../filerunner
  # names in wrapfilelist.txt are relative to this path
  # We have a chicken/egg problem here in that we need to
  # have correct permissions to do some work before we
  # actually make the files we want to set. So this is
  # called twice. We return a fail count if a file is missing.
  # On the final call a non-zero return is BAD!
  set failCount 0
  set notInRpm {}
  puts "failCount $failCount"
  set ::executableFiles {}
  set ::nonExecutableFiles {}
  
  foreach target $fileList {
    if {$target == {} || [string index [set target [string trim $target]] 0] == "#" } {
      continue
    }
    set target [string trim [regsub {(^\+)} $target {}]]
    # puts "target is $target"
    if {![file exists $target]} {
      incr failCount
      if {$reportErrors} {
	puts "File $target is missing. Pwd is [pwd]"
      }
      continue
    }
    if {[file isdir $target]} {continue}
    set mode "0664"
    # if {$target == "fr.tcl" ||\
    # 	    [file dir $target] == "Makefiles" ||\
    # 	    [file extension $target] == {}} {
      # Possible executable
      set test [string trim [read [set fidt [open $target r]] 10]]
      # puts "possible exc $target $test"
      close $fidt
      if {[string index $test 0] == "#" &&\
	      [string index \
		   [set p1 [string trim [string range $test 1 end]]] 0] == "!" &&\
	      [string index [string trim [string range $p1 1 end]] 0] == "/"} {
	# puts "found executable $target: $test"
	set mode 0775
	lappend ::executableFiles $target
      }
    # }
    if {$mode == "0664"} {
      lappend ::nonExecutableFiles $target
      # puts "nonEx file: $target"
    }
    chmod $mode $target
    # puts "mode $mode set on $target"
  }
  return $failCount
}
# This little stub makes it possible to run tcl/wish scripts in
# windows, given just the script...Using the same tools...

if {$tcl_platform(platform) eq "windows"} {
  lappend auto_path [file norm ../packages/mswindows]
  set MSW 1
} else {
  set MSW 0
}
set verbose [string match {*v *} $argv]
lappend auto_path [file norm ../]

proc uScriptInWindows {cmd} {
  if {!$::MSW} {return $cmd}
  return [fixMSWcommand $cmd {} -fonly 1]
}

# MSW, in particular, seems to be a little slow on allowing recent files
# to be renamed or deleted...

proc wait_for {cmd} {
  set failsafe 0
  while {[catch $cmd] != 0 && [incr failsafe] < 10} {
    if {[catch {sleep $failsafe}] != 0} {
      after ${failsafe}000
    }
  }
  if {$failsafe == 10} {
    puts "Command \"$cmd\" failed 10 times."
    puts "Stop the music."
    exit 1
  }
  puts "failsafe=$failsafe"
  return
}

proc try_this {what {OK no}} {
  set whatExpand [uScriptInWindows $what]
  puts "command is ($whatExpand)"
  set r [catch {exec -ignorestderr -keepnewline {*}$whatExpand} out]
  # puts "$out\n$::errorInfo"
  puts "$out"
  if {$r == 0} {return}

  puts "Error trying to execute: $what "
  puts "returned completion code: \"$r\""
  if {$OK == "no"} {
    puts  "Stop the music."
    exit 1
  } else {
    puts "continuing with out doing [lindex $what 0]."
  }
}
# Make sure we have the needed resources. Some of these stop the music
# Others we only need if doing certain builds.
#

set requir {
  tar 1
  "The \"tar\" utility is required."
  Tclx 0
  "The build requires the tcl package tclx"
  {rpmbuild fakeroot alien} 2 
  "The programs \"rpmbuild\" \"alien\" and \"fakeroot\" are required to\
   \nbuild the *.rpm files and the *.deb file. These will be skipped."
  {candle.exe light.exe} 4
  "The Wix programs \"candle.exe\" and \"light.exe\" are required to\
   \nbuild the *.msi file."
}
set stopM 0
foreach {what key com} $requir {
  if {$key == 0} {
    if {[catch {package require Tclx} er] != 0} {
      lappend missingParts $com
      set stopM [expr {$stopM | 1}]
    }
  } else {
    foreach pgr $what {
      if {[auto_execok $pgr] == {}} {
        lappend missingParts $com
        set stopM [expr {$stopM | $key}]
         break
      }
    }
  }
}
if {$stopM != 0} {
  puts "Required programs or tcl packages are missing as follows:"
  foreach mes $missingParts {
    puts \n$mes
  }
  if {$stopM & 1} {
    puts "\"tar\" and the tcl package \"tclx\" are required to continue."
    puts "bye"
    exit 1
  } else {
    puts "The build will continue skipping the parts that require missing programs."
  }
}
#
cd ..
puts "pwd is [pwd]"
setPermissions [split [read [set pid [open Makefiles/extras r]]] \n]
close $pid
cd $mkDir 
puts -nonewline "Indexing the Tip file... "
#return
try_this "./tip_index.tcl ../doc/Tips.txt"
puts "Building 'wrapfilelist.txt' ... "
try_this "./buildtclfilelist -c wraplistControl -o wrapfilelist.txt"
# now the Tarlist
puts "Building 'tarfilelist.txt' ... "
try_this "./buildtclfilelist -c extras -o tarfilelist.txt"

# At this point we have assembled a complete list of all the files
# in the release.

# We can now build the version file.
# First the exclude list:

lappend exclude \
    Makefiles/exclude \
    Makefiles/wrapfilelist.txt\
    Makefiles/tarfilelist.txt\
    frVersion.tcl \
    doc/HISTORY \
    doc/Tips.txt\
    */packages\
    */help/*\
    *tclIndex \
    */pkgIndex.tcl\
    Makefiles/makerelease\
    packageLinks.tcl \
    */config?.tcl\
    Makefiles/tip_index.tcl \
    Makefiles/tcl-inotify-1.4.1.tar.gz

# Now the all file list.  This will be all files listed in
# "tarfilelist.txt"
set allFiles [split [read [set tarpid [open $mkDir/tarfilelist.txt r]]] \n]
if {[lindex $allFiles end] == {}} {
  set allFiles [lrange $allFiles 0 end-1]
  # puts "Trimmed empty record from tarFiles"
}
close $tarpid
cd ..

# set cmd [list cat $mkDir/wrapfilelist.txt | $mkDir/expand.tcl]
# # set r [exec {*}$cmd]
# set cmd2 [list cat $mkDir/extras | $mkDir/expand.tcl]

# # eliminate dirs...
# set f1 [split [exec {*}$cmd] \n]
# set f2 [split [exec {*}$cmd2] \n]
# # puts "$f1"
# # puts "$f2"
# #exit
# foreach file [concat $f1 $f2] {
#   if {[file isdir $file]} {continue}
#   lappend nFileList $file
# }

lassign [upDateVersion $allFiles $exclude] revCode file err

if {$err} {
  puts "Stop the music."
  exit 1
}

set fid [open frVersion.tcl w]
puts $fid "# This file is generated by \".../Makefiles/makerelease\" and reflects\
          \n# the lastest file changed in the filerunner package.\
          \n#\n# The last file changed was: $file.\
          \n#\n# which generate the following date code/revision:\n"

puts $fid "set glob(version) $revCode"
close $fid


puts "Found last file modified to be: $file. Updateing \"frVersion\" to $revCode\n"

# if {[string match ver* $argv] } {
#   exit 0
# }

# handy stings...
set strRevMatch {[0-9][0-9].[0-9][0-9].[0-9][0-9].[0-9][0-9]*}

# convert rev code to the shortened form

regsub {20([0-9][0-9])([0-9][0-9])([0-9][0-9])\.([0-9][0-9]).+} \
         $revCode {\1.\2.\3.\4} revCode

# now we examine the History file...
#
puts -nonewline "Checking History file "
if {[catch {split [read [set fid [open $history r]]] \n} lines] !=0} {
  fileError $history $lines
}
close $fid
puts -nonewline ". "
set found 0
foreach ln $lines {
  if {[string match $strRevMatch $ln]} {
    incr found
    break
  }
  if {[incr hlines] >10} {break}
}
if {! $found} {
  puts "Failed to find version in first 10 lines of history ($history) file.
  Fix this."
  exit 1
}
puts ". version "
#
# we want to allow other characters around the date code...
#
regexp {(.*)([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]).*} $ln m one hver
set glob(version) 0
if {$hver != $revCode && ! [string match *win* $argv]} {
  puts "Version mismatch, \n$revCode from file $file\n$hver from $history "
  puts "fix this!"
  exit 1
}
puts "Ok!"
if {[string match ver* $argv] } {
  exit 0
}
puts "Found version $hver in $history, building change log"

cd $mkDir
wait_for "file delete -force ../release-$hver"
catch {file mkdir "../release-$hver"}
if {[catch {open "../release-$hver/changelog-$hver.txt" w} fid] != 0} {
  fileError "../release-$hver/changelog-$hver.txt" $fid
}

set revFound 0
while {$revFound < 3 && [set lines [lassign $lines ln]] != {} } {
  puts $fid $ln
  if {[string match $strRevMatch $ln] || \
	  [string match "[string repeat - 30]*" $ln]} {
    incr revFound
  }
}
close $fid

if {[string index $ln 0] != "-"} {
  puts "\nChange log terminated on revcode ($ln) rather than '-----------...'"
  puts "Fix this."
  exit 1
}

puts "Building packageLinks.tcl... "
try_this "./makepackagelinks.tcl wrapfilelist.txt"
# First set permissions...

set pw [pwd]
puts "pwd $pw"
cd ..
puts "pwd [pwd]"
if {[setPermissions $allFiles 1] != 0} {
  puts "Failed setting up permissions!"
  puts "Stop the music."
  exit 1
}
cd $pw
puts "Wraping filerunner... "
try_this "./wrapwindows $hver 64 32" yes
# stop here if asked for windows ...

if {[string match *win* $argv]} {exit 0}
puts "Building the tar file"
# this was in "pack" but is now local


# build the two tar files, one is the executable list
# the other is the rest
# Used only in MSW
if {$MSW} {
  foreach file {executableFiles nonExecutableFiles} {
    set pid [open $file.txt w]
    foreach fileX [set $file] {
      puts $pid $fileX
    }
    close $pid
  }
}
set tar [auto_execok tar]

cd ..
wait_for "file delete -force ./filerunner"
wait_for "file delete -force ./filerunnerX"

# This needs to be platform sensitive because windows does not have 'mode' and
# owner info with each file.
if {$::MSW} {
  try_this "Makefiles/toUnix.tcl -f Makefiles/nonExecutableFiles.txt filerunner"
  try_this "$tar -cf release-$hver/fr-$hver.tar\
  	    --numeric-owner --owner=0 --group=0 --mode=0664 filerunner/"

  wait_for "file rename -force filerunner filerunnerX"
  try_this "Makefiles/toUnix.tcl -f Makefiles/executableFiles.txt filerunner"

  try_this "$tar -cf release-$hver/fr-${hver}-2.tar\
            --numeric-owner --owner=0 --mode=0775 filerunner/"
  cd release-$hver
  try_this "$tar -Af fr-$hver.tar  fr-${hver}-2.tar"
 
  try_this "gzip  fr-$hver.tar"
  cd ..
} else {
  try_this "Makefiles/toUnix.tcl -f Makefiles/tarfilelist.txt filerunner"
  try_this "touch release-$hver/fr-$hver.tar.gz"
  try_this "tar -czv -f release-$hver/fr-$hver.tar.gz --numeric-owner filerunner/"
}
wait_for "file delete -force ./filerunner"
wait_for "file delete -force ./filerunnerX"
wait_for "file delete -force release-$hver/fr-${hver}-2.tar"

#try_this "./pack $hver"
# end of tar build...
if {[string match *tar* $argv]} {exit 0}
cd $mkDir
set fail {}

if {$tcl_platform(platform) eq "windows" || $stopM & 2} {
  if {!$stopM & 2} {
    puts "At this time we do not support building RPM & DEB files in MSW"
  }
} else {

  set verb [expr {$verbose ? "-v" : {}}]
  
  puts "building the RPM and DEB files"
  try_this "./buildRPM.tcl $verb EMAIL=tom@GandK.site \"NAME=Tom\
 Turkey\" TAR=../release-$hver/fr-$hver.tar.gz" 

  puts "Moving the RPM and DEB files to ../release-$hver"
  
  foreach {type file} [list RPMS noarch/*$hver* DEB *$hver*.deb SRPMS *$hver*] {
    if {[set ftmp [glob -nocomplain ~/rpmbuild/$type/$file]] == {}} {
      puts "Build of $type (file: ~/rpmbuild/$type/$file) failed."
      lappend fail $type
    } else {
      puts "Moving $type file to ../release-$hver"
      set r [catch {file rename -force $ftmp ../release-$hver} err]
      if {$r != 0} {
	puts "Error: $err"
      }
    }
  }

}
# Time to build the windows msi file...

if {$stopM & 4} {
  puts "Skipping *.mai build because Wix \"candle.exe\" and/or \"light.exe\" not found."
} else {

  puts "\nBuilding the 'msi' file\n"

  try_this "./msi-tool.tcl msiControl revision=$hver"


  if {![file exists Filerunner.msi]} {
    puts "Error: msi build failed"
    lappend fail msi
  } else {
    puts "Moving Filerunner.msi to ../release-$hver"

    set r [catch {file rename -force Filerunner.msi ../release-$hver/Filerunner_$hver.msi} err]
  }

  foreach err $fail {
    puts "Failed to build $err file."
  }
}

puts "Build finished.  All successful files put in ../release-$hver"
if {$fail == {}} {
  puts "Time to test!"
} else {
  puts "Time to fix the build problems."
}
