Skip to content

Commit

Permalink
feat: add conda-launchers recipe
Browse files Browse the repository at this point in the history
Signed-off-by: Chawye Hsu <su+git@chawyehsu.com>
  • Loading branch information
chawyehsu committed Nov 6, 2024
1 parent 37ae03a commit ae135f6
Show file tree
Hide file tree
Showing 6 changed files with 491 additions and 0 deletions.
29 changes: 29 additions & 0 deletions conda-launchers/build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
setlocal EnableDelayedExpansion

copy launcher.c.orig launcher.c /Y

set TARGET=x86_64-windows-gnu
set EXE_TARGET=64
if %TARGET_PLATFORM% == win-32 (
set TARGET=x86-windows-gnu
set EXE_TARGET=32
)
if %TARGET_PLATFORM% == win-arm64 (
set TARGET=aarch64-windows-gnu
set EXE_TARGET=arm64
)

@rem build cli launcher
zig build -Doptimize=ReleaseSmall -Dtarget=%TARGET% -Dgui=false --prefix-exe-dir %SCRIPTS%
@rem build gui launcher
zig build -Doptimize=ReleaseSmall -Dtarget=%TARGET% -Dgui=true --prefix-exe-dir %SCRIPTS%

IF %ERRORLEVEL% NEQ 0 exit 1

@REM @rem install launcher scripts
cd %SCRIPTS%
for %%f in (cli gui) do (
echo print^("%%f-%EXE_TARGET%.exe successfully launched the accompanying Python script"^)> "%%f-%EXE_TARGET%-script.py"
)

IF %ERRORLEVEL% NEQ 0 exit 1
303 changes: 303 additions & 0 deletions conda-launchers/cpython-launcher-c-mods-for-setuptools.3.7.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
--- launcher.c.orig 2020-02-23 11:24:16.544939800 +0100
+++ launcher.c 2020-02-23 11:32:24.454773100 +0100
@@ -8,11 +8,71 @@
* Curt Hagenlocher (job management)
*/

+#if defined(_MSC_VER)
+#ifdef NDEBUG
+// /Og (global optimizations), /Os (favor small code), /Oy (no frame pointers)
+#pragma optimize("gsy",on)
+
+// Note that merging the .rdata section will result in LARGER exe's if you using
+// MFC (esp. static link). If this is desirable, define _MERGE_RDATA_ in your project.
+#ifdef _MERGE_RDATA_
+#pragma comment(linker,"/merge:.rdata=.data")
+#endif // _MERGE_RDATA_
+
+#endif // NDEBUG
+#endif
+
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>
#include <tchar.h>

