mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 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 | proc fail { {error ""} {usage false} } { |
| 7 | if {[string length $error] > 0} then { |
| 8 | puts stdout $error |
| 9 | if {!$usage} then {exit 1} |
| 10 | } |
| 11 | |
| 12 | puts stdout "usage:\ |
| 13 | [file tail [info nameofexecutable]]\ |
| 14 | [file tail [info script]] <binaryDirectory> \[sourceDirectory\]" |
| 15 | |
| 16 | exit 1 |
| 17 | } |
| 18 | |
| 19 | proc getEnvironmentVariable { name } { |
| 20 | # |
| 21 | # NOTE: Returns the value of the specified environment variable or an empty |
| 22 | # string for environment variables that do not exist in the current |
| 23 | # process environment. |
| 24 | # |
| 25 | return [expr {[info exists ::env($name)] ? $::env($name) : ""}] |
| 26 | } |
| 27 | |
| 28 | proc getTemporaryPath {} { |
| 29 | # |
| 30 | # NOTE: Returns the normalized path to the first temporary directory found |
| 31 | # in the typical set of environment variables used for that purpose |
| 32 | # or an empty string to signal a failure to locate such a directory. |
| 33 | # |
| 34 | set names [list] |
| 35 | |
| 36 | foreach name [list TEMP TMP] { |
| 37 | lappend names [string toupper $name] [string tolower $name] \ |
| 38 | [string totitle $name] |
| 39 | } |
| 40 | |
| 41 | foreach name $names { |
| 42 | set value [getEnvironmentVariable $name] |
| 43 | |
| 44 | if {[string length $value] > 0} then { |
| 45 | return [file normalize $value] |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | return "" |
| 50 | } |
| 51 | |
| 52 | proc appendArgs { args } { |
| 53 | # |
| 54 | # NOTE: Returns all passed arguments joined together as a single string with |
| 55 | # no intervening spaces between arguments. |
| 56 | # |
| 57 | eval append result $args |
| 58 | } |
| 59 | |
| 60 | proc readFile { fileName } { |
| 61 | # |
| 62 | # NOTE: Reads and returns the entire contents of the specified file, which |
| 63 | # may contain binary data. |
| 64 | # |
| 65 | set file_id [open $fileName RDONLY] |
| 66 | fconfigure $file_id -encoding binary -translation binary |
| 67 | set result [read $file_id] |
| 68 | close $file_id |
| 69 | return $result |
| 70 | } |
| 71 | |
| 72 | proc writeFile { fileName data } { |
| 73 | # |
| 74 | # NOTE: Writes the entire contents of the specified file, which may contain |
| 75 | # binary data. |
| 76 | # |
| 77 | set file_id [open $fileName {WRONLY CREAT TRUNC}] |
| 78 | fconfigure $file_id -encoding binary -translation binary |
| 79 | puts -nonewline $file_id $data |
| 80 | close $file_id |
| 81 | return "" |
| 82 | } |
| 83 | |
| 84 | proc substFile { fileName } { |
| 85 | # |
| 86 | # NOTE: Performs all Tcl command, variable, and backslash substitutions in |
| 87 | # the specified file and then re-writes the contents of that same file |
| 88 | # with the substituted data. |
| 89 | # |
| 90 | return [writeFile $fileName [uplevel 1 [list subst [readFile $fileName]]]] |
| 91 | } |
| 92 | |
| 93 | proc replacePlatform { fileName platformName } { |
| 94 | # |
| 95 | # NOTE: Returns the specified file name containing the platform name instead |
| 96 | # of platform placeholder tokens. |
| 97 | # |
| 98 | return [string map [list <platform> $platformName] $fileName] |
| 99 | } |
| 100 | |
| 101 | set script [file normalize [info script]] |
| 102 | |
| 103 | if {[string length $script] == 0} then { |
| 104 | fail "script file currently being evaluated is unknown" true |
| 105 | } |
| 106 | |
| 107 | set path [file dirname $script] |
| 108 | set rootName [file rootname [file tail $script]] |
| 109 | |
| 110 | ############################################################################### |
| 111 | |
| 112 | # |
| 113 | # NOTE: Process and verify all the command line arguments. |
| 114 | # |
| 115 | set argc [llength $argv] |
| 116 | if {$argc != 1 && $argc != 2} then {fail} |
| 117 | |
| 118 | set binaryDirectory [lindex $argv 0] |
| 119 | |
| 120 | if {[string length $binaryDirectory] == 0} then { |
| 121 | fail "invalid binary directory" |
| 122 | } |
| 123 | |
| 124 | if {![file exists $binaryDirectory] || \ |
| 125 | ![file isdirectory $binaryDirectory]} then { |
| 126 | fail "binary directory does not exist" |
| 127 | } |
| 128 | |
| 129 | if {$argc == 2} then { |
| 130 | set sourceDirectory [lindex $argv 1] |
| 131 | } else { |
| 132 | # |
| 133 | # NOTE: Assume that the source directory is the parent directory of the one |
| 134 | # that contains this script file. |
| 135 | # |
| 136 | set sourceDirectory [file dirname $path] |
| 137 | } |
| 138 | |
| 139 | if {[string length $sourceDirectory] == 0} then { |
| 140 | fail "invalid source directory" |
| 141 | } |
| 142 | |
| 143 | if {![file exists $sourceDirectory] || \ |
| 144 | ![file isdirectory $sourceDirectory]} then { |
| 145 | fail "source directory does not exist" |
| 146 | } |
| 147 | |
| 148 | ############################################################################### |
| 149 | |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 150 | # |
| 151 | # NOTE: Evaluate the user-specific customizations file, if it exists. |
| 152 | # |
| 153 | set userFile [file join $path [appendArgs \ |
| 154 | $rootName . $tcl_platform(user) .tcl]] |
| 155 | |
| 156 | if {[file exists $userFile] && \ |
| 157 | [file isfile $userFile]} then { |
| 158 | source $userFile |
| 159 | } |
| 160 | |
| 161 | ############################################################################### |
| 162 | |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 163 | set templateFile [file join $path win sqlite.vsix] |
| 164 | |
| 165 | if {![file exists $templateFile] || \ |
| 166 | ![file isfile $templateFile]} then { |
| 167 | fail [appendArgs "template file \"" $templateFile "\" does not exist"] |
| 168 | } |
| 169 | |
| 170 | set currentDirectory [pwd] |
| 171 | set outputFile [file join $currentDirectory sqlite-output.vsix] |
| 172 | |
| 173 | if {[file exists $outputFile]} then { |
| 174 | fail [appendArgs "output file \"" $outputFile "\" already exists"] |
| 175 | } |
| 176 | |
| 177 | ############################################################################### |
| 178 | |
| 179 | # |
| 180 | # NOTE: Make sure that a valid temporary directory exists. |
| 181 | # |
| 182 | set temporaryDirectory [getTemporaryPath] |
| 183 | |
| 184 | if {[string length $temporaryDirectory] == 0 || \ |
| 185 | ![file exists $temporaryDirectory] || \ |
| 186 | ![file isdirectory $temporaryDirectory]} then { |
| 187 | fail "cannot locate a usable temporary directory" |
| 188 | } |
| 189 | |
| 190 | # |
| 191 | # NOTE: Setup the staging directory to have a unique name inside of the |
| 192 | # configured temporary directory. |
| 193 | # |
| 194 | set stagingDirectory [file normalize [file join $temporaryDirectory \ |
| 195 | [appendArgs $rootName . [pid]]]] |
| 196 | |
| 197 | ############################################################################### |
| 198 | |
| 199 | # |
| 200 | # NOTE: Configure the external zipping tool. First, see if it has already |
| 201 | # been pre-configured. If not, try to query it from the environment. |
| 202 | # Finally, fallback on the default of simply "zip", which will then |
| 203 | # be assumed to exist somewhere along the PATH. |
| 204 | # |
| 205 | if {![info exists zip]} then { |
| 206 | if {[info exists env(ZipTool)]} then { |
| 207 | set zip $env(ZipTool) |
| 208 | } |
| 209 | if {![info exists zip] || ![file exists $zip]} then { |
| 210 | set zip zip |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | # |
| 215 | # NOTE: Configure the external unzipping tool. First, see if it has already |
| 216 | # been pre-configured. If not, try to query it from the environment. |
| 217 | # Finally, fallback on the default of simply "unzip", which will then |
| 218 | # be assumed to exist somewhere along the PATH. |
| 219 | # |
| 220 | if {![info exists unzip]} then { |
| 221 | if {[info exists env(UnZipTool)]} then { |
| 222 | set unzip $env(UnZipTool) |
| 223 | } |
| 224 | if {![info exists unzip] || ![file exists $unzip]} then { |
| 225 | set unzip unzip |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | ############################################################################### |
| 230 | |
| 231 | # |
| 232 | # NOTE: Attempt to extract the SQLite version from the "sqlite3.h" header file |
| 233 | # in the source directory. This script assumes that the header file has |
| 234 | # already been generated by the build process. |
| 235 | # |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 236 | set pattern {^#define\s+SQLITE_VERSION\s+"(.*)"$} |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 237 | set data [readFile [file join $sourceDirectory sqlite3.h]] |
| 238 | |
| 239 | if {![regexp -line -- $pattern $data dummy version]} then { |
| 240 | fail [appendArgs "cannot locate SQLITE_VERSION value in \"" \ |
| 241 | [file join $sourceDirectory sqlite3.h] \"] |
| 242 | } |
| 243 | |
| 244 | ############################################################################### |
| 245 | |
| 246 | # |
| 247 | # NOTE: Setup the master file list data, including the necessary flags. |
| 248 | # |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 249 | if {![info exists fileNames(source)]} then { |
| 250 | set fileNames(source) [list "" "" "" \ |
| 251 | [file join $sourceDirectory sqlite3.h] \ |
| 252 | [file join $binaryDirectory <platform> sqlite3.lib] \ |
| 253 | [file join $binaryDirectory <platform> sqlite3.dll]] |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 254 | |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 255 | if {![info exists no(symbols)]} then { |
| 256 | lappend fileNames(source) \ |
| 257 | [file join $binaryDirectory <platform> sqlite3.pdb] |
| 258 | } |
| 259 | } |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 260 | |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 261 | if {![info exists fileNames(destination)]} then { |
| 262 | set fileNames(destination) [list \ |
| 263 | [file join $stagingDirectory extension.vsixmanifest] \ |
| 264 | [file join $stagingDirectory SDKManifest.xml] \ |
| 265 | [file join $stagingDirectory DesignTime CommonConfiguration \ |
| 266 | <platform> SQLite.WinRT.props] \ |
| 267 | [file join $stagingDirectory DesignTime CommonConfiguration \ |
| 268 | <platform> sqlite3.h] \ |
| 269 | [file join $stagingDirectory DesignTime CommonConfiguration \ |
| 270 | <platform> sqlite3.lib] \ |
| 271 | [file join $stagingDirectory Redist CommonConfiguration \ |
| 272 | <platform> sqlite3.dll]] |
| 273 | |
| 274 | if {![info exists no(symbols)]} then { |
| 275 | lappend fileNames(destination) \ |
mistachkin | ab8c4cf | 2012-08-06 22:29:26 +0000 | [diff] [blame] | 276 | [file join $stagingDirectory Redist Debug \ |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 277 | <platform> sqlite3.pdb] |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | if {![info exists fileNames(neutral)]} then { |
| 282 | set fileNames(neutral) [list 1 1 1 1 0 0] |
| 283 | |
| 284 | if {![info exists no(symbols)]} then { |
| 285 | lappend fileNames(neutral) 0 |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | if {![info exists fileNames(subst)]} then { |
| 290 | set fileNames(subst) [list 1 1 1 0 0 0] |
| 291 | |
| 292 | if {![info exists no(symbols)]} then { |
| 293 | lappend fileNames(subst) 0 |
| 294 | } |
| 295 | } |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 296 | |
| 297 | ############################################################################### |
| 298 | |
| 299 | # |
| 300 | # NOTE: Setup the list of platforms supported by this script. |
| 301 | # |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 302 | if {![info exists platformNames]} then { |
mistachkin | 50afa2a | 2012-07-31 08:15:56 +0000 | [diff] [blame] | 303 | set platformNames [list x86 x64 ARM] |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 304 | } |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 305 | |
| 306 | ############################################################################### |
| 307 | |
| 308 | # |
| 309 | # NOTE: Make sure the staging directory exists, creating it if necessary. |
| 310 | # |
| 311 | file mkdir $stagingDirectory |
| 312 | |
| 313 | # |
| 314 | # NOTE: Build the Tcl command used to extract the template package to the |
| 315 | # staging directory. |
| 316 | # |
| 317 | set extractCommand [list exec -- $unzip $templateFile -d $stagingDirectory] |
| 318 | |
| 319 | # |
| 320 | # NOTE: Extract the template package to the staging directory. |
| 321 | # |
| 322 | eval $extractCommand |
| 323 | |
| 324 | ############################################################################### |
| 325 | |
| 326 | # |
| 327 | # NOTE: Process each file in the master file list. There are actually four |
| 328 | # parallel lists that contain the source file names, destination file |
| 329 | # names, the platform-neutral flags, and the use-subst flags. When the |
| 330 | # platform-neutral flag is non-zero, the file is not platform-specific. |
| 331 | # When the use-subst flag is non-zero, the file is considered to be a |
| 332 | # text file that may contain Tcl variable and/or command replacements, |
| 333 | # to be dynamically replaced during processing. If the source file name |
| 334 | # is an empty string, then the destination file name will be assumed to |
| 335 | # already exist in the staging directory and will not be copied; however, |
| 336 | # dynamic replacements may still be performed on the destination file |
| 337 | # prior to the package being re-zipped. |
| 338 | # |
| 339 | foreach sourceFileName $fileNames(source) \ |
| 340 | destinationFileName $fileNames(destination) \ |
| 341 | isNeutral $fileNames(neutral) useSubst $fileNames(subst) { |
| 342 | # |
| 343 | # NOTE: If the current file is platform-neutral, then only one platform will |
| 344 | # be processed for it, namely "neutral"; otherwise, each supported |
| 345 | # platform will be processed for it individually. |
| 346 | # |
| 347 | foreach platformName [expr {$isNeutral ? [list neutral] : $platformNames}] { |
| 348 | # |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 349 | # NOTE: Use the actual platform name in the destination file name. |
| 350 | # |
| 351 | set newDestinationFileName [replacePlatform $destinationFileName \ |
| 352 | $platformName] |
| 353 | |
| 354 | # |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 355 | # NOTE: Does the source file need to be copied to the destination file? |
| 356 | # |
| 357 | if {[string length $sourceFileName] > 0} then { |
| 358 | # |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 359 | # NOTE: First, make sure the destination directory exists. |
| 360 | # |
| 361 | file mkdir [file dirname $newDestinationFileName] |
| 362 | |
| 363 | # |
| 364 | # NOTE: Then, copy the source file to the destination file verbatim. |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 365 | # |
| 366 | file copy [replacePlatform $sourceFileName $platformName] \ |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 367 | $newDestinationFileName |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 368 | } |
| 369 | |
| 370 | # |
| 371 | # NOTE: Does the destination file contain dynamic replacements that must |
| 372 | # be processed now? |
| 373 | # |
| 374 | if {$useSubst} then { |
| 375 | # |
| 376 | # NOTE: Perform any dynamic replacements contained in the destination |
| 377 | # file and then re-write it in-place. |
| 378 | # |
mistachkin | 391b364 | 2012-07-31 00:43:31 +0000 | [diff] [blame] | 379 | substFile $newDestinationFileName |
mistachkin | 4868019 | 2012-07-27 02:36:06 +0000 | [diff] [blame] | 380 | } |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | ############################################################################### |
| 385 | |
| 386 | # |
| 387 | # NOTE: Change the current directory to the staging directory so that the |
| 388 | # external archive building tool can pickup the necessary files using |
| 389 | # relative paths. |
| 390 | # |
| 391 | cd $stagingDirectory |
| 392 | |
| 393 | # |
| 394 | # NOTE: Build the Tcl command used to archive the final package in the |
| 395 | # output directory. |
| 396 | # |
| 397 | set archiveCommand [list exec -- $zip -r $outputFile *] |
| 398 | |
| 399 | # |
| 400 | # NOTE: Build the final package archive in the output directory. |
| 401 | # |
| 402 | eval $archiveCommand |
| 403 | |
| 404 | # |
| 405 | # NOTE: Change back to the previously saved current directory. |
| 406 | # |
| 407 | cd $currentDirectory |
| 408 | |
| 409 | # |
| 410 | # NOTE: Cleanup the temporary staging directory. |
| 411 | # |
| 412 | file delete -force $stagingDirectory |
| 413 | |
| 414 | ############################################################################### |
| 415 | |
| 416 | # |
| 417 | # NOTE: Success, emit the fully qualified path of the generated VSIX file. |
| 418 | # |
| 419 | puts stdout $outputFile |