summaryrefslogtreecommitdiff
path: root/tool/mkvsix.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tool/mkvsix.tcl')
-rw-r--r--tool/mkvsix.tcl419
1 files changed, 419 insertions, 0 deletions
diff --git a/tool/mkvsix.tcl b/tool/mkvsix.tcl
new file mode 100644
index 0000000..a751778
--- /dev/null
+++ b/tool/mkvsix.tcl
@@ -0,0 +1,419 @@
+#!/usr/bin/tclsh
+#
+# This script is used to generate a VSIX (Visual Studio Extension) file for
+# SQLite usable by Visual Studio.
+
+proc fail { {error ""} {usage false} } {
+ if {[string length $error] > 0} then {
+ puts stdout $error
+ if {!$usage} then {exit 1}
+ }
+
+ puts stdout "usage:\
+[file tail [info nameofexecutable]]\
+[file tail [info script]] <binaryDirectory> \[sourceDirectory\]"
+
+ exit 1
+}
+
+proc getEnvironmentVariable { name } {
+ #
+ # NOTE: Returns the value of the specified environment variable or an empty
+ # string for environment variables that do not exist in the current
+ # process environment.
+ #
+ return [expr {[info exists ::env($name)] ? $::env($name) : ""}]
+}
+
+proc getTemporaryPath {} {
+ #
+ # NOTE: Returns the normalized path to the first temporary directory found
+ # in the typical set of environment variables used for that purpose
+ # or an empty string to signal a failure to locate such a directory.
+ #
+ set names [list]
+
+ foreach name [list TEMP TMP] {
+ lappend names [string toupper $name] [string tolower $name] \
+ [string totitle $name]
+ }
+
+ foreach name $names {
+ set value [getEnvironmentVariable $name]
+
+ if {[string length $value] > 0} then {
+ return [file normalize $value]
+ }
+ }
+
+ return ""
+}
+
+proc appendArgs { args } {
+ #
+ # NOTE: Returns all passed arguments joined together as a single string with
+ # no intervening spaces between arguments.
+ #
+ eval append result $args
+}
+
+proc readFile { fileName } {
+ #
+ # NOTE: Reads and returns the entire contents of the specified file, which
+ # may contain binary data.
+ #
+ set file_id [open $fileName RDONLY]
+ fconfigure $file_id -encoding binary -translation binary
+ set result [read $file_id]
+ close $file_id
+ return $result
+}
+
+proc writeFile { fileName data } {
+ #
+ # NOTE: Writes the entire contents of the specified file, which may contain
+ # binary data.
+ #
+ set file_id [open $fileName {WRONLY CREAT TRUNC}]
+ fconfigure $file_id -encoding binary -translation binary
+ puts -nonewline $file_id $data
+ close $file_id
+ return ""
+}
+
+proc substFile { fileName } {
+ #
+ # NOTE: Performs all Tcl command, variable, and backslash substitutions in
+ # the specified file and then re-writes the contents of that same file
+ # with the substituted data.
+ #
+ return [writeFile $fileName [uplevel 1 [list subst [readFile $fileName]]]]
+}
+
+proc replacePlatform { fileName platformName } {
+ #
+ # NOTE: Returns the specified file name containing the platform name instead
+ # of platform placeholder tokens.
+ #
+ return [string map [list <platform> $platformName] $fileName]
+}
+
+set script [file normalize [info script]]
+
+if {[string length $script] == 0} then {
+ fail "script file currently being evaluated is unknown" true
+}
+
+set path [file dirname $script]
+set rootName [file rootname [file tail $script]]
+
+###############################################################################
+
+#
+# NOTE: Process and verify all the command line arguments.
+#
+set argc [llength $argv]
+if {$argc != 1 && $argc != 2} then {fail}
+
+set binaryDirectory [lindex $argv 0]
+
+if {[string length $binaryDirectory] == 0} then {
+ fail "invalid binary directory"
+}
+
+if {![file exists $binaryDirectory] || \
+ ![file isdirectory $binaryDirectory]} then {
+ fail "binary directory does not exist"
+}
+
+if {$argc == 2} then {
+ set sourceDirectory [lindex $argv 1]
+} else {
+ #
+ # NOTE: Assume that the source directory is the parent directory of the one
+ # that contains this script file.
+ #
+ set sourceDirectory [file dirname $path]
+}
+
+if {[string length $sourceDirectory] == 0} then {
+ fail "invalid source directory"
+}
+
+if {![file exists $sourceDirectory] || \
+ ![file isdirectory $sourceDirectory]} then {
+ fail "source directory does not exist"
+}
+
+###############################################################################
+
+#
+# NOTE: Evaluate the user-specific customizations file, if it exists.
+#
+set userFile [file join $path [appendArgs \
+ $rootName . $tcl_platform(user) .tcl]]
+
+if {[file exists $userFile] && \
+ [file isfile $userFile]} then {
+ source $userFile
+}
+
+###############################################################################
+
+set templateFile [file join $path win sqlite.vsix]
+
+if {![file exists $templateFile] || \
+ ![file isfile $templateFile]} then {
+ fail [appendArgs "template file \"" $templateFile "\" does not exist"]
+}
+
+set currentDirectory [pwd]
+set outputFile [file join $currentDirectory sqlite-output.vsix]
+
+if {[file exists $outputFile]} then {
+ fail [appendArgs "output file \"" $outputFile "\" already exists"]
+}
+
+###############################################################################
+
+#
+# NOTE: Make sure that a valid temporary directory exists.
+#
+set temporaryDirectory [getTemporaryPath]
+
+if {[string length $temporaryDirectory] == 0 || \
+ ![file exists $temporaryDirectory] || \
+ ![file isdirectory $temporaryDirectory]} then {
+ fail "cannot locate a usable temporary directory"
+}
+
+#
+# NOTE: Setup the staging directory to have a unique name inside of the
+# configured temporary directory.
+#
+set stagingDirectory [file normalize [file join $temporaryDirectory \
+ [appendArgs $rootName . [pid]]]]
+
+###############################################################################
+
+#
+# NOTE: Configure the external zipping tool. First, see if it has already
+# been pre-configured. If not, try to query it from the environment.
+# Finally, fallback on the default of simply "zip", which will then
+# be assumed to exist somewhere along the PATH.
+#
+if {![info exists zip]} then {
+ if {[info exists env(ZipTool)]} then {
+ set zip $env(ZipTool)
+ }
+ if {![info exists zip] || ![file exists $zip]} then {
+ set zip zip
+ }
+}
+
+#
+# NOTE: Configure the external unzipping tool. First, see if it has already
+# been pre-configured. If not, try to query it from the environment.
+# Finally, fallback on the default of simply "unzip", which will then
+# be assumed to exist somewhere along the PATH.
+#
+if {![info exists unzip]} then {
+ if {[info exists env(UnZipTool)]} then {
+ set unzip $env(UnZipTool)
+ }
+ if {![info exists unzip] || ![file exists $unzip]} then {
+ set unzip unzip
+ }
+}
+
+###############################################################################
+
+#
+# NOTE: Attempt to extract the SQLite version from the "sqlite3.h" header file
+# in the source directory. This script assumes that the header file has
+# already been generated by the build process.
+#
+set pattern {^#define\s+SQLITE_VERSION\s+"(.*)"$}
+set data [readFile [file join $sourceDirectory sqlite3.h]]
+
+if {![regexp -line -- $pattern $data dummy version]} then {
+ fail [appendArgs "cannot locate SQLITE_VERSION value in \"" \
+ [file join $sourceDirectory sqlite3.h] \"]
+}
+
+###############################################################################
+
+#
+# NOTE: Setup the master file list data, including the necessary flags.
+#
+if {![info exists fileNames(source)]} then {
+ set fileNames(source) [list "" "" "" \
+ [file join $sourceDirectory sqlite3.h] \
+ [file join $binaryDirectory <platform> sqlite3.lib] \
+ [file join $binaryDirectory <platform> sqlite3.dll]]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(source) \
+ [file join $binaryDirectory <platform> sqlite3.pdb]
+ }
+}
+
+if {![info exists fileNames(destination)]} then {
+ set fileNames(destination) [list \
+ [file join $stagingDirectory extension.vsixmanifest] \
+ [file join $stagingDirectory SDKManifest.xml] \
+ [file join $stagingDirectory DesignTime CommonConfiguration \
+ <platform> SQLite.WinRT.props] \
+ [file join $stagingDirectory DesignTime CommonConfiguration \
+ <platform> sqlite3.h] \
+ [file join $stagingDirectory DesignTime CommonConfiguration \
+ <platform> sqlite3.lib] \
+ [file join $stagingDirectory Redist CommonConfiguration \
+ <platform> sqlite3.dll]]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(destination) \
+ [file join $stagingDirectory Redist Debug \
+ <platform> sqlite3.pdb]
+ }
+}
+
+if {![info exists fileNames(neutral)]} then {
+ set fileNames(neutral) [list 1 1 1 1 0 0]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(neutral) 0
+ }
+}
+
+if {![info exists fileNames(subst)]} then {
+ set fileNames(subst) [list 1 1 1 0 0 0]
+
+ if {![info exists no(symbols)]} then {
+ lappend fileNames(subst) 0
+ }
+}
+
+###############################################################################
+
+#
+# NOTE: Setup the list of platforms supported by this script.
+#
+if {![info exists platformNames]} then {
+ set platformNames [list x86 x64 ARM]
+}
+
+###############################################################################
+
+#
+# NOTE: Make sure the staging directory exists, creating it if necessary.
+#
+file mkdir $stagingDirectory
+
+#
+# NOTE: Build the Tcl command used to extract the template package to the
+# staging directory.
+#
+set extractCommand [list exec -- $unzip $templateFile -d $stagingDirectory]
+
+#
+# NOTE: Extract the template package to the staging directory.
+#
+eval $extractCommand
+
+###############################################################################
+
+#
+# NOTE: Process each file in the master file list. There are actually four
+# parallel lists that contain the source file names, destination file
+# names, the platform-neutral flags, and the use-subst flags. When the
+# platform-neutral flag is non-zero, the file is not platform-specific.
+# When the use-subst flag is non-zero, the file is considered to be a
+# text file that may contain Tcl variable and/or command replacements,
+# to be dynamically replaced during processing. If the source file name
+# is an empty string, then the destination file name will be assumed to
+# already exist in the staging directory and will not be copied; however,
+# dynamic replacements may still be performed on the destination file
+# prior to the package being re-zipped.
+#
+foreach sourceFileName $fileNames(source) \
+ destinationFileName $fileNames(destination) \
+ isNeutral $fileNames(neutral) useSubst $fileNames(subst) {
+ #
+ # NOTE: If the current file is platform-neutral, then only one platform will
+ # be processed for it, namely "neutral"; otherwise, each supported
+ # platform will be processed for it individually.
+ #
+ foreach platformName [expr {$isNeutral ? [list neutral] : $platformNames}] {
+ #
+ # NOTE: Use the actual platform name in the destination file name.
+ #
+ set newDestinationFileName [replacePlatform $destinationFileName \
+ $platformName]
+
+ #
+ # NOTE: Does the source file need to be copied to the destination file?
+ #
+ if {[string length $sourceFileName] > 0} then {
+ #
+ # NOTE: First, make sure the destination directory exists.
+ #
+ file mkdir [file dirname $newDestinationFileName]
+
+ #
+ # NOTE: Then, copy the source file to the destination file verbatim.
+ #
+ file copy [replacePlatform $sourceFileName $platformName] \
+ $newDestinationFileName
+ }
+
+ #
+ # NOTE: Does the destination file contain dynamic replacements that must
+ # be processed now?
+ #
+ if {$useSubst} then {
+ #
+ # NOTE: Perform any dynamic replacements contained in the destination
+ # file and then re-write it in-place.
+ #
+ substFile $newDestinationFileName
+ }
+ }
+}
+
+###############################################################################
+
+#
+# NOTE: Change the current directory to the staging directory so that the
+# external archive building tool can pickup the necessary files using
+# relative paths.
+#
+cd $stagingDirectory
+
+#
+# NOTE: Build the Tcl command used to archive the final package in the
+# output directory.
+#
+set archiveCommand [list exec -- $zip -r $outputFile *]
+
+#
+# NOTE: Build the final package archive in the output directory.
+#
+eval $archiveCommand
+
+#
+# NOTE: Change back to the previously saved current directory.
+#
+cd $currentDirectory
+
+#
+# NOTE: Cleanup the temporary staging directory.
+#
+file delete -force $stagingDirectory
+
+###############################################################################
+
+#
+# NOTE: Success, emit the fully qualified path of the generated VSIX file.
+#
+puts stdout $outputFile