From b639d97fc24786c7866ae842b6163fa891a904ad Mon Sep 17 00:00:00 2001
From: Daira Hopwood <daira@jacaranda.org>
Date: Sun, 2 Nov 2014 21:51:00 +0000
Subject: [PATCH] WIP

---
 .../windows/installer/installer/installer.cpp | 183 ++++++++----------
 .../installer/installer/installer.vcproj      |   4 +
 .../windows/installer/installer/stdafx.h      |   1 +
 .../setuptools/command/scriptsetup.py         |  15 +-
 4 files changed, 98 insertions(+), 105 deletions(-)

diff --git a/misc/build_helpers/windows/installer/installer/installer.cpp b/misc/build_helpers/windows/installer/installer/installer.cpp
index fd072d75..f372e47d 100644
--- a/misc/build_helpers/windows/installer/installer/installer.cpp
+++ b/misc/build_helpers/windows/installer/installer/installer.cpp
@@ -10,18 +10,19 @@ void empty_directory(wchar_t *destination_dir);
 void unzip_from_executable(wchar_t *executable_path, wchar_t *destination_dir);
 size_t read_uint32_le(unsigned char *b);
 void unzip(wchar_t *zip_path, wchar_t *destination_dir);
-bool spawn_with_redirect(FILE *redirect, unsigned char *output_buf, size_t output_size, const wchar_t *argv[]);
 void install_python(wchar_t *python_installer_dir);
 void scriptsetup(wchar_t *destination_dir);
-void pause();
+bool spawn_with_redirect(FILE *redirect, unsigned char *output_buf, size_t output_size, const wchar_t *argv[]);
 
 #define fail_unless(x, s) if (!(x)) { fail(s); }
 void fail(char *s);
 void warn(char *s);
+void pause();
 
 #define REQUIRED_PYTHON_VERSION_PREFIX "Python 2.7."
+#define PYTHON_INSTALLER_FILESPEC L"python*.msi"
 
-// defines PKGNAME_AND_VERSION
+// defines PKGNAME_AND_VERSION, written by setup.py
 #include "_version.h"
 
 