+/* To build on MSYS2 use the following script:
+#!/usr/bin/env bash
+
+wget -c https://raw.githubusercontent.com/python/cpython/3.7/PC/launcher.c -O launcher.c
+patch -p0 < $(dirname ${BASH_SOURCE[0]})/cpython-launcher-c-mods-for-setuptools.3.7.patch
+RCFILE=$(dirname ${BASH_SOURCE[0]})/resources.rc
+[[ -f ${RCFILE} ]] && rm -f ${RCFILE}
+echo "#include \"winuser.h\"" > ${RCFILE}
+echo "1 RT_MANIFEST manifest.xml" >> ${RCFILE}
+for _BITS in 64 32; do
+ [[ -f resources-${_BITS}.res ]] && rm -f resources-${_BITS}.res
+ PATH=/mingw${_BITS}/bin:$PATH windres --input ${RCFILE} --output resources-${_BITS}.res --output-format=coff
+ for _TYPE in cli gui; do
+ if [[ ${_TYPE} == cli ]]; then
+ CPPFLAGS=
+ LDFLAGS=
+ else
+ CPPFLAGS="-D_WINDOWS -mwindows"
+ LDFLAGS="-mwindows"
+ fi
+ # You *could* use MSVC 2008 here, but you'd end up with much larger (~230k) executables.
+ # cl.exe -opt:nowin98 -D NDEBUG -D "GUI=0" -D "WIN32_LEAN_AND_MEAN" -ZI -Gy -MT -MERGE launcher.c -Os -link -MACHINE:x64 -SUBSYSTEM:CONSOLE version.lib advapi32.lib shell32.lib
+ PATH=/mingw${_BITS}/bin:$PATH gcc -O2 -DSCRIPT_WRAPPER -DUNICODE -D_UNICODE -DMINGW_HAS_SECURE_API ${CPPFLAGS} launcher.c -c -o ${_TYPE}-${_BITS}.o
+ PATH=/mingw${_BITS}/bin:$PATH gcc -Wl,-s --static -static-libgcc -municode ${LDFLAGS} ${_TYPE}-${_BITS}.o resources-${_BITS}.res -o ${_TYPE}-${_BITS}.exe
+ done
+done
+ls -l *.exe
+echo "Debug this from cmd.exe via:"
+echo "set PYLAUNCH_DEBUG=1"
+ */
+
+/* Previosuly GetFileVersionInfoW was used here, but I would rather save
+ * the 1.5k that costs. If we bring this back, add -lversion to the link
+ * command line.
+ */
+
+#define VERSION_HIGH 0
+#define VERSION_LOW 1
+
+/* Previously BUFSIZE was used for this but 256 is not enough for even 260
+ * ASCII characters and far too little for unicode (it is a char array, not
+ * a wchar_t array, though it needs to be even bigger than 260 wchar_ts as
+ * the source script file could contain UTF-8 or UTF-32 (wchar_t is 2-byte)
+ */
+#define SHEBANG_BUFSIZE 2048
+
#define BUFSIZE 256
#define MSGSIZE 1024

@@ -742,6 +802,7 @@
{ L"/usr/bin/env python", TRUE },
{ L"/usr/bin/python", FALSE },
{ L"/usr/local/bin/python", FALSE },
+ { L"python_d", FALSE },
{ L"python", FALSE },
{ NULL, FALSE },
};
@@ -796,6 +857,25 @@

static COMMAND path_command;

