diff --git a/.env b/.env deleted file mode 100644 index b55b7d9..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -CI_VER=0.16.0 -CI_WPI=Winpython64-3.9.10.0dot.exe \ No newline at end of file diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..961db96 --- /dev/null +++ b/.env.template @@ -0,0 +1,3 @@ +CI_VER=0.19.1 +CI_WPI=Winpython64-3.9.10.0dot.exe +VCVARS_PATH="C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat" \ No newline at end of file diff --git a/.gitignore b/.gitignore index e56778b..ff0876a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,15 @@ doctmp/ releases/ DataLab-WinPython*/ dependencies*.txt -nsis/icons/*.ico -nsis/images/*.bmp packages/ +launchers/ Win*.exe WPy*/ +# Visual Studio Code +.venv +.env + # Created by https://www.gitignore.io/api/python ### Python ### diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0618810..d2d54be 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -79,6 +79,33 @@ "clear": false } }, + { + "label": "Build launchers", + "type": "shell", + "command": "cmd", + "args": [ + "/c", + "build_launchers.bat" + ], + "options": { + "cwd": "scripts", + "env": { + "UNATTENDED": "1", + } + }, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + } + }, { "label": "Build installer", "type": "shell", @@ -116,6 +143,7 @@ "dependsOn": [ "Clean up", "Download", + "Build launchers", "Build distribution", "Build installer", ] diff --git a/README.md b/README.md index 26488db..2706ba7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Technologies DataLab-WinPython is based on the following technologies: * Windows batch scripts (to minimize prerequisites) -* NSIS for the launcher executable +* C++ for the launcher executable (to minimize dependencies), using MSVC (tested with Visual Studio 2022 Community Edition but should work with pretty much any version) * WiX Toolset for the installer * WinPython Python distribution * Python package manager `pip` diff --git a/nsis/launcher.nsi b/nsis/launcher.nsi deleted file mode 100644 index a6a0eb5..0000000 --- a/nsis/launcher.nsi +++ /dev/null @@ -1,44 +0,0 @@ -/* - -DataLab-WinPython launcher script ---------------------------------- - -Licensed under the terms of the BSD 3-Clause -(see ../LICENSE for details) - -*/ - -!define COMMAND "$%NSIS_COMMAND%" -!define PARAMETERS "$%NSIS_PARAMS%" -!define WORKDIR "$%NSIS_WORKDIR%" -!define FILENAME "$%NSIS_OUTFILE%" -Icon "$%NSIS_ICON%" -OutFile $%NSIS_OUTFILE% - -!include "WordFunc.nsh" -!include "FileFunc.nsh" - -Unicode true -SilentInstall silent -AutoCloseWindow true -ShowInstDetails nevershow -RequestExecutionLevel user - -Section "" -Call Execute -SectionEnd - -Function Execute -StrCmp ${WORKDIR} "" 0 workdir -System::Call "kernel32::GetCurrentDirectory(i ${NSIS_MAX_STRLEN}, t .r0)" -SetOutPath $0 -Goto end_workdir -workdir: -SetOutPath "${WORKDIR}" -end_workdir: -${GetParameters} $R1 -StrCmp "${PARAMETERS}" "" end_param 0 -StrCpy $R1 "${PARAMETERS} $R1" -end_param: -Exec '"${COMMAND}" $R1' -FunctionEnd \ No newline at end of file diff --git a/resources/deploy.bat b/resources/deploy.bat index bdb87f6..2807881 100644 --- a/resources/deploy.bat +++ b/resources/deploy.bat @@ -2,10 +2,10 @@ set INKSCAPE_PATH="C:\Program Files\Inkscape\bin\inkscape.exe" -@REM Generating images for NSIS installer +@REM Generating images for Wix installer %INKSCAPE_PATH% "WixUIBanner.svg" -o "temp.png" -w 493 -h 58 -magick convert "temp.png" bmp3:"banner.bmp" +magick "temp.png" bmp3:"banner.bmp" %INKSCAPE_PATH% "WixUIDialog.svg" -o "temp.png" -w 493 -h 312 -magick convert "temp.png" bmp3:"dialog.bmp" +magick "temp.png" bmp3:"dialog.bmp" del "temp.png" move /y *.bmp ..\wix diff --git a/scripts/build_distribution.bat b/scripts/build_distribution.bat index dc4338c..5561d87 100644 --- a/scripts/build_distribution.bat +++ b/scripts/build_distribution.bat @@ -62,19 +62,9 @@ for /d %%d in (patches\*) do ( @REM rd /s /q "%%d" @REM ) -@REM Create additional launchers +@REM Copy launchers (generated by build_launchers.bat) to the distribution @REM =========================================================================== -@REM Iterate over all .bat files in "executables" folder: -set NSIS_WORKDIR=$EXEDIR\scripts -set NSIS_COMMAND=wscript.exe -for %%f in (executables\*.bat) do ( - @REM Copy each .bat file to the "scripts" folder: - copy "executables\%%~nxf" "%ROOTPATH%\dist\%CI_DST%\scripts" - @REM Set NSIS_ICON, NSIS_OUTFILE and NSIS_PARAMS for each .bat file: - set NSIS_ICON=%ROOTPATH%\executables\%%~nf.ico - set NSIS_OUTFILE=%ROOTPATH%\dist\%CI_DST%\%%~nf.exe - set NSIS_PARAMS=Noshell.vbs %%~nxf - "C:\Program Files (x86)\NSIS\makensis.exe" nsis\launcher.nsi -) +xcopy /s /y executables\*.bat "dist\%CI_DST%\scripts" +xcopy /s /y launchers\*.exe "dist\%CI_DST%" call %FUNC% EndOfScript \ No newline at end of file diff --git a/scripts/build_launchers.bat b/scripts/build_launchers.bat new file mode 100644 index 0000000..fbb0818 --- /dev/null +++ b/scripts/build_launchers.bat @@ -0,0 +1,78 @@ +@echo off +setlocal EnableDelayedExpansion + +call %~dp0utils GetScriptPath SCRIPTPATH +call %FUNC% SetEnvVars +set ROOTPATH=%SCRIPTPATH%\..\ +cd %ROOTPATH% + +:: Check if MSVC environment is already initialized +if not defined VSINSTALLDIR ( + echo Initializing MSVC environment... + call %VCVARS_PATH% + if errorlevel 1 ( + echo [ERROR] Failed to initialize MSVC environment. + exit /b 1 + ) +) + +:: Paths +set SOURCE_FILE=src\launcher_template.cpp +set OUTPUT_DIR=launchers + +:: Ensure output directory exists +if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%" + +:: Walk through .bat files in the current directory +for %%B in (executables\*.bat) do ( + echo Processing %%B... + + :: Derive the base name and paths + set "BASE_NAME=%%~nB" + set "BAT_FILE=%%~dpB%%~nxB" + set "ICON_FILE=%%~dpB%%~nB.ico" + set "RESOURCE_FILE=%OUTPUT_DIR%\%%~nB.rc" + set "RESOURCE_OBJ=%OUTPUT_DIR%\%%~nB.res" + set "LAUNCHER_EXE=%OUTPUT_DIR%\%%~nB.exe" + + :: Check if the icon exists + if exist "%%~dpB%%~nB.ico" ( + echo Icon found: %%~dpB%%~nB.ico + ) else ( + echo No icon found for %%B. Using default icon. + set "ICON_FILE=default.ico" + ) + + :: Create resource file + echo Creating resource file... + > "!RESOURCE_FILE!" echo IDI_ICON1 ICON "!ICON_FILE!" + :: Compile resource + echo Compiling resource... + rc /fo "%OUTPUT_DIR%\%%~nB.res" "!RESOURCE_FILE!" + + :: Compile the launcher executable + echo Compiling launcher executable... + cl /EHsc /O2 /DUNICODE /W4 "%SOURCE_FILE%" "!RESOURCE_OBJ!" ^ + /Fe"!LAUNCHER_EXE!" ^ + /DLAUNCH_TARGET=\"%%~nxB\" ^ + User32.lib ^ + /link /SUBSYSTEM:WINDOWS + + :: Remove intermediate .obj file + del /q "launcher_template.obj" + + if errorlevel 1 ( + echo [ERROR] Failed to build launcher for %%B. + exit /b 1 + ) + + if exist "!LAUNCHER_EXE!" ( + echo [SUCCESS] Launcher created: !LAUNCHER_EXE! + ) else ( + echo [ERROR] Failed to build launcher for %%B. + exit /b 1 + ) +) + +echo All launchers processed. +exit /b 0 diff --git a/scripts/clean_up.bat b/scripts/clean_up.bat index 9f48347..82b230f 100644 --- a/scripts/clean_up.bat +++ b/scripts/clean_up.bat @@ -10,8 +10,10 @@ call %FUNC% SetEnvVars set ROOTPATH=%SCRIPTPATH%\..\ cd %ROOTPATH% +if exist *.obj del /s /q *.obj if exist "tmp" ( rmdir /s /q "tmp" ) if exist "dist" ( rmdir /s /q "dist" ) if exist "packages" ( rmdir /s /q "packages" ) +if exist "launchers" ( rmdir /s /q "launchers" ) call %FUNC% EndOfScript \ No newline at end of file diff --git a/src/launcher_template.cpp b/src/launcher_template.cpp new file mode 100644 index 0000000..511e257 --- /dev/null +++ b/src/launcher_template.cpp @@ -0,0 +1,75 @@ +/* +DataLab-WinPython launcher script +--------------------------------- + +Licensed under the terms of the BSD 3-Clause +(see ../LICENSE for details) + +*/ + +#include +#include + +int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/) { + // Get the path to the current executable + wchar_t exePath[MAX_PATH]; + GetModuleFileNameW(NULL, exePath, MAX_PATH); + + // Determine the directory of the executable + std::wstring exeDir = exePath; + exeDir = exeDir.substr(0, exeDir.find_last_of(L"\\/")); + + // Define the path to the "scripts" directory + std::wstring scriptsDir = exeDir + L"\\scripts"; + + // Check if the "scripts" directory exists + DWORD attributes = GetFileAttributesW(scriptsDir.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY)) { + MessageBoxW(NULL, L"The 'scripts' directory does not exist. Please ensure it is in the same folder as the launcher.", + L"Launcher Error", MB_ICONERROR); + return 1; + } + + // Set the working directory to the "scripts" folder + if (!SetCurrentDirectoryW(scriptsDir.c_str())) { + MessageBoxW(NULL, L"Failed to set the working directory to 'scripts'.", + L"Launcher Error", MB_ICONERROR); + return 1; + } + + // Define the command to run + std::wstring target = L"cmd.exe /c \"" LAUNCH_TARGET L"\""; + + // Configure the process startup info + STARTUPINFO si = { sizeof(si) }; + si.dwFlags = STARTF_USESHOWWINDOW; // Prevent the window from appearing + si.wShowWindow = SW_HIDE; // Hide the command window + + PROCESS_INFORMATION pi = {}; + + // Start the process with CREATE_NO_WINDOW flag + if (!CreateProcessW( + NULL, // Application name (NULL because we pass the command in the command line) + &target[0], // Command line + NULL, // Process security attributes + NULL, // Thread security attributes + FALSE, // Inherit handles + CREATE_NO_WINDOW, // Flags to prevent creating a window + NULL, // Environment block (NULL to inherit parent) + NULL, // Current directory (NULL to use the parent process's current directory) + &si, // Startup info + &pi // Process information + )) { + MessageBoxW(NULL, L"Failed to launch the script.", L"Launcher Error", MB_ICONERROR); + return 1; + } + + // Wait for the script to finish + WaitForSingleObject(pi.hProcess, INFINITE); + + // Cleanup + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return 0; +}