Background
I've been mulling over some ideas on a bootstrapper for D. I'd like users and contributors, on either Windows or Unixy systems, to be able to go from nothing to anything they need as easily as possible.
In a best case scenario, these would be the ideal qualities:
- No prerequisites, other than what's likely to be already installed on nearly any system.
- One-step setup: No "download this archive, extract it somewhere, and run the file named blahblahblah".
- No prebuilt binaries for the custom tools: Because then that would need to be bootstrapped, too. Not a big problem on Windows, but prebuilt binaries in the Unix words can be tricky due to all the varieties (not impossible of course, but non-trivial).
- Scriptable
- As little shell/batch scripting as possible: Get straight to a bootstrapped compiler so everything else can be written directly in D.
- Maximum compatibility
Those are lofty, conflicting, and perhaps unrealistic goals, but I'd like to get as close to that ideal as possible.
One interesting sub-problem of this is how to handle Windows:
Due to the first two goals, it's clear some way will probably be needed to automate downloading files from the internet. On Unixy systems this is easy - most already have either wget or curl installed (and the ones that don't are generally very expert-oriented anyway, so it's not too terrible to expect those users to install wget or curl themselves).
But Windows has no built-in command-line tool to download files. You can get curl and wget for Windows, but most Windows developers won't already have them installed. So what to do on Windows? It's not like we can just wget a copy of wget itself...
Step One: Download Tool in VBScript
We can't download files on just any run-of-the-mill Windows box using batch scripting. But we can do it using VBScript, which is a standard part of Windows:
Option Explicit
Dim args, http, fileSystem, adoStream, url, target, status
Set args = Wscript.Arguments
Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
url = args(0)
target = args(1)
WScript.Echo "Getting '" & target & "' from '" & url & "'..."
http.Open "GET", url, False
http.Send
status = http.Status
If status <> 200 Then
WScript.Echo "FAILED to download: HTTP Status " & status
WScript.Quit 1
End If
Set adoStream = CreateObject("ADODB.Stream")
adoStream.Open
adoStream.Type = 1
adoStream.Write http.ResponseBody
adoStream.Position = 0
Set fileSystem = CreateObject("Scripting.FileSystemObject")
If fileSystem.FileExists(target) Then fileSystem.DeleteFile target
adoStream.SaveToFile target
adoStream.Close
Run it like this:
> cscript //Nologo download.vbs
> cscript //Nologo download.vbs http://example.com blah.html
Not bad. In fact, with this tool, we already have what we need to download files from within batch. Just invoke the script from a batch file, and you're good.
But there are still some problems:
First of all, I'd like to be able to give users instructions that aren't prepended with cscript. Plus, I don't particularly feel like continuing down this path of trying to remember how to use VBScript. So, even though batch is hardly a VB-killing language, I would like to be invoking any VBScript code from a batch file.
But that leads to another problem. Now I have, at a minimum, two distinct files: A batch file and a VBScript file. This means I'll need the user to download two files, or make them download and extract a zip, or make a self-extracting archive which violates the "no custom prebuilt binaries" goal. Is there any way out? Without sacrificing any of my goals?
Step Two: Downloader Ex Machina
Like Unix, the Windows command line can redirect output to files. Use > to overwrite a file, or >> to append:
> echo First line > file.txt
> echo Second line >> file.txt
> type file.txt
First line
Second line
Do you see where I'm going with this?
A common trick in the Unix world, we can use echo and file redirection to generate a text file on-the-fly. A text file such as...a VBScript!:
@echo off
echo WScript.Echo "Hello world" > hello.vbs
cscript //Nologo hello.vbs
Run that batch, and it will create the following VBScript hello world:
WScript.Echo "Hello world"
Then the batch file invokes the VBScript file it just created, and "Hello world" is printed! We've just embedded a VBScript tool into a batch script.
Now we have everything we need to download a file using nothing more than a one-file batch script. All we do is embed the VBScript downloader tool into batch (using the caret ^ to escape any characters echo has trouble with):
@echo off
set DLOAD_SCRIPT=download.vbs
echo Option Explicit > %DLOAD_SCRIPT%
echo Dim args, http, fileSystem, adoStream, url, target, status >> %DLOAD_SCRIPT%
echo. >> %DLOAD_SCRIPT%
echo Set args = Wscript.Arguments >> %DLOAD_SCRIPT%
echo Set http = CreateObject("WinHttp.WinHttpRequest.5.1") >> %DLOAD_SCRIPT%
echo url = args(0) >> %DLOAD_SCRIPT%
echo target = args(1) >> %DLOAD_SCRIPT%
echo WScript.Echo "Getting '" ^& target ^& "' from '" ^& url ^& "'..." >> %DLOAD_SCRIPT%
echo. >> %DLOAD_SCRIPT%
echo http.Open "GET", url, False >> %DLOAD_SCRIPT%
echo http.Send >> %DLOAD_SCRIPT%
echo status = http.Status >> %DLOAD_SCRIPT%
echo. >> %DLOAD_SCRIPT%
echo If status ^<^> 200 Then >> %DLOAD_SCRIPT%
echo WScript.Echo "FAILED to download: HTTP Status " ^& status >> %DLOAD_SCRIPT%
echo WScript.Quit 1 >> %DLOAD_SCRIPT%
echo End If >> %DLOAD_SCRIPT%
echo. >> %DLOAD_SCRIPT%
echo Set adoStream = CreateObject("ADODB.Stream") >> %DLOAD_SCRIPT%
echo adoStream.Open >> %DLOAD_SCRIPT%
echo adoStream.Type = 1 >> %DLOAD_SCRIPT%
echo adoStream.Write http.ResponseBody >> %DLOAD_SCRIPT%
echo adoStream.Position = 0 >> %DLOAD_SCRIPT%
echo. >> %DLOAD_SCRIPT%
echo Set fileSystem = CreateObject("Scripting.FileSystemObject") >> %DLOAD_SCRIPT%
echo If fileSystem.FileExists(target) Then fileSystem.DeleteFile target >> %DLOAD_SCRIPT%
echo adoStream.SaveToFile target >> %DLOAD_SCRIPT%
echo adoStream.Close >> %DLOAD_SCRIPT%
echo. >> %DLOAD_SCRIPT%
cscript //Nologo %DLOAD_SCRIPT% http://example.com blah.html
echo File downloaded via batch!
Nice!
Read more