diff --git a/direct/src/showutil/FreezeTool.py b/direct/src/showutil/FreezeTool.py index c218bc8e41..e944c50cb2 100644 --- a/direct/src/showutil/FreezeTool.py +++ b/direct/src/showutil/FreezeTool.py @@ -1642,7 +1642,8 @@ class Freezer: return target - def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}): + def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}, + log_append=False): # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." @@ -1735,7 +1736,7 @@ class Freezer: # Determine the format of the header and module list entries depending # on the platform. - num_pointers = 11 + num_pointers = 12 stub_data = bytearray(stub_file.read()) bitnesses = self._get_executable_bitnesses(stub_data) @@ -1808,6 +1809,10 @@ class Freezer: # A null entry marks the end of the module table. blob += struct.pack(entry_layout, 0, 0, 0) + flags = 0 + if log_append: + flags |= 1 + # Compose the header we will be writing to the stub, to tell it # where to find the module data blob, as well as other variables. header = struct.pack(header_layout, @@ -1816,7 +1821,7 @@ class Freezer: 1, # Version number num_pointers, # Number of pointers that follow 0, # Codepage, not yet used - 0, # Flags, not yet used + flags, table_offset, # Module table pointer. # The following variables need to be set before static init # time. See configPageManager.cxx, where they are read. @@ -1830,6 +1835,7 @@ class Freezer: field_offsets.get('prc_executable_patterns', 0), field_offsets.get('prc_executable_args_envvar', 0), field_offsets.get('main_dir', 0), + field_offsets.get('log_filename', 0), 0) # Now, find the location of the 'blobinfo' symbol in the binary, diff --git a/direct/src/showutil/dist.py b/direct/src/showutil/dist.py index 5b40b7145f..9a143ce513 100644 --- a/direct/src/showutil/dist.py +++ b/direct/src/showutil/dist.py @@ -11,6 +11,7 @@ import shutil import struct import io import imp +import string import distutils.core import distutils.log @@ -86,6 +87,8 @@ class build_apps(distutils.core.Command): self.extra_prc_files = [] self.extra_prc_data = '' self.default_prc_dir = None + self.log_filename = None + self.log_append = False self.requirements_path = './requirements.txt' self.pypi_extra_indexes = [] self.file_handlers= { @@ -382,7 +385,9 @@ class build_apps(distutils.core.Command): 'prc_encryption_key': None, 'prc_executable_patterns': None, 'prc_executable_args_envvar': None, - }) + 'main_dir': None, + 'log_filename': self.expand_path(self.log_filename, platform), + }, self.log_append) stub_file.close() # Copy the dependencies. @@ -818,6 +823,20 @@ class build_apps(distutils.core.Command): return deps + def expand_path(self, path, platform): + "Substitutes variables in the given path string." + + if path is None: + return None + + t = string.Template(path) + if platform.startswith('win'): + return t.substitute(HOME='~', USER_APPDATA='~/AppData/Local') + elif platform.startswith('macosx'): + return t.substitute(HOME='~', USER_APPDATA='~/Documents') + else: + return t.substitute(HOME='~', USER_APPDATA='~/.local/share') + class bdist_apps(distutils.core.Command): DEFAULT_INSTALLERS = { diff --git a/dtool/src/prc/configPageManager.cxx b/dtool/src/prc/configPageManager.cxx index 53f541cbd8..2f584bd0c5 100644 --- a/dtool/src/prc/configPageManager.cxx +++ b/dtool/src/prc/configPageManager.cxx @@ -115,6 +115,7 @@ reload_implicit_pages() { const char *prc_executable_patterns; const char *prc_executable_args_envvar; const char *main_dir; + const char *log_filename; }; #ifdef _MSC_VER const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo"); diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 93d43b7935..e893e1711d 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -6712,12 +6712,12 @@ if PkgSkip("PYTHON") == 0: TargetAdd('deploy-stub.exe', input='deploy-stub.obj') if GetTarget() == 'windows': TargetAdd('deploy-stub.exe', input='frozen_dllmain.obj') - TargetAdd('deploy-stub.exe', opts=['PYTHON', 'DEPLOYSTUB', 'NOICON']) + TargetAdd('deploy-stub.exe', opts=['WINSHELL', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) if GetTarget() == 'windows': TargetAdd('deploy-stubw.exe', input='deploy-stub.obj') TargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj') - TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) + TargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSHELL', 'PYTHON', 'DEPLOYSTUB', 'NOICON']) elif GetTarget() == 'darwin': DefSymbol('MACOS_APP_BUNDLE', 'MACOS_APP_BUNDLE') OPTS = OPTS + ['MACOS_APP_BUNDLE'] diff --git a/pandatool/src/deploy-stub/deploy-stub.c b/pandatool/src/deploy-stub/deploy-stub.c index dc05a67171..230f799a07 100644 --- a/pandatool/src/deploy-stub/deploy-stub.c +++ b/pandatool/src/deploy-stub/deploy-stub.c @@ -3,8 +3,10 @@ #include "Python.h" #ifdef _WIN32 # include "malloc.h" +# include #else # include +# include #endif #ifdef __FreeBSD__ @@ -18,6 +20,7 @@ #include #include +#include #if PY_MAJOR_VERSION >= 3 # include @@ -31,6 +34,11 @@ other pointers that are being read by configPageManager.cxx. */ #define MAX_NUM_POINTERS 24 +/* Stored in the flags field of the blobinfo structure below. */ +enum Flags { + F_log_append = 1, +}; + /* Define an exposed symbol where we store the offset to the module data. */ #ifdef _MSC_VER __declspec(dllexport) @@ -113,6 +121,206 @@ static void set_main_dir(char *main_dir) { } } +/** + * Creates the parent directories of the given path. Returns 1 on success. + */ +#ifdef _WIN32 +static int mkdir_parent(const wchar_t *path) { + // Copy the path to a temporary buffer. + wchar_t buffer[4096]; + size_t buflen = wcslen(path); + if (buflen + 1 >= _countof(buffer)) { + return 0; + } + wcscpy_s(buffer, _countof(buffer), path); + + // Seek back to find the last path separator. + while (buflen-- > 0) { + if (buffer[buflen] == '/' || buffer[buflen] == '\\') { + buffer[buflen] = 0; + break; + } + } + if (buflen == 0) { + // There was no path separator. + return 0; + } + + if (CreateDirectoryW(buffer, NULL) != 0) { + // Success! + return 1; + } + + // Failed. + DWORD last_error = GetLastError(); + if (last_error == ERROR_ALREADY_EXISTS) { + // Not really an error: the directory is already there. + return 1; + } + + if (last_error == ERROR_PATH_NOT_FOUND) { + // We need to make the parent directory first. + if (mkdir_parent(buffer)) { + // Parent successfully created. Try again to make the child. + if (CreateDirectoryW(buffer, NULL) != 0) { + // Got it! + return 1; + } + } + } + return 0; +} +#else +static int mkdir_parent(const char *path) { + // Copy the path to a temporary buffer. + char buffer[4096]; + size_t buflen = strlen(path); + if (buflen + 1 >= sizeof(buffer)) { + return 0; + } + strcpy(buffer, path); + + // Seek back to find the last path separator. + while (buflen-- > 0) { + if (buffer[buflen] == '/') { + buffer[buflen] = 0; + break; + } + } + if (buflen == 0) { + // There was no path separator. + return 0; + } + if (mkdir(buffer, 0755) == 0) { + // Success! + return 1; + } + + // Failed. + if (errno == EEXIST) { + // Not really an error: the directory is already there. + return 1; + } + + if (errno == ENOENT || errno == EACCES) { + // We need to make the parent directory first. + if (mkdir_parent(buffer)) { + // Parent successfully created. Try again to make the child. + if (mkdir(buffer, 0755) == 0) { + // Got it! + return 1; + } + } + } + return 0; +} +#endif + +/** + * Redirects the output streams to point to the log file with the given path. + * + * @param path specifies the location of log file, may start with ~ + * @param append should be nonzero if it should not truncate the log file. + */ +static int setup_logging(const char *path, int append) { +#ifdef _WIN32 + // Does it start with a tilde? Perform tilde expansion if so. + wchar_t pathw[MAX_PATH * 2]; + size_t offset = 0; + if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) { + // Strip off the tilde. + ++path; + + // Get the home directory path for the current user. + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) { + return 0; + } + offset = wcslen(pathw); + } + + // We need to convert the rest of the path from UTF-8 to UTF-16. + if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset, + (int)(_countof(pathw) - offset)) == 0) { + return 0; + } + + DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE); + int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS; + HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ, + NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) { + // Make the parent directories first. + mkdir_parent(pathw); + handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ, + NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (handle == INVALID_HANDLE_VALUE) { + return 0; + } + + if (append) { + SetFilePointer(handle, 0, NULL, FILE_END); + } + + fflush(stdout); + fflush(stderr); + + int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | (append ? _O_APPEND : 0)); + SetStdHandle(STD_OUTPUT_HANDLE, handle); + _dup2(fd, 1); + + SetStdHandle(STD_ERROR_HANDLE, handle); + _dup2(fd, 2); + + _close(fd); + return 1; +#else + // Does it start with a tilde? Perform tilde expansion if so. + char buffer[PATH_MAX * 2]; + size_t offset = 0; + if (path[0] == '~' && (path[1] == 0 || path[1] == '/')) { + // Strip off the tilde. + ++path; + + // Get the home directory path for the current user. + const char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + home_dir = getpwuid(getuid())->pw_dir; + } + offset = strlen(home_dir); + assert(offset < sizeof(buffer)); + strncpy(buffer, home_dir, sizeof(buffer)); + } + + // Copy over the rest of the path. + strcpy(buffer + offset, path); + + mode_t mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC); + int fd = open(buffer, mode, 0644); + if (fd == -1) { + // Make the parent directories first. + mkdir_parent(buffer); + fd = open(buffer, mode, 0644); + } + + if (fd == -1) { + perror(buffer); + return 0; + } + + fflush(stdout); + fflush(stderr); + + dup2(fd, 1); + dup2(fd, 2); + + close(fd); + return 1; +#endif +} + /* Main program */ #ifdef WIN_UNICODE @@ -389,7 +597,9 @@ int main(int argc, char *argv[]) { #endif int retval; struct _frozen *moddef; + const char *log_filename; void *blob = NULL; + log_filename = NULL; /* printf("blob_offset: %d\n", (int)blobinfo.blob_offset); @@ -418,6 +628,9 @@ int main(int argc, char *argv[]) { blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob); } } + if (blobinfo.num_pointers >= 12) { + log_filename = blobinfo.pointers[11]; + } } else { blobinfo.pointers[0] = blob; } @@ -434,6 +647,10 @@ int main(int argc, char *argv[]) { } } + if (log_filename != NULL) { + setup_logging(log_filename, (blobinfo.flags & F_log_append) != 0); + } + #ifdef _WIN32 if (blobinfo.codepage != 0) { SetConsoleCP(blobinfo.codepage); diff --git a/samples/asteroids/setup.py b/samples/asteroids/setup.py index c592c0911f..8f7fd76902 100644 --- a/samples/asteroids/setup.py +++ b/samples/asteroids/setup.py @@ -12,6 +12,8 @@ setup( 'gui_apps': { 'asteroids': 'main.py', }, + 'log_filename': '$USER_APPDATA/Asteroids/output.log', + 'log_append': False, 'plugins': [ 'pandagl', 'p3openal_audio',