+#if !defined(_MSC_VER)
+errno_t _wdupenv_s_emulated(wchar_t **buffer,
+ size_t *numberOfElements,
+ const wchar_t *varname)
+{
+ size_t szreq;
+
+ errno_t err = _wgetenv_s(&szreq, NULL, 0, varname);
+ if (szreq == 0)
+ return 1;
+ *buffer = (wchar_t*) malloc(sizeof(wchar_t) * szreq);
+ if (!*buffer)
+ return 1;
+ err = _wgetenv_s(&szreq, *buffer, szreq, varname);
+ return err;
+}
+#define _wdupenv_s _wdupenv_s_emulated
+#endif
+
static COMMAND * find_on_path(wchar_t * name)
{
wchar_t * pathext;
@@ -919,7 +999,7 @@

static BOOL
parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command,
- wchar_t ** suffix, BOOL *search)
+ wchar_t ** suffix, BOOL *search, wchar_t * argv0)
{
BOOL rc = FALSE;
SHEBANG * vpp;
@@ -929,11 +1009,40 @@
wchar_t * endp = shebang_line + nchars - 1;
COMMAND * cp;
wchar_t * skipped;
+ wchar_t tidied[_MAX_DRIVE+_MAX_DIR+_MAX_FNAME+_MAX_EXT] = L"";

*command = NULL; /* failure return */
*suffix = NULL;
*search = FALSE;

+ if ((shebang_line[0] != L'#') || (shebang_line[1] != L'!')) {
+ /* This is deliberately very similar to find_exe() in:
+ * https://raw.githubusercontent.com/pypa/setuptools/master/launcher.c
+ * I was tempted to use _wsplitpath_s twice to get the parent dir, but
+ * any change of behaviour here would cause big trouble.
+ */
+ wchar_t drive[_MAX_DRIVE];
+ wchar_t dir[_MAX_DIR];
+ wchar_t fname[_MAX_FNAME];
+ wchar_t ext[_MAX_EXT];
+ wchar_t * tmp, wc;
+ debug(L"parse_shebang called without a valid shebang %s (for argv0 %s)\n", shebang_line, argv0);
+ if (wcslen(argv0)-1 < _countof(tidied)) {
+ wcsncpy_s(tidied, _countof(tidied), argv0, _TRUNCATE);
+ tmp = &tidied[0];
+ while (wc = *tmp++) {
+ if (wc == L'/') tmp[-1] = L'\\';
+ }
+ _wsplitpath_s(tidied, drive, _countof(drive), dir, _countof(dir), fname, _countof(fname), ext, _countof(ext));
+ tmp = dir+wcslen(dir)-1;
+ if (*tmp == L'\\') tmp--;
+ while (*tmp != L'\\' && tmp>=dir) *tmp-- = 0;
+ _snwprintf_s(tidied, _countof(tidied), _TRUNCATE, L"#!%s%s%s", drive, dir, PYTHON_EXECUTABLE);
+ debug(L"invented shebang: %s\n", tidied);
+ shebang_line = tidied;
+ }
+ }
+
if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) {
shebang_line = skip_whitespace(shebang_line);
if (*shebang_line) {
@@ -947,6 +1056,7 @@
* "python".
*/
*command = wcsstr(shebang_line, L"python");
+ debug(L"found in builtin_virtual_paths: %s, command: %s\n", vpp->shebang, *command);
break;
}
}
@@ -1072,10 +1182,13 @@
validate_version(wchar_t * p)
{
/*
+ Optionally _d,
Version information should start with the major version,
Optionally followed by a period and a minor version,
Optionally followed by a minus and one of 32 or 64.
Valid examples:
+ _d
+ _d3
2
3
2.7
@@ -1089,6 +1202,20 @@
*/
BOOL result = (p != NULL); /* Default to False if null pointer. */

+ /* Skip _d for Windows debug. Interestingly, the name-script.py files from a release build
+ do not end up containing a shebang while debug builds have #!python_d */
+ if (result)
+ debug(L"validate_version: in: %ls\n", p);
+ if (result && p[0] == '_' && p[1] == 'd')
+ {
+ debug(L"validate_version: Windows _d shebang\n");
+ p += 2;
+ }
+ if (!*p)
+ {
+ debug(L"validate_version: No version number provided\n");
+ return TRUE;
+ }
result = result && iswdigit(*p); /* Result = False if first string element is not a digit. */

while (result && iswdigit(*p)) /* Require a major version */
@@ -1171,7 +1298,7 @@
*/
FILE * fp;
errno_t rc = _wfopen_s(&fp, *argv, L"rb");
- char buffer[BUFSIZE];
+ char buffer[SHEBANG_BUFSIZE];
wchar_t shebang_line[BUFSIZE + 1];
size_t read;
char *p;
@@ -1188,7 +1315,7 @@
INSTALLED_PYTHON * ip;

if (rc == 0) {
- read = fread(buffer, sizeof(char), BUFSIZE, fp);
+ read = fread(buffer, sizeof(char), SHEBANG_BUFSIZE, fp);
debug(L"maybe_handle_shebang: read %zd bytes\n", read);
fclose(fp);

@@ -1213,7 +1340,7 @@
bom->code_page);
start = &buffer[bom->length];
}
- p = find_terminator(start, BUFSIZE, bom);
+ p = find_terminator(start, SHEBANG_BUFSIZE, bom);
/*
* If no CR or LF was found in the heading,
* we assume it's not a shebang file.
@@ -1297,10 +1424,10 @@
if (nchars > 0) {
shebang_line[--nchars] = L'\0';
is_virt = parse_shebang(shebang_line, nchars, &command,
- &suffix, &search);
+ &suffix, &search, *argv);
if (command != NULL) {
debug(L"parse_shebang: found command: %ls\n", command);
- if (!is_virt) {
+ if (!is_virt) { /* || !wcsncmp(command, L"python_d", 8)) { */
invoke_child(command, suffix, cmdline);
}
else {
@@ -1409,7 +1536,7 @@

get_version_info(version_text, MAX_PATH);
fwprintf(stdout, L"\
-Python Launcher for Windows Version %ls\n\n", version_text);
+Python Launcher for Windows (Anaconda/Setuptools variant) Version %ls\n\n", version_text);
fwprintf(stdout, L"\
usage:\n\
%ls [launcher-args] [python-args] script [script-args]\n\n", argv[0]);
@@ -1584,6 +1711,7 @@
int newlen;
HRESULT hr;
int index;
+ int quoted = 0;
#else
HRESULT hr;
int index;
@@ -1624,6 +1752,7 @@
}
#endif
argv0 = get_process_name();
+/*
size = GetFileVersionInfoSizeW(argv0, &size);
if (size == 0) {
winerror(GetLastError(), message, MSGSIZE);
@@ -1649,6 +1778,9 @@
free(version_data);
}
}
+*/
+ version_high = VERSION_HIGH;
+ version_low = VERSION_LOW;

