1#!/usr/bin/tclsh 2# 3# This script is used to generate a VSIX (Visual Studio Extension) file for 4# SQLite usable by Visual Studio. 5# 6# PREREQUISITES 7# 8# 1. Tcl 8.4 and later are supported, earlier versions have not been tested. 9# 10# 2. The "sqlite3.h" file is assumed to exist in the parent directory of the 11# directory containing this script. The [optional] second command line 12# argument to this script may be used to specify an alternate location. 13# This script also assumes that the "sqlite3.h" file corresponds with the 14# version of the binaries to be packaged. This assumption is not verified 15# by this script. 16# 17# 3. The temporary directory specified in the TEMP or TMP environment variables 18# must refer to an existing directory writable by the current user. 19# 20# 4. The "zip" and "unzip" command line tools must be located either in a 21# directory contained in the PATH environment variable or specified as the 22# exact file names to execute in the "ZipTool" and "UnZipTool" environment 23# variables, respectively. 24# 25# 5. The template VSIX file (which is basically a zip file) must be located in 26# a "win" directory inside the directory containing this script. It should 27# not contain any executable binaries. It should only contain dynamic 28# textual content files to be processed using [subst] and/or static content 29# files to be copied verbatim. 30# 31# 6. The executable and other compiled binary files to be packaged into the 32# final VSIX file (e.g. DLLs, LIBs, and PDBs) must be located in a single 33# directory tree. The top-level directory of the tree must be specified as 34# the first command line argument to this script. The second level 35# sub-directory names must match those of the build configuration (e.g. 36# "Debug" or "Retail"). The third level sub-directory names must match 37# those of the platform (e.g. "x86", "x64", and "ARM"). For example, the 38# binary files to be packaged would need to be organized as follows when 39# packaging the "Debug" and "Retail" build configurations for the "x86" and 40# "x64" platforms (in this example, "C:\temp" is the top-level directory as 41# specified in the first command line argument): 42# 43# C:\Temp\Debug\x86\sqlite3.lib 44# C:\Temp\Debug\x86\sqlite3.dll 45# C:\Temp\Debug\x86\sqlite3.pdb 46# C:\Temp\Debug\x64\sqlite3.lib 47# C:\Temp\Debug\x64\sqlite3.dll 48# C:\Temp\Debug\x64\sqlite3.pdb 49# C:\Temp\Retail\x86\sqlite3.lib 50# C:\Temp\Retail\x86\sqlite3.dll 51# C:\Temp\Retail\x86\sqlite3.pdb 52# C:\Temp\Retail\x64\sqlite3.lib 53# C:\Temp\Retail\x64\sqlite3.dll 54# C:\Temp\Retail\x64\sqlite3.pdb 55# 56# The above directory tree organization is performed automatically if the 57# "tool\build-all-msvc.bat" batch script is used to build the binary files 58# to be packaged. 59# 60# USAGE 61# 62# The first argument to this script is required and must be the name of the 63# top-level directory containing the directories and files organized into a 64# tree as described in item 6 of the PREREQUISITES section, above. The second 65# argument is optional and if present must contain the name of the directory 66# containing the root of the source tree for SQLite. The third argument is 67# optional and if present must contain the flavor the VSIX package to build. 68# Currently, the only supported package flavors are "WinRT", "WinRT81", and 69# "WP80". The fourth argument is optional and if present must be a string 70# containing a list of platforms to include in the VSIX package. The format 71# of the platform list string is "platform1,platform2,platform3". Typically, 72# when on Windows, this script is executed using commands similar to the 73# following from a normal Windows command prompt: 74# 75# CD /D C:\dev\sqlite\core 76# tclsh85 tool\mkvsix.tcl C:\Temp 77# 78# In the example above, "C:\dev\sqlite\core" represents the root of the source 79# tree for SQLite and "C:\Temp" represents the top-level directory containing 80# the executable and other compiled binary files, organized into a directory 81# tree as described in item 6 of the PREREQUISITES section, above. 82# 83# This script should work on non-Windows platforms as well, provided that all 84# the requirements listed in the PREREQUISITES section are met. 85# 86# NOTES 87# 88# The temporary directory is used as a staging area for the final VSIX file. 89# The template VSIX file is extracted, its contents processed, and then the 90# resulting files are packaged into the final VSIX file. 91# 92package require Tcl 8.4 93 94proc fail { {error ""} {usage false} } { 95 if {[string length $error] > 0} then { 96 puts stdout $error 97 if {!$usage} then {exit 1} 98 } 99 100 puts stdout "usage:\ 101[file tail [info nameofexecutable]]\ 102[file tail [info script]] <binaryDirectory> \[sourceDirectory\]\ 103\[packageFlavor\] \[platformNames\]" 104 105 exit 1 106} 107 108proc getEnvironmentVariable { name } { 109 # 110 # NOTE: Returns the value of the specified environment variable or an empty 111 # string for environment variables that do not exist in the current 112 # process environment. 113 # 114 return [expr {[info exists ::env($name)] ? $::env($name) : ""}] 115} 116 117proc getTemporaryPath {} { 118 # 119 # NOTE: Returns the normalized path to the first temporary directory found 120 # in the typical set of environment variables used for that purpose 121 # or an empty string to signal a failure to locate such a directory. 122 # 123 set names [list] 124 125 foreach name [list TEMP TMP] { 126 lappend names [string toupper $name] [string tolower $name] \ 127 [string totitle $name] 128 } 129 130 foreach name $names { 131 set value [getEnvironmentVariable $name] 132 133 if {[string length $value] > 0} then { 134 return [file normalize $value] 135 } 136 } 137 138 return "" 139} 140 141proc appendArgs { args } { 142 # 143 # NOTE: Returns all passed arguments joined together as a single string with 144 # no intervening spaces between arguments. 145 # 146 eval append result $args 147} 148 149proc readFile { fileName } { 150 # 151 # NOTE: Reads and returns the entire contents of the specified file, which 152 # may contain binary data. 153 # 154 set file_id [open $fileName RDONLY] 155 fconfigure $file_id -encoding binary -translation binary 156 set result [read $file_id] 157 close $file_id 158 return $result 159} 160 161proc writeFile { fileName data } { 162 # 163 # NOTE: Writes the entire contents of the specified file, which may contain 164 # binary data. 165 # 166 set file_id [open $fileName {WRONLY CREAT TRUNC}] 167 fconfigure $file_id -encoding binary -translation binary 168 puts -nonewline $file_id $data 169 close $file_id 170 return "" 171} 172 173proc substFile { fileName } { 174 # 175 # NOTE: Performs all Tcl command, variable, and backslash substitutions in 176 # the specified file and then rewrites the contents of that same file 177 # with the substituted data. 178 # 179 return [writeFile $fileName [uplevel 1 [list subst [readFile $fileName]]]] 180} 181 182proc replaceFileNameTokens { fileName name buildName platformName } { 183 # 184 # NOTE: Returns the specified file name containing the platform name instead 185 # of platform placeholder tokens. 186 # 187 return [string map [list <build> $buildName <platform> $platformName \ 188 <name> $name] $fileName] 189} 190 191# 192# NOTE: This is the entry point for this script. 193# 194set script [file normalize [info script]] 195 196if {[string length $script] == 0} then { 197 fail "script file currently being evaluated is unknown" true 198} 199 200set path [file dirname $script] 201set rootName [file rootname [file tail $script]] 202 203############################################################################### 204 205# 206# NOTE: Process and verify all the command line arguments. 207# 208set argc [llength $argv] 209if {$argc < 1 || $argc > 4} then {fail} 210 211set binaryDirectory [lindex $argv 0] 212 213if {[string length $binaryDirectory] == 0} then { 214 fail "invalid binary directory" 215} 216 217if {![file exists $binaryDirectory] || \ 218 ![file isdirectory $binaryDirectory]} then { 219 fail "binary directory does not exist" 220} 221 222if {$argc >= 2} then { 223 set sourceDirectory [lindex $argv 1] 224} else { 225 # 226 # NOTE: Assume that the source directory is the parent directory of the one 227 # that contains this script file. 228 # 229 set sourceDirectory [file dirname $path] 230} 231 232if {[string length $sourceDirectory] == 0} then { 233 fail "invalid source directory" 234} 235 236if {![file exists $sourceDirectory] || \ 237 ![file isdirectory $sourceDirectory]} then { 238 fail "source directory does not exist" 239} 240 241if {$argc >= 3} then { 242 set packageFlavor [lindex $argv 2] 243} else { 244 # 245 # NOTE: Assume the package flavor is WinRT. 246 # 247 set packageFlavor WinRT 248} 249 250if {[string length $packageFlavor] == 0} then { 251 fail "invalid package flavor" 252} 253 254if {[string equal -nocase $packageFlavor WinRT]} then { 255 set shortName SQLite.WinRT 256 set displayName "SQLite for Windows Runtime" 257 set targetPlatformIdentifier Windows 258 set targetPlatformVersion v8.0 259 set minVsVersion 11.0 260 set extraSdkPath "" 261 set extraFileListAttributes [appendArgs \ 262 "\r\n " {AppliesTo="WindowsAppContainer"} \ 263 "\r\n " {DependsOn="Microsoft.VCLibs, version=11.0"}] 264} elseif {[string equal -nocase $packageFlavor WinRT81]} then { 265 set shortName SQLite.WinRT81 266 set displayName "SQLite for Windows Runtime (Windows 8.1)" 267 set targetPlatformIdentifier Windows 268 set targetPlatformVersion v8.1 269 set minVsVersion 12.0 270 set extraSdkPath "" 271 set extraFileListAttributes [appendArgs \ 272 "\r\n " {AppliesTo="WindowsAppContainer"} \ 273 "\r\n " {DependsOn="Microsoft.VCLibs, version=12.0"}] 274} elseif {[string equal -nocase $packageFlavor WP80]} then { 275 set shortName SQLite.WP80 276 set displayName "SQLite for Windows Phone" 277 set targetPlatformIdentifier "Windows Phone" 278 set targetPlatformVersion v8.0 279 set minVsVersion 11.0 280 set extraSdkPath "\\..\\$targetPlatformIdentifier" 281 set extraFileListAttributes "" 282} elseif {[string equal -nocase $packageFlavor Win32]} then { 283 set shortName SQLite.Win32 284 set displayName "SQLite for Windows" 285 set targetPlatformIdentifier Windows 286 set targetPlatformVersion v8.0 287 set minVsVersion 11.0 288 set extraSdkPath "" 289 set extraFileListAttributes [appendArgs \ 290 "\r\n " {AppliesTo="VisualC"} \ 291 "\r\n " {DependsOn="Microsoft.VCLibs, version=11.0"}] 292} else { 293 fail "unsupported package flavor, must be one of: WinRT WinRT81 WP80 Win32" 294} 295 296if {$argc >= 4} then { 297 set platformNames [list] 298 299 foreach platformName [split [lindex $argv 3] ", "] { 300 if {[string length $platformName] > 0} then { 301 lappend platformNames $platformName 302 } 303 } 304} 305 306############################################################################### 307 308# 309# NOTE: Evaluate the user-specific customizations file, if it exists. 310# 311set userFile [file join $path [appendArgs \ 312 $rootName . $tcl_platform(user) .tcl]] 313 314if {[file exists $userFile] && \ 315 [file isfile $userFile]} then { 316 source $userFile 317} 318 319############################################################################### 320 321set templateFile [file join $path win sqlite.vsix] 322 323if {![file exists $templateFile] || \ 324 ![file isfile $templateFile]} then { 325 fail [appendArgs "template file \"" $templateFile "\" does not exist"] 326} 327 328set currentDirectory [pwd] 329set outputFile [file join $currentDirectory [appendArgs sqlite- \ 330 $packageFlavor -output.vsix]] 331 332if {[file exists $outputFile]} then { 333 fail [appendArgs "output file \"" $outputFile "\" already exists"] 334} 335 336############################################################################### 337 338# 339# NOTE: Make sure that a valid temporary directory exists. 340# 341set temporaryDirectory [getTemporaryPath] 342 343if {[string length $temporaryDirectory] == 0 || \ 344 ![file exists $temporaryDirectory] || \ 345 ![file isdirectory $temporaryDirectory]} then { 346 fail "cannot locate a usable temporary directory" 347} 348 349# 350# NOTE: Setup the staging directory to have a unique name inside of the 351# configured temporary directory. 352# 353set stagingDirectory [file normalize [file join $temporaryDirectory \ 354 [appendArgs $rootName . [pid]]]] 355 356############################################################################### 357 358# 359# NOTE: Configure the external zipping tool. First, see if it has already 360# been pre-configured. If not, try to query it from the environment. 361# Finally, fallback on the default of simply "zip", which will then 362# be assumed to exist somewhere along the PATH. 363# 364if {![info exists zip]} then { 365 if {[info exists env(ZipTool)]} then { 366 set zip $env(ZipTool) 367 } 368 if {![info exists zip] || ![file exists $zip]} then { 369 set zip zip 370 } 371} 372 373# 374# NOTE: Configure the external unzipping tool. First, see if it has already 375# been pre-configured. If not, try to query it from the environment. 376# Finally, fallback on the default of simply "unzip", which will then 377# be assumed to exist somewhere along the PATH. 378# 379if {![info exists unzip]} then { 380 if {[info exists env(UnZipTool)]} then { 381 set unzip $env(UnZipTool) 382 } 383 if {![info exists unzip] || ![file exists $unzip]} then { 384 set unzip unzip 385 } 386} 387 388############################################################################### 389 390# 391# NOTE: Attempt to extract the SQLite version from the "sqlite3.h" header file 392# in the source directory. This script assumes that the header file has 393# already been generated by the build process. 394# 395set pattern {^#define\s+SQLITE_VERSION\s+"(.*)"$} 396set data [readFile [file join $sourceDirectory sqlite3.h]] 397 398if {![regexp -line -- $pattern $data dummy version]} then { 399 fail [appendArgs "cannot locate SQLITE_VERSION value in \"" \ 400 [file join $sourceDirectory sqlite3.h] \"] 401} 402 403############################################################################### 404 405# 406# NOTE: Setup all the master file list data. This includes the source file 407# names, the destination file names, and the file processing flags. The 408# possible file processing flags are: 409# 410# "buildNeutral" -- This flag indicates the file location and content do 411# not depend on the build configuration. 412# 413# "platformNeutral" -- This flag indicates the file location and content 414# do not depend on the build platform. 415# 416# "subst" -- This flag indicates that the file contains dynamic textual 417# content that needs to be processed using [subst] prior to 418# packaging the file into the final VSIX package. The primary 419# use of this flag is to insert the name of the VSIX package, 420# some package flavor-specific value, or the SQLite version 421# into a file. 422# 423# "noDebug" -- This flag indicates that the file should be skipped when 424# processing the debug build. 425# 426# "noRetail" -- This flag indicates that the file should be skipped when 427# processing the retail build. 428# 429# "move" -- This flag indicates that the file should be moved from the 430# source to the destination instead of being copied. 431# 432# This file metadata may be overridden, either in whole or in part, via 433# the user-specific customizations file. 434# 435if {![info exists fileNames(source)]} then { 436 set fileNames(source) [list "" "" \ 437 [file join $stagingDirectory DesignTime <build> <platform> sqlite3.props] \ 438 [file join $sourceDirectory sqlite3.h] \ 439 [file join $binaryDirectory <build> <platform> sqlite3.lib] \ 440 [file join $binaryDirectory <build> <platform> sqlite3.dll]] 441 442 if {![info exists no(symbols)]} then { 443 lappend fileNames(source) \ 444 [file join $binaryDirectory <build> <platform> sqlite3.pdb] 445 } 446} 447 448if {![info exists fileNames(destination)]} then { 449 set fileNames(destination) [list \ 450 [file join $stagingDirectory extension.vsixmanifest] \ 451 [file join $stagingDirectory SDKManifest.xml] \ 452 [file join $stagingDirectory DesignTime <build> <platform> <name>.props] \ 453 [file join $stagingDirectory DesignTime <build> <platform> sqlite3.h] \ 454 [file join $stagingDirectory DesignTime <build> <platform> sqlite3.lib] \ 455 [file join $stagingDirectory Redist <build> <platform> sqlite3.dll]] 456 457 if {![info exists no(symbols)]} then { 458 lappend fileNames(destination) \ 459 [file join $stagingDirectory Redist <build> <platform> sqlite3.pdb] 460 } 461} 462 463if {![info exists fileNames(flags)]} then { 464 set fileNames(flags) [list \ 465 [list buildNeutral platformNeutral subst] \ 466 [list buildNeutral platformNeutral subst] \ 467 [list buildNeutral platformNeutral subst move] \ 468 [list buildNeutral platformNeutral] \ 469 [list] [list] [list noRetail]] 470 471 if {![info exists no(symbols)]} then { 472 lappend fileNames(flags) [list noRetail] 473 } 474} 475 476############################################################################### 477 478# 479# NOTE: Setup the list of builds supported by this script. These may be 480# overridden via the user-specific customizations file. 481# 482if {![info exists buildNames]} then { 483 set buildNames [list Debug Retail] 484} 485 486############################################################################### 487 488# 489# NOTE: Setup the list of platforms supported by this script. These may be 490# overridden via the command line or the user-specific customizations 491# file. 492# 493if {![info exists platformNames]} then { 494 set platformNames [list x86 x64 ARM] 495} 496 497############################################################################### 498 499# 500# NOTE: Make sure the staging directory exists, creating it if necessary. 501# 502file mkdir $stagingDirectory 503 504# 505# NOTE: Build the Tcl command used to extract the template VSIX package to 506# the staging directory. 507# 508set extractCommand [list exec -- $unzip $templateFile -d $stagingDirectory] 509 510# 511# NOTE: Extract the template VSIX package to the staging directory. 512# 513eval $extractCommand 514 515############################################################################### 516 517# 518# NOTE: Process each file in the master file list. There are actually three 519# parallel lists that contain the source file names, the destination file 520# names, and the file processing flags. If the "buildNeutral" flag is 521# present, the file location and content do not depend on the build 522# configuration and "CommonConfiguration" will be used in place of the 523# build configuration name. If the "platformNeutral" flag is present, 524# the file location and content do not depend on the build platform and 525# "neutral" will be used in place of the build platform name. If the 526# "subst" flag is present, the file is assumed to be a text file that may 527# contain Tcl variable, command, and backslash replacements, to be 528# dynamically replaced during processing using the Tcl [subst] command. 529# If the "noDebug" flag is present, the file will be skipped when 530# processing for the debug build. If the "noRetail" flag is present, the 531# file will be skipped when processing for the retail build. If the 532# "move" flag is present, the source file will be deleted after it is 533# copied to the destination file. If the source file name is an empty 534# string, the destination file name will be assumed to already exist in 535# the staging directory and will not be copied; however, Tcl variable, 536# command, and backslash replacements may still be performed on the 537# destination file prior to the final VSIX package being built if the 538# "subst" flag is present. 539# 540foreach sourceFileName $fileNames(source) \ 541 destinationFileName $fileNames(destination) \ 542 fileFlags $fileNames(flags) { 543 # 544 # NOTE: Process the file flags into separate boolean variables that may be 545 # used within the loop. 546 # 547 set isBuildNeutral [expr {[lsearch $fileFlags buildNeutral] != -1}] 548 set isPlatformNeutral [expr {[lsearch $fileFlags platformNeutral] != -1}] 549 set isMove [expr {[lsearch $fileFlags move] != -1}] 550 set useSubst [expr {[lsearch $fileFlags subst] != -1}] 551 552 # 553 # NOTE: If the current file is build-neutral, then only one build will 554 # be processed for it, namely "CommonConfiguration"; otherwise, each 555 # supported build will be processed for it individually. 556 # 557 foreach buildName \ 558 [expr {$isBuildNeutral ? [list CommonConfiguration] : $buildNames}] { 559 # 560 # NOTE: Should the current file be skipped for this build? 561 # 562 if {[lsearch $fileFlags no${buildName}] != -1} then { 563 continue 564 } 565 566 # 567 # NOTE: If the current file is platform-neutral, then only one platform 568 # will be processed for it, namely "neutral"; otherwise, each 569 # supported platform will be processed for it individually. 570 # 571 foreach platformName \ 572 [expr {$isPlatformNeutral ? [list neutral] : $platformNames}] { 573 # 574 # NOTE: Use the actual platform name in the destination file name. 575 # 576 set newDestinationFileName [replaceFileNameTokens $destinationFileName \ 577 $shortName $buildName $platformName] 578 579 # 580 # NOTE: Does the source file need to be copied to the destination file? 581 # 582 if {[string length $sourceFileName] > 0} then { 583 # 584 # NOTE: First, make sure the destination directory exists. 585 # 586 file mkdir [file dirname $newDestinationFileName] 587 588 # 589 # NOTE: Then, copy the source file to the destination file verbatim. 590 # 591 set newSourceFileName [replaceFileNameTokens $sourceFileName \ 592 $shortName $buildName $platformName] 593 594 file copy $newSourceFileName $newDestinationFileName 595 596 # 597 # NOTE: If this is a move instead of a copy, delete the source file 598 # now. 599 # 600 if {$isMove} then { 601 file delete $newSourceFileName 602 } 603 } 604 605 # 606 # NOTE: Does the destination file contain dynamic replacements that must 607 # be processed now? 608 # 609 if {$useSubst} then { 610 # 611 # NOTE: Perform any dynamic replacements contained in the destination 612 # file and then re-write it in-place. 613 # 614 substFile $newDestinationFileName 615 } 616 } 617 } 618} 619 620############################################################################### 621 622# 623# NOTE: Change the current directory to the staging directory so that the 624# external archive building tool can pickup the necessary files using 625# relative paths. 626# 627cd $stagingDirectory 628 629# 630# NOTE: Build the Tcl command used to archive the final VSIX package in the 631# output directory. 632# 633set archiveCommand [list exec -- $zip -r $outputFile *] 634 635# 636# NOTE: Build the final VSIX package archive in the output directory. 637# 638eval $archiveCommand 639 640# 641# NOTE: Change back to the previously saved current directory. 642# 643cd $currentDirectory 644 645# 646# NOTE: Cleanup the temporary staging directory. 647# 648file delete -force $stagingDirectory 649 650############################################################################### 651 652# 653# NOTE: Success, emit the fully qualified path of the generated VSIX file. 654# 655puts stdout $outputFile 656