From 10b17dc367890bc3efeb32e3ec6afc832899e045 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 21 Oct 2014 21:11:56 +0100 Subject: [PATCH] Make a start on the Windows installer. Signed-off-by: Daira Hopwood --- .../windows/installer/.gitattributes | 2 + .../windows/installer/installer.sln | 20 + .../windows/installer/installer/app.rc | 4 + .../windows/installer/installer/installer.cpp | 418 ++++++++++++++++++ .../installer/installer/installer.vcproj | 237 ++++++++++ .../windows/installer/installer/logo.ico | Bin 0 -> 304886 bytes .../windows/installer/installer/logo.svg | 157 +++++++ .../windows/installer/installer/make-icon.sh | 23 + .../windows/installer/installer/stdafx.cpp | 5 + .../windows/installer/installer/stdafx.h | 29 ++ .../windows/installer/installer/targetver.h | 13 + .../setuptools/command/scriptsetup.py | 15 +- 12 files changed, 919 insertions(+), 4 deletions(-) create mode 100644 misc/build_helpers/windows/installer/.gitattributes create mode 100644 misc/build_helpers/windows/installer/installer.sln create mode 100644 misc/build_helpers/windows/installer/installer/app.rc create mode 100644 misc/build_helpers/windows/installer/installer/installer.cpp create mode 100644 misc/build_helpers/windows/installer/installer/installer.vcproj create mode 100644 misc/build_helpers/windows/installer/installer/logo.ico create mode 100644 misc/build_helpers/windows/installer/installer/logo.svg create mode 100755 misc/build_helpers/windows/installer/installer/make-icon.sh create mode 100644 misc/build_helpers/windows/installer/installer/stdafx.cpp create mode 100644 misc/build_helpers/windows/installer/installer/stdafx.h create mode 100644 misc/build_helpers/windows/installer/installer/targetver.h diff --git a/misc/build_helpers/windows/installer/.gitattributes b/misc/build_helpers/windows/installer/.gitattributes new file mode 100644 index 00000000..33ffd98d --- /dev/null +++ b/misc/build_helpers/windows/installer/.gitattributes @@ -0,0 +1,2 @@ +installer.sln -text +installer/installer.vcproj -text diff --git a/misc/build_helpers/windows/installer/installer.sln b/misc/build_helpers/windows/installer/installer.sln new file mode 100644 index 00000000..95d7a25d --- /dev/null +++ b/misc/build_helpers/windows/installer/installer.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "installer\installer.vcproj", "{413A02C3-FBCC-42B4-9277-F1CCBDD274CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Debug|Win32.ActiveCfg = Debug|Win32 + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Debug|Win32.Build.0 = Debug|Win32 + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Release|Win32.ActiveCfg = Release|Win32 + {413A02C3-FBCC-42B4-9277-F1CCBDD274CA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/misc/build_helpers/windows/installer/installer/app.rc b/misc/build_helpers/windows/installer/installer/app.rc new file mode 100644 index 00000000..f82f4b65 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/app.rc @@ -0,0 +1,4 @@ +#include +#define IDC_STATIC -1 + +100 ICON "logo.ico" diff --git a/misc/build_helpers/windows/installer/installer/installer.cpp b/misc/build_helpers/windows/installer/installer/installer.cpp new file mode 100644 index 00000000..f372e47d --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/installer.cpp @@ -0,0 +1,418 @@ +// installer.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" + +int wmain(int argc, wchar_t *argv[]); +wchar_t * get_default_destination_dir(); +void self_extract(wchar_t *destination_dir); +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); +void install_python(wchar_t *python_installer_dir); +void scriptsetup(wchar_t *destination_dir); +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, written by setup.py +#include "_version.h" + + +void noop_handler(const wchar_t * expression, + const wchar_t * function, + const wchar_t * file, + unsigned int line, + uintptr_t pReserved) { +} + +int wmain(int argc, wchar_t *argv[]) { + _set_invalid_parameter_handler(noop_handler); + + if (argc >= 2 && wcscmp(argv[1], L"--help") == 0) { + printf("installer \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); + pause(); + + return 0; +} + +wchar_t * get_default_destination_dir() { + // TODO: get Program Files directory from the registry + return L"C:\\Program Files\\Tahoe-LAFS"; +} + +void self_extract(wchar_t *destination_dir) { + wchar_t executable_path[MAX_PATH]; + + HMODULE hModule = GetModuleHandle(NULL); + fail_unless(hModule != NULL, "Could not get the module handle."); + GetModuleFileNameW(hModule, executable_path, MAX_PATH); + fail_unless(GetLastError() == ERROR_SUCCESS, "Could not get the path of the current executable."); + + empty_directory(destination_dir); + unzip_from_executable(executable_path, destination_dir); +} + +void empty_directory(wchar_t *destination_dir) { + // Create an empty directory at destination_dir. + errno = 0; + int res = _wmkdir(destination_dir); + fail_unless((res == 0 && errno == 0) || errno == EEXIST, "Could not create destination directory."); +} + +void unzip_from_executable(wchar_t *executable_path, wchar_t *destination_dir) { + // shell32's zipped folder implementation is strict about the zip format and + // does not support unzipping a self-extracting exe directly. So we copy the + // original zip file that was appended to the exe to a temporary directory, + // and use shell32 to unzip it from there. To get the length of the zip file, + // we look at its "end of central directory record", which is documented at + // . + // For simplicity we only handle the case of a zip file that has no archive + // comment, that does not use disk spanning, and that does not have a + // "Zip64 end of central directory record". + + // APPNOTE.TXT section 4.3.16. + const size_t sizeof_eocd = 22; + unsigned char end_data[sizeof_eocd]; + unsigned char eocd_signature[] = {0x50, 0x4B, 0x05, 0x06}; + unsigned char comment_length[] = {0x00, 0x00}; + unsigned char disk_num[] = {0x00, 0x00}; + + errno = 0; + FILE *f = _wfopen(executable_path, L"rb"); + fail_unless(f != NULL && errno == 0 && ferror(f) == 0, + "Could not open executable file."); + + fseek(f, -(off_t) sizeof_eocd, SEEK_END); + fail_unless(errno == 0 && ferror(f) == 0, + "Could not seek to end-of-central-directory record."); + + __int64 eocd_offset = _ftelli64(f); + fail_unless(errno == 0 && ferror(f) == 0 && eocd_offset >= 0, + "Could not read position of end-of-central-directory record."); + fail_unless(eocd_offset + sizeof_eocd <= 0xFFFFFFFFi64, + "Cannot read an executable file >= 4 GiB."); + + size_t n = fread(end_data, sizeof(end_data), 1, f); + fail_unless(n == 1 && errno == 0 && ferror(f) == 0, + "Could not read end records."); + + fail_unless(memcmp(end_data + sizeof(end_data) - sizeof(comment_length), + comment_length, sizeof(comment_length)) == 0, + "Cannot read a zip file that has an archive comment."); + + fail_unless(memcmp(end_data, eocd_signature, sizeof(eocd_signature)) == 0, + "Could not find the end-of-central-directory signature."); + + fail_unless(memcmp(end_data + 4, disk_num, sizeof(disk_num)) == 0 && + memcmp(end_data + 6, disk_num, sizeof(disk_num)) == 0, + "Cannot read a zipfile that spans disks."); + + size_t cd_length = read_uint32_le(end_data + 12); + size_t cd_offset = read_uint32_le(end_data + 16); + __int64 zip_length = (__int64) cd_offset + cd_length + sizeof_eocd; + fail_unless(zip_length <= 0x7FFFFFFFi64, + "Cannot copy a zip file >= 2 GiB."); + + fseek(f, -(off_t) zip_length, SEEK_END); + fail_unless(errno == 0 && ferror(f) == 0, + "Could not seek to start of embedded zip file."); + + const wchar_t tmp_filename[] = L"tahoe-lafs.zip"; // FIXME make this more unique. + wchar_t tmp_path[MAX_PATH]; + DWORD len = GetTempPathW(MAX_PATH, tmp_path); + fail_unless(len > 0, "Could not obtain temporary directory path."); + fail_unless(len < MAX_PATH - wcslen(tmp_filename), "Temporary directory path is too long."); + wcscpy(tmp_path + len, tmp_filename); + + errno = 0; + FILE *tmp_file = _wfopen(tmp_path, L"wb"); + fail_unless(tmp_file != NULL && errno == 0 && ferror(f) == 0, + "Could not open temporary zip file."); + + // FIXME: delete the temporary file if there is an error. + unsigned char buf[16384]; + size_t remaining_length = (size_t) zip_length; + while (remaining_length > 0) { + size_t chunk_length = min(remaining_length, sizeof(buf)); + n = fread(buf, chunk_length, 1, f); + fail_unless(n == 1 && errno == 0 && ferror(f) == 0, + "Could not read from executable file."); + fwrite(buf, chunk_length, 1, tmp_file); + fail_unless(n == 1 && errno == 0 && ferror(f) == 0, + "Could not write to temporary file."); + remaining_length -= chunk_length; + } + int res = fclose(tmp_file); + fail_unless(res == 0, "Could not close temporary zip file."); + fclose(f); // ignore errors + + unzip(tmp_path, destination_dir); + _wunlink(tmp_path); // ignore errors +} + +// read unsigned little-endian 32-bit integer +size_t read_uint32_le(unsigned char *b) { + return ((size_t) b[0] ) | + ((size_t) b[1] << 8) | + ((size_t) b[2] << 16) | + ((size_t) b[3] << 24); +} + +void unzip(wchar_t *zip_path, wchar_t *destination_dir) { + // Based loosely on + // . + + // CoInitializeEx: + HRESULT res = CoInitializeEx(NULL, 0); + fail_unless(res == S_OK || res == S_FALSE, "Could not initialize COM."); + + // SysAllocString: + // BSTR: + + VARIANT zip_path_var; + zip_path_var.vt = VT_BSTR; + zip_path_var.bstrVal = SysAllocString(zip_path); + fail_unless(zip_path_var.bstrVal != NULL, "Could not allocate string for zip file path."); + + VARIANT destination_dir_var; + destination_dir_var.vt = VT_BSTR; + destination_dir_var.bstrVal = SysAllocString(destination_dir); + fail_unless(destination_dir_var.bstrVal != NULL, "Could not allocate string for destination directory path."); + + // CoCreateInstance: + IShellDispatch *shell; + res = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_INPROC_SERVER, IID_IShellDispatch, (void **) &shell); + fail_unless(res == S_OK, "Could not create Shell instance."); + + // Folder.NameSpace: + Folder *zip_folder = NULL; + res = shell->NameSpace(zip_path_var, &zip_folder); + fail_unless(res == S_OK && zip_folder != NULL, "Could not create zip Folder object."); + + Folder *destination_folder = NULL; + res = shell->NameSpace(destination_dir_var, &destination_folder); + fail_unless(res == S_OK && destination_folder != NULL, "Could not create destination Folder object."); + + FolderItems *zip_folderitems = NULL; + zip_folder->Items(&zip_folderitems); + fail_unless(zip_folderitems != NULL, "Could not create zip FolderItems object."); + + VARIANT zip_idispatch_var; + zip_idispatch_var.vt = VT_DISPATCH; + zip_idispatch_var.pdispVal = NULL; + zip_folderitems->QueryInterface(IID_IDispatch, (void **) &zip_idispatch_var.pdispVal); + fail_unless(zip_idispatch_var.pdispVal != NULL, "Could not create IDispatch for zip FolderItems object."); + + // Folder.CopyHere: + // (16) Respond with "Yes to All" for any dialog box that is displayed. + // (256) Display a progress dialog box but do not show the file names. + // (512) Do not confirm the creation of a new directory if the operation requires one to be created. + // (1024) Do not display a user interface if an error occurs. + // These options are ignored on Windows XP. + VARIANT options_var; + options_var.vt = VT_I4; + options_var.lVal = 16 | 256 | 512 | 1024; + + res = destination_folder->CopyHere(zip_idispatch_var, options_var); + fail_unless(res == S_OK, "Could not extract zip file contents to destination directory."); + + // We don't bother to free/release stuff unless we succeed, since we exit on failure. + + // SysFreeString: + SysFreeString(zip_path_var.bstrVal); + SysFreeString(destination_dir_var.bstrVal); + zip_idispatch_var.pdispVal->Release(); + zip_folderitems->Release(); + destination_folder->Release(); + zip_folder->Release(); + shell->Release(); + + // CoUninitialize: + 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); + + // + // 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."); + output_buf[0] = 0; + + // Redirection is annoyingly complicated. + int output_pipe[2]; + errno = 0; + int res = _pipe(output_pipe, 512, _O_BINARY | _O_NOINHERIT); + if (res != 0) { + warn("Could not create pipe."); + return false; + } + int output_read_fd = output_pipe[0], output_write_fd = output_pipe[1]; + + // Duplicate the redirected file descriptor (the call to _dup2 will close the original). + int original_fd = _dup(_fileno(redirect)); + if (errno != 0) { + warn("Could not duplicate original file descriptor."); + return false; + } + + // Duplicate write end of pipe to redirected file descriptor. + res = _dup2(output_write_fd, _fileno(redirect)); + if (res != 0 || errno != 0) { + warn("Could not redirect."); + return false; + } + + // Close original file descriptor for write end of pipe. + _close(output_write_fd); // ignore errors + + HANDLE process_handle = (HANDLE) _wspawnvp(P_NOWAIT, argv[0], argv); + if (process_handle == (HANDLE) -1) { + warn("Could not execute subprocess."); + } + + // Duplicate copy of original stdout back into stdout. + errno = 0; + res = _dup2(original_fd, _fileno(redirect)); + fail_unless(res == 0 && errno == 0, "Could not restore stdout."); + + // Close duplicate copy of original fd. + _close(original_fd); // ignore errors + + if (process_handle == (HANDLE) -1) { + return false; + } + + DWORD exit_code = 0; + errno = 0; + unsigned char *p = output_buf; + size_t remaining_size = output_size; + int bytes_read; + do { + if (remaining_size == 0) { + bytes_read = 0; + Sleep(100); + } else { + bytes_read = _read(output_read_fd, p, remaining_size-1); + if (errno != 0 || bytes_read < 0) { + warn("Could not read from subprocess output."); + return false; + } + fail_unless((size_t) bytes_read < output_size, "Unexpectedly long read."); + p += bytes_read; + remaining_size -= bytes_read; + *p = 0; + } + + // GetExitCodeProcess: + BOOL res = GetExitCodeProcess(process_handle, &exit_code); + if (!res) { + warn("Could not get subprocess exit code."); + return false; + } + } while (bytes_read > 0 && exit_code == STILL_ACTIVE); + + return (exit_code == 0); +} + +void fail(char *s) { + // TODO: show dialog box + fprintf(stderr, "%s\n", s); + pause(); + exit(1); +} + +void warn(char *s) { + fprintf(stderr, "%s\n", s); +} + +void pause() { + 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 new file mode 100644 index 00000000..a2699a2b --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/installer.vcproj @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/build_helpers/windows/installer/installer/logo.ico b/misc/build_helpers/windows/installer/installer/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..846567068128772052d16aaaa9b0a1429cb50432 GIT binary patch literal 304886 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 T;mXQzi@}>) literal 0 HcmV?d00001 diff --git a/misc/build_helpers/windows/installer/installer/logo.svg b/misc/build_helpers/windows/installer/installer/logo.svg new file mode 100644 index 00000000..6de798e3 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/logo.svg @@ -0,0 +1,157 @@ + + + + + + image/svg+xml + + Tahoe-LAFS logo + + + + + + Tahoe-LAFS logo + + A proposed logo for the Tahoe-LAFS Project. + + + + +Tahoe-LAFS Logo + by Kevin Reid + is licensed under a Creative Commons Attribution 3.0 Unported License +. + + + + + + + + + + + + + + + + diff --git a/misc/build_helpers/windows/installer/installer/make-icon.sh b/misc/build_helpers/windows/installer/installer/make-icon.sh new file mode 100755 index 00000000..987e0ecb --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/make-icon.sh @@ -0,0 +1,23 @@ +#! /bin/bash +# Based on +# converts the passed-in svgs to ico + +if [[ $# -eq 0 ]]; then + echo "Usage: $0 svg1 [svg2 [...]]" + exit 0 +fi + +temp=$(mktemp -d) +declare -a res=(16 24 32 48 64 256) +for f in $*; do + mkdir -p $temp/$(dirname $f) + for r in "${res[@]}"; do + inkscape -z -e $temp/${f}${r}.png -w $r -h $r $f + done + resm=( "${res[@]/#/$temp/$f}" ) + resm=( "${resm[@]/%/.png}" ) + for filetype in ico; do + convert "${resm[@]}" ${f%%.*}.$filetype + done +done +rm -rf $temp diff --git a/misc/build_helpers/windows/installer/installer/stdafx.cpp b/misc/build_helpers/windows/installer/installer/stdafx.cpp new file mode 100644 index 00000000..c1026abd --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/stdafx.cpp @@ -0,0 +1,5 @@ +// stdafx.cpp : source file that includes just the standard includes +// installer.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" diff --git a/misc/build_helpers/windows/installer/installer/stdafx.h b/misc/build_helpers/windows/installer/installer/stdafx.h new file mode 100644 index 00000000..9f8375b2 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/stdafx.h @@ -0,0 +1,29 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Turn off the warnings nagging you to use the more complicated *_s +// "secure" functions that are actually more difficult to use securely. +#pragma warning(disable:4996) + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include diff --git a/misc/build_helpers/windows/installer/installer/targetver.h b/misc/build_helpers/windows/installer/installer/targetver.h new file mode 100644 index 00000000..6fe8eb79 --- /dev/null +++ b/misc/build_helpers/windows/installer/installer/targetver.h @@ -0,0 +1,13 @@ +#pragma once + +// The following macros define the minimum required platform. The minimum required platform +// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run +// your application. The macros work by enabling all features available on platform versions up to and +// including the version specified. + +// Modify the following defines if you have to target a platform prior to the ones specified below. +// Refer to MSDN for the latest info on corresponding values for different platforms. +#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista. +#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. +#endif + diff --git a/setuptools-0.6c16dev6.egg/setuptools/command/scriptsetup.py b/setuptools-0.6c16dev6.egg/setuptools/command/scriptsetup.py index b3233ec1..b066820c 100644 --- a/setuptools-0.6c16dev6.egg/setuptools/command/scriptsetup.py +++ b/setuptools-0.6c16dev6.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, \ @@ -247,13 +252,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