1#!/usr/bin/tclsh 2# 3# This script is used to quickly test a VSIX (Visual Studio Extension) file 4# with Visual Studio 2015 on Windows. 5# 6# PREREQUISITES 7# 8# 1. This tool is Windows only. 9# 10# 2. This tool must be executed with "elevated administrator" privileges. 11# 12# 3. Tcl 8.4 and later are supported, earlier versions have not been tested. 13# 14# 4. The "sqlite-UWP-output.vsix" file is assumed to exist in the parent 15# directory of the directory containing this script. The [optional] first 16# command line argument to this script may be used to specify an alternate 17# file. However, currently, the file must be compatible with both Visual 18# Studio 2015 and the Universal Windows Platform. 19# 20# 5. The "VERSION" file is assumed to exist in the parent directory of the 21# directory containing this script. It must contain a version number that 22# matches the VSIX file being tested. 23# 24# 6. The temporary directory specified in the TEMP or TMP environment variables 25# must refer to an existing directory writable by the current user. 26# 27# 7. The VS140COMNTOOLS environment variable must refer to the Visual Studio 28# 2015 common tools directory. 29# 30# USAGE 31# 32# The first argument to this script is optional. If specified, it must be the 33# name of the VSIX file to test. 34# 35package require Tcl 8.4 36 37proc fail { {error ""} {usage false} } { 38 if {[string length $error] > 0} then { 39 puts stdout $error 40 if {!$usage} then {exit 1} 41 } 42 43 puts stdout "usage:\ 44[file tail [info nameofexecutable]]\ 45[file tail [info script]] \[vsixFile\]" 46 47 exit 1 48} 49 50proc isWindows {} { 51 # 52 # NOTE: Returns non-zero only when running on Windows. 53 # 54 return [expr {[info exists ::tcl_platform(platform)] && \ 55 $::tcl_platform(platform) eq "windows"}] 56} 57 58proc isAdministrator {} { 59 # 60 # NOTE: Returns non-zero only when running as "elevated administrator". 61 # 62 if {[isWindows]} then { 63 if {[catch {exec -- whoami /groups} groups] == 0} then { 64 set groups [string map [list \r\n \n] $groups] 65 66 foreach group [split $groups \n] { 67 # 68 # NOTE: Match this group line against the "well-known" SID for 69 # the "Administrators" group on Windows. 70 # 71 if {[regexp -- {\sS-1-5-32-544\s} $group]} then { 72 # 73 # NOTE: Match this group line against the attributes column 74 # sub-value that should be present when running with 75 # elevated administrator credentials. 76 # 77 if {[regexp -- {\sEnabled group(?:,|\s)} $group]} then { 78 return true 79 } 80 } 81 } 82 } 83 } 84 85 return false 86} 87 88proc getEnvironmentVariable { name } { 89 # 90 # NOTE: Returns the value of the specified environment variable or an empty 91 # string for environment variables that do not exist in the current 92 # process environment. 93 # 94 return [expr {[info exists ::env($name)] ? $::env($name) : ""}] 95} 96 97proc getTemporaryPath {} { 98 # 99 # NOTE: Returns the normalized path to the first temporary directory found 100 # in the typical set of environment variables used for that purpose 101 # or an empty string to signal a failure to locate such a directory. 102 # 103 set names [list] 104 105 foreach name [list TEMP TMP] { 106 lappend names [string toupper $name] [string tolower $name] \ 107 [string totitle $name] 108 } 109 110 foreach name $names { 111 set value [getEnvironmentVariable $name] 112 113 if {[string length $value] > 0} then { 114 return [file normalize $value] 115 } 116 } 117 118 return "" 119} 120 121proc appendArgs { args } { 122 # 123 # NOTE: Returns all passed arguments joined together as a single string 124 # with no intervening spaces between arguments. 125 # 126 eval append result $args 127} 128 129proc readFile { fileName } { 130 # 131 # NOTE: Reads and returns the entire contents of the specified file, which 132 # may contain binary data. 133 # 134 set file_id [open $fileName RDONLY] 135 fconfigure $file_id -encoding binary -translation binary 136 set result [read $file_id] 137 close $file_id 138 return $result 139} 140 141proc writeFile { fileName data } { 142 # 143 # NOTE: Writes the entire contents of the specified file, which may contain 144 # binary data. 145 # 146 set file_id [open $fileName {WRONLY CREAT TRUNC}] 147 fconfigure $file_id -encoding binary -translation binary 148 puts -nonewline $file_id $data 149 close $file_id 150 return "" 151} 152 153proc putsAndEval { command } { 154 # 155 # NOTE: Outputs a command to the standard output channel and then evaluates 156 # it in the callers context. 157 # 158 catch { 159 puts stdout [appendArgs "Running: " [lrange $command 1 end] ...\n] 160 } 161 162 return [uplevel 1 $command] 163} 164 165proc isBadDirectory { directory } { 166 # 167 # NOTE: Returns non-zero if the directory is empty, does not exist, -OR- is 168 # not a directory. 169 # 170 catch { 171 puts stdout [appendArgs "Checking directory \"" $directory \"...\n] 172 } 173 174 return [expr {[string length $directory] == 0 || \ 175 ![file exists $directory] || ![file isdirectory $directory]}] 176} 177 178proc isBadFile { fileName } { 179 # 180 # NOTE: Returns non-zero if the file name is empty, does not exist, -OR- is 181 # not a regular file. 182 # 183 catch { 184 puts stdout [appendArgs "Checking file \"" $fileName \"...\n] 185 } 186 187 return [expr {[string length $fileName] == 0 || \ 188 ![file exists $fileName] || ![file isfile $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 200if {![isWindows]} then { 201 fail "this tool only works properly on Windows" 202} 203 204if {![isAdministrator]} then { 205 fail "this tool must run with \"elevated administrator\" privileges" 206} 207 208set path [file normalize [file dirname $script]] 209set argc [llength $argv]; if {$argc > 1} then {fail "" true} 210 211if {$argc == 1} then { 212 set vsixFileName [lindex $argv 0] 213} else { 214 set vsixFileName [file join \ 215 [file dirname $path] sqlite-UWP-output.vsix] 216} 217 218############################################################################### 219 220if {[isBadFile $vsixFileName]} then { 221 fail [appendArgs \ 222 "VSIX file \"" $vsixFileName "\" does not exist"] 223} 224 225set versionFileName [file join [file dirname $path] VERSION] 226 227if {[isBadFile $versionFileName]} then { 228 fail [appendArgs \ 229 "Version file \"" $versionFileName "\" does not exist"] 230} 231 232set projectTemplateFileName [file join $path vsixtest.vcxproj.data] 233 234if {[isBadFile $projectTemplateFileName]} then { 235 fail [appendArgs \ 236 "Project template file \"" $projectTemplateFileName \ 237 "\" does not exist"] 238} 239 240set envVarName VS140COMNTOOLS 241set vsDirectory [getEnvironmentVariable $envVarName] 242 243if {[isBadDirectory $vsDirectory]} then { 244 fail [appendArgs \ 245 "Visual Studio 2015 directory \"" $vsDirectory \ 246 "\" from environment variable \"" $envVarName \ 247 "\" does not exist"] 248} 249 250set vsixInstaller [file join \ 251 [file dirname $vsDirectory] IDE VSIXInstaller.exe] 252 253if {[isBadFile $vsixInstaller]} then { 254 fail [appendArgs \ 255 "Visual Studio 2015 VSIX installer \"" $vsixInstaller \ 256 "\" does not exist"] 257} 258 259set envVarName ProgramFiles 260set programFiles [getEnvironmentVariable $envVarName] 261 262if {[isBadDirectory $programFiles]} then { 263 fail [appendArgs \ 264 "Program Files directory \"" $programFiles \ 265 "\" from environment variable \"" $envVarName \ 266 "\" does not exist"] 267} 268 269set msBuild [file join $programFiles MSBuild 14.0 Bin MSBuild.exe] 270 271if {[isBadFile $msBuild]} then { 272 fail [appendArgs \ 273 "MSBuild v14.0 executable file \"" $msBuild \ 274 "\" does not exist"] 275} 276 277set temporaryDirectory [getTemporaryPath] 278 279if {[isBadDirectory $temporaryDirectory]} then { 280 fail [appendArgs \ 281 "Temporary directory \"" $temporaryDirectory \ 282 "\" does not exist"] 283} 284 285############################################################################### 286 287set installLogFileName [appendArgs \ 288 [file rootname [file tail $vsixFileName]] \ 289 -install- [pid] .log] 290 291set commands(1) [list exec [file nativename $vsixInstaller]] 292 293lappend commands(1) /quiet /norepair 294lappend commands(1) [appendArgs /logFile: $installLogFileName] 295lappend commands(1) [file nativename $vsixFileName] 296 297############################################################################### 298 299set buildLogFileName [appendArgs \ 300 [file rootname [file tail $vsixFileName]] \ 301 -build-%configuration%-%platform%- [pid] .log] 302 303set commands(2) [list exec [file nativename $msBuild]] 304 305lappend commands(2) [file nativename [file join $path vsixtest.sln]] 306lappend commands(2) /target:Rebuild 307lappend commands(2) /property:Configuration=%configuration% 308lappend commands(2) /property:Platform=%platform% 309 310lappend commands(2) [appendArgs \ 311 /logger:FileLogger,Microsoft.Build.Engine\;Logfile= \ 312 [file nativename [file join $temporaryDirectory \ 313 $buildLogFileName]] \;Verbosity=diagnostic] 314 315############################################################################### 316 317set uninstallLogFileName [appendArgs \ 318 [file rootname [file tail $vsixFileName]] \ 319 -uninstall- [pid] .log] 320 321set commands(3) [list exec [file nativename $vsixInstaller]] 322 323lappend commands(3) /quiet /norepair 324lappend commands(3) [appendArgs /logFile: $uninstallLogFileName] 325lappend commands(3) [appendArgs /uninstall:SQLite.UWP.2015] 326 327############################################################################### 328 329if {1} then { 330 catch { 331 puts stdout [appendArgs \ 332 "Install log: \"" [file nativename [file join \ 333 $temporaryDirectory $installLogFileName]] \"\n] 334 } 335 336 catch { 337 puts stdout [appendArgs \ 338 "Build logs: \"" [file nativename [file join \ 339 $temporaryDirectory $buildLogFileName]] \"\n] 340 } 341 342 catch { 343 puts stdout [appendArgs \ 344 "Uninstall log: \"" [file nativename [file join \ 345 $temporaryDirectory $uninstallLogFileName]] \"\n] 346 } 347} 348 349############################################################################### 350 351if {1} then { 352 putsAndEval $commands(1) 353 354 set versionNumber [string trim [readFile $versionFileName]] 355 set data [readFile $projectTemplateFileName] 356 set data [string map [list %versionNumber% $versionNumber] $data] 357 358 set projectFileName [file join $path vsixtest.vcxproj] 359 writeFile $projectFileName $data 360 361 set platforms [list x86 x64 ARM] 362 set configurations [list Debug Release] 363 364 foreach platform $platforms { 365 foreach configuration $configurations { 366 putsAndEval [string map [list \ 367 %platform% $platform %configuration% $configuration] \ 368 $commands(2)] 369 } 370 } 371 372 putsAndEval $commands(3) 373} 374