#if defined(VENV_REDIRECT)
/* Allocate some extra space for new filenames */
@@ -1731,13 +1863,24 @@
locate_wrapped_script();

/* Add the wrapped script to the start of command */
- newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */
+ p = wcsrchr(wrapped_script_path, L' ');
+ if (p != NULL)
+ quoted = 1;
+ newlen = wcslen(wrapped_script_path) + wcslen(command) + 2 + (2 * quoted); /* ' ' + NUL */
newcommand = malloc(sizeof(wchar_t) * newlen);
if (!newcommand) {
error(RC_NO_MEMORY, L"Could not allocate new command line");
}
else {
- wcscpy_s(newcommand, newlen, wrapped_script_path);
+ /* This probably breaks already-quoted things. */
+ newcommand[0] = L'\0';
+ if (quoted) {
+ wcscpy_s(newcommand, newlen, L"\"");
+ }
+ wcscat_s(newcommand, newlen, wrapped_script_path);
+ if (quoted) {
+ wcscat_s(newcommand, newlen, L"\"");
+ }
wcscat_s(newcommand, newlen, L" ");
wcscat_s(newcommand, newlen, command);
debug(L"Running wrapped script with command line '%ls'\n", newcommand);
47 changes: 47 additions & 0 deletions conda-launchers/recipe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
context:
name: conda-launchers
version: "0.1.0"

package:
name: ${{ name|lower }}
version: ${{ version }}

source:
- url: https://raw.githubusercontent.com/python/cpython/3.7/PC/launcher.c
sha256: a3167e5908cefa942f63e0de5c6ec7a929e8ca82a382537ab8292e5aaa82ca2e
file_name: launcher.c.orig
patches:
- cpython-launcher-c-mods-for-setuptools.3.7.patch
- url: https://raw.githubusercontent.com/python/cpython/3.7/LICENSE
sha256: 96e4f59524cde5af4a2ea837ef5e52b65e51f1f999825fd8a9ec3b444cb82aea
file_name: cpython-LICENSE
- path: ./src

build:
number: 0
skip:
- unix

requirements:
build:
- zig >=0.13.0
run:
- python

tests:
- script:
# there's currently no python for win-arm64 so only do tests for win-64
- if: win64
then:
file: test

about:
homepage: https://github.com/conda
summary: Conda's Windows launchers for Python entry points
description: Windows launchers for Python entry points used in the conda ecosystem
license: Python-2.0 AND BSD-3-Clause
license_file: cpython-LICENSE

extra:
recipe-maintainers:
- chawyehsu
Loading

0 comments on commit ae135f6

Please sign in to comment.