@@ -37,9 +38,14 @@ int wmain(int argc, wchar_t *argv[]) {
 
 	if (argc >= 2 && wcscmp(argv[1], L"--help") == 0) {
 		printf("installer <destination_dir>\n");
+		return 1;
 	}
 	wchar_t *destination_dir = (argc >= 2) ? argv[1] : get_default_destination_dir();
 
+	size_t len = wcslen(destination_dir);
+	// FIXME: strip trailing slash rather than rejecting it
+	fail_unless(len > 0 && destination_dir[len-1] != '\\' && len < MAX_PATH-1, "Invalid destination directory.");
+
 	self_extract(destination_dir);
 	install_python(destination_dir);
 	scriptsetup(destination_dir);
@@ -66,31 +72,6 @@ void self_extract(wchar_t *destination_dir) {
 }
 
 void empty_directory(wchar_t *destination_dir) {
-#if 0
-	// Delete contents of destination_dir if it already exists.
-
-	struct _stat buf;
-	if (_wstat(destination_dir, &buf) == 0) {
-		wchar_t destination_dir_dblnul[MAX_PATH+1];
-		size_t len = wcslen(destination_dir);
-		fail_unless(len < MAX_PATH, "Destination path is too long.");
-		wcscpy(destination_dir_dblnul, destination_dir);
-		destination_dir_dblnul[len+1] = L'\0';
-
-		SHFILEOPSTRUCTW shell_file_op = {
-			NULL,
-			FO_DELETE,
-			destination_dir_dblnul,
-			NULL,
-			FOF_SILENT | FOF_NOERRORUI | FOF_NOCONFIRMATION,
-			FALSE,
-			NULL,
-			NULL
-		};
-		int res = SHFileOperationW(&shell_file_op);
-		fail_unless(res == 0, "Could not delete existing contents of destination directory.");
-	}
-#endif
 	// Create an empty directory at destination_dir.
 	errno = 0;
 	int res = _wmkdir(destination_dir);
@@ -269,6 +250,76 @@ void unzip(wchar_t *zip_path, wchar_t *destination_dir) {
 	CoUninitialize();
 }
 
+void install_python(wchar_t *python_installer_dir) {
+	printf("Checking for " REQUIRED_PYTHON_VERSION_PREFIX "..\n");
+
+	unsigned char output_buf[1024];
+	const wchar_t *argv[] = { L"python", L"-V", NULL };
+	bool res = spawn_with_redirect(stderr, output_buf, sizeof(output_buf), &argv[0]);
+	if (res) {
+		printf("Found %s", (char *) output_buf);
+		if (strncmp((char *) output_buf, REQUIRED_PYTHON_VERSION_PREFIX, strlen(REQUIRED_PYTHON_VERSION_PREFIX)) == 0) {
+			return;
+		} else {
+			printf("but we need a newer version.\n");
+		}
+	} else {
+		printf("No Python found.\n");
+	}
+
+	wchar_t installer_pattern[MAX_PATH];
+	int n = _snwprintf(installer_pattern, MAX_PATH, L"%ls\\%ls", python_installer_dir, PYTHON_INSTALLER_FILESPEC);
+	fail_unless(n >= 0 && n < MAX_PATH, "Could not construct wildcard path to Python installer.");
+
+	WIN32_FIND_DATA find_data;
+	HANDLE search_handle = FindFirstFileW(installer_pattern, &find_data);
+	fail_unless(search_handle != INVALID_HANDLE_VALUE, "Could not find the Python installer.")
+
+	wchar_t installer_path[MAX_PATH];
+	n = _snwprintf(installer_path, MAX_PATH, L"%ls\\%ls", python_installer_dir, find_data.cFileName);
+	fail_unless(n >= 0 && n < MAX_PATH, "Could not construct path to Python installer.");
+	printf("%ls\n", installer_path);
+
+	wchar_t installer_path_quoted[2+MAX_PATH];
+	n = _snwprintf(installer_path_quoted, 2+MAX_PATH, L"\"%ls\"", installer_path);
+	fail_unless(n >= 0 && n < 2+MAX_PATH, "Could not construct quoted path to Python installer.");
+	printf("%ls\n", installer_path_quoted);
+
+	// <https://www.python.org/download/releases/2.5/msi/>
+	// XXX I'm not sure whether or not we want "/passive". These options
+	// may silently remove a previous Python installation that was not
+	// detected by the check above, and we'd like that to prompt.
+	const wchar_t *python_installer_argv[] = {
+		L"msiexec", L"/i", installer_path_quoted,
+		L"/passive", L"/norestart", L"ALLUSERS=1", L"ADDLOCAL=Extensions", NULL
+	};
+	errno = 0;
+	intptr_t exit_code = _wspawnvp(P_WAIT, python_installer_argv[0], python_installer_argv);
+	int saved_errno = errno;
+
+	_wunlink(installer_path); // ignore errors
+
+	fail_unless(saved_errno == 0, "Could not execute Python installer.");
+	fail_unless(exit_code == 0, "Python installer failed.");
+}
+
+void scriptsetup(wchar_t *destination_dir) {
+	// 12 = length of --addpath=""
+	wchar_t addpath_option_quoted[12+MAX_PATH];
+	int n = _snwprintf(addpath_option_quoted, 12+MAX_PATH, L"--addpath=\"%ls\\%ls\\bin\"", destination_dir, PKGNAME_AND_VERSION);
+	fail_unless(n >= 0 && n < 12+MAX_PATH, "Could not construct path for bin directory.");
+
+	unsigned char output_buf[10240];
+	const wchar_t *scriptsetup_argv[] = {
+		L"python", L"setup.py", L"scriptsetup",
+		L"--allusers", addpath_option_quoted,
+		NULL
+	};
+	bool res = spawn_with_redirect(stdout, output_buf, sizeof(output_buf), &scriptsetup_argv[0]);
+	puts((char *) output_buf);
+	fail_unless(res, "Could not set up Python to run the 'tahoe' command.");
+}
+
 bool spawn_with_redirect(FILE *redirect, unsigned char *output_buf, size_t output_size, const wchar_t *argv[]) {
 	bool result = false;
 	fail_unless(output_size > 0, "Invalid output_size.");
@@ -350,79 +401,10 @@ bool spawn_with_redirect(FILE *redirect, unsigned char *output_buf, size_t outpu
 	return (exit_code == 0);
 }
 
-void install_python(wchar_t *python_installer_dir) {
-	printf("Checking for " REQUIRED_PYTHON_VERSION_PREFIX "..\n");
-
-	unsigned char output_buf[1024];
-	const wchar_t *argv[] = { L"python", L"-V", NULL };
-	bool res = spawn_with_redirect(stderr, output_buf, sizeof(output_buf), &argv[0]);
-	if (res) {
-		printf("Found %s", (char *) output_buf);
-		if (strncmp((char *) output_buf, REQUIRED_PYTHON_VERSION_PREFIX, strlen(REQUIRED_PYTHON_VERSION_PREFIX)) == 0) {
-			return;
-		} else {
-			printf("but we need a newer version.\n");
-		}
-	} else {
-		printf("No Python found.\n");
-	}
-
-	wchar_t installer_wildcard[] = L"\\python*.msi";
-	if (python_installer_dir[wcslen(python_installer_dir)-1] == '\\') {
-		wcscpy(installer_wildcard, L"*.msi");
-	}
-	wchar_t installer_pattern[MAX_PATH];
-	fail_unless(wcslen(python_installer_dir) < MAX_PATH - wcslen(installer_wildcard),
-	            "Could not construct pattern for Python installer.")
-	wcscpy(installer_pattern, python_installer_dir);
-	wcscat(installer_pattern, installer_wildcard);
-
-	WIN32_FIND_DATA find_data;
-	HANDLE search_handle = FindFirstFileW(installer_pattern, &find_data);
-	fail_unless(search_handle != INVALID_HANDLE_VALUE,
-	            "Could not find the Python installer.")
-
-	fail_unless(wcslen(python_installer_dir) < MAX_PATH - wcslen(find_data.cFileName),
-	            "Could not construct path to Python installer.")
-
-	wchar_t installer_path[MAX_PATH];
-	wcscpy(installer_path, python_installer_dir);
-	wcscat(installer_path, find_data.cFileName);
-
-	// <https://www.python.org/download/releases/2.5/msi/>
-	// "/qb!" works, but it may silently remove a previous Python installation
-	// that was not detected by the check above, and we want that to prompt.
-	const wchar_t *python_installer_argv[] = {
-		L"msiexec", L"/i", installer_path,
-		// L"/qb!",
-		L"ALLUSERS=1", L"ADDLOCAL=Extensions", NULL
-	};
-	errno = 0;
-	intptr_t exit_code = _wspawnvp(P_WAIT, python_installer_argv[0], python_installer_argv);
-	fail_unless(errno == 0, "Could not execute Python installer.");
-	fail_unless(exit_code == 0, "Python installer failed.");
-}
-
-void scriptsetup(wchar_t *destination_dir) {
-	wchar_t bin_dir[MAX_PATH];
-	int n = wsnprintf(bin_dir, L"%ls\\%ls\\bin", destination_dir, PKGNAME_AND_VERSION);
-	fail_unless(n >= 0 && n < MAX_PATH, "Could not construct path for bin directory.");
-
-	unsigned char output_buf[10240];
-	const wchar_t *scriptsetup_argv[] = {
-		L"python", L"setup.py", L"scriptsetup",
-		L"--allusers", L"--addpaths", bin_dir,
-		NULL
-	};
-	bool res = spawn_with_redirect(stdout, output_buf, sizeof(output_buf), &scriptsetup_argv[0]);
-	puts((char *) output_buf);
-	fail_unless(res, "Could not set up Python to run the 'tahoe' command.");
-}
-
 void fail(char *s) {
 	// TODO: show dialog box
 	fprintf(stderr, "%s\n", s);
-	pause()
+	pause();
 	exit(1);
 }
 
@@ -431,7 +413,6 @@ void warn(char *s) {
 }
 
 void pause() {
-	printf("Press any key to finish.");
-	char buf[2];
-	fgets(buf, 1, stdin);
+	printf("Press any key to finish.\n");
+	_getch();
 }
\ No newline at end of file
diff --git a/misc/build_helpers/windows/installer/installer/installer.vcproj b/misc/build_helpers/windows/installer/installer/installer.vcproj
index a2fb0851..a2699a2b 100644
--- a/misc/build_helpers/windows/installer/installer/installer.vcproj
+++ b/misc/build_helpers/windows/installer/installer/installer.vcproj
@@ -204,6 +204,10 @@
 			Filter="h;hpp;hxx;hm;inl;inc;xsd"
 			UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
 			>
+			<File
+				RelativePath=".\_version.h"
+				>
+			</File>
 			<File
 				RelativePath=".\stdafx.h"
 				>
diff --git a/misc/build_helpers/windows/installer/installer/stdafx.h b/misc/build_helpers/windows/installer/installer/stdafx.h
index 5cd085a4..9f8375b2 100644
--- a/misc/build_helpers/windows/installer/installer/stdafx.h
+++ b/misc/build_helpers/windows/installer/installer/stdafx.h
@@ -15,6 +15,7 @@
 #include <fcntl.h>
 #include <process.h>
 #include <sys/stat.h>
+#include <conio.h>
 
 // Turn off the warnings nagging you to use the more complicated *_s
 // "secure" functions that are actually more difficult to use securely.
diff --git a/setuptools-0.6c16dev5.egg/setuptools/command/scriptsetup.py b/setuptools-0.6c16dev5.egg/setuptools/command/scriptsetup.py
index db68c07a..e4dd9dd0 100644
--- a/setuptools-0.6c16dev5.egg/setuptools/command/scriptsetup.py
+++ b/setuptools-0.6c16dev5.egg/setuptools/command/scriptsetup.py
@@ -1,6 +1,6 @@
 from distutils.errors import DistutilsSetupError
 from setuptools import Command
-import sys
+import sys, os
 
 class scriptsetup(Command):
     action = (sys.platform == "win32"
@@ -10,11 +10,14 @@ class scriptsetup(Command):
     user_options = [
         ('allusers', 'a',
          'make changes for all users of this Windows installation (requires Administrator privileges)'),
+        ('addpath=', 'p',
+         'add a directory to the PATH (user by default; system if --allusers is specified)'),
     ]
     boolean_options = ['allusers']
 
     def initialize_options(self):
         self.allusers = False
+        self.addpath = None
 
     def finalize_options(self):
         pass
@@ -23,11 +26,13 @@ class scriptsetup(Command):
         if sys.platform != "win32":
             print "\n'scriptsetup' isn't needed on non-Windows platforms."
         else:
-            do_scriptsetup(self.allusers)
+            do_scriptsetup(self.allusers, self.addpath)
 
 
-def do_scriptsetup(allusers=False):
+def do_scriptsetup(allusers=False, addpath=None):
     print "\nSetting up environment to run scripts for %s..." % (allusers and "all users" or "the current user")
+    if addpath:
+		print "%r will be added to the PATH." % (addpath,)
 
     from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, \
         REG_SZ, REG_EXPAND_SZ, KEY_QUERY_VALUE, KEY_SET_VALUE, \
@@ -241,13 +246,15 @@ def do_scriptsetup(allusers=False):
         except Exception, e:
             print "Warning: %r" % (e,)
 
-
     changed_assoc = associate(".pyscript", "Python.File", allusers)
 
     changed_env = False
     try:
         changed_env |= add_to_environment("PATHEXT", ".pyscript", allusers)
         changed_env |= add_to_environment("PATHEXT", ".pyw",      allusers)
+        if addpath:
+            abs_path = os.path.abspath(os.path.normpath(addpath))
+            changed_env |= add_to_environment("PATH", abs_path, allusers)
     finally:
         CloseKey(user_env)
         CloseKey(system_env)
-- 
2.45.2